// sysutil/managedProcesses.ts
// Scope: system-owned tracking for child processes launched by Nodarama Verbatim.
// Safety rule: never kill arbitrary terminals or ports; only registered owned PIDs.

import { spawn, type ChildProcess } from "node:child_process";
import { existsSync } from "node:fs";
import { promises as fs } from "node:fs";
import path from "node:path";
import { logSystem } from "../playbackCore";
import {
  TEMP_STATE_DIR
} from "../pathList";

const REGISTRY_PATH = path.join(TEMP_STATE_DIR, "managed-processes.json");

type ManagedProcessRecord = {
  id: string;
  label: string;
  pid: number;
  command: string;
  args: string[];
  port?: number;
  startedAt: number;
};

function makeId(label: string, pid: number): string {
  return `${String(label || "process").replace(/[^a-z0-9]+/gi, "_").replace(/^_+|_+$/g, "").toLowerCase()}_${pid}`;
}

async function readRegistry(): Promise<ManagedProcessRecord[]> {
  try {
    const raw = await fs.readFile(REGISTRY_PATH, "utf8");
    const parsed = JSON.parse(raw);
    return Array.isArray(parsed) ? parsed.filter(item => Number(item?.pid) > 0) : [];
  } catch {
    return [];
  }
}

async function writeRegistry(records: ManagedProcessRecord[]): Promise<void> {
  await fs.mkdir(TEMP_STATE_DIR, { recursive: true });
  await fs.writeFile(REGISTRY_PATH, JSON.stringify(records, null, 2), "utf8");
}

function isProcessAlive(pid: number): boolean {
  try {
    process.kill(pid, 0);
    return true;
  } catch {
    return false;
  }
}

async function removeManagedProcess(pid: number): Promise<void> {
  const records = await readRegistry();
  await writeRegistry(records.filter(record => Number(record.pid) !== Number(pid)));
}

export async function registerManagedProcess(record: Omit<ManagedProcessRecord, "id" | "startedAt"> & { startedAt?: number }): Promise<ManagedProcessRecord> {
  const next: ManagedProcessRecord = {
    ...record,
    id: makeId(record.label, record.pid),
    startedAt: Number(record.startedAt || Date.now())
  };

  const records = (await readRegistry()).filter(item => isProcessAlive(item.pid) && item.pid !== next.pid);
  records.push(next);
  await writeRegistry(records);
  logSystem("POWER", { event: "managed_process_registered", label: next.label, pid: next.pid, port: next.port || null });
  return next;
}

export async function launchManagedProcess(label: string, command: string, args: string[] = [], options: { port?: number; cwd?: string } = {}): Promise<ChildProcess> {
  const child = spawn(command, args, {
    cwd: options.cwd || ROOT,
    stdio: "ignore",
    windowsHide: true,
    shell: false
  });

  child.unref();

  if (!child.pid) {
    throw new Error("managed_process_pid_missing");
  }

  await registerManagedProcess({
    label,
    pid: child.pid,
    command,
    args,
    port: options.port
  });

  child.once("exit", () => {
    removeManagedProcess(child.pid || 0).catch(() => {});
  });

  return child;
}

async function stopPid(pid: number, label: string, forceAfterMs = 1200): Promise<{ pid: number; label: string; stopped: boolean; method: string; error?: string }> {
  if (!isProcessAlive(pid)) {
    return { pid, label, stopped: true, method: "already_stopped" };
  }

  try {
    process.kill(pid, "SIGINT");
  } catch {}

  await new Promise(resolve => setTimeout(resolve, forceAfterMs));

  if (!isProcessAlive(pid)) {
    return { pid, label, stopped: true, method: "sigint" };
  }

  try {
    if (process.platform === "win32") {
      await new Promise<void>(resolve => {
        const killer = spawn("taskkill", ["/PID", String(pid), "/T", "/F"], { stdio: "ignore", windowsHide: true });
        killer.once("exit", () => resolve());
        killer.once("error", () => resolve());
      });
    } else {
      process.kill(pid, "SIGTERM");
    }
  } catch (err: any) {
    return { pid, label, stopped: false, method: "force", error: err?.message || String(err) };
  }

  await new Promise(resolve => setTimeout(resolve, 300));
  return { pid, label, stopped: !isProcessAlive(pid), method: "force" };
}

export async function shutdownManagedProcesses(): Promise<{ ok: boolean; stopped: any[]; remaining: ManagedProcessRecord[] }> {
  const records = await readRegistry();
  const liveRecords = records.filter(record => isProcessAlive(record.pid));
  const stopped: any[] = [];

  for (const record of liveRecords) {
    const result = await stopPid(record.pid, record.label);
    stopped.push(result);
    logSystem("POWER", { event: "managed_process_stop", ...result });
  }

  const remaining = liveRecords.filter(record => isProcessAlive(record.pid));
  await writeRegistry(remaining);

  return {
    ok: remaining.length === 0,
    stopped,
    remaining
  };
}

export async function listManagedProcesses(): Promise<ManagedProcessRecord[]> {
  const records = await readRegistry();
  const live = records.filter(record => isProcessAlive(record.pid));
  if (live.length !== records.length || !existsSync(REGISTRY_PATH)) {
    await writeRegistry(live);
  }
  return live;
}
