import { execFileSync } from 'node:child_process';
import { accessSync, constants } from 'node:fs';

export interface SpawnSpec {
  file: string;
  args: string[];
  env: NodeJS.ProcessEnv;
}

export interface SpawnOptions {
  command: 'claude' | 'bash';
  /** reprise d'une session existante (P2) : `--resume <id>`, `--fork-session` si fork. */
  resume?: { claudeSessionId: string; fork?: boolean };
  /** répertoires supplémentaires à relier dans une seule session (P6) : `--add-dir <path>` répété. */
  addDirs?: string[];
  /** chemin explicite du binaire `claude` (réglage UI) ; sinon résolution via PATH (`which`). */
  claudeBinPath?: string | null;
}

/** Diagnostic de résolution du binaire `claude` (exposé en lecture dans Réglages). */
export interface ClaudeBinDiagnostic {
  /** chemin résolu du binaire, ou null si introuvable. */
  path: string | null;
  /** 'configured' = réglage explicite ; 'path' = trouvé via PATH ; null = introuvable. */
  source: 'configured' | 'path' | null;
  /** true si le binaire est présent et exécutable. */
  ok: boolean;
}

let cachedClaudeBin: string | null = null;

/** Recherche `claude` dans le PATH (sans throw). null si absent. */
function findClaudeOnPath(): string | null {
  try {
    return execFileSync('which', ['claude'], { encoding: 'utf8' }).trim() || null;
  } catch {
    return null;
  }
}

function isExecutable(path: string): boolean {
  try {
    accessSync(path, constants.X_OK);
    return true;
  } catch {
    return false;
  }
}

/**
 * Résout le binaire `claude`. Si `configuredPath` est fourni (réglage UI), il est utilisé tel quel
 * (validé exécutable, message clair sinon) et JAMAIS mis en cache (modifiable à chaud). Sinon :
 * `which claude`, mis en cache. Un service systemd/launchd démarre avec un PATH minimal sans
 * ~/.local/bin → `which claude` y échoue ; d'où le réglage de chemin explicite (et le PATH figé par
 * `arboretum install`).
 */
export function resolveClaudeBin(configuredPath?: string | null): string {
  if (configuredPath) {
    if (!isExecutable(configuredPath)) {
      throw new Error(`Configured Claude CLI path is not executable: ${configuredPath}`);
    }
    return configuredPath;
  }
  if (cachedClaudeBin) return cachedClaudeBin;
  const found = findClaudeOnPath();
  if (!found) {
    throw new Error(
      'Claude Code CLI not found in PATH. Install it first: https://code.claude.com/docs/en/quickstart',
    );
  }
  cachedClaudeBin = found;
  return cachedClaudeBin;
}

/** Diagnostic non-throwing pour l'UI (Réglages) : recalculé à chaque appel, jamais caché. */
export function diagnoseClaudeBin(configuredPath?: string | null): ClaudeBinDiagnostic {
  if (configuredPath) {
    return { path: configuredPath, source: 'configured', ok: isExecutable(configuredPath) };
  }
  const found = findClaudeOnPath();
  return found ? { path: found, source: 'path', ok: true } : { path: null, source: null, ok: false };
}

/** Module volontairement abstrait : le plan B « BYO API key / Agent SDK » se brancherait ici. */
export function buildSpawnSpec(opts: SpawnOptions): SpawnSpec {
  const env: NodeJS.ProcessEnv = {
    ...process.env,
    TERM: 'xterm-256color',
    COLORTERM: 'truecolor',
  };
  if (opts.command === 'bash') {
    return { file: 'bash', args: ['--norc'], env };
  }
  const args: string[] = [];
  if (opts.resume) {
    // `--resume` doit toujours s'exécuter dans le cwd d'origine (garanti par l'appelant, spike S1).
    args.push('--resume', opts.resume.claudeSessionId);
    if (opts.resume.fork) args.push('--fork-session');
  }
  // Session de groupe : relie plusieurs repos/worktrees dans une seule session (P6).
  for (const dir of opts.addDirs ?? []) args.push('--add-dir', dir);
  return { file: resolveClaudeBin(opts.claudeBinPath), args, env };
}
