use std::collections::HashSet;

pub fn retrieval_recall(expected_sources: &[String], actual_sources: &[String]) -> f32 {
    if expected_sources.is_empty() {
        return 1.0;
    }

    let expected: HashSet<&String> = expected_sources.iter().collect();
    let actual: HashSet<&String> = actual_sources.iter().collect();
    let hits = expected.intersection(&actual).count();

    hits as f32 / expected.len() as f32
}

pub fn source_precision(expected_sources: &[String], actual_sources: &[String]) -> f32 {
    if actual_sources.is_empty() {
        return if expected_sources.is_empty() {
            1.0
        } else {
            0.0
        };
    }

    let expected: HashSet<&String> = expected_sources.iter().collect();
    let actual: HashSet<&String> = actual_sources.iter().collect();
    let hits = actual.intersection(&expected).count();

    hits as f32 / actual.len() as f32
}

pub fn refusal_accuracy(expected_refuse: bool, actual_refuse: bool) -> f32 {
    if expected_refuse == actual_refuse {
        1.0
    } else {
        0.0
    }
}

pub fn schema_validity_score(is_valid: bool) -> f32 {
    if is_valid {
        1.0
    } else {
        0.0
    }
}

pub fn average(values: &[f32]) -> f32 {
    if values.is_empty() {
        return 0.0;
    }
    values.iter().sum::<f32>() / values.len() as f32
}

#[cfg(test)]
mod tests {
    use super::{
        average, refusal_accuracy, retrieval_recall, schema_validity_score, source_precision,
    };

    #[test]
    fn recall_scores_partial_matches() {
        let expected = vec!["a".to_string(), "b".to_string()];
        let actual = vec!["a".to_string()];
        assert_eq!(retrieval_recall(&expected, &actual), 0.5);
    }

    #[test]
    fn precision_scores_extra_sources() {
        let expected = vec!["a".to_string()];
        let actual = vec!["a".to_string(), "b".to_string()];
        assert_eq!(source_precision(&expected, &actual), 0.5);
    }

    #[test]
    fn refusal_accuracy_scores_exact_match() {
        assert_eq!(refusal_accuracy(true, true), 1.0);
        assert_eq!(refusal_accuracy(false, false), 1.0);
        assert_eq!(refusal_accuracy(true, false), 0.0);
    }

    #[test]
    fn schema_validity_maps_bool_to_score() {
        assert_eq!(schema_validity_score(true), 1.0);
        assert_eq!(schema_validity_score(false), 0.0);
    }

    #[test]
    fn average_handles_empty_and_non_empty_input() {
        assert_eq!(average(&[]), 0.0);
        assert_eq!(average(&[1.0, 0.0, 1.0]), 2.0 / 3.0);
    }
}
