import { pathToFileURL } from "node:url";
import { buildRouter, type Router } from "@wrnexus/router";
import {
  emptyCatalog,
  mergeCatalogs,
  type AuthzCatalog,
  type AuthzModule,
  type CatalogSource,
} from "@wrnexus/authz";

/** Imports one declaration module. Defaults to a raw `import()`; the hot-reload
 * call site passes `loadModule` instead (see the note below on why). */
export type AuthzImporter = (file: string) => Promise<{ default?: AuthzModule }>;

const rawImport: AuthzImporter = (file) =>
  import(pathToFileURL(file).href) as Promise<{ default?: AuthzModule }>;

/**
 * Load and merge every `app/authz/*.ts` declaration. Conflicts throw so a
 * misconfigured catalog fails the boot rather than silently changing who can
 * do what. An app with no `app/authz/` directory gets an empty catalog rather
 * than an error, since not every app uses permissions.
 *
 * Accepts either an app directory — the original, standalone shape, still
 * used by the test suite and by any caller without a router on hand — or an
 * already-built `Router`. `startServer` passes its own router (built once at
 * `:315` with the full `componentDirs`/`externalRoutes`/`middlewareFiles`
 * options) to avoid a second, redundant filesystem scan of the whole `app/`
 * tree on every dev boot and on every hot reload of an `app/authz/*.ts` file.
 *
 * `importModule` defaults to a raw dynamic `import()`, correct for the
 * initial boot. On a HOT reload, the caller must instead pass `loadModule`
 * (from `./pipeline.ts`): Bun caches local TS/JS modules by filesystem path
 * and ignores query strings, so re-`import()`-ing the same absolute path
 * after an edit silently returns the stale, already-cached module —
 * `loadModule` is what copies an edited file to a versioned sibling path
 * specifically to defeat that cache.
 */
export async function loadAppAuthzCatalog(
  appDirOrRouter: string | Router,
  importModule: AuthzImporter = rawImport,
): Promise<AuthzCatalog> {
  const router = typeof appDirOrRouter === "string" ? buildRouter(appDirOrRouter) : appDirOrRouter;
  if (!router.authz.length) return emptyCatalog();
  const sources: CatalogSource[] = [];
  for (const entry of router.authz) {
    // buildRouter already skips *.gen.ts, so only real declarations arrive here.
    // A file that throws on import is intentionally NOT caught here: it is the
    // same failure class as a genuine conflict (a broken/misconfigured catalog),
    // and letting it propagate fails the boot loudly instead of silently
    // producing a partial catalog. Do not "helpfully" wrap this in a try/catch.
    const imported = await importModule(entry.file);
    if (!imported.default) {
      console.warn(`[wrnexus] authz declaration ${entry.file} has no default export; skipping.`);
      continue;
    }
    sources.push({ source: entry.file, module: imported.default });
  }
  return mergeCatalogs(sources);
}
