//! Generic refinement trait.

use crate::error::SpecError;
use crate::quality::Critique;
use async_trait::async_trait;

/// Improve `T` informed by a critique.
///
/// Takes ownership of `T` to make swap-and-replace cheap (the legacy
/// `Spec` aggregate is large; cloning it per iteration is wasteful).
/// Implementations that want to retain the previous value should
/// clone before calling.
#[async_trait]
pub trait Refiner<T>: Send + Sync
where
    T: Send + Sync,
{
    /// Refine `t` based on `critique` at iteration `iter`. Return the
    /// refined `T`. The loop will pass the result back to the
    /// `Critic` for re-evaluation.
    async fn refine(&self, t: T, critique: &Critique, iter: u8) -> Result<T, SpecError>;
}

#[cfg(test)]
mod tests {
    use super::*;

    #[allow(dead_code)]
    fn _trait_is_object_safe(_r: &dyn Refiner<String>) {}

    struct PadRefiner;
    #[async_trait]
    impl Refiner<String> for PadRefiner {
        async fn refine(&self, t: String, _c: &Critique, _i: u8) -> Result<String, SpecError> {
            Ok(format!("{t}-x"))
        }
    }

    #[tokio::test]
    async fn pad_refiner_grows_string() {
        let r = PadRefiner;
        let out = r
            .refine("a".to_string(), &Critique::fail("short"), 0)
            .await
            .unwrap();
        assert_eq!(out, "a-x");
    }
}
