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

pub struct WinPrize {
    expr: LongestMatchOf,
}

impl Default for WinPrize {
    fn default() -> Self {
        let verbs = Lrc::new(WordSet::new(&["win", "wins", "won", "winning"]));
        let miss = Lrc::new(WordSet::new(&["price", "prices", "prise", "prises"]));

        let pattern = SequenceExpr::with(verbs.clone())
            .then_whitespace()
            .then_determiner()
            .then_whitespace()
            .then(miss.clone())
            .or_longest(SequenceExpr::with(verbs).then_whitespace().then(miss));

        Self { expr: pattern }
    }
}

impl ExprLinter for WinPrize {
    type Unit = Chunk;

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

    fn match_to_lint(&self, toks: &[Token], src: &[char]) -> Option<Lint> {
        let candidate = toks.last()?;
        let raw = candidate.get_str(src).to_lowercase();
        let repl = match raw.as_str() {
            "price" | "prise" => "prize",
            "prices" | "prises" => "prizes",
            _ => return None,
        };

        Some(Lint {
            span: candidate.span,
            lint_kind: LintKind::Miscellaneous,
            suggestions: vec![Suggestion::ReplaceWith(repl.chars().collect())],
            message: format!("Perhaps you meant `{repl}`, the word for an award."),
            priority: 50,
        })
    }

    fn description(&self) -> &str {
        "Catches the mix-up between `price`/`prise` and `prize` after the verb `win`."
    }
}

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

    #[test]
    fn fix_price_singular() {
        assert_suggestion_result(
            "Lena won a price in the coding marathon.",
            WinPrize::default(),
            "Lena won a prize in the coding marathon.",
        );
    }

    #[test]
    fn fix_price_plural() {
        assert_suggestion_result(
            "Our team won the prices announced yesterday.",
            WinPrize::default(),
            "Our team won the prizes announced yesterday.",
        );
    }

    #[test]
    fn fix_prise_singular() {
        assert_suggestion_result(
            "He finally won the prise he'd dreamed of.",
            WinPrize::default(),
            "He finally won the prize he'd dreamed of.",
        );
    }

    #[test]
    fn fix_prise_plural() {
        assert_suggestion_result(
            "The inventors won several prises at the expo.",
            WinPrize::default(),
            "The inventors won several prizes at the expo.",
        );
    }

    #[test]
    fn ignore_correct_prize() {
        assert_lint_count(
            "Miranda won the grand prize last year.",
            WinPrize::default(),
            0,
        );
    }

    #[test]
    fn fix_no_det() {
        assert_suggestion_result("I won prices!", WinPrize::default(), "I won prizes!");
    }
}
