use crate::error::BibtexError;
use crate::model::{EntryType, Reference};

/// Returns the required fields for a given BibTeX entry type.
fn required_fields(entry_type: &EntryType) -> &'static [&'static str] {
    match entry_type {
        EntryType::Article => &["author", "title", "journal", "year"],
        EntryType::Book => &["title", "publisher", "year"],
        EntryType::Booklet => &["title"],
        EntryType::InBook => &["title", "publisher", "year", "chapter"],
        EntryType::InCollection => &["author", "title", "booktitle", "publisher", "year"],
        EntryType::InProceedings => &["author", "title", "booktitle", "year"],
        EntryType::Manual => &["title"],
        EntryType::MastersThesis => &["author", "title", "school", "year"],
        EntryType::Misc => &[],
        EntryType::PhdThesis => &["author", "title", "school", "year"],
        EntryType::Proceedings => &["title", "year"],
        EntryType::TechReport => &["author", "title", "institution", "year"],
        EntryType::Unpublished => &["author", "title", "note"],
        EntryType::Online => &["title", "url"],
    }
}

/// Validate that a reference has all required fields for BibTeX export.
/// Returns an error describing the first missing required field found.
pub fn validate_for_export(reference: &Reference) -> Result<(), BibtexError> {
    let required = required_fields(&reference.entry_type);

    for &field in required {
        let present = match field {
            "author" => !reference.authors.is_empty(),
            "editor" => !reference.editors.is_empty(),
            _ => reference.fields.contains_key(field),
        };
        if !present {
            return Err(BibtexError::MissingRequiredField {
                cite_key: reference.cite_key.clone(),
                entry_type: reference.entry_type.bibtex_name().to_owned(),
                field: field.to_owned(),
            });
        }
    }

    Ok(())
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::model::{EntryType, Person, Reference};

    fn make_article() -> Reference {
        let mut r = Reference::new("doe2024", EntryType::Article);
        r.authors.push(Person::new("Doe"));
        r.fields.insert("title".into(), "A Paper".into());
        r.fields.insert("journal".into(), "Nature".into());
        r.fields.insert("year".into(), "2024".into());
        r
    }

    #[test]
    fn valid_article_passes() {
        let r = make_article();
        assert!(validate_for_export(&r).is_ok());
    }

    #[test]
    fn article_missing_author_fails() {
        let mut r = make_article();
        r.authors.clear();
        let err = validate_for_export(&r).unwrap_err();
        assert!(
            matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "author")
        );
    }

    #[test]
    fn article_missing_journal_fails() {
        let mut r = make_article();
        r.fields.remove("journal");
        let err = validate_for_export(&r).unwrap_err();
        assert!(
            matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "journal")
        );
    }

    #[test]
    fn misc_has_no_required_fields() {
        let r = Reference::new("anon", EntryType::Misc);
        assert!(validate_for_export(&r).is_ok());
    }

    #[test]
    fn phd_thesis_requires_school() {
        let mut r = Reference::new("smith2020", EntryType::PhdThesis);
        r.authors.push(Person::new("Smith"));
        r.fields.insert("title".into(), "A Thesis".into());
        r.fields.insert("year".into(), "2020".into());
        let err = validate_for_export(&r).unwrap_err();
        assert!(
            matches!(err, BibtexError::MissingRequiredField { field, .. } if field == "school")
        );
    }
}
