//! Shared JSON parsing utilities for LLM responses.

use serde::Deserialize;

use crate::types::Claim;

use super::error::{LlmError, Result};

/// Parse claims from JSON, handling potential markdown code blocks.
///
/// LLMs sometimes wrap JSON responses in markdown code fences (```json ... ```).
/// This function strips those before parsing.
pub fn parse_claims_json(text: &str) -> Result<Vec<Claim>> {
    let json_text = strip_markdown_fences(text);

    #[derive(Deserialize)]
    struct RawClaim {
        subject: String,
        predicate: String,
        object: String,
        confidence: f32,
        quote: String,
    }

    let raw_claims: Vec<RawClaim> =
        serde_json::from_str(json_text).map_err(|e| LlmError::JsonParse(e.to_string()))?;

    Ok(raw_claims
        .into_iter()
        .map(|r| Claim {
            subject: r.subject,
            predicate: r.predicate,
            object: r.object,
            confidence: r.confidence,
            quote: r.quote,
            source: None,
        })
        .collect())
}

/// Strip markdown code fences from text if present.
fn strip_markdown_fences(text: &str) -> &str {
    text.trim()
        .strip_prefix("```json")
        .or_else(|| text.trim().strip_prefix("```"))
        .unwrap_or(text)
        .trim()
        .strip_suffix("```")
        .unwrap_or(text)
        .trim()
}

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

    #[test]
    fn test_parse_plain_json() {
        let json = r#"[{"subject":"test","predicate":"is","object":"working","confidence":0.9,"quote":"test quote"}]"#;
        let claims = parse_claims_json(json).expect("Should parse");
        assert_eq!(claims.len(), 1);
        assert_eq!(claims[0].subject, "test");
    }

    #[test]
    fn test_parse_with_markdown_fences() {
        let json = r#"```json
[{"subject":"test","predicate":"is","object":"working","confidence":0.9,"quote":"test quote"}]
```"#;
        let claims = parse_claims_json(json).expect("Should parse");
        assert_eq!(claims.len(), 1);
    }

    #[test]
    fn test_parse_with_plain_fences() {
        let json = r#"```
[{"subject":"test","predicate":"is","object":"working","confidence":0.9,"quote":"test quote"}]
```"#;
        let claims = parse_claims_json(json).expect("Should parse");
        assert_eq!(claims.len(), 1);
    }

    #[test]
    fn test_parse_invalid_json() {
        let result = parse_claims_json("not json");
        assert!(result.is_err());
    }
}
