// Lecture du registre ~/.claude/sessions (un fichier <pid>.json par session CLI vive).
// Module partagé : P2 l'utilise pour la vivacité + le lookup par pid ; P3-B (claude-adapter)
// s'en sert comme source primaire d'état fin (busy/idle/waiting + waitingFor).
import { readdirSync, readFileSync } from 'node:fs';
import { join } from 'node:path';
import type { SessionRegistryStatus } from '@arboretum/shared';

export interface RegistryEntry {
  claudeSessionId: string | null;
  cwd: string | null;
  pid: number;
  procStart: string | null;
  status: SessionRegistryStatus | null;
  waitingFor: string | null;
  /** vivacité réelle = pid + procStart concordants (jamais la simple présence du fichier). */
  live: boolean;
}

/** starttime (champ 22 de /proc/<pid>/stat) ou null si le process n'existe pas. Linux-only. */
export function readProcStart(pid: number): string | null {
  try {
    const stat = readFileSync(`/proc/${pid}/stat`, 'utf8');
    // Le nom du process (champ 2) peut contenir espaces/parenthèses → parser après le dernier ')'.
    const after = stat.slice(stat.lastIndexOf(')') + 2).split(' ');
    return after[19] ?? null; // champ 22 global = index 19 après le champ 3
  } catch {
    return null;
  }
}

/**
 * Vivacité par pid + procStart (spike S1/S4) : jamais la présence d'un fichier registre (stale possible).
 * "Incertain → vivant" : si le process existe mais que le registre n'a pas de procStart, on traite vivant.
 */
export function isLive(pid: number, procStart: string | number | null | undefined): boolean {
  const actual = readProcStart(pid);
  if (actual === null) return false; // process absent → mort
  if (procStart === null || procStart === undefined) return true; // incertain → vivant
  return String(procStart) === actual; // procStart discordant → pid recyclé → stale → mort
}

function normalizeStatus(v: unknown): SessionRegistryStatus | null {
  return v === 'busy' || v === 'idle' || v === 'waiting' ? v : null;
}

function toEntry(r: Record<string, unknown>): RegistryEntry | null {
  const pid = typeof r.pid === 'number' ? r.pid : Number(r.pid);
  if (!Number.isInteger(pid)) return null;
  const procStart = r.procStart === undefined || r.procStart === null ? null : String(r.procStart);
  return {
    claudeSessionId: typeof r.sessionId === 'string' ? r.sessionId : null,
    cwd: typeof r.cwd === 'string' ? r.cwd : null,
    pid,
    procStart,
    status: normalizeStatus(r.status),
    waitingFor: typeof r.waitingFor === 'string' ? r.waitingFor : null,
    live: isLive(pid, procStart),
  };
}

/**
 * Lit toutes les entrées de `sessionsDir`. Tolérant aux fichiers en cours d'écriture / JSON invalide
 * (ignorés). Dossier absent → tableau vide (pas une erreur).
 */
export function readRegistry(sessionsDir: string): RegistryEntry[] {
  let files: string[];
  try {
    files = readdirSync(sessionsDir);
  } catch {
    return [];
  }
  const out: RegistryEntry[] = [];
  for (const f of files) {
    if (!f.endsWith('.json')) continue;
    try {
      const r = JSON.parse(readFileSync(join(sessionsDir, f), 'utf8')) as Record<string, unknown>;
      const entry = toEntry(r);
      if (entry) out.push(entry);
    } catch {
      /* fichier en cours d'écriture / JSON invalide : ignoré */
    }
  }
  return out;
}

/**
 * Entrée registre correspondant à un pid donné (le CLI nomme son fichier <pid>.json, mais on
 * matche sur le champ `pid` pour ne pas dépendre du nommage). null si absente/illisible.
 */
export function findByPid(sessionsDir: string, pid: number): RegistryEntry | null {
  return readRegistry(sessionsDir).find((e) => e.pid === pid) ?? null;
}
