use crate::expr::{Expr, SequenceExpr, SpaceOrHyphen};
use crate::linting::expr_linter::Chunk;
use crate::{Token, TokenStringExt};

use super::{ExprLinter, Lint, LintKind, Suggestion};

pub struct Handful {
    expr: SequenceExpr,
}

impl Default for Handful {
    fn default() -> Self {
        let expr = SequenceExpr::any_capitalization_of("hand")
            .then_one_or_more(SpaceOrHyphen)
            .then_any_capitalization_of("full")
            .then_one_or_more(SpaceOrHyphen)
            .then_any_capitalization_of("of");

        Self { expr }
    }
}

impl ExprLinter for Handful {
    type Unit = Chunk;

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

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

        let mut highlight_end = matched_tokens.len() - 1;
        while highlight_end > 0 {
            let prev = &matched_tokens[highlight_end - 1];
            if prev.kind.is_whitespace() || prev.kind.is_hyphen() {
                highlight_end -= 1;
            } else {
                break;
            }
        }

        if highlight_end == 0 {
            return None;
        }

        let replacement = &matched_tokens[..highlight_end];
        let span = replacement.span()?;
        let template = matched_tokens.first()?.get_ch(source);

        Some(Lint {
            span,
            lint_kind: LintKind::BoundaryError,
            suggestions: vec![Suggestion::replace_with_match_case(
                "handful".chars().collect(),
                template,
            )],
            message: "Write this quantity as the single word `handful`.".to_owned(),
            priority: 31,
        })
    }

    fn description(&self) -> &'static str {
        "Keeps the palm-sized quantity expressed by `handful` as one word."
    }
}

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

    #[test]
    fn suggests_plain_spacing() {
        assert_suggestion_result(
            "Her basket held a hand full of berries.",
            Handful::default(),
            "Her basket held a handful of berries.",
        );
    }

    #[test]
    fn suggests_capitalized_form() {
        assert_suggestion_result(
            "Hand full of tales lined the shelf.",
            Handful::default(),
            "Handful of tales lined the shelf.",
        );
    }

    #[test]
    fn suggests_hyphenated_form() {
        assert_suggestion_result(
            "A hand-full of marbles scattered across the floor.",
            Handful::default(),
            "A handful of marbles scattered across the floor.",
        );
    }

    #[test]
    fn suggests_space_hyphen_combo() {
        assert_suggestion_result(
            "A hand - full of seeds spilled on the workbench.",
            Handful::default(),
            "A handful of seeds spilled on the workbench.",
        );
    }

    #[test]
    fn suggests_initial_hyphen_variants() {
        assert_suggestion_result(
            "Hand-Full of furniture, the cart creaked slowly.",
            Handful::default(),
            "Handful of furniture, the cart creaked slowly.",
        );
    }

    #[test]
    fn flags_multiple_instances() {
        assert_lint_count(
            "She carried a hand full of carrots and a hand full of radishes.",
            Handful::default(),
            2,
        );
    }

    #[test]
    fn allows_correct_handful() {
        assert_no_lints(
            "A handful of volunteers arrived in time.",
            Handful::default(),
        );
    }

    #[test]
    fn allows_parenthetical_hand() {
        assert_no_lints(
            "His hand, full of ink, kept writing without pause.",
            Handful::default(),
        );
    }

    #[test]
    fn allows_hand_is_full() {
        assert_no_lints("The hand is full of water.", Handful::default());
    }

    #[test]
    fn allows_handfull_typo() {
        assert_no_lints(
            "The word handfull is an incorrect spelling.",
            Handful::default(),
        );
    }
}
