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

pub struct CureFor {
    expr: SequenceExpr,
}

impl Default for CureFor {
    fn default() -> Self {
        let expr = SequenceExpr::with(DerivedFrom::new_from_str("cure"))
            .t_ws()
            .t_aco("against");

        Self { expr }
    }
}

impl ExprLinter for CureFor {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let against = matched_tokens.last()?;

        let template: Vec<char> = against.get_ch(source).to_vec();
        let suggestion = Suggestion::replace_with_match_case_str("for", &template);

        Some(Lint {
            span: Span::new(against.span.start, against.span.end),
            lint_kind: LintKind::Usage,
            suggestions: vec![suggestion],
            message: "Prefer `cure for` when describing a treatment target.".to_owned(),
            priority: 31,
        })
    }

    fn description(&self) -> &str {
        "Flags `cure against` and prefers the standard `cure for` pairing."
    }
}

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

    #[test]
    fn corrects_simple_cure_against() {
        assert_suggestion_result(
            "Researchers sought a cure against the stubborn illness.",
            CureFor::default(),
            "Researchers sought a cure for the stubborn illness.",
        );
    }

    #[test]
    fn corrects_plural_cures_against() {
        assert_suggestion_result(
            "Doctors insist this serum cures against the new variant.",
            CureFor::default(),
            "Doctors insist this serum cures for the new variant.",
        );
    }

    #[test]
    fn corrects_past_participle_cured_against() {
        assert_suggestion_result(
            "The remedy was cured against the infection last spring.",
            CureFor::default(),
            "The remedy was cured for the infection last spring.",
        );
    }

    #[test]
    fn corrects_uppercase_against() {
        assert_suggestion_result(
            "We still trust the cure AGAINST the dreaded plague.",
            CureFor::default(),
            "We still trust the cure FOR the dreaded plague.",
        );
    }

    #[test]
    fn corrects_at_sentence_start() {
        assert_suggestion_result(
            "Cure against that condition became the rallying cry.",
            CureFor::default(),
            "Cure for that condition became the rallying cry.",
        );
    }

    #[test]
    fn does_not_flag_cure_for() {
        assert_lint_count(
            "They finally found a cure for the fever.",
            CureFor::default(),
            0,
        );
    }

    #[test]
    fn does_not_flag_cure_from() {
        assert_lint_count(
            "A cure from this rare herb is on the horizon.",
            CureFor::default(),
            0,
        );
    }

    #[test]
    fn does_not_flag_with_comma() {
        assert_lint_count(
            "A cure, against all odds, appeared in the files.",
            CureFor::default(),
            0,
        );
    }

    #[test]
    fn does_not_flag_unrelated_against() {
        assert_lint_count(
            "Travelers stand against the roaring wind on the cliffs.",
            CureFor::default(),
            0,
        );
    }

    #[test]
    fn does_not_flag_secure_against() {
        assert_lint_count(
            "The fortress stayed secure against the invaders.",
            CureFor::default(),
            0,
        );
    }
}
