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

//! Implements DirectMatch for the TagCriterium, doesn’t export any functions

use crate::direct_match::DirectMatchResult;
use crate::direct_match::DirectMatchResultKind;
use crate::tag::TagCriterium;
use crate::DirectMatch;

impl<T, D> DirectMatch<[D]> for TagCriterium<T>
where
	T: DirectMatch<D>,
{
	type Output = T::Output;

	fn criterium_match(&self, data: &[D]) -> Self::Output {
		match self {
			Self::Has(chain) => {
				// Implement a logical OR as a match with any of the elements is okay
				let mut output: Option<Self::Output> = None;
				let mut output_is_unknown = false;
				for d in data {
					let out = chain.criterium_match(d);
					match out.kind() {
						DirectMatchResultKind::True => return out,
						DirectMatchResultKind::False => {
							if output.is_none() {
								output = Some(out)
							}
						}
						DirectMatchResultKind::Unknown => {
							if !output_is_unknown {
								output = Some(out);
								output_is_unknown = true;
							}
						}
						DirectMatchResultKind::Error => return out,
					}
				}
				return output.unwrap_or(Self::Output::new(false));
			}
			Self::HasNot(chain) => {
				// Implement a logical NOR as a match on any element means this has failed
				let mut output: Option<Self::Output> = None;
				let mut output_is_unknown = false;
				for d in data {
					let out = chain.criterium_match(d).invert();
					match out.kind() {
						DirectMatchResultKind::True => return out.invert(),
						DirectMatchResultKind::False => {
							if output.is_none() {
								output = Some(out)
							}
						}
						DirectMatchResultKind::Unknown => {
							if !output_is_unknown {
								output = Some(out);
								output_is_unknown = true;
							}
						}
						DirectMatchResultKind::Error => return out,
					}
				}
				return output.unwrap_or(Self::Output::new(false));
			}
			Self::HasNOf { n, chain } => {
				let mut count: i64 = 0;
				let mut count_unknown: i64 = 0;
				let mut unknown_output: Option<Self::Output> = None;
				for d in data {
					let out = chain.criterium_match(d).invert();
					match out.kind() {
						DirectMatchResultKind::True => count += 1,
						DirectMatchResultKind::False => { /* noop */ }
						DirectMatchResultKind::Unknown => {
							if unknown_output.is_none() {
								unknown_output = Some(out)
							};
							count_unknown += 1;
						}
						DirectMatchResultKind::Error => return out,
					}
				}
				if let Some(unknown_output) = unknown_output {
					// Use a range map if unkown output is possible
					return n
						.criterium_match(&(count..=count + count_unknown))
						.map(Self::Output::new)
						.unwrap_or(unknown_output);
				} else {
					// Directly match if no unknowns were involved
					return Self::Output::new(n.criterium_match(&count));
				}
			}
		}
	}
}
