use hir::db::HirDatabase;
use ra_syntax::{
    algo::non_trivia_sibling,
    ast::{self, AstNode},
    Direction, T,
};

use crate::{Assist, AssistCtx, AssistId};

// Assist: flip_trait_bound
//
// Flips two trait bounds.
//
// ```
// fn foo<T: Clone +<|> Copy>() { }
// ```
// ->
// ```
// fn foo<T: Copy + Clone>() { }
// ```
pub(crate) fn flip_trait_bound(ctx: AssistCtx<impl HirDatabase>) -> Option<Assist> {
    // We want to replicate the behavior of `flip_binexpr` by only suggesting
    // the assist when the cursor is on a `+`
    let plus = ctx.find_token_at_offset(T![+])?;

    // Make sure we're in a `TypeBoundList`
    if ast::TypeBoundList::cast(plus.parent()).is_none() {
        return None;
    }

    let (before, after) = (
        non_trivia_sibling(plus.clone().into(), Direction::Prev)?,
        non_trivia_sibling(plus.clone().into(), Direction::Next)?,
    );

    ctx.add_assist(AssistId("flip_trait_bound"), "flip trait bound", |edit| {
        edit.target(plus.text_range());
        edit.replace(before.text_range(), after.to_string());
        edit.replace(after.text_range(), before.to_string());
    })
}

#[cfg(test)]
mod tests {
    use super::*;

    use crate::helpers::{check_assist, check_assist_not_applicable, check_assist_target};

    #[test]
    fn flip_trait_bound_assist_available() {
        check_assist_target(flip_trait_bound, "struct S<T> where T: A <|>+ B + C { }", "+")
    }

    #[test]
    fn flip_trait_bound_not_applicable_for_single_trait_bound() {
        check_assist_not_applicable(flip_trait_bound, "struct S<T> where T: <|>A { }")
    }

    #[test]
    fn flip_trait_bound_works_for_struct() {
        check_assist(
            flip_trait_bound,
            "struct S<T> where T: A <|>+ B { }",
            "struct S<T> where T: B <|>+ A { }",
        )
    }

    #[test]
    fn flip_trait_bound_works_for_trait_impl() {
        check_assist(
            flip_trait_bound,
            "impl X for S<T> where T: A +<|> B { }",
            "impl X for S<T> where T: B +<|> A { }",
        )
    }

    #[test]
    fn flip_trait_bound_works_for_fn() {
        check_assist(flip_trait_bound, "fn f<T: A <|>+ B>(t: T) { }", "fn f<T: B <|>+ A>(t: T) { }")
    }

    #[test]
    fn flip_trait_bound_works_for_fn_where_clause() {
        check_assist(
            flip_trait_bound,
            "fn f<T>(t: T) where T: A +<|> B { }",
            "fn f<T>(t: T) where T: B +<|> A { }",
        )
    }

    #[test]
    fn flip_trait_bound_works_for_lifetime() {
        check_assist(
            flip_trait_bound,
            "fn f<T>(t: T) where T: A <|>+ 'static { }",
            "fn f<T>(t: T) where T: 'static <|>+ A { }",
        )
    }

    #[test]
    fn flip_trait_bound_works_for_complex_bounds() {
        check_assist(
            flip_trait_bound,
            "struct S<T> where T: A<T> <|>+ b_mod::B<T> + C<T> { }",
            "struct S<T> where T: b_mod::B<T> <|>+ A<T> + C<T> { }",
        )
    }

    #[test]
    fn flip_trait_bound_works_for_long_bounds() {
        check_assist(
            flip_trait_bound,
            "struct S<T> where T: A + B + C + D + E + F +<|> G + H + I + J { }",
            "struct S<T> where T: A + B + C + D + E + G +<|> F + H + I + J { }",
        )
    }
}