//! `Policy` contract + its error type.

#![expect(
    clippy::type_complexity,
    reason = "`Result<Vec<Box<dyn AdapterRequirement>>, PolicyError>` is the canonical erased policy output; aliasing it would hide the contract."
)]

use crate::adapter_requirement::AdapterRequirement;

/// Why running a policy failed.
#[derive(Debug, Clone)]
#[expect(
    clippy::module_name_repetitions,
    reason = "PolicyError is the policy contract's error; the name reads at call sites and is not about the module."
)]
pub enum PolicyError {
    /// The policy's `params` could not be deserialized or failed validation.
    Params(String),
}

impl core::fmt::Display for PolicyError {
    fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
        match self {
            Self::Params(detail) => write!(f, "policy params error: {detail}"),
        }
    }
}

impl core::error::Error for PolicyError {}

/// A policy: a named opinion that turns typed `params` into requirements for
/// one or more adapters. Pure: no I/O, no workspace knowledge.
pub trait Policy {
    /// Stable policy id (matches the `g3rs.json` `overrides` key).
    fn id(&self) -> &'static str;
    /// Deserialize `params` into the policy's typed input, run it, and return
    /// the erased adapter requirements (each carrying its `adapter_id`).
    ///
    /// # Errors
    ///
    /// Returns [`PolicyError::Params`] if `params` cannot be deserialized into
    /// the policy's input type or fails the policy's own validation.
    fn invoke(
        &self,
        params: &serde_json::Value,
    ) -> Result<Vec<Box<dyn AdapterRequirement>>, PolicyError>;
}
