//! Implementation of the `def_kind!` macro.
//!
//! This module handles the generation of a new `Kind` trait based on a signature.

use crate::{generate::generate_name, parse::KindInput};
use proc_macro2::TokenStream;
use quote::quote;
use syn::GenericParam;

/// Generates the implementation for the `def_kind!` macro.
///
/// This function takes the parsed input and generates a trait definition
/// for a Higher-Kinded Type signature with multiple associated types.
pub fn def_kind_impl(input: KindInput) -> TokenStream {
	let name = generate_name(&input);

	let assoc_types_tokens = input.assoc_types.iter().map(|assoc| {
		let ident = &assoc.ident;
		let generics = &assoc.generics;
		let output_bounds = &assoc.output_bounds;
		let output_bounds_tokens =
			if output_bounds.is_empty() { quote!() } else { quote!(: #output_bounds) };

		quote! {
			type #ident #generics #output_bounds_tokens;
		}
	});

	// Build summary for the first line
	let assoc_types_summary = input
		.assoc_types
		.iter()
		.map(|assoc| {
			let ident = &assoc.ident;
			let generics = &assoc.generics;
			let output_bounds = &assoc.output_bounds;
			let output_bounds_tokens =
				if output_bounds.is_empty() { quote!() } else { quote!(: #output_bounds) };

			let s = quote!(#ident #generics #output_bounds_tokens).to_string();
			// Clean up quote! output to match requested format
			let cleaned =
				s.replace(" < ", "<").replace(" >", ">").replace(" , ", ", ").replace(" : ", ": ");
			format!("`{cleaned}`")
		})
		.collect::<Vec<_>>()
		.join("; ");

	// Build implementation example
	let impl_example_body = input
		.assoc_types
		.iter()
		.map(|assoc| {
			let ident = &assoc.ident;
			let generics = &assoc.generics;
			let s = quote!(type #ident #generics = ConcreteType;).to_string();
			s.replace(" < ", "<")
				.replace(" >", ">")
				.replace(" , ", ", ")
				.replace(" ;", ";")
				.replace(" :", ":")
		})
		.collect::<Vec<_>>()
		.join("\n        ");

	// Build comprehensive documentation
	let assoc_types_doc = input
		.assoc_types
		.iter()
		.map(|assoc| {
			let ident = &assoc.ident;

			let mut l_count = 0;
			let mut t_count = 0;
			let mut lifetimes_doc = Vec::new();
			let mut types_doc = Vec::new();

			for param in &assoc.generics.params {
				match param {
					GenericParam::Lifetime(lt) => {
						l_count += 1;
						lifetimes_doc.push(format!("`{}`", lt.lifetime));
					}
					GenericParam::Type(ty) => {
						t_count += 1;
						let bounds_str = if ty.bounds.is_empty() {
							String::new()
						} else {
							let bounds = &ty.bounds;
							format!(": {}", quote!(#bounds))
						};
						types_doc.push(format!("`{}{}`", ty.ident, bounds_str));
					}
					_ => {}
				}
			}

			let output_bounds = &assoc.output_bounds;
			let output_bounds_doc = if output_bounds.is_empty() {
				"None".to_string()
			} else {
				format!("`{}`", quote!(#output_bounds))
			};

			format!(
				r#"### `type {ident}`

* **Lifetimes** ({l_count}): {}
* **Type parameters** ({t_count}): {}
* **Output bounds**: {output_bounds_doc}"#,
				if l_count == 0 { "None".to_string() } else { lifetimes_doc.join(", ") },
				if t_count == 0 { "None".to_string() } else { types_doc.join(", ") }
			)
		})
		.collect::<Vec<_>>()
		.join("\n\n");

	let header = match input.assoc_types.len() {
		0 => "`Kind` trait.".to_string(),
		1 => format!("`Kind` with associated type: {assoc_types_summary}."),
		_ => format!("`Kind` with associated types: {assoc_types_summary}."),
	};

	let doc_string = format!(
		r#"{header}

Higher-Kinded Type (HKT) trait auto-generated by [`def_kind!`](crate::def_kind!), representing
type constructors that can be applied to generic parameters to produce
concrete types.

# Associated Types

{assoc_types_doc}

# Implementation

To implement this trait for your type constructor, use the [`impl_kind!`](crate::impl_kind!) macro:

```ignore
impl_kind! {{
    for BrandType {{
        {impl_example_body}
    }}
}}
```

# Naming

The trait name `{name}` is a deterministic hash of the canonical signature,
ensuring that semantically equivalent signatures always map to the same trait.

# See Also

* [`Kind!`](crate::Kind!) - Macro to generate the name of a Kind trait
* [`impl_kind!`](crate::impl_kind!) - Macro to implement a Kind trait for a brand
* [`Apply!`](crate::Apply!) - Macro to apply a Kind to generic arguments"#
	);

	quote! {
		#[doc = #doc_string]
		#[allow(non_camel_case_types)]
		pub trait #name {
			#(#assoc_types_tokens)*
		}
	}
}

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

	/// Helper function to parse a KindInput from a string.
	fn parse_kind_input(input: &str) -> KindInput {
		syn::parse_str(input).expect("Failed to parse KindInput")
	}

	// ===========================================================================
	// def_kind! Tests
	// ===========================================================================

	/// Tests def_kind! with a single associated type.
	#[test]
	fn test_def_kind_simple() {
		let input = parse_kind_input("type Of<A>;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		assert!(output_str.contains("pub trait Kind_"));
		assert!(output_str.contains("type Of < A > ;"));
	}

	/// Tests def_kind! with multiple associated types.
	#[test]
	fn test_def_kind_multiple() {
		let input = parse_kind_input(
			"
			type Of<'a, T>: Display;
			type SendOf<U>: Send;
		",
		);
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		assert!(output_str.contains("pub trait Kind_"));
		assert!(output_str.contains("type Of < 'a , T > : Display ;"));
		assert!(output_str.contains("type SendOf < U > : Send ;"));
	}

	/// Tests def_kind! with complex bounds.
	#[test]
	fn test_def_kind_complex() {
		let input = parse_kind_input("type Of<'a, T: 'a + Clone>: Debug + Display;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		assert!(output_str.contains("type Of < 'a , T : 'a + Clone > : Debug + Display ;"));
	}

	/// Tests that documentation correctly renders type parameter bounds.
	/// This specifically tests for the bug where `#ty.bounds` was incorrectly
	/// used in quote!, resulting in output like "A: A : 'a.bounds" instead of "A: 'a".
	#[test]
	fn test_def_kind_doc_type_param_bounds() {
		let input = parse_kind_input("type Of<'a, A: 'a>: 'a;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		// Verify the documentation contains correct type parameter bounds
		assert!(
			output_str.contains(r#"**Type parameters** (1): `A: 'a`"#),
			"Expected documentation to contain 'Type parameters (1): `A: 'a`', got: {}",
			output_str
		);

		// Ensure the buggy output is not present
		assert!(
			!output_str.contains(".bounds"),
			"Documentation should not contain '.bounds', got: {}",
			output_str
		);
		assert!(
			!output_str.contains("A: A"),
			"Documentation should not contain 'A: A', got: {}",
			output_str
		);
	}

	/// Tests documentation for type parameters without bounds.
	#[test]
	fn test_def_kind_doc_type_param_no_bounds() {
		let input = parse_kind_input("type Of<A>;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		// Type parameter without bounds should just show the identifier
		assert!(
			output_str.contains(r#"**Type parameters** (1): `A`"#),
			"Expected documentation to contain 'Type parameters (1): `A`', got: {}",
			output_str
		);
	}

	/// Tests that documentation correctly renders the impl_kind! example.
	#[test]
	fn test_def_kind_doc_impl_example() {
		let input = parse_kind_input("type Of<'a, T>; type SendOf<U>;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		// Verify the documentation contains the correct impl_kind! example
		// We check for the presence of the generated lines.
		// Based on our logic:
		// quote! -> type Of < 'a , T > = ConcreteType ;
		// cleanup -> type Of<'a, T> = ConcreteType;

		assert!(
			output_str.contains("type Of<'a, T> = ConcreteType;"),
			"Expected 'type Of<'a, T> = ConcreteType;' in documentation, got: {}",
			output_str
		);
		assert!(
			output_str.contains("type SendOf<U> = ConcreteType;"),
			"Expected 'type SendOf<U> = ConcreteType;' in documentation, got: {}",
			output_str
		);
	}

	/// Tests documentation for multiple type parameters with various bounds.
	#[test]
	fn test_def_kind_doc_multiple_type_params() {
		let input = parse_kind_input("type Of<'a, T: Clone, U: 'a + Send>: Debug;");
		let output = def_kind_impl(input);
		let output_str = output.to_string();

		// Verify lifetimes doc
		assert!(
			output_str.contains(r#"**Lifetimes** (1): `'a`"#),
			"Expected documentation to contain lifetime 'a, got: {}",
			output_str
		);

		// Verify type parameters doc (both T and U with their bounds)
		assert!(
			output_str.contains(r#"**Type parameters** (2):"#),
			"Expected 2 type parameters, got: {}",
			output_str
		);
		assert!(
			output_str.contains("`T: Clone`"),
			"Expected T: Clone in documentation, got: {}",
			output_str
		);
		assert!(
			output_str.contains("`U: 'a + Send`"),
			"Expected U: 'a + Send in documentation, got: {}",
			output_str
		);

		// Verify output bounds doc
		assert!(
			output_str.contains(r#"**Output bounds**: `Debug`"#),
			"Expected output bounds Debug, got: {}",
			output_str
		);
	}
}
