//! atproto-static-web   A simple web viewer for AT Proto.
//! Copyright (C) 2025  AverageHelper
//!
//! This program is free software: you can redistribute it and/or modify
//! it under the terms of the GNU General Public License as published by
//! the Free Software Foundation, either version 3 of the License, or
//! (at your option) any later version.
//!
//! This program is distributed in the hope that it will be useful,
//! but WITHOUT ANY WARRANTY; without even the implied warranty of
//! MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
//! GNU General Public License for more details.
//!
//! You should have received a copy of the GNU General Public License
//! along with this program.  If not, see <https://www.gnu.org/licenses/>.

use super::too_long_error::{LengthKind, TooLongError};
use core::ops::Deref;
use serde::{Deserialize, de};

/// A `Vec` whose item count is limited.
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct LimitedVec<const MAX_ITEMS: usize, T>(Vec<T>);

impl<const L: usize, T> Default for LimitedVec<L, T> {
	/// Creates an empty [`LimitedVec`]
	#[inline]
	fn default() -> Self {
		Self(Default::default()) // empty vec always fits the limits
	}
}

impl<const L: usize, T> Deref for LimitedVec<L, T> {
	type Target = [T];

	#[inline]
	fn deref(&self) -> &Self::Target {
		&self.0
	}
}

impl<const L: usize, T> AsRef<[T]> for LimitedVec<L, T> {
	#[inline]
	fn as_ref(&self) -> &[T] {
		self.0.as_ref()
	}
}

impl<const L: usize, T: PartialEq> PartialEq<Vec<T>> for LimitedVec<L, T> {
	#[inline]
	fn eq(&self, other: &Vec<T>) -> bool {
		self.0.eq(other)
	}
}

impl<const MAX_ITEMS: usize, T> TryFrom<Vec<T>> for LimitedVec<MAX_ITEMS, T> {
	type Error = TooLongError;

	#[inline]
	fn try_from(value: Vec<T>) -> Result<Self, Self::Error> {
		// Check that the value's byte length is within range
		let len = value.len();
		if len > MAX_ITEMS {
			return Err(TooLongError::new(len, MAX_ITEMS, LengthKind::Items));
		}

		Ok(Self(value))
	}
}

impl<const MAX_ITEMS: usize, T: Clone> TryFrom<&[T]> for LimitedVec<MAX_ITEMS, T> {
	type Error = TooLongError;

	#[inline]
	fn try_from(value: &[T]) -> Result<Self, Self::Error> {
		Self::try_from(value.to_vec())
	}
}

impl<'de, const MAX_ITEMS: usize, T: Deserialize<'de>> Deserialize<'de>
	for LimitedVec<MAX_ITEMS, T>
{
	#[inline]
	fn deserialize<D>(deserializer: D) -> Result<Self, D::Error>
	where
		D: serde::Deserializer<'de>,
	{
		let value = Vec::<T>::deserialize(deserializer)?;

		Self::try_from(value).map_err(|err| de::Error::invalid_length(err.actual(), err.expected()))
	}
}

// MARK: - Tests

#[cfg(test)]
mod tests {
	use super::*;

	#[test]
	fn test_deserializes_empty_vec() {
		let data = r"[]";
		let result: LimitedVec<4, &str> = serde_json::from_str(data).unwrap();
		assert_eq!(result, Vec::new());
	}

	#[test]
	fn test_deserializes_short_vec() {
		let data = r#"["あ"]"#;
		let result: LimitedVec<4, &str> = serde_json::from_str(data).unwrap();
		assert_eq!(result, vec!["あ"]);
	}

	#[test]
	fn test_fails_on_too_many_items() {
		let data = r#"["あ","あ","あ","あ","あ"]"#;
		let result = serde_json::from_str::<LimitedVec<4, &str>>(data);
		assert!(result.is_err());

		let error = result.err().unwrap();
		assert_eq!(
			format!("{error}"),
			"invalid length 5, expected length at most 4 items"
		);
	}
}
