import { isAbsolute, relative, sep } from "node:path";

export interface PathSemantics {
  relative(from: string, to: string): string;
  isAbsolute(path: string): boolean;
  sep: string;
}

const NATIVE_PATH_SEMANTICS: PathSemantics = { relative, isAbsolute, sep };

/**
 * Checks containment using the host path rules.  In particular, this avoids
 * assuming that the directory separator is `/`, which is false on Windows.
 * `semantics` is injectable so the Windows rules can be exercised on CI hosts
 * that do not themselves run Windows.
 */
export function isPathWithinRoot(
  root: string,
  candidate: string,
  semantics: PathSemantics = NATIVE_PATH_SEMANTICS,
): boolean {
  const relativePath = semantics.relative(root, candidate);
  return relativePath !== ""
    && relativePath !== ".."
    && !relativePath.startsWith(`..${semantics.sep}`)
    && !semantics.isAbsolute(relativePath);
}
