/**
 * JS-порт `backend/app/core/auth.py::is_path_allowed`.
 *
 * Backend — source of truth: глобы из `auth/roles.yaml` парсятся в regex
 * по тем же правилам что и здесь. Фронт использует это только для UI-gating
 * (фильтр top-nav, route-guard перед навигацией) — реальная проверка
 * по-прежнему происходит в backend middleware на каждый /api/v1/* request.
 *
 * Семантика glob (mirror of `_glob_to_regex`):
 *   `/**`         → matches everything (admin scope)
 *   `/foo/**`     → matches `/foo`, `/foo/`, `/foo/bar`, `/foo/bar/baz`, …
 *   `/foo/*`      → matches one segment after `/foo/` (no slashes)
 *   `/foo`        → exact match only
 *
 * Path allowed iff (a) matches >=1 allow-pattern AND (b) doesn't match any
 * deny-pattern. Deny final (admin uses empty deny → всегда побеждает).
 */

const REGEX_META = /[.+^${}()|[\]\\]/g;

function escapeRegex(s: string): string {
  return s.replace(REGEX_META, "\\$&");
}

function globToRegex(pattern: string): RegExp {
  // Sentinels — никогда не встречаются в реальном path или в glob-метасимволах.
  const dstar = "\x00DSTAR\x00";
  const sstar = "\x00SSTAR\x00";

  const work = pattern.replace(/\*\*/g, dstar).replace(/\*/g, sstar);
  let regex = escapeRegex(work);
  regex = regex.replace(new RegExp(escapeRegex(dstar), "g"), ".*");
  regex = regex.replace(new RegExp(escapeRegex(sstar), "g"), "[^/]*");

  // Special-case `/foo/**` → также матчит `/foo` (без trailing-сегмента),
  // чтобы `/admin/**` покрывал bare `/admin` request — что и ждут callers.
  if (regex.endsWith("/.*")) {
    regex = regex.slice(0, -"/.*".length) + "(?:/.*)?";
  }
  return new RegExp(`^${regex}$`);
}

// Кэш скомпилированных regex — UI часто гоняет isPathAllowed на каждый рендер
// nav. patterns массив маленький и стабильный per-session, поэтому Map по
// pattern-строке достаточно.
const REGEX_CACHE = new Map<string, RegExp>();

function compileGlob(pattern: string): RegExp {
  const cached = REGEX_CACHE.get(pattern);
  if (cached) return cached;
  const compiled = globToRegex(pattern);
  REGEX_CACHE.set(pattern, compiled);
  return compiled;
}

/**
 * Mirror of backend `is_path_allowed(role, path)`.
 *
 * @param allowedPaths — глобы из `/api/v1/me` `allowed_paths`
 * @param denyPaths   — глобы из `/api/v1/me` `deny_paths`
 * @param path        — путь который пытаемся открыть (например `/admin/scrape`)
 *
 * Note: role-аргумент не нужен — allow/deny уже резолвлены backend'ом для
 * текущего юзера и приехали в /me. Этот хелпер чистый list-matcher.
 */
export function isPathAllowed(
  allowedPaths: readonly string[],
  denyPaths: readonly string[],
  path: string,
): boolean {
  // Deny overrides allow — даже если path в allowed, deny-match блокирует.
  for (const p of denyPaths) {
    if (compileGlob(p).test(path)) return false;
  }
  for (const p of allowedPaths) {
    if (compileGlob(p).test(path)) return true;
  }
  return false;
}
