use std::fmt::Debug;
use crate::{
	errors::parse_lexing_error, parse_bracketed, property_key::PropertyKey,
	throw_unexpected_token_with_token, tokens::token_as_identifier, ASTNode, CursorId, Expression,
	ImmutableVariableOrPropertyPart, MutableVariablePart, ParseError, ParseOptions, ParseResult,
	Span, TSXToken, Token, VisitSettings, Visitable, WithComment,
};
use derive_partial_eq_extras::PartialEqExtras;
use get_field_by_type::GetFieldByType;
use iterator_endiate::EndiateIteratorExt;
use tokenizer_lib::TokenReader;
#[derive(Debug, PartialEqExtras, Eq, Clone, GetFieldByType)]
#[partial_eq_ignore_types(Span)]
#[get_field_by_type_target(Span)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum VariableIdentifier {
	Standard(String, Span),
	#[cfg_attr(feature = "self-rust-tokenize", self_tokenize_field(0))]
	Cursor(CursorId<Self>, Span),
}
impl ASTNode for VariableIdentifier {
	fn from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		_state: &mut crate::ParsingState,
		_settings: &ParseOptions,
	) -> ParseResult<Self> {
		let token = reader.next().ok_or_else(parse_lexing_error)?;
		Ok(if let Token(TSXToken::Cursor(id), start) = token {
			Self::Cursor(id.into_cursor(), start.with_length(0))
		} else {
			let (ident, span) = token_as_identifier(token, "variable identifier")?;
			Self::Standard(ident, span)
		})
	}
	fn to_string_from_buffer<T: source_map::ToString>(
		&self,
		buf: &mut T,
		_settings: &crate::ToStringOptions,
		_depth: u8,
	) {
		buf.push_str(self.as_str());
	}
	fn get_position(&self) -> &Span {
		self.get()
	}
}
impl VariableIdentifier {
	pub fn as_str(&self) -> &str {
		match self {
			VariableIdentifier::Standard(name, _) => name.as_str(),
			VariableIdentifier::Cursor(_, _) => "",
		}
	}
}
#[derive(Debug, Clone)]
pub enum VariableField<T: VariableFieldKind> {
	Name(VariableIdentifier),
	Array(Vec<ArrayDestructuringField<T>>, Span),
	Object(Vec<WithComment<ObjectDestructuringField<T>>>, Span),
}
#[cfg(feature = "self-rust-tokenize")]
impl<T: VariableFieldKind> self_rust_tokenize::SelfRustTokenize for VariableField<T>
where
	T::OptionalExpression: self_rust_tokenize::SelfRustTokenize,
{
	fn append_to_token_stream(
		&self,
		token_stream: &mut self_rust_tokenize::proc_macro2::TokenStream,
	) {
		use self_rust_tokenize::{quote, SelfRustTokenize};
		let tokens = match self {
			VariableField::Name(identifier) => {
				let tokens = identifier.to_tokens();
				quote!(VariableField::Name(#tokens))
			}
			VariableField::Array(value, span) => {
				let (value, span) =
					(SelfRustTokenize::to_tokens(value), SelfRustTokenize::to_tokens(span));
				quote!(VariableField::Array(#value, #span))
			}
			VariableField::Object(value, span) => {
				let (value, span) =
					(SelfRustTokenize::to_tokens(value), SelfRustTokenize::to_tokens(span));
				quote!(VariableField::Object(#value, #span))
			}
		};
		token_stream.extend(tokens);
	}
}
#[cfg(feature = "serde-serialize")]
impl<T: VariableFieldKind> serde::Serialize for VariableField<T>
where
	T: serde::Serialize,
	T::OptionalExpression: serde::Serialize,
{
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: serde::Serializer,
	{
		match self {
			VariableField::Name(name) => {
				serializer.serialize_newtype_variant("VariableField", 0, "Name", name)
			}
			VariableField::Array(items, _) => {
				serializer.serialize_newtype_variant("VariableField", 0, "Array", items)
			}
			VariableField::Object(items, _) => {
				serializer.serialize_newtype_variant("VariableField", 0, "Object", items)
			}
		}
	}
}
impl<T: VariableFieldKind> From<VariableIdentifier> for VariableField<T> {
	fn from(value: VariableIdentifier) -> Self {
		Self::Name(value)
	}
}
impl<T: VariableFieldKind + PartialEq> PartialEq for VariableField<T> {
	fn eq(&self, other: &Self) -> bool {
		match (self, other) {
			(Self::Name(l0), Self::Name(r0)) => l0 == r0,
			(Self::Array(l0, _), Self::Array(r0, _)) => l0 == r0,
			(Self::Object(l0, _), Self::Object(r0, _)) => l0 == r0,
			_ => false,
		}
	}
}
impl<T: VariableFieldKind> Eq for VariableField<T> {}
pub trait VariableFieldKind: PartialEq + Eq + Debug + Clone + 'static {
	type OptionalExpression: PartialEq + Eq + Debug + Clone + Sync + Send;
	fn optional_expression_from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		state: &mut crate::ParsingState,
		settings: &ParseOptions,
	) -> Result<Self::OptionalExpression, ParseError>;
	fn optional_expression_to_string_from_buffer<T: source_map::ToString>(
		optional_expression: &Self::OptionalExpression,
		buf: &mut T,
		settings: &crate::ToStringOptions,
		depth: u8,
	);
	fn optional_expression_get_position(
		optional_expression: &Self::OptionalExpression,
	) -> Option<&Span>;
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub struct VariableFieldInSourceCode;
impl VariableFieldKind for VariableFieldInSourceCode {
	type OptionalExpression = Option<Expression>;
	fn optional_expression_from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		state: &mut crate::ParsingState,
		settings: &ParseOptions,
	) -> Result<Self::OptionalExpression, ParseError> {
		Ok(if reader.conditional_next(|tok| matches!(tok, TSXToken::Assign)).is_some() {
			Some(Expression::from_reader(reader, state, settings)?)
		} else {
			None
		})
	}
	fn optional_expression_to_string_from_buffer<T: source_map::ToString>(
		optional_expression: &Self::OptionalExpression,
		buf: &mut T,
		settings: &crate::ToStringOptions,
		depth: u8,
	) {
		if let Some(optional_expression) = optional_expression {
			buf.push_str(if settings.pretty { " = " } else { "=" });
			optional_expression.to_string_from_buffer(buf, settings, depth)
		}
	}
	fn optional_expression_get_position(
		optional_expression: &Self::OptionalExpression,
	) -> Option<&Span> {
		optional_expression.as_ref().map(|expr| expr.get_position())
	}
}
#[derive(Debug, Clone, PartialEq, Eq)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub struct VariableFieldInTypeAnnotation;
impl VariableFieldKind for VariableFieldInTypeAnnotation {
	type OptionalExpression = ();
	fn optional_expression_from_reader(
		_reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		_state: &mut crate::ParsingState,
		_settings: &ParseOptions,
	) -> Result<Self::OptionalExpression, ParseError> {
		Ok(())
	}
	fn optional_expression_to_string_from_buffer<T: source_map::ToString>(
		_optional_expression: &Self::OptionalExpression,
		_buf: &mut T,
		_settings: &crate::ToStringOptions,
		_depth: u8,
	) {
	}
	fn optional_expression_get_position(
		_optional_expression: &Self::OptionalExpression,
	) -> Option<&Span> {
		None
	}
}
impl<U: VariableFieldKind> ASTNode for VariableField<U> {
	fn from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		state: &mut crate::ParsingState,
		settings: &ParseOptions,
	) -> ParseResult<Self> {
		match reader.peek().ok_or_else(parse_lexing_error)?.0 {
			TSXToken::OpenBrace => {
				let Token(_, start_pos) = reader.next().unwrap();
				let (members, last_pos) =
					parse_bracketed(reader, state, settings, None, TSXToken::CloseBrace)?;
				Ok(Self::Object(members, start_pos.union(last_pos)))
			}
			TSXToken::OpenBracket => {
				let Token(_, start_pos) = reader.next().unwrap();
				let (items, end_pos) =
					parse_bracketed(reader, state, settings, None, TSXToken::CloseBracket)?;
				Ok(Self::Array(items, start_pos.union(end_pos)))
			}
			_ => Ok(Self::Name(VariableIdentifier::from_reader(reader, state, settings)?)),
		}
	}
	fn to_string_from_buffer<T: source_map::ToString>(
		&self,
		buf: &mut T,
		settings: &crate::ToStringOptions,
		depth: u8,
	) {
		match self {
			Self::Name(identifier) => buf.push_str(identifier.as_str()),
			Self::Array(members, _) => {
				buf.push('[');
				for (at_end, member) in members.iter().endiate() {
					member.to_string_from_buffer(buf, settings, depth);
					if !at_end {
						buf.push(',');
						settings.add_gap(buf);
					}
				}
				buf.push(']');
			}
			Self::Object(members, _) => {
				buf.push('{');
				settings.add_gap(buf);
				for (at_end, member) in members.iter().endiate() {
					member.to_string_from_buffer(buf, settings, depth);
					if !at_end {
						buf.push(',');
						settings.add_gap(buf);
					}
				}
				settings.add_gap(buf);
				buf.push('}');
			}
		}
	}
	fn get_position(&self) -> &Span {
		match self {
			VariableField::Array(_, position) | VariableField::Object(_, position) => position,
			VariableField::Name(id) => id.get_position(),
		}
	}
}
#[derive(Debug, Clone, PartialEqExtras, get_field_by_type::GetFieldByType)]
#[get_field_by_type_target(Span)]
#[partial_eq_ignore_types(Span)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum ObjectDestructuringField<T: VariableFieldKind> {
	Name(VariableIdentifier, T::OptionalExpression, Span),
	Spread(VariableIdentifier, Span),
	Map {
		from: PropertyKey<crate::property_key::AlwaysPublic>,
		name: WithComment<VariableField<T>>,
		default_value: T::OptionalExpression,
		position: Span,
	},
}
impl<U: VariableFieldKind> ASTNode for ObjectDestructuringField<U> {
	fn from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		state: &mut crate::ParsingState,
		settings: &ParseOptions,
	) -> ParseResult<Self> {
		match reader.peek().ok_or_else(parse_lexing_error)? {
			Token(TSXToken::Spread, _) => {
				let token = reader.next().unwrap();
				let identifier = VariableIdentifier::from_reader(reader, state, settings)?;
				let position = token.get_span().union(identifier.get_position());
				Ok(Self::Spread(identifier, position))
			}
			_ => {
				let key = PropertyKey::from_reader(reader, state, settings)?;
				if matches!(reader.peek(), Some(Token(TSXToken::Colon, _))) {
					reader.next();
					let variable_name =
						WithComment::<VariableField<U>>::from_reader(reader, state, settings)?;
					let default_value =
						U::optional_expression_from_reader(reader, state, settings)?;
					let position =
						if let Some(pos) = U::optional_expression_get_position(&default_value) {
							key.get_position().union(pos)
						} else {
							key.get_position().clone()
						};
					Ok(Self::Map { from: key, name: variable_name, default_value, position })
				} else if let PropertyKey::Ident(name, key_pos, _) = key {
					let default_value =
						U::optional_expression_from_reader(reader, state, settings)?;
					let standard = VariableIdentifier::Standard(name, key_pos);
					let position =
						if let Some(pos) = U::optional_expression_get_position(&default_value) {
							standard.get_position().union(pos)
						} else {
							standard.get_position().clone()
						};
					Ok(Self::Name(standard, default_value, position))
				} else {
					let token = reader.next().ok_or_else(parse_lexing_error)?;
					throw_unexpected_token_with_token(token, &[TSXToken::Colon])
				}
			}
		}
	}
	fn to_string_from_buffer<T: source_map::ToString>(
		&self,
		buf: &mut T,
		settings: &crate::ToStringOptions,
		depth: u8,
	) {
		match self {
			Self::Spread(name, _) => {
				buf.push_str("...");
				buf.push_str(name.as_str());
			}
			Self::Name(name, default_value, _) => {
				buf.push_str(name.as_str());
				U::optional_expression_to_string_from_buffer(default_value, buf, settings, depth)
			}
			Self::Map { from, name: variable_name, default_value, .. } => {
				from.to_string_from_buffer(buf, settings, depth);
				buf.push(':');
				variable_name.to_string_from_buffer(buf, settings, depth);
				U::optional_expression_to_string_from_buffer(default_value, buf, settings, depth)
			}
		}
	}
	fn get_position(&self) -> &Span {
		self.get()
	}
}
#[derive(Debug, Clone)]
#[cfg_attr(feature = "self-rust-tokenize", derive(self_rust_tokenize::SelfRustTokenize))]
#[cfg_attr(feature = "serde-serialize", derive(serde::Serialize))]
pub enum ArrayDestructuringField<T: VariableFieldKind> {
	Spread(Span, VariableIdentifier),
	Name(WithComment<VariableField<T>>, T::OptionalExpression),
	None,
}
impl<T: VariableFieldKind + PartialEq> PartialEq for ArrayDestructuringField<T> {
	fn eq(&self, other: &Self) -> bool {
		match (self, other) {
			(Self::Spread(_, l0), Self::Spread(_, r0)) => l0 == r0,
			(Self::Name(l0, l1), Self::Name(r0, r1)) => l0 == r0 && l1 == r1,
			_ => core::mem::discriminant(self) == core::mem::discriminant(other),
		}
	}
}
impl<T: VariableFieldKind> Eq for ArrayDestructuringField<T> {}
impl<U: VariableFieldKind> ASTNode for ArrayDestructuringField<U> {
	fn from_reader(
		reader: &mut impl TokenReader<TSXToken, crate::TokenStart>,
		state: &mut crate::ParsingState,
		settings: &ParseOptions,
	) -> ParseResult<Self> {
		match reader.peek().ok_or_else(parse_lexing_error)?.0 {
			TSXToken::Spread => {
				let token = reader.next().unwrap();
				Ok(Self::Spread(
					token.get_span(),
					VariableIdentifier::from_reader(reader, state, settings)?,
				))
			}
			TSXToken::Comma | TSXToken::CloseBracket => Ok(Self::None),
			_ => {
				let name = WithComment::<VariableField<U>>::from_reader(reader, state, settings)?;
				let expression = U::optional_expression_from_reader(reader, state, settings)?;
				Ok(Self::Name(name, expression))
			}
		}
	}
	fn to_string_from_buffer<T: source_map::ToString>(
		&self,
		buf: &mut T,
		settings: &crate::ToStringOptions,
		depth: u8,
	) {
		match self {
			Self::Spread(_, name) => {
				buf.push_str("...");
				buf.push_str(name.as_str());
			}
			Self::Name(name, default_value) => {
				name.to_string_from_buffer(buf, settings, depth);
				U::optional_expression_to_string_from_buffer(default_value, buf, settings, depth)
			}
			Self::None => {}
		}
	}
	fn get_position(&self) -> &Span {
		todo!()
	}
}
impl Visitable for VariableField<VariableFieldInSourceCode> {
	fn visit<TData>(
		&self,
		visitors: &mut (impl crate::VisitorReceiver<TData> + ?Sized),
		data: &mut TData,
		settings: &VisitSettings,
		chain: &mut temporary_annex::Annex<crate::visiting::Chain>,
	) {
		match self {
			VariableField::Name(id) => {
				if let VariableIdentifier::Standard(name, pos) = id {
					let item = ImmutableVariableOrPropertyPart::VariableFieldName(name, pos);
					visitors.visit_variable(&item, data, chain);
				}
			}
			VariableField::Array(array_destructuring_fields, _) => {
				for field in array_destructuring_fields.iter() {
					visitors.visit_variable(
						&ImmutableVariableOrPropertyPart::ArrayDestructuringMember(field),
						data,
						chain,
					);
					match field {
						ArrayDestructuringField::Spread(_, _id) => todo!(),
						ArrayDestructuringField::None => {}
						ArrayDestructuringField::Name(variable_field, expression) => {
							variable_field.visit(visitors, data, settings, chain);
							expression.visit(visitors, data, settings, chain);
						}
					}
				}
			}
			VariableField::Object(object_destructuring_fields, _) => {
				for field in object_destructuring_fields.iter() {
					visitors.visit_variable(
						&ImmutableVariableOrPropertyPart::ObjectDestructuringMember(field),
						data,
						chain,
					);
					match field.get_ast_ref() {
						ObjectDestructuringField::Spread(_name, _) => {}
						ObjectDestructuringField::Name(_name, default_value, _) => {
							default_value.visit(visitors, data, settings, chain);
						}
						ObjectDestructuringField::Map {
							name: variable_name,
							default_value,
							..
						} => {
							variable_name.visit(visitors, data, settings, chain);
							default_value.visit(visitors, data, settings, chain);
						}
					}
				}
			}
		}
	}
	fn visit_mut<TData>(
		&mut self,
		visitors: &mut (impl crate::VisitorMutReceiver<TData> + ?Sized),
		data: &mut TData,
		settings: &VisitSettings,
		chain: &mut temporary_annex::Annex<crate::visiting::Chain>,
	) {
		match self {
			VariableField::Name(identifier) => {
				if let VariableIdentifier::Standard(name, _span) = identifier {
					visitors.visit_variable_mut(
						&mut MutableVariablePart::VariableFieldName(name),
						data,
						chain,
					);
				}
			}
			VariableField::Array(array_destructuring_fields, _) => {
				for field in array_destructuring_fields.iter_mut() {
					visitors.visit_variable_mut(
						&mut MutableVariablePart::ArrayDestructuringMember(field),
						data,
						chain,
					);
					match field {
						ArrayDestructuringField::Spread(_, _id) => todo!(),
						ArrayDestructuringField::None => {}
						ArrayDestructuringField::Name(variable_field, default_value) => {
							variable_field.visit_mut(visitors, data, settings, chain);
							default_value.visit_mut(visitors, data, settings, chain);
						}
					}
				}
			}
			VariableField::Object(object_destructuring_fields, _) => {
				for field in object_destructuring_fields.iter_mut() {
					visitors.visit_variable_mut(
						&mut MutableVariablePart::ObjectDestructuringMember(field),
						data,
						chain,
					);
					match field.get_ast_mut() {
						ObjectDestructuringField::Spread(_id, _) => {}
						ObjectDestructuringField::Name(_id, default_value, _) => {
							default_value.visit_mut(visitors, data, settings, chain);
						}
						ObjectDestructuringField::Map {
							name: variable_name,
							default_value,
							..
						} => {
							variable_name.visit_mut(visitors, data, settings, chain);
							default_value.visit_mut(visitors, data, settings, chain);
						}
					}
				}
			}
		}
	}
}
#[cfg(test)]
mod tests {
	use super::*;
	use crate::{assert_matches_ast, span};
	type VariableField = super::VariableField<VariableFieldInSourceCode>;
	#[test]
	fn name() {
		assert_matches_ast!(
			"x",
			VariableField::Name(VariableIdentifier::Standard(Deref @ "x", Span { start: 0, end: 1, .. }))
		);
	}
	#[test]
	fn array() {
		assert_matches_ast!(
			"[x, y, z]",
			VariableField::Array(
				Deref @ [ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "x",
						span!(1, 2),
					))),
					None,
				), ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "y",
						span!(4, 5),
					))),
					None,
				), ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "z",
						span!(7, 8),
					))),
					None,
				)],
				_,
			)
		);
		assert_matches_ast!(
			"[x,, z]",
			VariableField::Array(
				Deref @ [ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "x",
						span!(1, 2),
					))),
					None,
				), ArrayDestructuringField::None, ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "z",
						span!(5, 6),
					))),
					None,
				)],
				span!(0, 7),
			)
		);
	}
	#[test]
	fn object() {
		assert_matches_ast!(
			"{x, y, z}",
			VariableField::Object(
				Deref @ [WithComment::None(ObjectDestructuringField::Name(
					VariableIdentifier::Standard(Deref @ "x", span!(1, 2)),
					None,
					span!(1, 2),
				)), WithComment::None(ObjectDestructuringField::Name(
					VariableIdentifier::Standard(Deref @ "y", span!(4, 5)),
					None,
					span!(4, 5),
				)), WithComment::None(ObjectDestructuringField::Name(
					VariableIdentifier::Standard(Deref @ "z", span!(7, 8)),
					None,
					span!(7, 8),
				))],
				span!(0, 9),
			)
		);
	}
	#[test]
	fn name_with_default() {
		assert_matches_ast!(
			"{ x = 2 }",
			VariableField::Object(
				Deref @ [WithComment::None(ObjectDestructuringField::Name(
					VariableIdentifier::Standard(Deref @ "x", span!(2, 3)),
					Some(Expression::NumberLiteral(
						crate::NumberRepresentation::Number { .. },
						span!(6, 7),
					)),
					span!(2, 7),
				))],
				span!(0, 9),
			)
		);
	}
	#[test]
	fn array_spread() {
		assert_matches_ast!(
			"[x, ...y]",
			VariableField::Array(
				Deref @ [ArrayDestructuringField::Name(
					WithComment::None(VariableField::Name(VariableIdentifier::Standard(
						Deref @ "x",
						span!(1, 2),
					))),
					None,
				), ArrayDestructuringField::Spread(
					span!(4, 7),
					VariableIdentifier::Standard(Deref @ "y", span!(7, 8)),
				)],
				span!(0, 9),
			)
		);
	}
}