//! End-to-end loop: a length-checking critic + a string-padding
//! refiner converge on a target length.

use async_trait::async_trait;
use klieo_spec::{Critic, Critique, QualityLoop, Refiner, SpecError};

struct LenCritic {
    min: usize,
}
#[async_trait]
impl Critic<String> for LenCritic {
    async fn evaluate(&self, t: &String, _iter: u8) -> Result<Critique, SpecError> {
        if t.len() >= self.min {
            Ok(Critique::pass(format!("ok: {}", t.len())))
        } else {
            Ok(Critique::fail(format!("short: {} < {}", t.len(), self.min)))
        }
    }
}

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 loop_grows_string_until_long_enough() {
    let l = QualityLoop::new(LenCritic { min: 6 }, PadRefiner).with_max_iterations(20);
    let (out, meta) = l.run("a".into()).await.unwrap();
    assert!(meta.passed, "loop should converge");
    assert!(out.len() >= 6, "{out:?} too short");
    // Padding adds 2 chars per refine ("-x"); start length 1, target 6
    // → 3 refines, iter index reaches 3 (the iter at which critic
    // passes is *not* counted as a refine).
    assert!(meta.iterations_used <= 4, "{:?} took too long", meta);
}

#[tokio::test]
async fn loop_returns_unmodified_value_when_critic_passes_first_time() {
    let l = QualityLoop::new(LenCritic { min: 1 }, PadRefiner);
    let (out, meta) = l.run("hello".into()).await.unwrap();
    assert!(meta.passed);
    assert_eq!(out, "hello");
    assert_eq!(meta.iterations_used, 0);
}

#[tokio::test]
async fn loop_reports_non_passing_metadata() {
    struct AlwaysFail;
    #[async_trait]
    impl Critic<String> for AlwaysFail {
        async fn evaluate(&self, _t: &String, _i: u8) -> Result<Critique, SpecError> {
            Ok(Critique::fail("never enough"))
        }
    }

    let l = QualityLoop::new(AlwaysFail, PadRefiner).with_max_iterations(2);
    let (_out, meta) = l.run("a".into()).await.unwrap();
    assert!(!meta.passed);
    assert_eq!(meta.iterations_used, 2);
    assert_eq!(meta.max_iterations, 2);
    assert!(meta.final_critique.is_some());
}
