use super::{ExprLinter, Suggestion};
use crate::Lint;
use crate::expr::{Expr, LongestMatchOf, SequenceExpr};
use crate::linting::LintKind;
use crate::linting::expr_linter::Chunk;
use crate::linting::expr_linter::find_the_only_token_matching;
use crate::{CharStringExt, Token};

pub struct Cant {
    expr: LongestMatchOf,
}

impl Default for Cant {
    fn default() -> Self {
        let nom_cant = SequenceExpr::default()
            .then_kind_except(|kind| kind.is_nominal(), &["or"])
            .t_ws()
            .t_aco("cant");
        let cant_pron = SequenceExpr::aco("cant").t_ws().then_personal_pronoun();
        let cant_verb = SequenceExpr::aco("cant")
            .t_ws()
            .then_kind_is_but_is_not(|kind| kind.is_verb_lemma(), |kind| kind.is_noun());

        Self {
            expr: LongestMatchOf::new(vec![
                Box::new(nom_cant),
                Box::new(cant_pron),
                Box::new(cant_verb),
            ]),
        }
    }
}

impl ExprLinter for Cant {
    type Unit = Chunk;

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

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        let token = find_the_only_token_matching(toks, src, |tok, src| {
            tok.get_ch(src).eq_ch(&['c', 'a', 'n', 't'])
        })?;

        let jargon = token.get_ch(src);
        let cannot = "can't";

        Some(Lint {
            span: token.span,
            lint_kind: LintKind::Enhancement,
            suggestions: vec![Suggestion::replace_with_match_case_str(cannot, jargon)],
            message: "`Cant` is secret language or jargon. If that's not what you mean you should use `can't` here.".to_owned(),
            priority: 127,
        })
    }

    fn description(&self) -> &'static str {
        "Suggests correcting `cant` to `can't`."
    }
}

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

    #[test]
    fn corrects_pronoun_cant() {
        assert_suggestion_result(
            "I cant go to the store.",
            Cant::default(),
            "I can't go to the store.",
        );
    }

    #[test]
    fn corrects_proper_noun_cant() {
        assert_suggestion_result(
            "Bob cant go to the store.",
            Cant::default(),
            "Bob can't go to the store.",
        );
    }

    #[test]
    fn corrects_common_noun_cant() {
        // "dog" and "cat" are
        assert_suggestion_result(
            "A horse cant drink bottled water.",
            Cant::default(),
            "A horse can't drink bottled water.",
        );
    }

    #[test]
    fn corrects_cant_pronoun() {
        assert_suggestion_result(
            "Cant you go to the store?",
            Cant::default(),
            "Can't you go to the store?",
        );
    }

    #[test]
    fn dont_flag_if_cant_is_part_of_noun_phrase() {
        assert_lint_count("Cant cant be the same as jargon.", Cant::default(), 0);
    }

    #[test]
    fn dont_flag_cant_project() {
        assert_lint_count(
            "The CANT project is designed to allow people to screw around with CAN easily at layers 1/2.",
            Cant::default(),
            0,
        );
    }

    #[test]
    #[ignore = "'Convert' is also a noun, so a 'cant convert' could be a person who switched to speaking jargon"]
    fn corrects_cant_verb() {
        assert_suggestion_result(
            "Cant convert widget to input",
            Cant::default(),
            "Can't convert widget to input",
        );
    }

    #[test]
    fn dont_flag_legit_noun_sense() {
        assert_lint_count(
            "CB Slang Dictionary is the distinctive anti-language, argot or cant which developed amongst users of citizens' band radio (CB), especially truck drivers",
            Cant::default(),
            0,
        );
    }
}
