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

pub struct AskNoPreposition {
    expr: SequenceExpr,
}

impl Default for AskNoPreposition {
    fn default() -> Self {
        let verbs = WordSet::new(&[
            "ask", "asks", "asked", "asking", "tell", "tells", "told", "telling",
        ]);

        let objs = WordSet::new(&["me", "you", "him", "her", "it", "us", "them", "one"]);

        let pattern = SequenceExpr::with(verbs)
            .then_whitespace()
            .t_aco("to")
            .then_whitespace()
            .then(objs);

        Self { expr: pattern }
    }
}

impl ExprLinter for AskNoPreposition {
    type Unit = Chunk;

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

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        if toks.len() < 5 {
            return None;
        }

        let verb = toks[0].get_str(src).to_lowercase();
        let span = Span::new(toks[2].span.start, toks[3].span.end);

        Some(Lint {
            span,
            lint_kind: LintKind::WordChoice,
            suggestions: vec![Suggestion::ReplaceWith(Vec::new())],
            message: format!(
                "The verb `to {verb} someone` should not be preceded by the preposition `to`."
            ),
            priority: 63,
        })
    }

    fn description(&self) -> &str {
        "Identifies sequences like `ask to us` or `tell to him` and recommends removing the superfluous “to”."
    }
}

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

    #[test]
    fn flags_ask() {
        assert_suggestion_result(
            "Nora asked to us about the concert lineup.",
            AskNoPreposition::default(),
            "Nora asked us about the concert lineup.",
        );
    }

    #[test]
    fn flags_ask_all_caps() {
        assert_suggestion_result(
            "NORA ASKED TO US ABOUT THE CONCERT LINEUP.",
            AskNoPreposition::default(),
            "NORA ASKED US ABOUT THE CONCERT LINEUP.",
        );
    }

    #[test]
    fn flags_tell() {
        assert_suggestion_result(
            "Please tell to him the results promptly.",
            AskNoPreposition::default(),
            "Please tell him the results promptly.",
        );
    }

    #[test]
    fn ignores_correct_usage() {
        assert_lint_count(
            "She asked her mentor a difficult question.",
            AskNoPreposition::default(),
            0,
        );
    }

    #[test]
    fn flags_ask_us() {
        assert_suggestion_result(
            "Can you ask to us for directions?",
            AskNoPreposition::default(),
            "Can you ask us for directions?",
        );
    }

    #[test]
    fn flags_asks_him() {
        assert_suggestion_result(
            "Julia asks to him every morning about the report.",
            AskNoPreposition::default(),
            "Julia asks him every morning about the report.",
        );
    }

    #[test]
    fn flags_asked_me() {
        assert_suggestion_result(
            "They asked to me why I left early.",
            AskNoPreposition::default(),
            "They asked me why I left early.",
        );
    }

    #[test]
    fn flags_told_one() {
        assert_suggestion_result(
            "The guide told to one the secret path.",
            AskNoPreposition::default(),
            "The guide told one the secret path.",
        );
    }

    #[test]
    fn flags_telling_it() {
        assert_suggestion_result(
            "She is telling to it with gentle words.",
            AskNoPreposition::default(),
            "She is telling it with gentle words.",
        );
    }

    #[test]
    fn flags_tells_them() {
        assert_suggestion_result(
            "He tells to them stories at night.",
            AskNoPreposition::default(),
            "He tells them stories at night.",
        );
    }

    #[test]
    fn flags_telling_him() {
        assert_suggestion_result(
            "I was telling to him the latest news.",
            AskNoPreposition::default(),
            "I was telling him the latest news.",
        );
    }

    #[test]
    fn flags_asking_you() {
        assert_suggestion_result(
            "Someone is asking to you for help.",
            AskNoPreposition::default(),
            "Someone is asking you for help.",
        );
    }

    #[test]
    fn ignores_ask_question() {
        assert_lint_count(
            "Ask her the question directly.",
            AskNoPreposition::default(),
            0,
        );
    }

    #[test]
    fn ignores_told_to_leave() {
        assert_lint_count(
            "He was told to leave immediately.",
            AskNoPreposition::default(),
            0,
        );
    }

    #[test]
    fn ignores_tell_us() {
        assert_lint_count("Please tell us your name.", AskNoPreposition::default(), 0);
    }

    #[test]
    fn ignores_ask_about() {
        assert_lint_count(
            "They asked about the schedule.",
            AskNoPreposition::default(),
            0,
        );
    }
}
