//! Shared infrastructure — creates Runtimes that share state, log, and policies.

use crate::budget::{BudgetError, BudgetLimits, CoordinationBudget};
use crate::types::AgentOutput;
use car_engine::Runtime;
use car_eventlog::EventLog;
use car_policy::PolicyEngine;
use car_state::StateStore;
use std::sync::Arc;
use tokio::sync::{Mutex as TokioMutex, RwLock as TokioRwLock};

/// Factory for creating Runtime instances with shared state, event log, and policies.
///
/// In a multi-agent system, all agents see the same state store and write to the
/// same event log. Each agent gets its own tool set and executor.
///
/// A [`CoordinationBudget`] is always present (unbounded by default). Patterns
/// gate each spawn through [`begin_agent`](Self::begin_agent) and report spend
/// through [`record_output`](Self::record_output), so budget enforcement is a
/// uniform, always-on code path that costs nothing when no limits are set.
pub struct SharedInfra {
    pub state: Arc<StateStore>,
    pub log: Arc<TokioMutex<EventLog>>,
    pub policies: Arc<TokioRwLock<PolicyEngine>>,
    pub budget: Arc<CoordinationBudget>,
}

impl SharedInfra {
    pub fn new() -> Self {
        Self {
            state: Arc::new(StateStore::new()),
            log: Arc::new(TokioMutex::new(EventLog::new())),
            policies: Arc::new(TokioRwLock::new(PolicyEngine::new())),
            budget: Arc::new(CoordinationBudget::unbounded()),
        }
    }

    /// Attach a coordination budget built from the given limits. Patterns run
    /// against this infra will refuse to start agents once a limit is crossed.
    pub fn with_budget(mut self, limits: BudgetLimits) -> Self {
        self.budget = Arc::new(CoordinationBudget::new(limits));
        self
    }

    /// Attach a pre-built (possibly shared) coordination budget.
    pub fn with_shared_budget(mut self, budget: Arc<CoordinationBudget>) -> Self {
        self.budget = budget;
        self
    }

    /// Reserve a budget slot for one agent. `Ok(())` means the agent may run;
    /// `Err` carries why it was denied. Patterns call this immediately before a
    /// spawn and record a [`budget_skipped_output`](crate::budget::budget_skipped_output)
    /// on denial.
    pub fn begin_agent(&self) -> Result<(), BudgetError> {
        self.budget.try_begin_agent()
    }

    /// Record an agent's reported token/cost spend against the budget.
    pub fn record_output(&self, out: &AgentOutput) {
        self.budget.record_output(out);
    }

    /// Create a Runtime that shares this infra's state, log, and policies.
    ///
    /// Each runtime gets its own tool set, executor, and idempotency cache.
    pub fn make_runtime(&self) -> Runtime {
        Runtime::with_shared(
            Arc::clone(&self.state),
            Arc::clone(&self.log),
            Arc::clone(&self.policies),
        )
    }

    /// Create a Runtime with per-agent isolated state overlay.
    /// Writes go to a local StateStore; reads fall through to shared state.
    /// Call `AgentContext::merge_to_parent()` after the agent completes.
    pub fn make_isolated_runtime(
        &self,
        agent_name: &str,
    ) -> (Runtime, crate::task_context::AgentContext) {
        let ctx = crate::task_context::AgentContext::new(agent_name, Arc::clone(&self.state));
        let rt = Runtime::with_shared(
            Arc::clone(&ctx.local_state),
            Arc::clone(&ctx.local_log),
            Arc::clone(&self.policies),
        );
        (rt, ctx)
    }
}

impl Default for SharedInfra {
    fn default() -> Self {
        Self::new()
    }
}
