use expect_test::{Expect, expect};
use hir_def::{
    AdtId, DefWithBodyId, LocalFieldId, VariantId,
    expr_store::{Body, ExpressionStore},
    hir::{BindingId, ExprOrPatId},
};
use hir_expand::{HirFileId, files::InFileWrapper};
use itertools::Itertools;
use rustc_type_ir::inherent::IntoKind;
use span::{Edition, TextRange};
use stdx::{format_to, never};
use syntax::{AstNode, AstPtr};
use test_fixture::WithFixture;

use crate::{
    InferenceResult,
    closure_analysis::Place,
    display::{DisplayTarget, HirDisplay},
    next_solver::TyKind,
    test_db::TestDB,
};

use super::{setup_tracing, visit_module};

fn display_place(db: &TestDB, store: &ExpressionStore, place: &Place, local: BindingId) -> String {
    let mut result = store[local].name.display(db, Edition::LATEST).to_string();
    let mut last_was_deref = false;
    for (i, proj) in place.projections.iter().enumerate() {
        match proj.kind {
            hir_ty::closure_analysis::ProjectionKind::Deref => {
                result.insert(0, '*');
                last_was_deref = true;
            }
            hir_ty::closure_analysis::ProjectionKind::Field { field_idx, variant_idx } => {
                if last_was_deref {
                    result.insert(0, '(');
                    result.push(')');
                    last_was_deref = false;
                }

                let ty = place.ty_before_projection(i);
                match ty.kind() {
                    TyKind::Tuple(_) => format_to!(result, ".{field_idx}"),
                    TyKind::Adt(adt_def, _) => {
                        let variant = match adt_def.def_id() {
                            AdtId::StructId(id) => VariantId::from(id),
                            AdtId::UnionId(id) => id.into(),
                            AdtId::EnumId(id) => {
                                // Can't really do that for an enum, unfortunately, so try to do something alike.
                                id.enum_variants(db).variants[variant_idx as usize].0.into()
                            }
                        };
                        let field = &variant.fields(db).fields()
                            [LocalFieldId::from_raw(la_arena::RawIdx::from_u32(field_idx))];
                        format_to!(result, ".{}", field.name.display(db, Edition::LATEST));
                    }
                    _ => never!("mismatching projection type"),
                }
            }
            _ => never!("unexpected projection kind"),
        }
    }
    result
}

fn check_closure_captures(#[rust_analyzer::rust_fixture] ra_fixture: &str, expect: Expect) {
    let _tracing = setup_tracing();
    let (db, file_id) = TestDB::with_single_file(ra_fixture);
    crate::attach_db(&db, || {
        let module = db.module_for_file(file_id.file_id(&db));
        let def_map = module.def_map(&db);

        let mut defs = Vec::new();
        visit_module(&db, def_map, module, &mut |it| defs.push(it));

        let mut captures_info = Vec::new();
        for def in defs {
            let def: DefWithBodyId = match def {
                hir_def::ModuleDefId::FunctionId(it) => it.into(),
                hir_def::ModuleDefId::EnumVariantId(it) => it.into(),
                hir_def::ModuleDefId::ConstId(it) => it.into(),
                hir_def::ModuleDefId::StaticId(it) => it.into(),
                _ => continue,
            };
            let infer = InferenceResult::of(&db, def);
            let db = &db;
            captures_info.extend(infer.closures_data.iter().flat_map(|(closure, closure_data)| {
                let (body, source_map) = Body::with_source_map(db, def);
                let closure_text_range = source_map
                    .expr_syntax(*closure)
                    .expect("failed to map closure to SyntaxNode")
                    .value
                    .text_range();
                closure_data.min_captures.values().flatten().map(move |capture| {
                    fn text_range<N: AstNode>(
                        db: &TestDB,
                        syntax: InFileWrapper<HirFileId, AstPtr<N>>,
                    ) -> TextRange {
                        let root = syntax.file_syntax(db);
                        syntax.value.to_node(&root).syntax().text_range()
                    }

                    // FIXME: Deduplicate this with hir::Local::sources().
                    let captured_local = capture.captured_local();
                    let local_text_range = if body.self_params.contains(&captured_local)
                        && let Some(source) = source_map.self_param_syntax()
                    {
                        format!("{:?}", text_range(db, source))
                    } else {
                        source_map
                            .patterns_for_binding(captured_local)
                            .iter()
                            .map(|&definition| {
                                text_range(db, source_map.pat_syntax(definition).unwrap())
                            })
                            .map(|it| format!("{it:?}"))
                            .join(", ")
                    };
                    let place = display_place(db, body, &capture.place, captured_local);
                    let capture_ty = capture
                        .captured_ty(db)
                        .display_test(db, DisplayTarget::from_crate(db, module.krate(db)))
                        .to_string();
                    let spans = capture
                        .info
                        .sources
                        .iter()
                        .flat_map(|span| match span.final_source() {
                            ExprOrPatId::ExprId(expr) => {
                                vec![text_range(db, source_map.expr_syntax(expr).unwrap())]
                            }
                            ExprOrPatId::PatId(pat) => {
                                vec![text_range(db, source_map.pat_syntax(pat).unwrap())]
                            }
                        })
                        .sorted_by_key(|it| it.start())
                        .map(|it| format!("{it:?}"))
                        .join(",");

                    (
                        closure_text_range,
                        local_text_range,
                        spans,
                        place,
                        capture_ty,
                        capture.info.capture_kind,
                    )
                })
            }));
        }
        captures_info.sort_unstable_by_key(|(closure_text_range, local_text_range, ..)| {
            (closure_text_range.start(), local_text_range.clone())
        });

        let rendered = captures_info
        .iter()
        .map(|(closure_text_range, local_text_range, spans, place, capture_ty, capture_kind)| {
            format!(
                "{closure_text_range:?};{local_text_range};{spans} {capture_kind:?} {place} {capture_ty}"
            )
        })
        .join("\n");

        expect.assert_eq(&rendered);
    })
}

#[test]
fn deref_in_let() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { let b = *a; };
}
"#,
        expect!["53..71;20..21;66..68 ByRef(Immutable) *a &'<erased> bool"],
    );
}

#[test]
fn deref_then_ref_pattern() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { let &mut ref b = a; };
}
"#,
        expect!["53..79;20..21;62..72 ByRef(Immutable) *a &'<erased> bool"],
    );
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { let &mut ref mut b = a; };
}
"#,
        expect!["53..83;20..21;62..76 ByRef(Mutable) *a &'<erased> mut bool"],
    );
}

#[test]
fn unique_borrow() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { *a = false; };
}
"#,
        expect!["53..71;20..21;58..60 ByRef(Mutable) *a &'<erased> mut bool"],
    );
}

#[test]
fn deref_ref_mut() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { let ref mut b = *a; };
}
"#,
        expect!["53..79;20..21;74..76 ByRef(Mutable) *a &'<erased> mut bool"],
    );
}

#[test]
fn let_else_not_consuming() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let a = &mut true;
    let closure = || { let _ = *a else { return; }; };
}
"#,
        expect![""],
    );
}

#[test]
fn consume() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct NonCopy;
fn main() {
    let a = NonCopy;
    let closure = || { let b = a; };
}
"#,
        expect!["67..84;36..37;80..81 ByValue a NonCopy"],
    );
}

#[test]
fn ref_to_upvar() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct NonCopy;
fn main() {
    let mut a = NonCopy;
    let closure = || { let b = &a; };
    let closure = || { let c = &mut a; };
}
"#,
        expect![[r#"
            71..89;36..41;85..86 ByRef(Immutable) a &'<erased> NonCopy
            109..131;36..41;127..128 ByRef(Mutable) a &'<erased> mut NonCopy"#]],
    );
}

#[test]
fn field() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct Foo { a: i32, b: i32 }
fn main() {
    let a = Foo { a: 0, b: 0 };
    let closure = || { let b = a.a; };
}
"#,
        expect!["92..111;50..51;105..108 ByRef(Immutable) a.a &'<erased> i32"],
    );
}

#[test]
fn fields_different_mode() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct NonCopy;
struct Foo { a: i32, b: i32, c: NonCopy, d: bool }
fn main() {
    let mut a = Foo { a: 0, b: 0 };
    let closure = || {
        let b = &a.a;
        let c = &mut a.b;
        let d = a.c;
    };
}
"#,
        expect![[r#"
            133..212;87..92;155..158 ByRef(Immutable) a.a &'<erased> i32
            133..212;87..92;181..184 ByRef(Mutable) a.b &'<erased> mut i32
            133..212;87..92;202..205 ByValue a.c NonCopy"#]],
    );
}

#[test]
fn autoref() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct Foo;
impl Foo {
    fn imm(&self) {}
    fn mut_(&mut self) {}
}
fn main() {
    let mut a = Foo;
    let closure = || a.imm();
    let closure = || a.mut_();
}
"#,
        expect![[r#"
            123..133;92..97;126..127 ByRef(Immutable) a &'<erased> Foo
            153..164;92..97;156..157 ByRef(Mutable) a &'<erased> mut Foo"#]],
    );
}

#[test]
fn captures_priority() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct NonCopy;
fn main() {
    let mut a = &mut true;
    // Max ByRef(Mut { kind: Default })
    let closure = || {
        *a = false;
        let b = &mut a;
    };
    // Max ByValue
    let mut a = NonCopy;
    let closure = || {
        let b = a;
        let c = &mut a;
        let d = &a;
    };
}
"#,
        expect![[r#"
            113..167;36..41;127..128,159..160 ByRef(Mutable) a &'<erased> mut &'? mut bool
            231..304;196..201;252..253,276..277,296..297 ByValue a NonCopy"#]],
    );
}

#[test]
fn let_underscore() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let mut a = true;
    let closure = || { let _ = a; };
}
"#,
        expect![""],
    );
}

#[test]
fn match_wildcard() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct NonCopy;
fn main() {
    let mut a = NonCopy;
    let closure = || match a {
        _ => {}
    };
    let closure = || match a {
        ref b => {}
    };
    let closure = || match a {
        ref mut b => {}
    };
}
"#,
        expect![[r#"
            125..163;36..41;134..135 ByRef(Immutable) a &'<erased> NonCopy
            183..225;36..41;192..193 ByRef(Mutable) a &'<erased> mut NonCopy"#]],
    );
}

#[test]
fn multiple_bindings() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let mut a = false;
    let mut closure = || { let (b | b) = a; };
}
"#,
        expect!["57..80;20..25;76..77 ByRef(Immutable) a &'<erased> bool"],
    );
}

#[test]
fn multiple_usages() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let mut a = false;
    let mut closure = || {
        let b = &a;
        let c = &a;
        let d = &mut a;
        a = true;
    };
}
"#,
        expect![
            "57..149;20..25;79..80,99..100,123..124,134..135 ByRef(Mutable) a &'<erased> mut bool"
        ],
    );
}

#[test]
fn ref_then_deref() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let mut a = false;
    let mut closure = || { let b = *&mut a; };
}
"#,
        expect!["57..80;20..25;76..77 ByRef(Mutable) a &'<erased> mut bool"],
    );
}

#[test]
fn ref_of_ref() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn main() {
    let mut a = &false;
    let closure = || { let b = &a; };
    let closure = || { let b = &mut a; };
    let a = &mut false;
    let closure = || { let b = &a; };
    let closure = || { let b = &mut a; };
}
"#,
        expect![[r#"
            54..72;20..25;68..69 ByRef(Immutable) a &'<erased> &'? bool
            92..114;20..25;110..111 ByRef(Mutable) a &'<erased> mut &'? bool
            158..176;124..125;172..173 ByRef(Immutable) a &'<erased> &'? mut bool
            196..218;124..125;214..215 ByRef(Mutable) a &'<erased> mut &'? mut bool"#]],
    );
}

#[test]
fn multiple_capture_usages() {
    check_closure_captures(
        r#"
//- minicore: copy, fn
struct A { a: i32, b: bool }
fn main() {
    let mut a = A { a: 123, b: false };
    let closure = |$0| {
        let b = a.b;
        a = A { a: 456, b: true };
    };
    closure();
}
"#,
        expect!["99..165;49..54;120..121,133..134 ByRef(Mutable) a &'<erased> mut A"],
    );
}

#[test]
fn let_binding_is_a_ref_capture_in_ref_binding() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
struct S;
fn main() {
    let mut s = S;
    let s_ref = &mut s;
    let mut s2 = S;
    let s_ref2 = &mut s2;
    let closure = || {
        if let ref cb = s_ref {
        } else if let ref mut cb = s_ref2 {
        }
    };
}
"#,
        expect![[r#"
            129..225;49..54;158..163 ByRef(Immutable) s_ref &'<erased> &'? mut S
            129..225;93..99;201..207 ByRef(Mutable) s_ref2 &'<erased> mut &'? mut S"#]],
    );
}

#[test]
fn let_binding_is_a_value_capture_in_binding() {
    check_closure_captures(
        r#"
//- minicore:copy, fn, option
struct Box(i32);
fn main() {
    let b = Some(Box(0));
    let closure = || {
        if let Some(b) = b {
            let _move = b;
        }
    };
}
"#,
        expect!["73..149;37..38;103..104 ByValue b Option<Box>"],
    );
}

#[test]
fn alias_needs_to_be_normalized() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
trait Trait {
    type Associated;
}
struct A;
struct B { x: i32 }
impl Trait for A {
    type Associated = B;
}
struct C { b: <A as Trait>::Associated }
fn main() {
    let c: C = C { b: B { x: 1 } };
    let closure = || {
        let _move = c.b.x;
    };
}
"#,
        expect!["220..257;174..175;245..250 ByRef(Immutable) c.b.x &'<erased> i32"],
    );
}

#[test]
fn nested_ref_captures_from_outer() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn f() {
    let a = 1;
    let a_closure = || {
        let b_closure = || {
            { a };
        };
    };
}
"#,
        expect![[r#"
            44..113;17..18;92..93 ByRef(Immutable) a &'<erased> i32
            73..106;17..18;92..93 ByRef(Immutable) a &'<erased> i32"#]],
    );
}

#[test]
fn nested_ref_captures() {
    check_closure_captures(
        r#"
//- minicore:copy, fn
fn f() {
    let a_closure = || {
        let b = 2;
        let b_closure = || {
            { b };
        };
    };
}
"#,
        expect!["77..110;46..47;96..97 ByRef(Immutable) b &'<erased> i32"],
    );
}

#[test]
fn fail_to_normalize_place() {
    check_closure_captures(
        r#"
//- minicore: index, slice
const FAIL_CONST: usize = loop {};
struct Foo {
    arr: [i32; FAIL_CONST],
}
fn foo(foo: &Foo) {
    || { return foo.arr[0] };
}
    "#,
        expect!["102..126;85..88;114..117 ByRef(Immutable) *foo &'<erased> Foo"],
    );
}
