//! Per-agent capability permissions for the Common Agent Runtime.
//!
//! A [`CapabilitySet`] defines what an agent is allowed to do: which tools
//! it may invoke, which state keys it may access, and how many actions it
//! may include in a single proposal.

use serde::{Deserialize, Serialize};
use std::collections::HashSet;

/// Capability set defining what an agent is allowed to do.
#[derive(Debug, Clone, Default, Serialize, Deserialize)]
pub struct CapabilitySet {
    /// If set, only these tools are allowed. Empty = all tools allowed.
    pub allowed_tools: HashSet<String>,
    /// These tools are always denied (overrides allowed_tools).
    pub denied_tools: HashSet<String>,
    /// If set, only these state keys can be read/written. Empty = all keys allowed.
    pub allowed_state_keys: HashSet<String>,
    /// Maximum actions per proposal. None = unlimited.
    pub max_actions: Option<u32>,
}

impl CapabilitySet {
    pub fn new() -> Self {
        Self::default()
    }

    pub fn allow_tool(mut self, tool: &str) -> Self {
        self.allowed_tools.insert(tool.to_string());
        self
    }

    pub fn deny_tool(mut self, tool: &str) -> Self {
        self.denied_tools.insert(tool.to_string());
        self
    }

    pub fn allow_state_key(mut self, key: &str) -> Self {
        self.allowed_state_keys.insert(key.to_string());
        self
    }

    pub fn with_max_actions(mut self, max: u32) -> Self {
        self.max_actions = Some(max);
        self
    }

    /// Check if a tool is permitted.
    pub fn tool_allowed(&self, tool: &str) -> bool {
        if self.denied_tools.contains(tool) {
            return false;
        }
        if self.allowed_tools.is_empty() {
            return true;
        }
        self.allowed_tools.contains(tool)
    }

    /// Check if a state key is permitted.
    pub fn state_key_allowed(&self, key: &str) -> bool {
        if self.allowed_state_keys.is_empty() {
            return true;
        }
        self.allowed_state_keys.contains(key)
    }

    /// Check if action count is within budget.
    pub fn actions_within_budget(&self, count: u32) -> bool {
        match self.max_actions {
            None => true,
            Some(max) => count <= max,
        }
    }
}
