use {
	fp_macros::{
		Apply,
		impl_kind,
		trait_kind,
	},
	std::fmt::Display,
};

// ===========================================================================
// Test 1: Simple `Kind` (No Lifetimes)
// ===========================================================================

// Define a `Kind` with 1 type parameter and no bounds
trait_kind!(
	type Of<T>;
);

struct Wrapper<T>(T);
struct WrapperBrand;

impl_kind! {
	impl for WrapperBrand {
		type Of<T> = Wrapper<T>;
	}
}

#[test]
fn test_wrapper_kind() {
	let w = Wrapper(42);
	assert_eq!(w.0, 42);
}

#[test]
fn test_apply_macro_simple() {
	type Applied = Apply!(<WrapperBrand as Kind!( type Of<T>; )>::Of<i32>);

	let w: Applied = Wrapper(100);
	assert_eq!(w.0, 100);
}

// ===========================================================================
// Test 2: `Kind` with Lifetimes
// ===========================================================================

// Define a `Kind` with 1 lifetime and 1 type bounded by that lifetime
trait_kind!(
	type Of<'a, T: 'a>;
);

struct RefWrapper<'a, T: 'a>(&'a T);
struct RefWrapperBrand;

impl_kind! {
	impl for RefWrapperBrand {
		// T must be bounded by 'a to match trait_kind and struct definition
		type Of<'a, T: 'a> = RefWrapper<'a, T>;
	}
}

#[test]
fn test_ref_wrapper_kind() {
	let val = 42;
	let w = RefWrapper(&val);
	assert_eq!(*w.0, 42);
}

#[test]
fn test_apply_macro_with_lifetime() {
	type Applied<'a> = Apply!(<RefWrapperBrand as Kind!( type Of<'a, T: 'a>; )>::Of<'a, i32>);

	let val = 100;
	let w: Applied = RefWrapper(&val);
	assert_eq!(*w.0, 100);
}

// ===========================================================================
// Test 3: `Kind` with Bounds
// ===========================================================================

// Define a `Kind` where the type parameter must implement Display
trait_kind!(
	type Of<T: Display>;
);

struct DisplayWrapper<T: Display>(T);
struct DisplayWrapperBrand;

impl_kind! {
	impl for DisplayWrapperBrand {
		type Of<T: Display> = DisplayWrapper<T>;
	}
}

#[test]
fn test_bounded_kind() {
	type Applied = Apply!(<DisplayWrapperBrand as Kind!( type Of<T: Display>; )>::Of<String>);

	let w: Applied = DisplayWrapper("hello".to_string());
	assert_eq!(w.0, "hello");
}

// ===========================================================================
// Test 4: `Kind` with Output Bounds
// ===========================================================================

// Define a `Kind` where the output type must implement Clone
// We must also require T: Clone because CloneWrapper<T> only implements Clone if T: Clone
trait_kind!(
	type Of<T: Clone>: Clone;
);

#[derive(Clone)]
struct CloneWrapper<T>(T);
struct CloneWrapperBrand;

impl_kind! {
	impl for CloneWrapperBrand {
		// We must specify the output bounds here so impl_kind! generates the correct `Kind` name
		type Of<T: Clone>: Clone = CloneWrapper<T>;
	}
}

#[test]
fn test_output_bounded_kind() {
	// This test just verifies that the code compiles and the trait is found
	let _ = CloneWrapper(10);
}

#[test]
fn test_apply_output_bounded() {
	type Applied = Apply!(<CloneWrapperBrand as Kind!( type Of<T: Clone>: Clone; )>::Of<i32>);

	let w: Applied = CloneWrapper(10);
	let _ = w.clone();
}

// ===========================================================================
// Test 5: Explicit `Kind` Mode
// ===========================================================================

#[test]
fn test_apply_explicit_kind() {
	// Use the `Kind` trait generated by trait_kind! in Test 1
	// The name is generated based on the signature (T) -> ()
	// We can't easily know the generated name here without duplicating logic,
	// but we can define a new explicit `Kind` trait manually to test this mode.

	trait MyExplicitKind {
		type Of<T>;
	}

	struct ExplicitWrapper<T>(T);
	struct ExplicitBrand;

	impl MyExplicitKind for ExplicitBrand {
		type Of<T> = ExplicitWrapper<T>;
	}

	// Converted to standard Rust syntax as Apply! no longer supports explicit kind mode
	type Applied = <ExplicitBrand as MyExplicitKind>::Of<i32>;

	let w: Applied = ExplicitWrapper(99);
	assert_eq!(w.0, 99);
}
