// Cedar policy AST — policy nodes and builder methods.
// References:
//   Rust: cedar-policy-core/src/ast/ (Policy, TemplateBody)
//   Go:   cedar-go/x/exp/ast/ (policy.go, scope.go)

// ---------------------------------------------------------------------------
// Scope Constraints
// ---------------------------------------------------------------------------

///|
/// Scope constraint for principal, action, or resource in a policy.
///
/// Not every variant is valid for every PARC position:
///   - Principal:  All | Eq | In | Is | IsIn
///   - Action:     All | Eq | In | InSet
///   - Resource:   All | Eq | In | Is | IsIn
pub(all) enum ScopeConstraint {
  All
  Eq(EntityUID)
  In(EntityUID)
  InSet(Array[EntityUID]) // valid for action scope
  Is(EntityType) // valid for principal & resource scope
  IsIn(EntityType, EntityUID) // valid for principal & resource scope
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Conditions & Annotations
// ---------------------------------------------------------------------------

///|
pub(all) enum ConditionKind {
  When
  Unless
} derive(Debug, Eq)

///|
pub(all) struct Condition {
  kind : ConditionKind
  body : Expr
} derive(Debug, Eq)

///|
/// Annotation — key-value metadata attached to a policy.
/// Has no effect on policy evaluation.
pub(all) struct Annotation {
  key : String
  value : String
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Policy
// ---------------------------------------------------------------------------

///|
/// A complete Cedar policy.
pub(all) struct Policy {
  id : String
  effect : PolicyEffect
  annotations : Array[Annotation]
  principal : ScopeConstraint
  action : ScopeConstraint
  resource : ScopeConstraint
  conditions : Array[Condition]
} derive(Debug, Eq)

///|
pub(all) enum PolicyEffect {
  Permit
  Forbid
} derive(Debug, Eq)

// ---------------------------------------------------------------------------
// Policy builder
// ---------------------------------------------------------------------------

///|
/// Construct an EntityUID in builder contexts.
pub fn entity_uid(type_ : String, id : String) -> EntityUID {
  { type_, id }
}

///|
/// Construct an EntityType newtype in builder contexts.
pub fn entity_type(name : String) -> EntityType {
  EntityType(name)
}

///|
/// Return a zero-valued Policy with Permit effect, All scopes, empty arrays.
pub fn default() -> Policy {
  {
    id: "",
    effect: Permit,
    annotations: [],
    principal: All,
    action: All,
    resource: All,
    conditions: [],
  }
}

// ---------------------------------------------------------------------------
// Effect
// ---------------------------------------------------------------------------

///|
pub fn Policy::permit(self : Policy) -> Policy {
  Policy::{ ..self, effect: Permit }
}

///|
pub fn Policy::forbid(self : Policy) -> Policy {
  Policy::{ ..self, effect: Forbid }
}

// ---------------------------------------------------------------------------
// Scope — principal
// ---------------------------------------------------------------------------

///|
pub fn Policy::principal_eq(
  self : Policy,
  type_ : String,
  id : String,
) -> Policy {
  Policy::{ ..self, principal: Eq(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::principal_in(
  self : Policy,
  type_ : String,
  id : String,
) -> Policy {
  Policy::{ ..self, principal: In(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::principal_is(self : Policy, type_ : String) -> Policy {
  Policy::{ ..self, principal: Is(EntityType(type_)) }
}

///|
pub fn Policy::principal_is_in(
  self : Policy,
  ty : String,
  parent_ty : String,
  parent_id : String,
) -> Policy {
  Policy::{
    ..self,
    principal: IsIn(EntityType(ty), EntityUID::{
      type_: parent_ty,
      id: parent_id,
    }),
  }
}

// ---------------------------------------------------------------------------
// Scope — action
// ---------------------------------------------------------------------------

///|
pub fn Policy::action_eq(self : Policy, type_ : String, id : String) -> Policy {
  Policy::{ ..self, action: Eq(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::action_in(self : Policy, type_ : String, id : String) -> Policy {
  Policy::{ ..self, action: In(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::action_in_set(
  self : Policy,
  entities : Array[(String, String)],
) -> Policy {
  Policy::{
    ..self,
    action: InSet(
      entities.map(fn(e : (String, String)) -> EntityUID {
        let (t, i) = e
        EntityUID::{ type_: t, id: i }
      }),
    ),
  }
}

// ---------------------------------------------------------------------------
// Scope — resource
// ---------------------------------------------------------------------------

///|
pub fn Policy::resource_eq(
  self : Policy,
  type_ : String,
  id : String,
) -> Policy {
  Policy::{ ..self, resource: Eq(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::resource_in(
  self : Policy,
  type_ : String,
  id : String,
) -> Policy {
  Policy::{ ..self, resource: In(EntityUID::{ type_, id }) }
}

///|
pub fn Policy::resource_is(self : Policy, type_ : String) -> Policy {
  Policy::{ ..self, resource: Is(EntityType(type_)) }
}

///|
pub fn Policy::resource_is_in(
  self : Policy,
  ty : String,
  parent_ty : String,
  parent_id : String,
) -> Policy {
  Policy::{
    ..self,
    resource: IsIn(EntityType(ty), EntityUID::{
      type_: parent_ty,
      id: parent_id,
    }),
  }
}

// ---------------------------------------------------------------------------
// Metadata
// ---------------------------------------------------------------------------

///|
pub fn Policy::with_id(self : Policy, id : String) -> Policy {
  Policy::{ ..self, id, }
}

///|
pub fn Policy::annotate(self : Policy, key : String, value : String) -> Policy {
  Policy::{
    ..self,
    annotations: self.annotations + [Annotation::{ key, value }],
  }
}

// ---------------------------------------------------------------------------
// Conditions
// ---------------------------------------------------------------------------

///|
pub fn Policy::when_(self : Policy, body : Expr) -> Policy {
  Policy::{
    ..self,
    conditions: self.conditions + [Condition::{ kind: When, body }],
  }
}

///|
pub fn Policy::unless(self : Policy, body : Expr) -> Policy {
  Policy::{
    ..self,
    conditions: self.conditions + [Condition::{ kind: Unless, body }],
  }
}