import {
  readFileSync,
  writeFileSync,
  existsSync,
  mkdirSync,
  copyFileSync,
  renameSync,
  readdirSync,
  unlinkSync,
  statSync,
} from 'node:fs';
import { dirname, join, relative, resolve } from 'node:path';

const MAX_BACKUPS = 3;

/**
 * Atomic write: write to temp file, then rename.
 * Creates parent directories as needed.
 */
export function atomicWrite(filePath: string, content: string): void {
  mkdirSync(dirname(filePath), { recursive: true });
  const tmpPath = `${filePath}.tmp-${process.pid.toString()}`;
  writeFileSync(tmpPath, content, 'utf-8');
  renameSync(tmpPath, filePath);
}

/**
 * Create a backup of a file before overwriting.
 * Rotates backups to keep at most MAX_BACKUPS.
 */
export function backupFile(filePath: string): string | null {
  if (!existsSync(filePath)) return null;
  const timestamp = new Date().toISOString().replace(/[:.]/g, '').replace('T', '-').slice(0, 19);
  const backupPath = `${filePath}.bak-${timestamp}`;
  copyFileSync(filePath, backupPath);
  rotateBackups(filePath);
  return backupPath;
}

function rotateBackups(filePath: string): void {
  const dir = dirname(filePath);
  const baseName = filePath.split('/').pop() ?? '';
  const prefix = `${baseName}.bak-`;

  try {
    const backups = readdirSync(dir)
      .filter((f: string) => f.startsWith(prefix))
      .sort()
      .reverse();

    for (let i = MAX_BACKUPS; i < backups.length; i++) {
      const backup = backups[i];
      if (backup !== undefined) {
        unlinkSync(join(dir, backup));
      }
    }
  } catch {
    // Non-fatal: backup rotation failure doesn't block writes
  }
}

/**
 * Sync a source directory to a target, with optional preserve paths.
 * Replaces the rsync/cp logic from install.sh.
 */
export function syncDirectory(
  source: string,
  target: string,
  options: { preserve?: string[]; excludeGit?: boolean } = {},
): void {
  // Guard: source and target are the same directory — nothing to sync
  if (resolve(source) === resolve(target)) return;

  const preserveSet = new Set(options.preserve ?? []);

  // Collect files from source
  function copyRecursive(src: string, dest: string, relBase: string): void {
    if (!existsSync(src)) return;

    const stat = statSync(src);
    if (stat.isDirectory()) {
      const relPath = relative(relBase, src);
      const dirName = relPath.split('/').pop() ?? '';

      // Skip any .git directory (top-level or nested, e.g. sources/agent-skills/.git)
      if (options.excludeGit && (dirName === '.git' || relPath.includes('/.git'))) return;

      // Skip preserved paths at top level
      if (preserveSet.has(relPath) && existsSync(dest)) return;

      mkdirSync(dest, { recursive: true });
      for (const entry of readdirSync(src)) {
        copyRecursive(join(src, entry), join(dest, entry), relBase);
      }
    } else {
      const relPath = relative(relBase, src);

      // Skip files inside .git directories
      if (options.excludeGit && relPath.includes('/.git/')) return;

      // Skip preserved files at top level
      if (preserveSet.has(relPath) && existsSync(dest)) return;

      mkdirSync(dirname(dest), { recursive: true });
      copyFileSync(src, dest);
    }
  }

  copyRecursive(source, target, source);
}

/**
 * Safely read a file, returning null if it doesn't exist.
 */
export function safeReadFile(filePath: string): string | null {
  try {
    return readFileSync(filePath, 'utf-8');
  } catch {
    return null;
  }
}
