'use strict';

const fs = require('fs');
const path = require('path');
const { execFileSync } = require('child_process');

function fail(error, status) {
  return { ok: false, status: status || 400, error };
}

function gitText(cwd, args) {
  return execFileSync('git', args, {
    cwd,
    encoding: 'utf8',
    stdio: ['ignore', 'pipe', 'pipe'],
    timeout: 5000,
  }).trim();
}

function firstWorktreePath(porcelain) {
  for (const line of String(porcelain || '').split('\n')) {
    if (line.startsWith('worktree ')) return line.slice('worktree '.length).trim();
  }
  return '';
}

function realpathOrResolve(p) {
  try {
    return fs.realpathSync(p);
  } catch (_) {
    return path.resolve(p);
  }
}

// resolveWorktreeRepoRoot does 2 synchronous git execs (rev-parse --show-toplevel +
// worktree list --porcelain) plus realpath/stat. It's hit on the worktree API hot path
// (~per request), and a CPU profile showed these synchronous execFileSync calls blocking
// the event loop. The repo root for a given cwd is invariant (always the primary checkout,
// regardless of worktrees being added/removed), so cache successful resolutions briefly.
const _repoRootCache = new Map(); // raw cwd -> { at, result }
const _REPO_ROOT_CACHE_TTL_MS = Number(process.env.CTM_WORKTREE_REPO_ROOT_CACHE_TTL_MS || 60000);

function resolveWorktreeRepoRoot(cwd) {
  if (typeof cwd !== 'string' || !cwd.trim()) return fail('cwd is required');
  const raw = cwd.trim();
  const cached = _repoRootCache.get(raw);
  if (cached && (Date.now() - cached.at) < _REPO_ROOT_CACHE_TTL_MS) return cached.result;
  if (raw.includes('~')) {
    return fail('cwd must be an absolute path with no `~` (the literal-tilde is the source of ghost worktrees).');
  }
  if (!path.isAbsolute(raw)) return fail('cwd must be an absolute path');

  let realCwd;
  try {
    realCwd = fs.realpathSync(raw);
  } catch (_) {
    return fail('cwd does not exist');
  }

  let stat;
  try {
    stat = fs.statSync(realCwd);
  } catch (_) {
    return fail('cwd does not exist');
  }
  if (!stat.isDirectory()) return fail('cwd must be a directory');

  let topLevel;
  try {
    topLevel = gitText(realCwd, ['rev-parse', '--show-toplevel']);
  } catch (_) {
    return fail('cwd must be inside a Git work tree');
  }
  if (!topLevel) return fail('cwd must be inside a Git work tree');

  let primaryRoot = '';
  try {
    primaryRoot = firstWorktreePath(gitText(realCwd, ['worktree', 'list', '--porcelain']));
  } catch (_) {
    primaryRoot = '';
  }

  const result = {
    ok: true,
    cwd: realpathOrResolve(primaryRoot || topLevel),
    requestedCwd: realCwd,
  };
  // Cache only successful resolutions (a failing cwd may be created later). Bound the map.
  if (_repoRootCache.size > 200) _repoRootCache.clear();
  _repoRootCache.set(raw, { at: Date.now(), result });
  return result;
}

function resolveWorktreeRepoRootWithFallback(cwd, fallbackCwd) {
  const primary = resolveWorktreeRepoRoot(cwd);
  if (primary.ok) return { ...primary, fallbackUsed: false };

  const fallback = resolveWorktreeRepoRoot(fallbackCwd);
  if (!fallback.ok) return primary;

  return {
    ...fallback,
    fallbackUsed: true,
    fallbackReason: primary.error,
    fallbackStatus: primary.status || 400,
    requestedCwd: primary.requestedCwd || cwd,
  };
}

module.exports = {
  resolveWorktreeRepoRoot,
  resolveWorktreeRepoRootWithFallback,
};
