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

//! Makes it easier to build CriteriumChain enums.

use std::ops::Deref;
use std::ops::DerefMut;

use crate::CriteriumChain;
use crate::CriteriumChainList;

/// The operator a chain will use after it has been constructed.
#[derive(Clone, Debug)]
pub enum CriteriumChainBuilderType {
	/// All of the conditions in the cahin will have to match for the chain to match.
	And,
	/// One of the conditions will have to match.
	Or,
}

/// A builder struct for making a [CriteriumChain].
///
/// Note that this is mostly a [CriteriumChainList] with an `And`/`Or` metadata field tagged on.
/// This type dereferences to a CriteriumChainList, meaning you can use its methods as if they
/// were implemented directly on this Builder.
///
/// If you are okay with the `And`/`Or` being tagged on at the last step of construction you should
/// use [CriteriumChainList] directly and finish with a call to `as_and()` or `as_or()`.
///
/// Example Usage:
/// ```rust,ignore
/// use criterium::CriteriumChainBuilder;
///
/// let mut or_chain_builder = CriteriumChainBuilder::or(false);
/// or_chain_builder.add_criterium(a);
/// or_chain_builder.add_criterium(b);
///
/// let mut and_chain_builder = CriteriumChainBuilder::and(false);
/// and_chain_builder.add_chain(or_chain_builder.to_chain());
/// and_chain_builder.add_criterium(c);
///
/// let chain = and_chain_builder.to_chain();
/// ```
///
#[derive(Clone, Debug)]
pub struct CriteriumChainBuilder<T> {
	/// The operator to use for the assembled chain.
	pub typ: CriteriumChainBuilderType,

	/// The ceiterium chain list this builder is assembling.
	pub list: CriteriumChainList<T>,
}

impl<T> CriteriumChainBuilder<T> {
	/// Constructs a new `CriteriumChainBuilder` using the given type and fallback value.
	///
	/// Also see the `and()` and `or()` constructors.
	pub fn new(typ: CriteriumChainBuilderType, fallback: bool) -> Self {
		Self {
			typ: typ,
			list: CriteriumChainList::empty(fallback),
		}
	}

	/// Convenience wrapper around `new()` constructing a chain with an `And` operator.
	pub fn and(fallback: bool) -> Self {
		Self::new(CriteriumChainBuilderType::And, fallback)
	}

	/// Convenience wrapper around `new()` constructing a chain with an `Or` operator.
	pub fn or(fallback: bool) -> Self {
		Self::new(CriteriumChainBuilderType::Or, fallback)
	}

	/// Assembles the `CriteriumChain` consuming the builder.
	///
	/// This will not optimize the chain, so an empty and/or chain
	/// will stay an empty and/or chain keeping the fallback value.
	pub fn to_chain(self) -> CriteriumChain<T> {
		match self.typ {
			CriteriumChainBuilderType::And => self.list.as_and(),
			CriteriumChainBuilderType::Or => self.list.as_or(),
		}
	}
}

impl<T> Deref for CriteriumChainBuilder<T> {
	type Target = CriteriumChainList<T>;

	fn deref(&self) -> &Self::Target {
		&self.list
	}
}

impl<T> DerefMut for CriteriumChainBuilder<T> {
	fn deref_mut(&mut self) -> &mut Self::Target {
		&mut self.list
	}
}

impl<T> From<CriteriumChainBuilder<T>> for CriteriumChain<T> {
	/// Uses the `to_chain()` method to convert a `CriteriumChainBuilder`
	/// to a `CriteriumChain`.
	fn from(builder: CriteriumChainBuilder<T>) -> CriteriumChain<T> {
		return builder.to_chain();
	}
}

impl<T> From<CriteriumChainBuilder<T>> for CriteriumChainList<T> {
	/// Uses the `to_chain()` method to convert a `CriteriumChainBuilder`
	/// to a `CriteriumChain`.
	fn from(builder: CriteriumChainBuilder<T>) -> CriteriumChainList<T> {
		return builder.list;
	}
}

impl<T> TryFrom<CriteriumChain<T>> for CriteriumChainBuilder<T> {
	type Error = ();

	/// Converts `And` and `Or` chains to a `CriteriumChainBuilder`,
	/// returns an error for all other kinds of chain.
	fn try_from(chain: CriteriumChain<T>) -> Result<Self, ()> {
		match chain {
			CriteriumChain::And(list) => Ok(CriteriumChainBuilder {
				typ: CriteriumChainBuilderType::And,
				list: list,
			}),
			CriteriumChain::Or(list) => Ok(CriteriumChainBuilder {
				typ: CriteriumChainBuilderType::Or,
				list: list,
			}),
			_ => Err(()),
		}
	}
}
