# RBAC Composition Template
#
# Copy to site/rbac.ncl and activate the features your site uses.
# Remove feature imports you don't need.
#
# Validate: nickel export site/rbac.ncl

let C     = import "rbac/contracts.ncl" in
let Roles = import "config/roles.ncl" in

let valid_role_names = Roles.roles |> std.array.map (fun r => r.name) in

let RoleNamesConsistent =
  std.contract.custom (fun _label value =>
    let unknown_groups =
      value.groups
      |> std.array.filter (fun g => !(std.array.elem g.name valid_role_names))
      |> std.array.map (fun g => g.name)
    in
    let unknown_parents =
      value.groups
      |> std.array.flat_map (fun g =>
          g.parent_groups
          |> std.array.filter (fun p => !(std.array.elem p valid_role_names))
        )
    in
    let all_invalid = unknown_groups @ unknown_parents in
    if all_invalid == [] then 'Ok value
    else 'Error { message = "Unknown roles: " ++ std.string.join ", " all_invalid }
  )
in

# ── Feature fragments — uncomment what your site uses ────────────────────────
# Each fragment exposes permissions arrays keyed by role name.

let base      = import "rustelo/resources/nickel/rbac/rbac-base.ncl" in
let auth      = import "rustelo/resources/nickel/rbac/features/auth.ncl" in
let content   = import "rustelo/resources/nickel/rbac/features/content.ncl" in
let ws        = import "rustelo/resources/nickel/rbac/features/websocket.ncl" in
let analytics = import "rustelo/resources/nickel/rbac/features/analytics.ncl" in
# let dashboard = import "rustelo/resources/nickel/rbac/features/user-dashboard.ncl" in
# let admin_ft  = import "rustelo/resources/nickel/rbac/features/admin.ncl" in

# ── Site-specific permissions ─────────────────────────────────────────────────
# Add rules specific to your site that don't belong in any framework feature.
let site = {
  admin    = [],
  user     = [],
  guest    = [
    { resource = "page", pattern = "/*", outcome = "allow", methods = [], effects = [] },
  ],
} in

{
  groups = [
    {
      name          = "admin",
      parent_groups = [],
      permissions   =
        auth.admin
        @ content.admin
        @ ws.admin
        @ analytics.admin
        # @ dashboard.admin
        # @ admin_ft.admin
        @ site.admin
        @ [
          # admin catch-all — keep last
          { resource = "endpoint", pattern = "/api/*", outcome = "allow", methods = [], effects = base.effects.audit_allow },
          { resource = "page",     pattern = "/*",     outcome = "allow", methods = [], effects = [] },
        ],
    },
    {
      name          = "user",
      parent_groups = [],
      permissions   =
        auth.user
        @ content.user
        @ ws.user
        @ analytics.user
        # @ dashboard.user
        # @ admin_ft.user
        @ site.user
        @ [
          # authenticated catch-all deny — keep last
          { resource = "endpoint", pattern = "/api/*", outcome = "deny", methods = [], effects = base.effects.audit_deny @ base.effects.api_forbidden },
        ],
    },
    {
      name          = "guest",
      parent_groups = [],
      permissions   =
        auth.guest
        @ content.guest
        @ ws.guest
        @ analytics.guest
        # @ dashboard.guest
        @ site.guest,
    },
  ],

  user_overrides = [],

  defaults = base.defaults & {
    unauthenticated_allow = [
      # Public assets and health — always allow
      { resource = "asset",    pattern = "/*",          outcome = "allow", methods = [],       effects = [] },
      { resource = "endpoint", pattern = "/api/health", outcome = "allow", methods = [],       effects = [] },
      # Site-specific public routes — add your pages here
    ],
  },
} | C.FileRbacConfig | RoleNamesConsistent
