// SPDX-FileCopyrightText: 2025 Slatian
//
// SPDX-License-Identifier: LGPL-3.0-only

#[cfg(feature = "serde")]
use serde::{Deserialize, Deserializer, Serialize, Serializer};

use std::iter::FromIterator;
use std::slice::Iter;

use crate::CriteriumChain;

/// Criteria in this list, how they should be evaluated depends on
/// external metadata, see [CriteriumChain].
#[derive(Clone, Debug, Default)]
pub struct CriteriumChainList<T> {
	/// Criteria in this list, how they should be evaluated depends on
	/// external metadata, see [CriteriumChain].
	pub criteria: Vec<CriteriumChain<T>>,

	/// Fallback value the list evaluates to, if it is empty
	pub fallback: bool,
}

impl<T> CriteriumChainList<T> {
	/// Constructs a new CriteriumChainList with the given content
	pub fn new(list: Vec<CriteriumChain<T>>, fallback: bool) -> Self {
		Self {
			criteria: list,
			fallback: fallback,
		}
	}

	/// Construct an empty CriteriumChainList with the given fallback value
	pub fn empty(fallback: bool) -> Self {
		Self {
			criteria: Vec::new(),
			fallback: fallback,
		}
	}

	/// Convert this list to an And joined [CriteriumChain].
	pub fn as_and(self) -> CriteriumChain<T> {
		CriteriumChain::And(self)
	}

	/// Convert this list to an Or joined [CriteriumChain].
	pub fn as_or(self) -> CriteriumChain<T> {
		CriteriumChain::Or(self)
	}

	/// Shortcut for getting the length of the `criteria` list.
	#[inline]
	pub fn len(&self) -> usize {
		self.criteria.len()
	}

	/// Shortcut for getting wheter the `criteria` list is empty.
	#[inline]
	pub fn is_empty(&self) -> bool {
		self.criteria.is_empty()
	}

	/// Shortcut for iterating over all direct members of the criterium chain list using a `map()` iterator.
	pub fn map<B, F>(self, f: F) -> CriteriumChainList<B>
	where
		F: FnMut(CriteriumChain<T>) -> CriteriumChain<B>,
	{
		CriteriumChainList::new(self.criteria.into_iter().map(f).collect(), self.fallback)
	}

	/// Shortcut for iterating over all direct members of the criterium chain list visiting each member.
	pub fn for_each<F>(&self, f: F)
	where
		F: FnMut(&CriteriumChain<T>),
	{
		self.criteria.iter().for_each(f);
	}

	/// Adds a Criterium to the chain currently under construction.
	pub fn add_criterium(&mut self, entry: T) {
		self.criteria.push(CriteriumChain::Match(entry));
	}

	/// Adds a Criterium to the chain currently under construction
	/// and inverts its output.
	///
	/// Similar to a `not` or `!` in other languages.
	pub fn add_criterium_inverted(&mut self, entry: T) {
		self.criteria.push(CriteriumChain::Not(entry));
	}

	/// Adds a Criterium to the chain currently under construction
	/// and inverts its output if `invert` is true.
	pub fn add_criterium_invertable(&mut self, entry: T, invert: bool) {
		if invert {
			self.add_criterium_inverted(entry);
		} else {
			self.add_criterium(entry);
		}
	}

	/// Like `add_criterium()` but does nothing if `None` is passed.
	///
	/// Intended to be used when converting from a fixed set of criteria
	/// where a `None` communicates to ignore a condition.
	///
	/// Be careful when using with `into()`, it is also implemented
	/// for converting `Option<T>` to a check for a null value.
	pub fn add_some_criterium(&mut self, entry: Option<T>) {
		if let Some(entry) = entry {
			self.criteria.push(CriteriumChain::Match(entry));
		}
	}

	/// Combined functionality of `add_criterium_inverted()` and
	/// `add_some_criterium()`.
	pub fn add_some_criterium_inverted(&mut self, entry: Option<T>) {
		if let Some(entry) = entry {
			self.criteria.push(CriteriumChain::Not(entry));
		}
	}

	/// Combined functionality of `add_criterium_invertable()` and
	/// `add_some_criterium()`.
	pub fn add_some_criterium_invertable(&mut self, entry: Option<T>, invert: bool) {
		if let Some(entry) = entry {
			if invert {
				self.add_criterium_inverted(entry);
			} else {
				self.add_criterium(entry);
			}
		}
	}

	/// Add another chain of the same type as a Criterium.
	///
	/// If you are missing the `_inverted` and `_invertable` variants
	/// have a look at the Chains [invert()][CriteriumChain::invert]
	/// and [invert_if()][CriteriumChain::invert_if] methods.
	///
	/// Example:
	///
	/// ```text
	/// (a or b) and c
	/// ```
	///
	/// translates to:
	///
	/// ```rust,ignore
	/// use criterium::CriteriumChainList;
	///
	/// let mut or_chain_builder = CriteriumChainList::empty(false);
	/// or_chain_builder.add_criterium(a);
	/// or_chain_builder.add_criterium(b);
	///
	/// let mut and_chain_builder = CriteriumChainList::emtpy(false);
	/// and_chain_builder.add_chain(or_chain_builder.as_or());
	/// and_chain_builder.add_criterium(c);
	///
	/// let chain = and_chain_builder.as_and();
	/// ```
	pub fn add_chain(&mut self, chain: CriteriumChain<T>) {
		self.criteria.push(chain);
	}

	/// Like [add_chain()][Self::add_chain],
	/// but only appends a chain if `Some(chain)` is given
	pub fn add_some_chain(&mut self, chain: Option<CriteriumChain<T>>) {
		if let Some(chain) = chain {
			self.add_chain(chain);
		}
	}
}

impl<T> From<Vec<CriteriumChain<T>>> for CriteriumChainList<T> {
	fn from(chain_list: Vec<CriteriumChain<T>>) -> Self {
		Self {
			criteria: chain_list,
			fallback: false,
		}
	}
}

impl<T> FromIterator<CriteriumChain<T>> for CriteriumChainList<T> {
	fn from_iter<I>(iter: I) -> Self
	where
		I: IntoIterator<Item = CriteriumChain<T>>,
	{
		Self {
			criteria: iter.into_iter().collect(),
			fallback: false,
		}
	}
}

impl<'a, T> IntoIterator for &'a CriteriumChainList<T> {
	type Item = &'a CriteriumChain<T>;
	type IntoIter = Iter<'a, CriteriumChain<T>>;

	fn into_iter(self) -> Self::IntoIter {
		self.criteria.iter()
	}
}

impl<'a, T> IntoIterator for &'a mut CriteriumChainList<T> {
	type Item = &'a CriteriumChain<T>;
	type IntoIter = Iter<'a, CriteriumChain<T>>;

	fn into_iter(self) -> Self::IntoIter {
		self.criteria.iter()
	}
}

/// Pass-through implementation, that makes only the contained vec visible to serde.
#[cfg(feature = "serde")]
impl<T> Serialize for CriteriumChainList<T>
where
	T: Serialize,
{
	fn serialize<S>(&self, serializer: S) -> Result<S::Ok, S::Error>
	where
		S: Serializer,
	{
		if !self.is_empty() {
			return self.criteria.serialize(serializer);
		} else {
			return vec![CriteriumChain::<T>::constant(self.fallback)].serialize(serializer);
		}
	}
}

/// Pass-through implementation, that makes only the `criteria` list visible to serde, the fallback value is set to false.
#[cfg(feature = "serde")]
impl<'de, T> Deserialize<'de> for CriteriumChainList<T>
where
	T: Deserialize<'de>,
{
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: Deserializer<'de>,
	{
		return Ok(Self {
			criteria: Vec::deserialize(deserializer)?,
			fallback: false,
		});
	}
}
