/**
 * GitFlow Git Operations — Thin async wrappers around git commands.
 * Self-contained: only uses node built-ins.
 */

import { execFile } from 'child_process';
import type { AheadBehind, GitStatus } from './types.js';

export interface ExecGitResult {
  stdout: string;
  stderr: string;
  exitCode: number;
}

export function execGit(args: string[], cwd?: string): Promise<ExecGitResult> {
  return new Promise((resolve) => {
    execFile('git', args, {
      cwd,
      encoding: 'utf-8',
      timeout: 30_000,
      maxBuffer: 10 * 1024 * 1024,
    }, (error, stdout, stderr) => {
      resolve({
        stdout: (stdout || '').trim(),
        stderr: (stderr || '').trim(),
        exitCode: error ? (typeof (error as NodeJS.ErrnoException).code === 'number' ? (error as NodeJS.ErrnoException).code as unknown as number : 1) : 0,
      });
    });
  });
}

export async function execGitOrFail(args: string[], cwd?: string): Promise<string> {
  const result = await execGit(args, cwd);
  if (result.exitCode !== 0) {
    throw new Error(`git ${args[0]} failed: ${result.stderr || result.stdout}`);
  }
  return result.stdout;
}

export async function getCurrentBranch(cwd?: string): Promise<string> {
  return (await execGit(['branch', '--show-current'], cwd)).stdout || 'HEAD';
}

export async function fetch(remote = 'origin', cwd?: string): Promise<void> {
  await execGit(['fetch', remote, '--quiet'], cwd);
}

export async function fetchAll(cwd?: string): Promise<void> {
  await execGit(['fetch', '--all', '--quiet'], cwd);
}

export async function push(branch: string, cwd?: string, setUpstream = false, force = false): Promise<ExecGitResult> {
  const args = ['push'];
  if (setUpstream) args.push('-u');
  if (force) args.push('--force-with-lease');
  args.push('origin', branch);
  return execGit(args, cwd);
}

export async function getAheadBehind(branch: string, remote: string, cwd?: string): Promise<AheadBehind> {
  const aheadResult = await execGit(['rev-list', '--count', `${remote}..${branch}`], cwd);
  const behindResult = await execGit(['rev-list', '--count', `${branch}..${remote}`], cwd);
  return {
    ahead: parseInt(aheadResult.stdout, 10) || 0,
    behind: parseInt(behindResult.stdout, 10) || 0,
  };
}

export async function getStatus(cwd?: string): Promise<GitStatus> {
  const result = await execGit(['status', '--porcelain'], cwd);
  const lines = result.stdout.split('\n').filter(Boolean);

  const staged: string[] = [];
  const modified: string[] = [];
  const untracked: string[] = [];

  for (const line of lines) {
    const index = line[0];
    const working = line[1];
    const file = line.slice(3);

    if (index === '?') {
      untracked.push(file);
    } else {
      if (index !== ' ' && index !== '?') staged.push(file);
      if (working !== ' ' && working !== '?') modified.push(file);
    }
  }

  return { staged, modified, untracked, dirty: lines.length > 0 };
}

export async function getLog(count: number, format?: string, cwd?: string): Promise<string[]> {
  const fmt = format || '%H|%aI|%s';
  const result = await execGit(['log', `--format=${fmt}`, `-${count}`], cwd);
  return result.stdout.split('\n').filter(Boolean);
}

export async function createTag(name: string, message: string, cwd?: string): Promise<void> {
  await execGitOrFail(['tag', '-a', name, '-m', message], cwd);
}

export async function pushTag(name: string, cwd?: string): Promise<void> {
  await execGitOrFail(['push', 'origin', name], cwd);
}

export async function rebase(onto: string, cwd?: string): Promise<{ success: boolean; conflicts: string[] }> {
  const result = await execGit(['rebase', onto], cwd);
  if (result.exitCode === 0) {
    return { success: true, conflicts: [] };
  }
  const status = await execGit(['diff', '--name-only', '--diff-filter=U'], cwd);
  return { success: false, conflicts: status.stdout.split('\n').filter(Boolean) };
}

export async function abortRebase(cwd?: string): Promise<ExecGitResult> {
  return execGit(['rebase', '--abort'], cwd);
}

export async function abortMerge(cwd?: string): Promise<ExecGitResult> {
  return execGit(['merge', '--abort'], cwd);
}

export async function mergeNoFf(branch: string, message: string, cwd?: string): Promise<void> {
  await execGitOrFail(['merge', branch, '--no-ff', '-m', message], cwd);
}

export async function checkout(branch: string, cwd?: string): Promise<void> {
  await execGitOrFail(['checkout', branch], cwd);
}

export async function pull(remote = 'origin', branch?: string, cwd?: string): Promise<ExecGitResult> {
  const args = ['pull', remote];
  if (branch) args.push(branch);
  return execGit(args, cwd);
}

export async function stash(cwd?: string): Promise<ExecGitResult> {
  return execGit(['stash'], cwd);
}

export async function stashPop(cwd?: string): Promise<ExecGitResult> {
  return execGit(['stash', 'pop'], cwd);
}

export async function branchExists(branch: string, remote = false, cwd?: string): Promise<boolean> {
  if (remote) {
    const result = await execGit(['ls-remote', '--heads', 'origin', branch], cwd);
    return result.stdout.length > 0;
  }
  const result = await execGit(['rev-parse', '--verify', branch], cwd);
  return result.exitCode === 0;
}

export async function isMerged(branch: string, into: string, cwd?: string): Promise<boolean> {
  const result = await execGit(['branch', '-r', '--merged', `origin/${into}`], cwd);
  return result.stdout.includes(`origin/${branch}`);
}

export async function getRemoteUrl(cwd?: string): Promise<string | null> {
  const result = await execGit(['remote', 'get-url', 'origin'], cwd);
  return result.exitCode === 0 ? result.stdout : null;
}

export async function detectInProgressOp(cwd?: string): Promise<'rebase' | 'merge' | 'cherry-pick' | null> {
  const gitDir = (await execGit(['rev-parse', '--git-dir'], cwd)).stdout;
  if (!gitDir) return null;

  const { existsSync } = await import('fs');
  const { join, isAbsolute } = await import('path');
  // `--git-dir` is absolute in a linked worktree, relative (".git") at the root.
  const base = isAbsolute(gitDir) ? gitDir : join(cwd ?? process.cwd(), gitDir);

  if (existsSync(join(base, 'rebase-merge')) || existsSync(join(base, 'rebase-apply'))) return 'rebase';
  if (existsSync(join(base, 'MERGE_HEAD'))) return 'merge';
  if (existsSync(join(base, 'CHERRY_PICK_HEAD'))) return 'cherry-pick';
  return null;
}

export async function isGitRepo(cwd?: string): Promise<boolean> {
  return (await execGit(['rev-parse', '--git-dir'], cwd)).exitCode === 0;
}

export async function getGitCommonDir(cwd?: string): Promise<string | null> {
  const result = await execGit(['rev-parse', '--git-common-dir'], cwd);
  return result.exitCode === 0 ? result.stdout : null;
}

export async function getDiffStats(base: string, cwd?: string): Promise<string> {
  return (await execGit(['diff', '--stat', base], cwd)).stdout;
}

export async function getDiffFiles(base: string, cwd?: string): Promise<string[]> {
  const result = await execGit(['diff', '--name-only', base], cwd);
  return result.stdout.split('\n').filter(Boolean);
}

export async function addFiles(files: string[], cwd?: string): Promise<void> {
  if (files.length === 0) return;
  await execGitOrFail(['add', ...files], cwd);
}

export async function commit(message: string, cwd?: string): Promise<ExecGitResult> {
  return execGit(['commit', '-m', message], cwd);
}

export async function deleteBranch(branch: string, remote = false, force = false, cwd?: string): Promise<ExecGitResult> {
  if (remote) {
    return execGit(['push', 'origin', '--delete', branch], cwd);
  }
  return execGit(['branch', force ? '-D' : '-d', branch], cwd);
}
