/**
 * Minimal POSIX path utilities — works in browser and Bun without depending
 * on Node's built-in `node:path`. Only handles forward-slash absolute paths.
 */

/**
 * Normalize a POSIX absolute path: collapse `//`, resolve `.` and `..`,
 * preserve trailing slash semantics. Returns `'/'` for `''`/`'/'` input.
 *
 * Throws nothing on invalid input — sanitization is the caller's job
 * (`validatePath` in `server/path-policy.ts` does that).
 */
export function posixNormalize(path: string): string {
  if (path === '' || path === '/') return '/';
  const isAbs = path.startsWith('/');
  const segments = path.split('/');
  const stack: string[] = [];
  for (const seg of segments) {
    if (seg === '' || seg === '.') continue;
    if (seg === '..') {
      if (stack.length > 0 && stack[stack.length - 1] !== '..') {
        stack.pop();
      } else if (!isAbs) {
        stack.push('..');
      }
      // For absolute paths, `..` past root is silently dropped.
      continue;
    }
    stack.push(seg);
  }
  const joined = stack.join('/');
  if (isAbs) return '/' + joined;
  return joined === '' ? '.' : joined;
}

/** Join path segments with POSIX semantics. */
export function posixJoin(...parts: string[]): string {
  if (parts.length === 0) return '.';
  const joined = parts.filter((p) => p !== '').join('/');
  return posixNormalize(joined);
}

/** Return the directory portion of a path, or `'/'` for root. */
export function posixDirname(path: string): string {
  const normalized = posixNormalize(path);
  if (normalized === '/') return '/';
  const lastSlash = normalized.lastIndexOf('/');
  if (lastSlash === 0) return '/';
  if (lastSlash === -1) return '.';
  return normalized.slice(0, lastSlash);
}

/** Return the file/dir name (last component). */
export function posixBasename(path: string): string {
  const normalized = posixNormalize(path);
  if (normalized === '/') return '';
  const lastSlash = normalized.lastIndexOf('/');
  return lastSlash === -1 ? normalized : normalized.slice(lastSlash + 1);
}

/**
 * Decode percent-escapes BEFORE security checks so attackers can't
 * smuggle `..` via `%2e%2e`. Decodes UTF-8 sequences where possible;
 * leaves invalid sequences untouched (the caller still rejects them).
 */
export function decodePercentEscapes(s: string): string {
  if (!s.includes('%')) return s;
  try {
    return decodeURIComponent(s);
  } catch {
    // Invalid percent-encoding — leave as-is so policy rejects on the raw form.
    return s;
  }
}

/**
 * Predicate: does `child` lie under `root` after both are normalized?
 * Both must be absolute. `root` is treated as a directory boundary.
 */
export function isPathInside(child: string, root: string): boolean {
  const c = posixNormalize(child);
  const r = posixNormalize(root);
  if (r === '/') return c.startsWith('/');
  // Ensure boundary alignment so `/foo` is NOT inside `/foobar`.
  return c === r || c.startsWith(r + '/');
}
