/**
 * GitFlow Worktree — Create, remove, repair, list, validate.
 * Self-contained: imports only from local lib + node built-ins.
 */

import { existsSync, readFileSync, writeFileSync, readdirSync, statSync, mkdirSync } from 'fs';
import { join, dirname, relative, resolve, basename } from 'path';
import type { WorktreeInfo, GitFlowConfig } from './types.js';
import { execGit, execGitOrFail } from './git.js';
import { toForwardSlashes } from './paths.js';

export async function listWorktrees(cwd?: string): Promise<WorktreeInfo[]> {
  const result = await execGit(['worktree', 'list', '--porcelain'], cwd);
  if (result.exitCode !== 0) return [];

  const worktrees: WorktreeInfo[] = [];
  let current: Partial<WorktreeInfo> = {};

  for (const line of result.stdout.split('\n')) {
    if (line.startsWith('worktree ')) {
      if (current.path) worktrees.push(current as WorktreeInfo);
      current = { path: line.slice(9), isBare: false, isPrunable: false, branch: '', head: '' };
    } else if (line.startsWith('HEAD ')) {
      current.head = line.slice(5);
    } else if (line.startsWith('branch ')) {
      current.branch = line.slice(7).replace('refs/heads/', '');
    } else if (line === 'bare') {
      current.isBare = true;
    } else if (line === 'prunable') {
      current.isPrunable = true;
    }
  }
  if (current.path) worktrees.push(current as WorktreeInfo);

  return worktrees;
}

export async function createWorktree(
  branch: string,
  targetPath: string,
  baseBranch: string,
  cwd?: string,
): Promise<void> {
  mkdirSync(dirname(targetPath), { recursive: true });
  await execGitOrFail(
    ['worktree', 'add', '-b', branch, targetPath, `origin/${baseBranch}`],
    cwd,
  );
}

export async function removeWorktree(path: string, cwd?: string): Promise<void> {
  if (existsSync(path)) {
    await execGit(['worktree', 'remove', path, '--force'], cwd);
  }
  await execGit(['worktree', 'prune'], cwd);
}

export function getWorktreePath(branch: string, config: GitFlowConfig): string {
  const { structure } = config.worktrees;
  if (branch.startsWith('feature/')) return join(structure.features, branch.replace('feature/', ''));
  if (branch.startsWith('release/')) return join(structure.releases, branch.replace('release/', ''));
  if (branch.startsWith('hotfix/')) return join(structure.hotfixes, branch.replace('hotfix/', ''));
  return '';
}

export function validateWorktreePath(worktreePath: string): { valid: boolean; error?: string } {
  const name = basename(worktreePath);
  if (/^\d{2}-/.test(name) && name !== '01-Main' && name !== '02-Develop') {
    return {
      valid: false,
      error: `Numbered directory "${name}" forbidden. Use config paths: features/{name}, releases/v{version}, hotfixes/{name}`,
    };
  }
  return { valid: true };
}

export function repairWorktreePaths(bareDir: string): { repaired: number; details: string[] } {
  const worktreesDir = join(bareDir, 'worktrees');
  if (!existsSync(worktreesDir)) return { repaired: 0, details: [] };

  let repaired = 0;
  const details: string[] = [];

  const entries = readdirSync(worktreesDir);
  for (const wtName of entries) {
    const wtDir = join(worktreesDir, wtName);
    try {
      if (!statSync(wtDir).isDirectory()) continue;

      const gitdirFile = join(wtDir, 'gitdir');
      if (!existsSync(gitdirFile)) continue;

      const storedPath = readFileSync(gitdirFile, 'utf-8').trim();

      let resolvedPath: string;
      if (/^[A-Za-z]:[/\\]/.test(storedPath) || storedPath.startsWith('/')) {
        resolvedPath = storedPath.replace(/\\/g, '/');
      } else {
        resolvedPath = resolve(wtDir, storedPath);
      }

      const wtRealDir = dirname(resolvedPath);
      if (!existsSync(wtRealDir)) continue;

      const relGitdir = toForwardSlashes(relative(resolve(wtDir), join(resolve(wtRealDir), '.git')));
      if (storedPath !== relGitdir) {
        writeFileSync(gitdirFile, relGitdir + '\n');
        details.push(`${wtName}/gitdir → ${relGitdir}`);
        repaired++;
      }

      const wtGitFile = join(wtRealDir, '.git');
      if (existsSync(wtGitFile) && statSync(wtGitFile).isFile()) {
        const gitContent = readFileSync(wtGitFile, 'utf-8').trim();
        const gitPath = gitContent.replace('gitdir: ', '');
        const relBare = toForwardSlashes(relative(resolve(wtRealDir), resolve(wtDir)));

        if (gitPath !== relBare) {
          writeFileSync(wtGitFile, `gitdir: ${relBare}\n`);
          details.push(`${wtName}/.git → ${relBare}`);
          repaired++;
        }
      }
    } catch (err: unknown) {
      const code = (err as NodeJS.ErrnoException).code;
      if (code === 'EPERM' || code === 'EBUSY') {
        details.push(`${wtName} → skipped (${code}: file locked)`);
      } else {
        throw err;
      }
    }
  }

  return { repaired, details };
}

export function findBareDir(startDir?: string): string | null {
  let dir = startDir || process.cwd();
  const root = resolve('/');

  while (dir !== root) {
    const bare = join(dir, '.bare');
    if (existsSync(bare) && statSync(bare).isDirectory()) return bare;
    const parent = dirname(dir);
    if (parent === dir) break;
    dir = parent;
  }
  return null;
}

export async function preflightGitCheck(cwd?: string): Promise<{ ok: boolean; repaired: number }> {
  const quickTest = await execGit(['rev-parse', '--git-dir'], cwd);
  if (quickTest.exitCode === 0) return { ok: true, repaired: 0 };

  const bareDir = findBareDir(cwd);
  if (!bareDir) return { ok: false, repaired: 0 };

  const { repaired } = repairWorktreePaths(bareDir);

  const verifyTest = await execGit(['rev-parse', '--git-dir'], cwd);
  return { ok: verifyTest.exitCode === 0, repaired };
}

export async function cloneBare(url: string, bareDir: string): Promise<void> {
  mkdirSync(dirname(bareDir), { recursive: true });
  await execGitOrFail(['clone', '--bare', url, bareDir]);

  await execGitOrFail(['config', 'remote.origin.fetch', '+refs/heads/*:refs/remotes/origin/*'], bareDir);
  await execGit(['fetch', 'origin'], bareDir);
}
