#![warn(clippy::pedantic)]
#![allow(clippy::disallowed_names, clippy::type_complexity)]

use typed_builder::TypedBuilder;

#[test]
fn test_simple() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        x: i32,
        y: i32,
    }

    assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 });
    assert!(Foo::builder().y(1).x(2).build() == Foo { x: 2, y: 1 });
}

#[test]
fn test_lifetime() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<'a, 'b> {
        x: &'a i32,
        y: &'b i32,
    }

    assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 });
}

#[test]
fn test_lifetime_bounded() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<'a, 'b: 'a> {
        x: &'a i32,
        y: &'b i32,
    }

    assert!(Foo::builder().x(&1).y(&2).build() == Foo { x: &1, y: &2 });
}

#[test]
fn test_mutable_borrows() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<'a, 'b> {
        x: &'a mut i32,
        y: &'b mut i32,
    }

    let mut a = 1;
    let mut b = 2;
    {
        let foo = Foo::builder().x(&mut a).y(&mut b).build();
        *foo.x *= 10;
        *foo.y *= 100;
    }
    assert!(a == 10);
    assert!(b == 200);
}

#[test]
fn test_generics() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<S, T: Default> {
        x: S,
        y: T,
    }

    assert!(Foo::builder().x(1).y(2).build() == Foo { x: 1, y: 2 });
}

#[test]
fn test_2d_const_generics() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<const NUM_COLS: usize, const NUM_ROWS: usize> {
        data: [[u32; NUM_ROWS]; NUM_COLS],
    }
    assert!(Foo::builder().data([[]]).build() == Foo { data: [[]] });
}

#[test]
fn test_multiple_const_generics() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<const A: usize, const B: usize, const C: usize> {
        data: [u32; A],
        data2: [u32; B],
        data3: [u32; C],
    }
    assert!(
        Foo::builder().data([1]).data2([2, 3]).data3([]).build()
            == Foo {
                data: [1],
                data2: [2, 3],
                data3: []
            }
    );
}

#[test]
fn test_const_generics_with_other_generics() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<const A: usize, B> {
        data: [B; A],
        data2: [B; 3],
    }
    assert!(
        Foo::builder().data([3]).data2([0, 1, 2]).build()
            == Foo {
                data: [3],
                data2: [0, 1, 2]
            }
    );
}

#[test]
fn test_into() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(setter(into))]
        x: i32,
    }

    assert!(Foo::builder().x(1_u8).build() == Foo { x: 1 });
}

#[test]
fn test_strip_option_with_into() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(setter(strip_option, into))]
        x: Option<i32>,
    }

    assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) });
}

#[test]
fn test_into_with_strip_option() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(setter(into, strip_option))]
        x: Option<i32>,
    }

    assert!(Foo::builder().x(1_u8).build() == Foo { x: Some(1) });
}

#[test]
fn test_strip_bool() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(setter(into, strip_bool))]
        x: bool,
    }

    assert!(Foo::builder().x().build() == Foo { x: true });
    assert!(Foo::builder().build() == Foo { x: false });
}

#[test]
fn test_default() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(default, setter(strip_option))]
        x: Option<i32>,
        #[builder(default = 10)]
        y: i32,
        #[builder(default = vec![20, 30, 40])]
        z: Vec<i32>,
    }

    assert!(
        Foo::builder().build()
            == Foo {
                x: None,
                y: 10,
                z: vec![20, 30, 40]
            }
    );
    assert!(
        Foo::builder().x(1).build()
            == Foo {
                x: Some(1),
                y: 10,
                z: vec![20, 30, 40]
            }
    );
    assert!(
        Foo::builder().y(2).build()
            == Foo {
                x: None,
                y: 2,
                z: vec![20, 30, 40]
            }
    );
    assert!(
        Foo::builder().x(1).y(2).build()
            == Foo {
                x: Some(1),
                y: 2,
                z: vec![20, 30, 40]
            }
    );
    assert!(
        Foo::builder().z(vec![1, 2, 3]).build()
            == Foo {
                x: None,
                y: 10,
                z: vec![1, 2, 3]
            }
    );
}

#[test]
fn test_field_dependencies_in_build() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(default, setter(strip_option))]
        x: Option<i32>,
        #[builder(default = 10)]
        y: i32,
        #[builder(default = vec![y, 30, 40])]
        z: Vec<i32>,
    }

    assert!(
        Foo::builder().build()
            == Foo {
                x: None,
                y: 10,
                z: vec![10, 30, 40]
            }
    );
    assert!(
        Foo::builder().x(1).build()
            == Foo {
                x: Some(1),
                y: 10,
                z: vec![10, 30, 40]
            }
    );
    assert!(
        Foo::builder().y(2).build()
            == Foo {
                x: None,
                y: 2,
                z: vec![2, 30, 40]
            }
    );
    assert!(
        Foo::builder().x(1).y(2).build()
            == Foo {
                x: Some(1),
                y: 2,
                z: vec![2, 30, 40]
            }
    );
    assert!(
        Foo::builder().z(vec![1, 2, 3]).build()
            == Foo {
                x: None,
                y: 10,
                z: vec![1, 2, 3]
            }
    );
}

// compile-fail tests for skip are in src/lib.rs out of necessity. These are just the bland
// successful cases.
#[test]
fn test_skip() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(default, setter(skip))]
        x: i32,
        #[builder(setter(into))]
        y: i32,
        #[builder(default = y + 1, setter(skip))]
        z: i32,
    }

    assert!(Foo::builder().y(1_u8).build() == Foo { x: 0, y: 1, z: 2 });
}

#[test]
fn test_docs() {
    #[derive(TypedBuilder)]
    #[builder(
        builder_method(doc = "Point::builder() method docs"),
        builder_type(doc = "PointBuilder type docs"),
        build_method(doc = "PointBuilder.build() method docs")
    )]
    struct Point {
        #[allow(dead_code)]
        x: i32,
        #[builder(
                default = x,
                setter(
                    doc = "Set `z`. If you don't specify a value it'll default to the value specified for `x`.",
                ),
            )]
        #[allow(dead_code)]
        y: i32,
    }

    let _ = Point::builder();
}

#[test]
fn test_builder_name() {
    #[derive(TypedBuilder)]
    struct Foo {}

    let _: FooBuilder<_> = Foo::builder();
}

// NOTE: `test_builder_type_stability` and `test_builder_type_stability_with_other_generics` are
//       meant to ensure we don't break things for people that use custom `impl`s on the builder
//       type before the tuple field generic param transformation traits are in.
//       See:
//        - https://github.com/idanarye/rust-typed-builder/issues/22
//        - https://github.com/idanarye/rust-typed-builder/issues/23
#[test]
fn test_builder_type_stability() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        x: i32,
        y: i32,
        z: i32,
    }

    impl<Y> FooBuilder<((), Y, ())> {
        fn xz(self, x: i32, z: i32) -> FooBuilder<((i32,), Y, (i32,))> {
            self.x(x).z(z)
        }
    }

    assert!(Foo::builder().xz(1, 2).y(3).build() == Foo { x: 1, y: 3, z: 2 });
    assert!(Foo::builder().xz(1, 2).y(3).build() == Foo::builder().x(1).z(2).y(3).build());

    assert!(Foo::builder().y(1).xz(2, 3).build() == Foo { x: 2, y: 1, z: 3 });
    assert!(Foo::builder().y(1).xz(2, 3).build() == Foo::builder().y(1).x(2).z(3).build());
}

#[test]
fn test_builder_type_stability_with_other_generics() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<X: Default, Y> {
        x: X,
        y: Y,
    }

    impl<X: Default, Y, Y_> FooBuilder<X, Y, ((), Y_)> {
        fn x_default(self) -> FooBuilder<X, Y, ((X,), Y_)> {
            self.x(X::default())
        }
    }

    assert!(Foo::builder().x_default().y(1.0).build() == Foo { x: 0, y: 1.0 });
    assert!(
        Foo::builder().y("hello".to_owned()).x_default().build()
            == Foo {
                x: "",
                y: "hello".to_owned()
            }
    );
}

#[test]
#[allow(clippy::items_after_statements)]
fn test_builder_type_with_default_on_generic_type() {
    #[derive(PartialEq, TypedBuilder)]
    struct Types<X, Y = ()> {
        x: X,
        y: Y,
    }
    assert!(Types::builder().x(()).y(()).build() == Types { x: (), y: () });

    #[derive(PartialEq, TypedBuilder)]
    struct TypeAndLifetime<'a, X, Y: Default, Z = usize> {
        x: X,
        y: Y,
        z: &'a Z,
    }
    let a = 0;
    assert!(TypeAndLifetime::builder().x(()).y(0).z(&a).build() == TypeAndLifetime { x: (), y: 0, z: &0 });

    #[derive(PartialEq, TypedBuilder)]
    struct Foo<'a, X, Y: Default, Z: Default = usize, M = ()> {
        x: X,
        y: &'a Y,
        z: Z,
        m: M,
    }

    impl<'a, X, Y: Default, M, X_, Y_, M_> FooBuilder<'a, X, Y, usize, M, (X_, Y_, (), M_)> {
        fn z_default(self) -> FooBuilder<'a, X, Y, usize, M, (X_, Y_, (usize,), M_)> {
            self.z(usize::default())
        }
    }

    impl<'a, X, Y: Default, Z: Default, X_, Y_, Z_> FooBuilder<'a, X, Y, Z, (), (X_, Y_, Z_, ())> {
        fn m_default(self) -> FooBuilder<'a, X, Y, Z, (), (X_, Y_, Z_, ((),))> {
            self.m(())
        }
    }

    // compile test if rustc can infer type for `z` and `m`
    Foo::<(), _, _, f64>::builder().x(()).y(&a).z_default().m(1.0).build();
    Foo::<(), _, _, _>::builder().x(()).y(&a).z_default().m_default().build();

    assert!(
        Foo::builder().x(()).y(&a).z_default().m(1.0).build()
            == Foo {
                x: (),
                y: &0,
                z: 0,
                m: 1.0
            }
    );
    assert!(
        Foo::builder().x(()).y(&a).z(9).m(1.0).build()
            == Foo {
                x: (),
                y: &0,
                z: 9,
                m: 1.0
            }
    );
}

#[test]
fn test_builder_type_skip_into() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo<X> {
        x: X,
    }

    // compile test if rustc can infer type for `x`
    Foo::builder().x(()).build();

    assert!(Foo::builder().x(()).build() == Foo { x: () });
}

#[test]
fn test_default_code() {
    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(default_code = "\"text1\".to_owned()")]
        x: String,

        #[builder(default_code = r#""text2".to_owned()"#)]
        y: String,
    }

    assert!(
        Foo::builder().build()
            == Foo {
                x: "text1".to_owned(),
                y: "text2".to_owned()
            }
    );
}

#[test]
fn test_field_defaults_default_value() {
    #[derive(PartialEq, TypedBuilder)]
    #[builder(field_defaults(default = 12))]
    struct Foo {
        x: i32,
        #[builder(!default)]
        y: String,
        #[builder(default = 13)]
        z: i32,
    }

    assert!(
        Foo::builder().y("bla".to_owned()).build()
            == Foo {
                x: 12,
                y: "bla".to_owned(),
                z: 13
            }
    );
}

#[test]
fn test_field_defaults_setter_options() {
    #[derive(PartialEq, TypedBuilder)]
    #[builder(field_defaults(setter(strip_option)))]
    struct Foo {
        x: Option<i32>,
        #[builder(setter(!strip_option))]
        y: i32,
    }

    assert!(Foo::builder().x(1).y(2).build() == Foo { x: Some(1), y: 2 });
}

#[test]
fn test_clone_builder() {
    #[derive(PartialEq, Default)]
    struct Uncloneable;

    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        x: i32,
        y: i32,
        #[builder(default)]
        z: Uncloneable,
    }

    let semi_built = Foo::builder().x(1);

    assert!(
        semi_built.clone().y(2).build()
            == Foo {
                x: 1,
                y: 2,
                z: Uncloneable
            }
    );
    assert!(
        semi_built.y(3).build()
            == Foo {
                x: 1,
                y: 3,
                z: Uncloneable
            }
    );
}

#[test]
#[allow(clippy::items_after_statements)]
fn test_clone_builder_with_generics() {
    #[derive(PartialEq, Default)]
    struct Uncloneable;

    #[derive(PartialEq, TypedBuilder)]
    struct Foo<T> {
        x: T,
        y: i32,
    }

    let semi_built1 = Foo::builder().x(1);

    assert!(semi_built1.clone().y(2).build() == Foo { x: 1, y: 2 });
    assert!(semi_built1.y(3).build() == Foo { x: 1, y: 3 });

    let semi_built2 = Foo::builder().x("four");

    assert!(semi_built2.clone().y(5).build() == Foo { x: "four", y: 5 });
    assert!(semi_built2.y(6).build() == Foo { x: "four", y: 6 });

    // Just to make sure it can build with generic bounds
    #[allow(dead_code)]
    #[derive(TypedBuilder)]
    struct Bar<T: std::fmt::Debug>
    where
        T: std::fmt::Display,
    {
        x: T,
    }
}

#[test]
fn test_builder_on_struct_with_keywords() {
    #[allow(non_camel_case_types)]
    #[derive(PartialEq, TypedBuilder)]
    struct r#struct {
        r#fn: u32,
        #[builder(default, setter(strip_option))]
        r#type: Option<u32>,
        #[builder(default = Some(()), setter(skip))]
        r#enum: Option<()>,
        #[builder(setter(into))]
        r#union: String,
    }

    assert!(
        r#struct::builder().r#fn(1).r#union("two").build()
            == r#struct {
                r#fn: 1,
                r#type: None,
                r#enum: Some(()),
                r#union: "two".to_owned(),
            }
    );
}

#[test]
fn test_builder_on_struct_with_keywords_prefix_suffix() {
    #[allow(non_camel_case_types)]
    #[derive(PartialEq, TypedBuilder)]
    #[builder(field_defaults(setter(prefix = "set_", suffix = "_value")))]
    struct r#struct {
        r#fn: u32,
        #[builder(default, setter(strip_option))]
        r#type: Option<u32>,
        #[builder(default = Some(()), setter(skip))]
        r#enum: Option<()>,
        #[builder(setter(into))]
        r#union: String,
    }

    assert!(
        r#struct::builder().r#set_fn_value(1).r#set_union_value("two").build()
            == r#struct {
                r#fn: 1,
                r#type: None,
                r#enum: Some(()),
                r#union: "two".to_owned(),
            }
    );
}

#[test]
fn test_unsized_generic_params() {
    use std::marker::PhantomData;

    #[derive(TypedBuilder)]
    struct GenericStructure<K, V>
    where
        K: ?Sized,
    {
        key: PhantomData<K>,
        value: PhantomData<V>,
    }
}

#[test]
fn test_field_setter_transform() {
    #[derive(PartialEq)]
    struct Point {
        x: i32,
        y: i32,
    }

    #[derive(PartialEq, TypedBuilder)]
    struct Foo {
        #[builder(setter(transform = |x: i32, y: i32| Point { x, y }))]
        point: Point,
    }

    assert!(
        Foo::builder().point(1, 2).build()
            == Foo {
                point: Point { x: 1, y: 2 }
            }
    );
}

#[test]
fn test_build_method() {
    #[derive(PartialEq, TypedBuilder)]
    #[builder(build_method(vis="", name=__build))]
    struct Foo {
        x: i32,
    }

    assert!(Foo::builder().x(1).__build() == Foo { x: 1 });
}

#[test]
fn test_builder_method() {
    #[derive(PartialEq, TypedBuilder)]
    #[builder(builder_method(vis="", name=__builder))]
    struct Foo {
        x: i32,
    }

    assert!(Foo::__builder().x(1).build() == Foo { x: 1 });
}

#[test]
fn test_builder_type() {
    #[derive(PartialEq, TypedBuilder)]
    #[builder(builder_type(vis="", name=__FooBuilder))]
    struct Foo {
        x: i32,
    }

    let builder: __FooBuilder<_> = Foo::builder();
    assert!(builder.x(1).build() == Foo { x: 1 });
}

#[test]
fn test_default_builder_type() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(builder_method(vis = ""), builder_type(name = InnerBuilder), build_method(into = Outer))]
    struct Inner {
        a: i32,
        b: i32,
    }

    #[derive(Debug, PartialEq)]
    struct Outer(Inner);

    impl Outer {
        pub fn builder() -> InnerBuilder {
            Inner::builder()
        }
    }

    impl From<Inner> for Outer {
        fn from(value: Inner) -> Self {
            Self(value)
        }
    }

    let outer = Outer::builder().a(3).b(5).build();
    assert_eq!(outer, Outer(Inner { a: 3, b: 5 }));
}

#[test]
fn test_into_set_generic_impl_from() {
    #[derive(TypedBuilder)]
    #[builder(build_method(into))]
    struct Foo {
        value: i32,
    }

    #[derive(Debug, PartialEq)]
    struct Bar {
        value: i32,
    }

    impl From<Foo> for Bar {
        fn from(value: Foo) -> Self {
            Self { value: value.value }
        }
    }

    let bar: Bar = Foo::builder().value(42).build();
    assert_eq!(bar, Bar { value: 42 });
}

#[test]
fn test_into_angle_bracket_type() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(build_method(into = std::sync::Arc<Foo>))]
    struct Foo {
        value: i32,
    }

    let foo: std::sync::Arc<Foo> = Foo::builder().value(42).build();
    assert_eq!(*foo, Foo { value: 42 });
}

#[test]
fn test_into_set_generic_impl_into() {
    #[derive(TypedBuilder)]
    #[builder(build_method(into))]
    struct Foo {
        value: i32,
    }

    #[derive(Debug, PartialEq)]
    struct Bar {
        value: i32,
    }

    impl From<Foo> for Bar {
        fn from(val: Foo) -> Self {
            Self { value: val.value }
        }
    }

    let bar: Bar = Foo::builder().value(42).build();
    assert_eq!(bar, Bar { value: 42 });
}

#[test]
fn test_prefix() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(field_defaults(setter(prefix = "with_")))]
    struct Foo {
        x: i32,
        y: i32,
    }

    let foo = Foo::builder().with_x(1).with_y(2).build();
    assert_eq!(foo, Foo { x: 1, y: 2 });
}

#[test]
fn test_suffix() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(field_defaults(setter(suffix = "_value")))]
    struct Foo {
        x: i32,
        y: i32,
    }

    let foo = Foo::builder().x_value(1).y_value(2).build();
    assert_eq!(foo, Foo { x: 1, y: 2 });
}

#[test]
fn test_prefix_and_suffix() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(field_defaults(setter(prefix = "with_", suffix = "_value")))]
    struct Foo {
        x: i32,
        y: i32,
    }

    let foo = Foo::builder().with_x_value(1).with_y_value(2).build();
    assert_eq!(foo, Foo { x: 1, y: 2 });
}

#[test]
fn test_issue_118() {
    #[derive(TypedBuilder)]
    #[builder(build_method(into=Bar))]
    struct Foo<T> {
        #[builder(default, setter(skip))]
        #[allow(dead_code)]
        foo: Option<T>,
    }

    struct Bar;

    impl<T> From<Foo<T>> for Bar {
        fn from(_value: Foo<T>) -> Self {
            Self
        }
    }

    let _ = Foo::<u32>::builder().build();
}

#[test]
fn test_mutable_defaults() {
    #[derive(TypedBuilder, PartialEq, Debug)]
    struct Foo {
        #[builder(default, mutable_during_default_resolution, setter(strip_option))]
        x: Option<i32>,
        #[builder(default = if let Some(x) = x.as_mut() {
            *x *= 2;
            *x
        } else {
            Default::default()
        })]
        y: i32,
    }

    let foo = Foo::builder().x(5).build();

    assert_eq!(foo, Foo { x: Some(10), y: 10 });
}

#[test]
fn test_preinitialized_fields() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    struct Foo {
        x: i32,
        #[builder(via_mutators)]
        y: i32,
        #[builder(via_mutators = 2)]
        z: i32,
        #[builder(via_mutators(init = 2))]
        w: i32,
    }

    let foo = Foo::builder().x(1).build();
    assert_eq!(foo, Foo { x: 1, y: 0, z: 2, w: 2 });
}

#[test]
fn test_mutators_item() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(mutators(
        #[mutator(requires = [x])]
        fn inc_x(self) {
            self.x += 1;
        }
        #[mutator(requires = [x])]
        fn inc_x_by(self, x: i32) {
            self.x += x;
        }
        fn inc_preset(self) {
            self.y += 1;
            self.z += 1;
            self.w += 1;
        }
        #[mutator(requires = [x])]
        fn inc_y_by_x(self) {
            self.y += self.x;
        }
    ))]
    struct Foo {
        x: i32,
        #[builder(via_mutators)]
        y: i32,
        #[builder(via_mutators = 2)]
        z: i32,
        #[builder(via_mutators(init = 2))]
        w: i32,
    }

    let foo = Foo::builder().x(1).inc_x().inc_preset().build();
    assert_eq!(foo, Foo { x: 2, y: 1, z: 3, w: 3 });
    let foo = Foo::builder().x(1).inc_x_by(4).inc_y_by_x().build();
    assert_eq!(foo, Foo { x: 5, y: 5, z: 2, w: 2 });
}

#[test]
fn test_mutators_field() {
    #[derive(Debug, PartialEq, TypedBuilder)]
    #[builder(mutators())]
    struct Foo {
        #[builder(mutators(
            fn inc_x(self) {
                self.x += 1;
            }
            #[mutator(requires = [y])]
            fn inc_y_by_x(self) {
                self.y += self.x;
            }
        ))]
        x: i32,
        #[builder(default)]
        y: i32,
        #[builder(via_mutators = 2, mutators(
            fn inc_preset(self) {
                self.z += 1;
                self.w += 1;
            }
        ))]
        z: i32,
        #[builder(via_mutators(init = 2))]
        w: i32,
    }

    let foo = Foo::builder().x(1).inc_x().inc_preset().build();
    assert_eq!(foo, Foo { x: 2, y: 0, z: 3, w: 3 });
    let foo = Foo::builder().x(1).y(1).inc_y_by_x().build();
    assert_eq!(foo, Foo { x: 1, y: 2, z: 2, w: 2 });
}
