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

const TO_MAKE: &[&str] = &["make", "made", "makes", "making"];

pub struct ClaimToFame {
    expr: SequenceExpr,
}

impl Default for ClaimToFame {
    fn default() -> Self {
        Self {
            expr: SequenceExpr::word_set(&["claim", "claims"])
                .t_ws()
                .then_word_seq(&["for", "fame"]),
        }
    }
}

impl ExprLinter for ClaimToFame {
    type Unit = Chunk;

    fn match_to_lint_with_context(
        &self,
        toks: &[Token],
        src: &[char],
        ctx: Option<(&[Token], &[Token])>,
    ) -> Option<Lint> {
        if let Some((before, _)) = ctx
            && let [before @ .., word, ws] = before
            && ws.kind.is_whitespace()
        {
            if word.get_ch(src).eq_any_ignore_ascii_case_str(TO_MAKE) {
                return None;
            } else if (word.kind.is_determiner() || word.kind.is_adjective())
                && let [.., word, ws] = before
                && ws.kind.is_whitespace()
                && word.kind.is_word()
                && word.get_ch(src).eq_any_ignore_ascii_case_str(TO_MAKE)
            {
                return None;
            }
        }

        if let Some((_, after)) = ctx
            && let [ws1, word1, ws2, word2, ..] = after
            && ws1.kind.is_whitespace()
            && word1.kind.is_word()
            && ws2.kind.is_whitespace()
            && word2.kind.is_word()
            && word1.get_ch(src).eq_str("and")
            && (!word2.kind.is_conjunction() && word2.kind.is_noun())
        {
            return None;
        }

        let span = toks.get(2)?.span;

        Some(Lint {
            span,
            lint_kind: LintKind::Eggcorn,
            suggestions: vec![Suggestion::replace_with_match_case_str(
                "to",
                span.get_content(src),
            )],
            message: "The correct idiom is `claim to fame`.".to_owned(),
            ..Default::default()
        })
    }

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

    fn description(&self) -> &str {
        "Corrects `claim for fame` to the idiom `claim to fame`."
    }
}

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

    #[test]
    fn correct_claim_for_fame() {
        assert_suggestion_result(
            "Although it is nice to go archive diving, the main claim for fame is of course the derivation of modern technologies",
            ClaimToFame::default(),
            "Although it is nice to go archive diving, the main claim to fame is of course the derivation of modern technologies",
        )
    }

    #[test]
    fn correct_claims_for_fame() {
        assert_suggestion_result(
            "we note and dismiss offhand some of the bulimic claims for fame that have been attached to Kuhn's classic",
            ClaimToFame::default(),
            "we note and dismiss offhand some of the bulimic claims to fame that have been attached to Kuhn's classic",
        )
    }

    #[test]
    fn dont_made_claims_for_reasons_including_fame() {
        assert_no_lints(
            "His attorney says that he made those claims for fame and money.",
            ClaimToFame::default(),
        );
        assert_no_lints(
            "denied the allegations, saying Davis has acknowledged making claims for fame and notoriety",
            ClaimToFame::default(),
        );
    }

    #[test]
    fn fix_claim_for_fame_and_pron() {
        assert_suggestion_result(
            "My one claim for fame and I blew it.",
            ClaimToFame::default(),
            "My one claim to fame and I blew it.",
        )
    }

    #[test]
    fn dont_flag_make_grand_claims_for_fame() {
        assert_no_lints(
            "There have always been people throughout history who are innovative and make grand claims for fame.",
            ClaimToFame::default(),
        );
    }

    #[test]
    fn fix_claim_for_fame_and_so() {
        assert_suggestion_result(
            "Alan Holt has placed his claim for fame and so I've corrected this lovely image",
            ClaimToFame::default(),
            "Alan Holt has placed his claim to fame and so I've corrected this lovely image",
        );
    }

    #[test]
    fn cant_flag_ambiguous_and_verb_is_also_adj() {
        assert_suggestion_result(
            "VRAAQ and level criteria reinforced the students' claim for fame and enhanced the establishment of trust",
            ClaimToFame::default(),
            "VRAAQ and level criteria reinforced the students' claim to fame and enhanced the establishment of trust",
        );
    }
}
