/**
 * lib/git.ts — Git helpers for all SmartStack Studio dev CLIs.
 * Ported from SmartStack.cli/src/mcp/utils/git.ts.
 *
 * All functions accept an optional `cwd` parameter (defaults to process.cwd()).
 * Errors are swallowed and return null/[] for most accessors — callers check
 * for null results and can add their own error handling if needed.
 */
import { exec } from 'node:child_process';
import { promisify } from 'node:util';
import path from 'node:path';
import { directoryExists } from './fs.js';

const execAsync = promisify(exec);

export class GitError extends Error {
  constructor(
    message: string,
    public readonly command: string,
    public readonly cwd?: string,
    public readonly cause?: Error,
  ) {
    super(message);
    this.name = 'GitError';
  }
}

export async function git(command: string, cwd?: string): Promise<string> {
  const options = cwd ? { cwd, maxBuffer: 10 * 1024 * 1024 } : { maxBuffer: 10 * 1024 * 1024 };
  try {
    const { stdout } = await execAsync(`git ${command}`, options);
    return stdout.trim();
  } catch (error) {
    const err = error instanceof Error ? error : new Error(String(error));
    const stderr = (error as { stderr?: string })?.stderr || '';
    throw new GitError(
      `Git command failed: git ${command}${stderr ? ` - ${stderr.trim()}` : ''}`,
      command,
      cwd,
      err,
    );
  }
}

export async function isGitRepo(cwd?: string): Promise<boolean> {
  const gitDir = path.join(cwd || process.cwd(), '.git');
  return directoryExists(gitDir);
}

export async function getCurrentBranch(cwd?: string): Promise<string | null> {
  try {
    return await git('branch --show-current', cwd);
  } catch {
    return null;
  }
}

export async function isClean(cwd?: string): Promise<boolean> {
  try {
    const status = await git('status --porcelain', cwd);
    return status === '';
  } catch {
    return false;
  }
}

export async function getChangedFiles(cwd?: string): Promise<string[]> {
  try {
    const status = await git('status --porcelain', cwd);
    if (!status) return [];
    return status
      .split('\n')
      .map((line) => line.substring(3).trim())
      .filter(Boolean);
  } catch {
    return [];
  }
}

export async function getStagedFiles(cwd?: string): Promise<string[]> {
  try {
    const status = await git('diff --cached --name-only', cwd);
    if (!status) return [];
    return status.split('\n').filter(Boolean);
  } catch {
    return [];
  }
}

export async function branchExists(branch: string, cwd?: string): Promise<boolean> {
  try {
    await git(`rev-parse --verify ${branch}`, cwd);
    return true;
  } catch {
    return false;
  }
}

export async function getFileFromBranch(
  branch: string,
  filePath: string,
  cwd?: string,
): Promise<string | null> {
  try {
    return await git(`show ${branch}:${filePath}`, cwd);
  } catch {
    return null;
  }
}

export async function getDiff(
  fromBranch: string,
  toBranch: string,
  filePath?: string,
  cwd?: string,
): Promise<string> {
  try {
    const pathArg = filePath ? ` -- ${filePath}` : '';
    return await git(`diff ${fromBranch}...${toBranch}${pathArg}`, cwd);
  } catch {
    return '';
  }
}

export async function getRemoteUrl(
  remote: string = 'origin',
  cwd?: string,
): Promise<string | null> {
  try {
    return await git(`remote get-url ${remote}`, cwd);
  } catch {
    return null;
  }
}
