//! RBAC policy loader for build-time auth pattern extraction.
//!
//! Reads `site/rbac.ncl` (via `nickel export`) and returns the URL patterns
//! that are denied for anonymous users.  These patterns are the **single source
//! of truth** for route protection — no duplication in `config.ncl`.

use anyhow::{Context, Result};
use serde::Deserialize;
use std::path::Path;

#[derive(Debug, Deserialize)]
struct RbacConfig {
    defaults: RbacDefaults,
}

#[derive(Debug, Deserialize)]
struct RbacDefaults {
    unauthenticated_allow: Vec<RbacEntry>,
}

#[derive(Debug, Deserialize)]
struct RbacEntry {
    resource: String,
    pattern: String,
    outcome: String,
}

/// Return URL patterns denied for anonymous users (from `defaults.unauthenticated_allow`).
///
/// Filters to `resource = "page"` + `outcome = "deny"` entries in the order they
/// appear in the policy — the order matters for display but not for the generated
/// pattern matcher, which checks all entries.
///
/// Returns an empty vec if `rbac.ncl` is absent (non-fatal; no protected routes).
pub fn load_rbac_anonymous_deny_patterns(manifest_dir: &Path) -> Result<Vec<String>> {
    let rbac_ncl = manifest_dir.join("site").join("rbac.ncl");

    if !rbac_ncl.exists() {
        return Ok(Vec::new());
    }

    let json = rustelo_config::nickel::export_to_json(&rbac_ncl)
        .with_context(|| format!("Nickel export failed for {}", rbac_ncl.display()))?;

    let config: RbacConfig = serde_json::from_str(&json)
        .with_context(|| format!("JSON deserialisation failed for {}", rbac_ncl.display()))?;

    let patterns = config
        .defaults
        .unauthenticated_allow
        .into_iter()
        .filter(|e| e.resource == "page" && e.outcome == "deny")
        .map(|e| e.pattern)
        .collect();

    Ok(patterns)
}
