use crate::linting::expr_linter::Chunk;
use crate::{
    Lrc, Token, TokenStringExt,
    expr::{AnchorStart, Expr, FirstMatchOf, FixedPhrase, SequenceExpr},
    linting::{ExprLinter, Lint, LintKind, Suggestion},
};

pub struct IAmAgreement {
    expr: FirstMatchOf,
}

impl Default for IAmAgreement {
    fn default() -> Self {
        let i_are = Lrc::new(FixedPhrase::from_phrase("I are"));

        let nothing_before_i_are = SequenceExpr::with(AnchorStart).then(i_are.clone());

        let non_and_word_before_i_are = SequenceExpr::default()
            .then_word_except(&["and"])
            .t_ws()
            .then(i_are);

        let expr = FirstMatchOf::new(vec![
            Box::new(nothing_before_i_are),
            Box::new(non_and_word_before_i_are),
        ]);

        Self { expr }
    }
}

impl ExprLinter for IAmAgreement {
    type Unit = Chunk;

    fn expr(&self) -> &dyn Expr {
        &self.expr
    }

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        let toks = &toks[toks.len() - 3..];
        Some(Lint {
            span: toks.span()?,
            lint_kind: LintKind::Agreement,
            suggestions: vec![Suggestion::replace_with_match_case(
                "I am".chars().collect(),
                toks.span()?.get_content(src),
            )],
            message: "The first-person singular pronoun `I` requires the verb form `am`; `are` belongs to second-person or plural contexts.".to_owned(),
            priority: 31,
        })
    }

    fn description(&self) -> &str {
        "Corrects `I are` to `I am`."
    }
}

#[cfg(test)]
mod tests {
    use super::*;
    use crate::linting::tests::{assert_lint_count, assert_suggestion_result};

    #[test]
    fn corrects_i_are_simple() {
        assert_suggestion_result("I are", IAmAgreement::default(), "I am");
    }

    #[test]
    fn corrects_i_are() {
        assert_suggestion_result(
            "I are really happy about this release.",
            IAmAgreement::default(),
            "I am really happy about this release.",
        );
    }

    #[test]
    fn dont_flag_you_and_i_are() {
        assert_lint_count(
            "You know, you and I are sitting on the Titanic and the iceberg is over there.",
            IAmAgreement::default(),
            0,
        );
    }

    #[test]
    fn dont_flag_mention_and_i_are() {
        assert_lint_count(
            "Hello, @another-rex and I are attempting to package packageurl-go for Debian as we need it for a build dependency.",
            IAmAgreement::default(),
            0,
        );
    }

    #[test]
    fn dont_flag_z_and_i_are() {
        assert_lint_count(
            "The url is copied from a manual search, and Z and I are modified.",
            IAmAgreement::default(),
            0,
        )
    }

    #[test]
    fn dont_flag_name_and_i_are() {
        assert_lint_count(
            "Paper that Lena Baunaz and I are working on as part of my SNSF-funded 'Focus in diachrony'",
            IAmAgreement::default(),
            0,
        );
    }

    #[test]
    fn fix_so_i_are() {
        assert_suggestion_result(
            "I have not yet been able to reproduce this issue in my environment, so I are still trying to figure it out",
            IAmAgreement::default(),
            "I have not yet been able to reproduce this issue in my environment, so I am still trying to figure it out",
        );
    }

    #[test]
    fn fix_if_i_are() {
        assert_suggestion_result(
            "If i are on creative inventory, and try to clean my inventory holding shift is disconnected too.",
            IAmAgreement::default(),
            "If i am on creative inventory, and try to clean my inventory holding shift is disconnected too.",
        );
    }

    #[test]
    fn fix_what_i_are() {
        assert_suggestion_result(
            "in this situation I can't see what I are typing",
            IAmAgreement::default(),
            "in this situation I can't see what I am typing",
        );
    }

    #[test]
    fn fix_where_i_are() {
        assert_suggestion_result(
            "I have a logging application where I are append to a topic",
            IAmAgreement::default(),
            "I have a logging application where I am append to a topic",
        );
    }
}
