import { readFileSync, unlinkSync } from 'node:fs';

// Killing an `opencode serve` that outlived the sidecar that started it.
//
// Its own file rather than part of `index.ts` for one practical reason: `index.ts` spawns a serve and
// dials the API at module scope, so importing it from a test would start a real one. This has no side
// effects until called.

/**
 * Does this pid still look like the serve we started?
 *
 * The pidfile's one real hazard is pid REUSE: the recorded process died, the OS handed its number to
 * something unrelated, and we would then SIGTERM an innocent bystander — possibly the user's editor.
 * Checking the command line first makes that impossible, and costs one `ps`.
 *
 * Separated from the `ps` call so it can be tested against real output rather than an invented format.
 */
export function looksLikeOpenCodeServe(psOutput: string): boolean {
  // Command and subcommand, not "contains the word serve". Both platforms print
  // `/abs/path/to/opencode serve --port N --hostname 127.0.0.1` with no leading whitespace (verified on
  // macOS 26 and on alpha). Searching the whole line for `serve` would match an `opencode run` whose
  // PROMPT happened to contain the word — and a run is precisely what must not be swept.
  const [command, subcommand] = psOutput.trim().split(/\s+/);
  return !!command && command.includes('opencode') && subcommand === 'serve';
}

/**
 * Kill the serve this sidecar started last time, if it outlived us.
 *
 * The `/proc` sweep in `index.ts` is Linux-only and therefore a no-op on the Mac, where orphaned serves
 * simply accumulated — one per unclean exit, each holding a port. This is the portable half, and it is
 * also the more precise one: a pid we wrote down ourselves needs no cwd guard to prove it is ours, which
 * is the part `ps` cannot answer portably (macOS would need `lsof`). An `opencode serve` the user
 * started by hand is never in this file and so is never touched.
 */
export function sweepRecordedServe(pidFile: string): void {
  let recorded: number;
  try {
    recorded = Number(readFileSync(pidFile, 'utf8').trim());
  } catch {
    return; // no pidfile — first run, or a clean exit removed it
  }
  if (!Number.isInteger(recorded) || recorded <= 0) return;

  try {
    const ps = Bun.spawnSync(['ps', '-p', String(recorded), '-o', 'command=']);
    if (!ps.success) return; // pid is gone; nothing to sweep
    if (!looksLikeOpenCodeServe(ps.stdout.toString())) return; // pid was reused — not ours to kill

    process.kill(recorded, 'SIGTERM');
    console.log(`[opencode] swept recorded serve pid ${recorded}`);
  } catch {
    /* vanished mid-check, or no permission — leave it to the /proc sweep on Linux */
  } finally {
    try {
      unlinkSync(pidFile);
    } catch {
      /* already gone */
    }
  }
}
