use proc_macro2::{Span, TokenStream};
use quote::{quote, quote_spanned};

use crate::Args;
use crate::Context;
use crate::check::{check_binary_op, check_bool_expr};

pub(crate) fn assert(args: Args) -> TokenStream {
	let context = args.into_context();

	let mut output = TokenStream::new();
	for (i, predicate) in context.predicates.iter().enumerate() {
		let assertion = match &predicate.expr {
			syn::Expr::Let(expr) => assert_let_expr(&context, i, expr),
			syn::Expr::Binary(expr) => assert_binary_expr(&context, i, expr),
			expr => assert_bool_expr(&context, i, expr),
		};
		output.extend(assertion);
	}
	output
}

fn assert_binary_expr(
	context: &super::Context,
	index: usize,
	expr: &syn::ExprBinary,
) -> TokenStream {
	let check = check_binary_op(context, index, expr, quote! { ::core::result::Result::Ok::<(), ()>(()) });
	quote! {
		match #check {
			::core::result::Result::Ok(()) => (),
			::core::result::Result::Err(()) => ::core::panic!("assertion failed"),
		}
	}
}

fn assert_bool_expr(
	context: &super::Context,
	index: usize,
	expr: &syn::Expr,
) -> TokenStream {
	let check = check_bool_expr(context, index, expr, quote! { ::core::result::Result::Ok::<(), ()>(()) });
	quote! {
		match #check {
			::core::result::Result::Ok(()) => (),
			::core::result::Result::Err(()) => ::core::panic!("assertion failed"),
		}
	}
}

fn assert_let_expr(
	context: &super::Context,
	index: usize,
	let_expr: &syn::ExprLet,
) -> TokenStream {
	let pattern = &*let_expr.pat;
	let expression = &*let_expr.expr;
	let value = quote_spanned!{ Span::mixed_site() => value };

	let Context {
		crate_name,
		macro_name,
		predicates: _,
		print_predicates,
		multiline,
		fragments,
		custom_msg,
	} = context;

	quote! {
		let #value = #expression;
		let #pattern = #value else {
			#[allow(unused)]
			use #crate_name::__assert2_impl::maybe_debug::{IsDebug, IsMaybeNotDebug};
			let value = (&&#crate_name::__assert2_impl::maybe_debug::Wrap(&#value)).__assert2_maybe_debug().wrap(&#value);
			#crate_name::__assert2_impl::print::FailedCheck {
				macro_name: #macro_name,
				file: file!(),
				line: line!(),
				column: column!(),
				custom_msg: #custom_msg,
				predicates: #print_predicates,
				multiline: #multiline,
				failed: #index,
				expansion: #crate_name::__assert2_impl::print::Expansion::Let {
					expression: &value as &dyn ::core::fmt::Debug,
				},
				fragments: #fragments,
			}.print();
			::core::panic!("assertion failed");
		};
	}
}
