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

pub struct OutOfTheWindow {
    expr: SequenceExpr,
    dialect: Dialect,
}

impl OutOfTheWindow {
    pub fn new(dialect: Dialect) -> Self {
        Self {
            expr: SequenceExpr::aco("out")
                .then_optional(SequenceExpr::whitespace().t_aco("of"))
                .t_ws()
                .t_aco("the")
                .t_ws()
                .t_aco("window"),
            dialect,
        }
    }
}

impl ExprLinter for OutOfTheWindow {
    type Unit = Chunk;

    fn match_to_lint(&self, toks: &[Token], _src: &[char]) -> Option<Lint> {
        let (span, sugg, msg) = match (toks.len(), self.dialect) {
            (7, Dialect::American | Dialect::Australian | Dialect::Canadian) => {
                (
                    toks[1..=2].span()?,
                    Suggestion::Remove,
                    format!("If this is the idiom about abandoning a plan, it's more usual to leave out `of` in {} English", self.dialect),
                )
            }
            (5, Dialect::British) => {
                (
                    toks[0].span,
                    Suggestion::InsertAfter(vec![' ', 'o', 'f']),
                    "If this is the idiom about abandoning a plan, consider using `of` in British English".to_string(),
                )
            }
            _ => return None,
        };

        Some(Lint {
            span,
            lint_kind: LintKind::Regionalism,
            suggestions: vec![sugg],
            message: msg,
            ..Default::default()
        })
    }

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

    fn description(&self) -> &str {
        "A linter for the idiom `out (of) the window`."
    }
}

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

    use super::OutOfTheWindow;

    #[test]
    fn us_english() {
        assert_suggestion_result(
            "The whole Gnome session goes out of the window instantly, both upon explicit screen locking and upon automatic locking after a timeout.",
            OutOfTheWindow::new(Dialect::American),
            "The whole Gnome session goes out the window instantly, both upon explicit screen locking and upon automatic locking after a timeout.",
        );
    }

    #[test]
    fn uk_english() {
        assert_suggestion_result(
            "determinism went out the window, everything is terrible",
            OutOfTheWindow::new(Dialect::British),
            "determinism went out of the window, everything is terrible",
        );
    }

    #[test]
    fn in_english_doesnt_flag_with_of() {
        assert_no_lints(
            "errors and orchestration in general all go out of the window",
            OutOfTheWindow::new(Dialect::Indian),
        );
    }

    #[test]
    fn in_english_doesnt_flag_without_of() {
        assert_no_lints(
            "I was so excited to develop my new app in .NET MAUI but then all this excitement went out the window due to two things stability and tooling.",
            OutOfTheWindow::new(Dialect::Indian),
        );
    }

    #[test]
    fn gives_the_right_message_for_canadian_english() {
        assert_lint_message(
            "This all works great unless I use middlware and then it all goes out the window.",
            OutOfTheWindow::new(Dialect::Canadian),
            "If this is the idiom about abandoning a plan, it's more usual to leave out `of` in Canadian English",
        );
    }
}
