use std::ops::Range;

use crate::expr::{Expr, ExprMap, SequenceExpr};
use crate::patterns::DerivedFrom;
use crate::sync::Lrc;
use crate::{Token, TokenStringExt};

use super::{ExprLinter, Lint, LintKind, Suggestion};
use crate::linting::expr_linter::Chunk;

pub struct CallThem {
    expr: ExprMap<Range<usize>>,
}

impl Default for CallThem {
    fn default() -> Self {
        let mut map = ExprMap::default();

        let post_exception = Lrc::new(SequenceExpr::default().t_ws().then_word_set(&["if", "it"]));

        map.insert(
            SequenceExpr::with(DerivedFrom::new_from_str("call"))
                .t_ws()
                .then_pronoun()
                .t_ws()
                .t_aco("as")
                .then_unless(post_exception.clone()),
            3..5,
        );

        map.insert(
            SequenceExpr::with(DerivedFrom::new_from_str("call"))
                .t_ws()
                .t_aco("as")
                .t_ws()
                .then_pronoun()
                .then_unless(post_exception.clone()),
            1..3,
        );

        Self { expr: map }
    }
}

impl ExprLinter for CallThem {
    type Unit = Chunk;

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

    fn match_to_lint(&self, matched_tokens: &[Token], source: &[char]) -> Option<Lint> {
        let removal_range = self.expr.lookup(0, matched_tokens, source)?.clone();
        let offending_tokens = matched_tokens.get(removal_range)?;

        Some(Lint {
            span: offending_tokens.span()?,
            lint_kind: LintKind::Redundancy,
            suggestions: vec![Suggestion::Remove],
            message: "`as` is redundant in this context.".to_owned(),
            ..Default::default()
        })
    }

    fn description(&self) -> &'static str {
        "Addresses the non-idiomatic phrases `call them as`."
    }
}

#[cfg(test)]
mod tests {
    #[allow(unused_imports)]
    use crate::Document;
    use crate::linting::tests::{assert_no_lints, assert_suggestion_result};

    use super::CallThem;

    #[test]
    fn prefer_plug_and_receptacle() {
        assert_suggestion_result(
            r#"I prefer to call them as Plug (male) and Receptacle (female). Receptacles are seen in laptops, mobile phones etc.."#,
            CallThem::default(),
            r#"I prefer to call them Plug (male) and Receptacle (female). Receptacles are seen in laptops, mobile phones etc.."#,
        );
    }

    #[test]
    fn builtins_id() {
        assert_suggestion_result(
            r#"I’d categorically ignore *id* as a builtin, and when you do need it in a module, make it super explicit and `import builtins` and call it as `builtins.id`."#,
            CallThem::default(),
            r#"I’d categorically ignore *id* as a builtin, and when you do need it in a module, make it super explicit and `import builtins` and call it `builtins.id`."#,
        );
    }

    #[test]
    fn non_modal_dialogue() {
        assert_suggestion_result(
            r#"We usually call it as non-modal dialogue e.g. when hit Gmail compose button, a nonmodal dialogue opens."#,
            CallThem::default(),
            r#"We usually call it non-modal dialogue e.g. when hit Gmail compose button, a nonmodal dialogue opens."#,
        );
    }

    #[test]
    fn prefer_to_call_them() {
        assert_suggestion_result(
            r#"So, how do you typically prefer to call them as?"#,
            CallThem::default(),
            r#"So, how do you typically prefer to call them?"#,
        );
    }

    #[test]
    fn called_them_allies() {
        assert_suggestion_result(
            r#"Yes as tribes or nomads you called them as allies but you didn’t get their levies as your own."#,
            CallThem::default(),
            r#"Yes as tribes or nomads you called them allies but you didn’t get their levies as your own."#,
        );
    }

    #[test]
    fn character_development() {
        assert_suggestion_result(
            r#"I call this as character development."#,
            CallThem::default(),
            r#"I call this character development."#,
        );
    }

    #[test]
    fn fate_or_time() {
        assert_suggestion_result(
            r#"Should I Call It As Fate Or Time"#,
            CallThem::default(),
            r#"Should I Call It Fate Or Time"#,
        );
    }

    #[test]
    fn abstract_latte_art() {
        assert_suggestion_result(
            r#"Can we just call it as abstract latte art."#,
            CallThem::default(),
            r#"Can we just call it abstract latte art."#,
        );
    }

    #[test]
    fn sounding_boards() {
        assert_suggestion_result(
            r#"I call them as my ‘sounding boards’"#,
            CallThem::default(),
            r#"I call them my ‘sounding boards’"#,
        );
    }

    #[test]
    fn calling_them_disaster() {
        assert_suggestion_result(
            r#"I totally disagree with your point listed and calling them as disaster."#,
            CallThem::default(),
            r#"I totally disagree with your point listed and calling them disaster."#,
        );
    }

    #[test]
    fn battle_of_boxes() {
        assert_suggestion_result(
            r#"Windows Sandbox and VirtualBox or I would like to call this as “Battle of Boxes.”"#,
            CallThem::default(),
            r#"Windows Sandbox and VirtualBox or I would like to call this “Battle of Boxes.”"#,
        );
    }

    #[test]
    fn called_her_shinnasan() {
        assert_suggestion_result(
            r#"Nice meeting a follower from reddit I called her as Shinna-san, welcome again to Toram!!"#,
            CallThem::default(),
            r#"Nice meeting a follower from reddit I called her Shinna-san, welcome again to Toram!!"#,
        );
    }

    #[test]
    fn calling_it_otp() {
        assert_suggestion_result(
            r#"Calling it as OTP in this case misleading"#,
            CallThem::default(),
            r#"Calling it OTP in this case misleading"#,
        );
    }

    #[test]
    fn call_it_procrastination() {
        assert_suggestion_result(
            r#"To summarise it in just one word I would call it as procrastination."#,
            CallThem::default(),
            r#"To summarise it in just one word I would call it procrastination."#,
        );
    }

    #[test]
    fn call_her_important() {
        assert_suggestion_result(
            r#"Liked the article overall but to call her as important to rap as Jay or Dre is a bold overstatement."#,
            CallThem::default(),
            r#"Liked the article overall but to call her important to rap as Jay or Dre is a bold overstatement."#,
        );
    }

    #[test]
    fn call_him_kindles() {
        assert_suggestion_result(
            r#"The days when I had my first best friend, I would rather call him as human version of kindle audiobook, who keeps on talking about everything under the umbrella."#,
            CallThem::default(),
            r#"The days when I had my first best friend, I would rather call him human version of kindle audiobook, who keeps on talking about everything under the umbrella."#,
        );
    }

    #[test]
    fn call_them_defenders() {
        assert_suggestion_result(
            r#"Declaring war challenging land of a vassal should call them as defenders!"#,
            CallThem::default(),
            r#"Declaring war challenging land of a vassal should call them defenders!"#,
        );
    }

    #[test]
    fn call_it_magical() {
        assert_suggestion_result(
            r#"I would like to call it as magical."#,
            CallThem::default(),
            r#"I would like to call it magical."#,
        );
    }

    #[test]
    fn forward_lateral() {
        assert_suggestion_result(
            r#"Surprised the refs didn’t call this as a forward lateral."#,
            CallThem::default(),
            r#"Surprised the refs didn’t call this a forward lateral."#,
        );
    }

    #[test]
    fn calling_best_friend() {
        assert_suggestion_result(
            r#"Meet my buddy! I love calling him as my best friend, because he never failed to bring some cheer in me!"#,
            CallThem::default(),
            r#"Meet my buddy! I love calling him my best friend, because he never failed to bring some cheer in me!"#,
        );
    }

    #[test]
    fn calling_everyone_titles() {
        assert_suggestion_result(
            r#"Currently, I’m teaching in Asia and the students have the local custom of calling everyone as Mr. Givenname or Miss Givenname"#,
            CallThem::default(),
            r#"Currently, I’m teaching in Asia and the students have the local custom of calling everyone Mr. Givenname or Miss Givenname"#,
        );
    }

    #[test]
    fn called_as_he() {
        assert_suggestion_result(
            r#"I prefer to be called as he when referred in 3rd person and I’m sure that everyone would be ok to call me as he."#,
            CallThem::default(),
            r#"I prefer to be called he when referred in 3rd person and I’m sure that everyone would be ok to call me he."#,
        );
    }

    #[test]
    fn calls_him_bob() {
        assert_suggestion_result(
            r#"In Twelve Monkeys, Cole hears someone who calls him as “Bob”"#,
            CallThem::default(),
            r#"In Twelve Monkeys, Cole hears someone who calls him “Bob”"#,
        );
    }

    #[test]
    fn pliny_called_it() {
        assert_suggestion_result(
            r#"Pliny the Elder called it as lake of Gennesaret or Taricheae in his encyclopedia, Natural History."#,
            CallThem::default(),
            r#"Pliny the Elder called it lake of Gennesaret or Taricheae in his encyclopedia, Natural History."#,
        );
    }

    #[test]
    fn students_call_you() {
        assert_suggestion_result(
            r#"In the same way your students will call you as ~先生 even after they graduated/move to higher education."#,
            CallThem::default(),
            r#"In the same way your students will call you ~先生 even after they graduated/move to higher education."#,
        );
    }

    #[test]
    fn paradoxical_reaction() {
        assert_suggestion_result(
            r#"We can call it as Paradoxical Reaction which means a medicine which is used to reduce pain increases the pain when it is"#,
            CallThem::default(),
            r#"We can call it Paradoxical Reaction which means a medicine which is used to reduce pain increases the pain when it is"#,
        );
    }

    #[test]
    fn rust_module() {
        assert_no_lints(
            "I want to call them as if they were just another Rust module",
            CallThem::default(),
        );
    }

    #[test]
    fn want_to_do() {
        assert_no_lints(
            "however its a design choice to not call it as it does things I don't want to do.",
            CallThem::default(),
        );
    }
}
