import { realpath } from "node:fs/promises";
import { isAbsolute, join, relative, sep } from "node:path";

export async function resolveInsideWorkspace(rootPath: string, relativePath: string | undefined): Promise<{ root: string; target: string; relativePath: string }> {
  const requested = normalizeRelativePath(relativePath);
  const root = await realpath(rootPath);
  const joined = join(root, requested);
  const target = await realpath(joined).catch((error: unknown) => {
    if (isNodeErrorWithCode(error, "ENOENT")) throw new Error("Path does not exist");
    throw error;
  });
  ensureInside(root, target);
  return { root, target, relativePath: requested };
}

export async function resolveParentInsideWorkspace(rootPath: string, relativePath: string): Promise<{ root: string; target: string; relativePath: string }> {
  const requested = normalizeRelativePath(relativePath);
  const root = await realpath(rootPath);
  const target = join(root, requested);
  ensureInside(root, target);
  return { root, target, relativePath: requested };
}

export function normalizeRelativePath(input: string | undefined): string {
  const value = input ?? "";
  if (value === "" || value === ".") return "";
  if (isAbsolute(value)) throw new Error("Absolute paths are not allowed");
  const parts = value.split(/[\\/]+/).filter((part) => part !== "" && part !== ".");
  if (parts.some((part) => part === "..")) throw new Error("Path traversal is not allowed");
  return parts.join("/");
}

function isNodeErrorWithCode(error: unknown, code: string): error is NodeJS.ErrnoException {
  return typeof error === "object" && error !== null && "code" in error && error.code === code;
}

function ensureInside(root: string, target: string): void {
  const rel = relative(root, target);
  if (rel === "") return;
  if (rel.startsWith("..") || isAbsolute(rel)) throw new Error("Path escapes workspace");
  if (sep !== "/" && rel.split(sep).includes("..")) throw new Error("Path escapes workspace");
}
