use syntax::{
    ast::{self, edit_in_place::Indent, syntax_factory::SyntaxFactory},
    AstNode,
};

use crate::{AssistContext, AssistId, AssistKind, Assists};

// Assist: add_braces
//
// Adds braces to lambda and match arm expressions.
//
// ```
// fn foo(n: i32) -> i32 {
//     match n {
//         1 =>$0 n + 1,
//         _ => 0
//     }
// }
// ```
// ->
// ```
// fn foo(n: i32) -> i32 {
//     match n {
//         1 => {
//             n + 1
//         },
//         _ => 0
//     }
// }
// ```
pub(crate) fn add_braces(acc: &mut Assists, ctx: &AssistContext<'_>) -> Option<()> {
    let (expr_type, expr) = get_replacement_node(ctx)?;

    acc.add(
        AssistId("add_braces", AssistKind::RefactorRewrite),
        match expr_type {
            ParentType::ClosureExpr => "Add braces to closure body",
            ParentType::MatchArmExpr => "Add braces to arm expression",
        },
        expr.syntax().text_range(),
        |builder| {
            let make = SyntaxFactory::new();
            let mut editor = builder.make_editor(expr.syntax());

            let block_expr = make.block_expr(None, Some(expr.clone()));
            block_expr.indent(expr.indent_level());

            editor.replace(expr.syntax(), block_expr.syntax());

            editor.add_mappings(make.finish_with_mappings());
            builder.add_file_edits(ctx.file_id(), editor);
        },
    )
}

enum ParentType {
    MatchArmExpr,
    ClosureExpr,
}

fn get_replacement_node(ctx: &AssistContext<'_>) -> Option<(ParentType, ast::Expr)> {
    if let Some(match_arm) = ctx.find_node_at_offset::<ast::MatchArm>() {
        let match_arm_expr = match_arm.expr()?;

        if matches!(match_arm_expr, ast::Expr::BlockExpr(_)) {
            return None;
        }

        return Some((ParentType::MatchArmExpr, match_arm_expr));
    } else if let Some(closure_expr) = ctx.find_node_at_offset::<ast::ClosureExpr>() {
        let body = closure_expr.body()?;

        if matches!(body, ast::Expr::BlockExpr(_)) {
            return None;
        }

        return Some((ParentType::ClosureExpr, body));
    }

    None
}

#[cfg(test)]
mod tests {
    use crate::tests::{check_assist, check_assist_not_applicable};

    use super::*;

    #[test]
    fn suggest_add_braces_for_closure() {
        check_assist(
            add_braces,
            r#"
fn foo() {
    t(|n|$0 n + 100);
}
"#,
            r#"
fn foo() {
    t(|n| {
        n + 100
    });
}
"#,
        );
    }

    #[test]
    fn no_assist_for_closures_with_braces() {
        check_assist_not_applicable(
            add_braces,
            r#"
fn foo() {
    t(|n|$0 { n + 100 });
}
"#,
        );
    }

    #[test]
    fn suggest_add_braces_for_match() {
        check_assist(
            add_braces,
            r#"
fn foo() {
    match n {
        Some(n) $0=> 29,
        _ => ()
    };
}
"#,
            r#"
fn foo() {
    match n {
        Some(n) => {
            29
        },
        _ => ()
    };
}
"#,
        );
    }

    #[test]
    fn no_assist_for_match_with_braces() {
        check_assist_not_applicable(
            add_braces,
            r#"
fn foo() {
    match n {
        Some(n) $0=> { return 29; },
        _ => ()
    };
}
"#,
        );
    }
}
