//! Serde helpers for SRBN trace types.
//!
//! This companion crate keeps `serde` out of the core `srbn` crate while still
//! offering ergonomic JSON export for applications that want it.

use serde::Serialize;
use serde_json::{Value, json};
use srbn::{Attempt, BarrierResult, CheckResult, Evidence, RunResult, StabilizationResult, Status};

/// Convert a terminal status into its canonical string value.
pub fn status_value(status: Status) -> Value {
    json!(status.as_str())
}

/// Convert deterministic SRBN string evidence into JSON.
pub fn evidence_value(evidence: &Evidence) -> Value {
    json!(evidence)
}

/// Convert one text check result into JSON.
pub fn check_result_value(result: &CheckResult) -> Value {
    json!({
        "name": result.name,
        "ok": result.ok,
        "score": result.score,
        "feedback": result.feedback,
        "evidence": result.evidence,
    })
}

/// Convert one text attempt into JSON.
pub fn attempt_value(attempt: &Attempt) -> Value {
    json!({
        "index": attempt.index,
        "output": attempt.output,
        "score": attempt.score,
        "ok": attempt.ok,
        "feedback": attempt.feedback,
        "check_results": attempt.check_results.iter().map(check_result_value).collect::<Vec<_>>(),
    })
}

/// Convert a text run result into JSON.
pub fn run_result_value(result: &RunResult) -> Value {
    json!({
        "ok": result.ok,
        "status": result.status.as_str(),
        "output": result.output,
        "score": result.score,
        "feedback": result.feedback,
        "attempts": result.attempts.iter().map(attempt_value).collect::<Vec<_>>(),
    })
}

/// Serialize a text run result as compact JSON.
pub fn run_result_json(result: &RunResult) -> serde_json::Result<String> {
    serde_json::to_string(&run_result_value(result))
}

/// Serialize a text run result as pretty JSON.
pub fn run_result_pretty_json(result: &RunResult) -> serde_json::Result<String> {
    serde_json::to_string_pretty(&run_result_value(result))
}

/// Convert a typed barrier result into JSON.
pub fn barrier_result_value<Correction, EvidenceT>(
    result: &BarrierResult<Correction, EvidenceT>,
) -> serde_json::Result<Value>
where
    Correction: Serialize,
    EvidenceT: Serialize,
{
    Ok(json!({
        "name": result.name,
        "ok": result.ok,
        "score": result.score,
        "feedback": result.feedback,
        "correction": result.correction,
        "evidence": result.evidence,
    }))
}

/// Convert a typed stabilization result into JSON.
pub fn stabilization_result_value<State, Correction, EvidenceT>(
    result: &StabilizationResult<State, Correction, EvidenceT>,
) -> serde_json::Result<Value>
where
    State: Serialize,
    Correction: Serialize,
    EvidenceT: Serialize,
{
    let attempts = result
        .attempts
        .iter()
        .map(|attempt| {
            Ok(json!({
                "index": attempt.index,
                "state": attempt.state,
                "barrier": barrier_result_value(&attempt.barrier)?,
            }))
        })
        .collect::<serde_json::Result<Vec<_>>>()?;

    Ok(json!({
        "ok": result.ok,
        "status": result.status.as_str(),
        "state": result.state,
        "barrier": barrier_result_value(&result.barrier)?,
        "attempts": attempts,
    }))
}

/// Serialize a typed stabilization result as compact JSON.
pub fn stabilization_result_json<State, Correction, EvidenceT>(
    result: &StabilizationResult<State, Correction, EvidenceT>,
) -> serde_json::Result<String>
where
    State: Serialize,
    Correction: Serialize,
    EvidenceT: Serialize,
{
    serde_json::to_string(&stabilization_result_value(result)?)
}

#[cfg(test)]
mod tests {
    use srbn::{Policy, boxed_check, contains, run};

    use super::*;

    #[test]
    fn serializes_run_result_with_serde_json() {
        let checks = vec![boxed_check(contains("ok"))];
        let result = run(
            "task",
            |_prompt: &str, _feedback: Option<&str>| Ok("ok".to_string()),
            &checks,
            Policy::default(),
        )
        .unwrap();

        let value = run_result_value(&result);
        assert_eq!(value["status"], "stable");
        assert_eq!(value["attempts"].as_array().unwrap().len(), 1);
    }
}
