#![doc = include_str!(env!("README_PATH"))]
#![deny(unsafe_code)]
#![deny(missing_docs)]
use core::ops::Deref;
use proc_macro2::Span;
use quote::{quote_spanned, ToTokens};
use syn::Ident;
mod test;
#[derive(Clone)]
#[cfg_attr(feature = "derive_debug", derive(Debug))]
pub enum Warning {
	Deprecated {
		name: String,
		index: Option<usize>,
		message: String,
		links: Vec<String>,
		span: Span,
	},
}
#[derive(Clone)]
#[cfg_attr(feature = "derive_debug", derive(Debug))]
pub enum FormattedWarning {
	Deprecated {
		name: Ident,
		note: String,
		span: Option<Span>,
	},
}
impl FormattedWarning {
	#[must_use]
	pub fn new_deprecated<S, T>(name: S, note: T, span: Span) -> Self
	where
		S: AsRef<str>,
		T: Into<String>,
	{
		Self::Deprecated {
			name: Ident::new(name.as_ref(), span),
			note: note.into(),
			span: Some(span),
		}
	}
}
#[derive(Default, Clone)]
#[cfg_attr(feature = "derive_debug", derive(Debug))]
pub struct DeprecatedWarningBuilder {
	title: String,
	index: Option<usize>,
	old: Option<String>,
	new: Option<String>,
	links: Vec<String>,
	span: Option<Span>,
}
impl DeprecatedWarningBuilder {
	#[must_use]
	pub fn from_title<S: Into<String>>(title: S) -> Self {
		Self { title: title.into(), ..Default::default() }
	}
	#[must_use]
	pub fn index<S: Into<usize>>(self, index: S) -> Self {
		Self { index: Some(index.into()), ..self }
	}
	#[must_use]
	pub fn old<S: Into<String>>(self, old: S) -> Self {
		Self { old: Some(old.into()), ..self }
	}
	#[must_use]
	pub fn new<S: Into<String>>(self, new: S) -> Self {
		Self { new: Some(new.into()), ..self }
	}
	#[must_use]
	pub fn help_link<S: Into<String>>(self, link: S) -> Self {
		Self { links: vec![link.into()], ..self }
	}
	#[must_use]
	pub fn help_links(self, links: &[&str]) -> Self {
		Self { links: links.iter().map(|s| s.deref().into()).collect(), ..self }
	}
	#[must_use]
	pub fn span(self, span: Span) -> Self {
		Self { span: Some(span), ..self }
	}
	#[deprecated(note = "Use `try_build` instead; Will be removed after Q1 2024")]
	pub fn maybe_build(self) -> Result<Warning, String> {
		self.try_build()
	}
	pub fn try_build(self) -> Result<Warning, String> {
		let span = self.span.unwrap_or_else(Span::call_site);
		let title = self.title;
		let old = self.old.ok_or("Missing old")?;
		let new = self.new.ok_or("Missing new")?;
		let message = format!("It is deprecated to {}.\nPlease instead {}.", old, new);
		Ok(Warning::Deprecated { name: title, index: self.index, message, links: self.links, span })
	}
	#[must_use]
	#[deprecated(note = "Use `build_or_panic` instead; Will be removed after Q1 2024")]
	pub fn build(self) -> Warning {
		self.build_or_panic()
	}
	#[must_use]
	pub fn build_or_panic(self) -> Warning {
		self.try_build().expect("maybe_build failed")
	}
}
impl Warning {
	#[must_use]
	pub fn new_deprecated<S: Into<String>>(title: S) -> DeprecatedWarningBuilder {
		DeprecatedWarningBuilder::from_title(title)
	}
	fn final_deprecated_message(&self) -> String {
		let (message, links) = match self {
			Self::Deprecated { message, links, .. } => (message, links),
		};
		let lines = message.trim().lines().map(|line| line.trim_start());
		let message = lines.map(|line| format!("\t\t{}", line)).collect::<Vec<_>>().join("\n");
		if !links.is_empty() {
			let link =
				links.iter().map(|l| format!("<{}>", l)).collect::<Vec<_>>().join("\n\t\t\t");
			format!("\n{}\n\n\t\tFor more info see:\n\t\t\t{}", message, link)
		} else {
			format!("\n{}", message)
		}
	}
	fn final_deprecated_name(&self) -> Ident {
		let (index, name, span) = match self {
			Self::Deprecated { index, name, span, .. } => (*index, name, *span),
		};
		let name = match index {
			Some(i) => format!("{}_{}", name, i),
			None => name.clone(),
		};
		Ident::new(&name, span)
	}
}
impl From<Warning> for FormattedWarning {
	fn from(val: Warning) -> Self {
		match val {
			Warning::Deprecated { span, .. } => Self::Deprecated {
				name: val.final_deprecated_name(),
				note: val.final_deprecated_message(),
				span: Some(span),
			},
		}
	}
}
impl ToTokens for Warning {
	fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
		let formatted: FormattedWarning = self.clone().into();
		formatted.to_tokens(stream);
	}
}
impl ToTokens for FormattedWarning {
	fn to_tokens(&self, stream: &mut proc_macro2::TokenStream) {
		let (name, note, span) = match self {
			Self::Deprecated { name, note, span } => (name, note, span),
		};
		let span = span.unwrap_or_else(Span::call_site);
		let q = quote_spanned!(span =>
			#[allow(dead_code)]
			#[allow(non_camel_case_types)]
			#[allow(non_snake_case)]
			fn #name() {
				#[deprecated(note = #note)]
				#[allow(non_upper_case_globals)]
				const _w: () = ();
				let _ = _w;
			}
		);
		q.to_tokens(stream);
	}
}