/**
 * src/http/git-path.ts (w4-commit-push) — realpath-based path containment.
 *
 * Extracted so the git-write engine (git-ops.ts) shares ONE containment routine
 * instead of duplicating the M2 pattern from worktrees.ts. Following the plan,
 * only the two primitives live here; refactoring worktrees.ts onto them is a
 * deferred follow-up (out of this task's lane).
 *
 * Security (M2 / SEC-H3): a path is proven contained by realpath-resolving BOTH
 * the base and the candidate (symlinks followed) and then doing a prefix compare.
 * This defeats `../` traversal AND pre-planted-symlink escapes — the lighter
 * three-prong isValidGitDir check at the route does NOT follow symlinks, so this
 * is the defense-in-depth backstop for every untrusted file path.
 */

import fs from 'node:fs/promises'
import path from 'node:path'

/**
 * Resolve symlinks on the longest existing prefix of `target`, re-appending any
 * not-yet-existing trailing segments. Lets us canonicalise a path that may not
 * exist on disk (e.g. a staged deletion whose file is already gone) without it
 * having to exist. Mirrors worktrees.ts resolveRealPath (M2). Never throws.
 */
export async function resolveRealPath(target: string): Promise<string> {
  let current = path.resolve(target)
  const tail: string[] = []
  for (;;) {
    try {
      const real = await fs.realpath(current)
      return tail.length === 0 ? real : path.join(real, ...tail.slice().reverse())
    } catch {
      const parent = path.dirname(current)
      if (parent === current) return path.resolve(target) // reached an unresolvable root
      tail.push(path.basename(current))
      current = parent
    }
  }
}

/**
 * True iff `realCandidate` is `realBase` itself or lives strictly inside it. Both
 * arguments must ALREADY be realpath-resolved (call resolveRealPath first). The
 * `realBase + path.sep` guard prevents a sibling-prefix escape (`/repo-evil` is
 * NOT inside `/repo`). Pure.
 */
export function isContained(realBase: string, realCandidate: string): boolean {
  return realCandidate === realBase || realCandidate.startsWith(realBase + path.sep)
}
