import { lstat, realpath } from 'node:fs/promises';
import path from 'node:path';

const realpathOrDeepestExisting = async (target: string): Promise<string> => {
  let current = target;
  const tail: string[] = [];
  for (;;) {
    try {
      const real = await realpath(current);
      return path.join(real, ...tail.reverse());
    } catch (error) {
      if ((error as { code?: string }).code !== 'ENOENT') throw error;
      const parent = path.dirname(current);
      if (parent === current) throw error;
      tail.push(path.basename(current));
      current = parent;
    }
  }
};

export const assertContainedRealPath = async (
  root: string,
  requested: string,
  opts: { forWrite?: boolean; label?: string } = {},
): Promise<string> => {
  const lexical = path.resolve(root, requested);
  const realRoot = await realpath(root);
  const resolved = await realpathOrDeepestExisting(lexical);
  if (resolved !== realRoot && !resolved.startsWith(`${realRoot}${path.sep}`)) {
    throw new Error(
      `Refusing to access path outside ${opts.label ?? 'root'}: ${requested}`,
    );
  }
  if (opts.forWrite) {
    const info = await lstat(lexical).catch(() => null);
    if (info?.isSymbolicLink()) {
      throw new Error(`Refusing to write through a symlink: ${requested}`);
    }
  }
  return resolved;
};
