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

pub struct Respond {
    expr: ExprMap<usize>,
}

impl Default for Respond {
    fn default() -> Self {
        let mut map = ExprMap::default();

        let helper_verb = |tok: &Token, src: &[char]| {
            if tok.kind.is_auxiliary_verb() {
                return true;
            }

            if !tok.kind.is_verb() {
                return false;
            }

            let lower = tok.get_str(src).to_lowercase();
            matches!(
                lower.as_str(),
                "do" | "did" | "does" | "won't" | "don't" | "didn't" | "doesn't"
            )
        };

        map.insert(
            SequenceExpr::default()
                .then_nominal()
                .t_ws()
                .then(helper_verb)
                .t_ws()
                .t_aco("response"),
            4,
        );

        map.insert(
            SequenceExpr::default()
                .then_nominal()
                .t_ws()
                .then(helper_verb)
                .t_ws()
                .then_adverb()
                .t_ws()
                .t_aco("response"),
            6,
        );

        Self { expr: map }
    }
}

impl ExprLinter for Respond {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let response_index = *self.expr.lookup(0, matched_tokens, source)?;
        let response_token = matched_tokens.get(response_index)?;

        Some(Lint {
            span: response_token.span,
            lint_kind: LintKind::WordChoice,
            suggestions: vec![Suggestion::replace_with_match_case_str(
                "respond",
                response_token.get_ch(source),
            )],
            message: "Use the verb `respond` here.".to_owned(),
            priority: 40,
        })
    }

    fn description(&self) -> &'static str {
        "Flags uses of the noun `response` where the verb `respond` is needed after an auxiliary."
    }
}

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

    #[test]
    fn fixes_will_response() {
        assert_suggestion_result(
            "He will response soon.",
            Respond::default(),
            "He will respond soon.",
        );
    }

    #[test]
    fn fixes_can_response() {
        assert_suggestion_result(
            "They can response to the survey.",
            Respond::default(),
            "They can respond to the survey.",
        );
    }

    #[test]
    fn fixes_did_not_response() {
        assert_suggestion_result(
            "I did not response yesterday.",
            Respond::default(),
            "I did not respond yesterday.",
        );
    }

    #[test]
    fn fixes_might_quickly_response() {
        assert_suggestion_result(
            "She might quickly response to feedback.",
            Respond::default(),
            "She might quickly respond to feedback.",
        );
    }

    #[test]
    fn fixes_wont_response() {
        assert_suggestion_result(
            "They won't response in time.",
            Respond::default(),
            "They won't respond in time.",
        );
    }

    #[test]
    fn fixes_would_response() {
        assert_suggestion_result(
            "We would response if we could.",
            Respond::default(),
            "We would respond if we could.",
        );
    }

    #[test]
    fn fixes_should_response() {
        assert_suggestion_result(
            "You should response politely.",
            Respond::default(),
            "You should respond politely.",
        );
    }

    #[test]
    fn does_not_flag_correct_respond() {
        assert_no_lints("Please respond when you can.", Respond::default());
    }

    #[test]
    fn does_not_flag_noun_use() {
        assert_no_lints("The response time was great.", Respond::default());
    }

    #[test]
    fn does_not_flag_question_subject() {
        assert_lint_count("Should response times be logged?", Respond::default(), 0);
    }

    #[test]
    fn does_not_flag_response_as_object() {
        assert_no_lints("I have no response for that.", Respond::default());
    }
}
