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

pub struct JealousOf {
    expr: SequenceExpr,
}

impl Default for JealousOf {
    fn default() -> Self {
        let valid_object = |tok: &Token, _source: &[char]| {
            (tok.kind.is_nominal() || !tok.kind.is_verb())
                && (tok.kind.is_oov() || tok.kind.is_nominal())
                && !tok.kind.is_preposition()
        };

        let pattern = SequenceExpr::default()
            .t_aco("jealous")
            .t_ws()
            .t_aco("from")
            .t_ws()
            .then_optional(SequenceExpr::default().then_determiner().t_ws())
            .then(valid_object);

        Self { expr: pattern }
    }
}

impl ExprLinter for JealousOf {
    type Unit = Chunk;

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

    fn match_to_lint(&self, tokens: &[Token], source: &[char]) -> Option<Lint> {
        let from_token = &tokens[2];

        Some(Lint {
            span: from_token.span,
            lint_kind: LintKind::Usage,
            suggestions: vec![Suggestion::replace_with_match_case_str(
                "of",
                from_token.get_ch(source),
            )],
            message: "Use `of` after `jealous`.".to_owned(),
            ..Default::default()
        })
    }

    fn description(&self) -> &str {
        "Encourages the standard preposition after `jealous`."
    }
}

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

    #[test]
    fn replaces_basic_from() {
        assert_suggestion_result(
            "She was jealous from her sister's success.",
            JealousOf::default(),
            "She was jealous of her sister's success.",
        );
    }

    #[test]
    fn handles_optional_determiner() {
        assert_suggestion_result(
            "He grew jealous from the attention.",
            JealousOf::default(),
            "He grew jealous of the attention.",
        );
    }

    #[test]
    fn fixes_pronoun_object() {
        assert_suggestion_result(
            "They became jealous from him.",
            JealousOf::default(),
            "They became jealous of him.",
        );
    }

    #[test]
    fn allows_oov_target() {
        assert_suggestion_result(
            "I'm jealous from Zybrix.",
            JealousOf::default(),
            "I'm jealous of Zybrix.",
        );
    }

    #[test]
    fn corrects_uppercase_preposition() {
        assert_suggestion_result(
            "Jealous FROM his fame.",
            JealousOf::default(),
            "Jealous OF his fame.",
        );
    }

    #[test]
    fn fixes_longer_phrase() {
        assert_suggestion_result(
            "They felt jealous from the sudden praise she received.",
            JealousOf::default(),
            "They felt jealous of the sudden praise she received.",
        );
    }

    #[test]
    fn fixes_minimal_phrase() {
        assert_suggestion_result(
            "jealous from success",
            JealousOf::default(),
            "jealous of success",
        );
    }

    #[test]
    fn does_not_flag_correct_usage() {
        assert_lint_count(
            "She was jealous of her sister's success.",
            JealousOf::default(),
            0,
        );
    }

    #[test]
    fn does_not_flag_other_preposition_sequence() {
        assert_lint_count(
            "They stayed jealous from within the fortress.",
            JealousOf::default(),
            0,
        );
    }

    #[test]
    fn fixes_following_gerund() {
        assert_suggestion_result(
            "He was jealous from being ignored.",
            JealousOf::default(),
            "He was jealous of being ignored.",
        );
    }

    #[test]
    fn ignores_numbers_after_from() {
        assert_lint_count(
            "She remained jealous from 2010 through 2015.",
            JealousOf::default(),
            0,
        );
    }
}
