import { existsSync, readdirSync, readFileSync, statSync } from "node:fs";
import { hostname } from "node:os";
import { join } from "node:path";
import { execFileSync } from "node:child_process";
import type { Adapter } from "../adapter";
import type { AdapterResult } from "../schema";
import { defaultLedgerDir, readLedger, type LedgerReadResult } from "../sessions/ledger";
import type { RequestsStore } from "../requests/requests-store";

export const SEATS_PANEL_ID = "seats";
export type SeatState = "free" | "held" | "wedged" | "unknown";
export interface SeatRow { host: string; slot: number; state: SeatState; holder: null | { pid: number; sessionId: string | null; sessionName: string | null; workKey: string | null; adwId: string | null; heldForSec: number } }
export interface SeatsSnapshot { hosts: { host: string; cap: number; queueDepth: number; emergencyDebt: number }[]; rows: SeatRow[]; observedAt: string }

export interface SeatsOptions {
  dir?: string; cap?: number; host?: string; interval?: number; ledgerDir?: string; requests?: RequestsStore;
  now?: () => number; readLedgerImpl?: (dir: string) => Promise<LedgerReadResult>; probe?: (path: string) => boolean; holders?: (path: string) => number[] | null;
}
const DEFAULT_DIR = `/run/user/${process.getuid?.() ?? 1000}/agent-session-slots`;
const number = (raw: string | undefined, fallback: number) => /^\d+$/.test(raw ?? "") && Number(raw) > 0 ? Number(raw) : fallback;
function stamp(path: string): { pid: number; ticks: string; started: number } | null {
  try { const [pid, ticks] = readFileSync(path, "utf8").trim().split("\t"); const stat = statSync(path); return pid !== undefined && ticks !== undefined && /^\d+$/.test(pid) && /^\d+$/.test(ticks) ? { pid: Number(pid), ticks, started: stat.mtimeMs } : null; } catch { return null; }
}
function alive(value: { pid: number; ticks: string } | null): boolean { try { return !!value && readFileSync(`/proc/${value.pid}/stat`, "utf8").split(" ")[21] === value.ticks; } catch { return false; } }
function defaultProbe(path: string): boolean { if (!existsSync(path)) return true; try { execFileSync("flock", ["-n", path, "true"], { stdio: "ignore" }); return true; } catch { return false; } }
/** lsof is only a confirmation and holder enumeration; no limiter mutation is ever performed. */
function defaultHolders(path: string): number[] | null { try { const out = execFileSync("lsof", ["-t", "--", path], { encoding: "utf8", stdio: ["ignore", "pipe", "ignore"] }); return out.trim() ? out.trim().split(/\s+/).map(Number).filter(Number.isSafeInteger) : []; } catch { return null; } }
function descendants(pid: number): Set<number> { const all = new Set([pid]); let changed = true; while (changed) { changed = false; try { for (const entry of readdirSync("/proc")) { if (!/^\d+$/.test(entry)) continue; const fields = readFileSync(`/proc/${entry}/stat`, "utf8").split(" "); if (all.has(Number(fields[3])) && !all.has(Number(entry))) { all.add(Number(entry)); changed = true; } } } catch { return all; } } return all; }

export async function collectSeats(options: SeatsOptions = {}): Promise<SeatsSnapshot> {
  const dir = options.dir ?? process.env.AGENT_SESSION_SLOT_DIR ?? DEFAULT_DIR; const cap = options.cap ?? number(process.env.AGENT_SESSION_SLOTS, 4); const now = options.now ?? Date.now;
  if (!existsSync(dir)) throw new Error("limiter state unavailable");
  try { readdirSync(dir); } catch { throw new Error("limiter state unavailable"); }
  const ledger = await (options.readLedgerImpl ?? readLedger)(options.ledgerDir ?? defaultLedgerDir()); const probe = options.probe ?? defaultProbe; const holders = options.holders ?? defaultHolders;
  const rows: SeatRow[] = [];
  for (let slot = 0; slot < cap; slot++) {
    const path = join(dir, `slot-${slot}.lock`); const free = probe(path); const marked = stamp(path);
    if (free) { rows.push({ host: options.host ?? hostname(), slot, state: "free", holder: null }); continue; }
    const inodeHolders = holders(path);
    const state: SeatState = alive(marked) ? "held" : inodeHolders === null ? "unknown" : inodeHolders.length === 0 ? "wedged" : "unknown";
    const actual = new Set((inodeHolders ?? []).flatMap((pid) => [...descendants(pid)]));
    const session = ledger.sessions.find((entry) => entry.pid !== null && actual.has(entry.pid));
    const request = session ? options.requests?.list().find((entry) => entry.session_id === session.id || entry.session_name === session.title) : undefined;
    rows.push({ host: options.host ?? hostname(), slot, state, holder: { pid: session?.pid ?? marked?.pid ?? 0, sessionId: session?.id ?? null, sessionName: session?.title ?? null, workKey: request?.id ?? session?.buildKey ?? null, adwId: session?.runId ?? null, heldForSec: Math.max(0, Math.floor((now() - (marked?.started ?? now())) / 1000)) } });
  }
  const queueDepth = (() => { try { return readFileSync(join(dir, "queue"), "utf8").split(/\r?\n/).filter(Boolean).length; } catch { return 0; } })();
  // The limiter leaves named wedge artifacts as durable diagnostics; show them without
  // touching or rotating any lock inode.
  try { for (const name of readdirSync(dir)) if (name.startsWith("queue.lock.wedged-")) rows.push({ host: options.host ?? hostname(), slot: -1, state: "wedged", holder: null }); } catch { /* checked above */ }
  return { hosts: [{ host: options.host ?? hostname(), cap, queueDepth, emergencyDebt: 0 }], rows, observedAt: new Date(now()).toISOString() };
}
export function createSeatsAdapter(options: SeatsOptions = {}): Adapter { return { id: "seats", interval: options.interval ?? 10_000, async poll(): Promise<AdapterResult> { const snapshot = await collectSeats(options); return { items: [], panels: [{ id: SEATS_PANEL_ID, ts: snapshot.observedAt, data: snapshot }] }; } }; }
