// Copyright Marcus Del Favero 2025
// Licensed under the GNU AGPLv3 with an exception, see `README.md` for details
use std::{sync::Arc, ops::Deref, fmt::{Display, Formatter, Result as FmtResult}, borrow::Borrow};
#[cfg(feature = "client")] use std::ops::Range;

use serde::{Serialize, Deserialize};
#[cfg(feature = "client")] use egui::{TextBuffer, text_selection::text_cursor_state};

// INVARIANT: self.0.len() <= N
pub struct MaxLenStr<'a, const N: usize>(&'a str);

impl<const N: usize> MaxLenStr<'_, N> {
	pub const fn new(s: &str) -> Option<MaxLenStr<'_, N>> {
		if s.len() <= N { Some(MaxLenStr(s)) }
		else { None }
	}

	pub fn owned(&self) -> MaxLenString<N> {
		MaxLenString(String::from(self.0))
	}

	#[cfg(feature = "client")]
	pub fn shared(&self) -> MaxLenArcStr<N> {
		MaxLenArcStr(Arc::from(self.0))
	}
}

// INVARIANT: self.0.len() <= N
#[derive(Debug, Default, Clone, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize)]
#[serde(try_from = "Arc<str>")]
pub struct MaxLenArcStr<const N: usize>(Arc<str>);

impl<const N: usize> MaxLenArcStr<N> {
	pub fn empty() -> MaxLenArcStr<N> {
		MaxLenArcStr(Arc::from("")) // N is a usize, so 0 = length <= N and so the invariant holds
	}

	pub fn inner(&self) -> Arc<str> {
		Arc::clone(&self.0)
	}

	pub fn get(&self) -> &str {
		self.0.as_ref()
	}
}

impl<const N: usize> TryFrom<Arc<str>> for MaxLenArcStr<N> {
	type Error = &'static str;

	fn try_from(s: Arc<str>) -> Result<MaxLenArcStr<N>, &'static str> {
		(s.len() <= N).then_some(MaxLenArcStr(s)).ok_or("string too long")
	}
}

impl<const N: usize> Deref for MaxLenArcStr<N> {
	type Target = str;

	fn deref(&self) -> &str {
		self.0.as_ref()
	}
}

impl<const N: usize> Display for MaxLenArcStr<N> {
	fn fmt(&self, fmt: &mut Formatter<'_>) -> FmtResult {
		self.0.fmt(fmt)
	}
}

impl<const N: usize> Borrow<str> for MaxLenArcStr<N> {
	fn borrow(&self) -> &str {
		self.get()
	}
}

// INVARIANT: self.0.len() <= N
#[derive(Default, Clone, Serialize, Deserialize)]
#[serde(try_from = "String", into = "String")]
pub struct MaxLenString<const N: usize>(String);

impl<const N: usize> MaxLenString<N> {
	pub fn new() -> MaxLenString<N> {
		MaxLenString(String::new())
	}

	pub fn new_truncate(mut s: String) -> MaxLenString<N> {
		const ELLIPSIS: char = '…';
		const ELLIPSIS_LEN: usize = ELLIPSIS.len_utf8();

		if s.len() <= N {
			return MaxLenString(s);
		}

		while s.len() > N - ELLIPSIS_LEN {
			s.pop();
		}

		s.push(ELLIPSIS);
		MaxLenString(s)
	}

	pub fn get(&self) -> &str {
		&self.0
	}

	pub fn try_push(&mut self, ch: char) {
		if self.0.len() + ch.len_utf8() <= N {
			self.0.push(ch);
		}
	}

	pub fn to_shared(&self) -> MaxLenArcStr<N> {
		MaxLenArcStr(Arc::from(self.0.as_str()))
	}

	pub fn pop(&mut self) {
		self.0.pop();
	}
}

impl<const N: usize> TryFrom<String> for MaxLenString<N> {
	type Error = &'static str;

	fn try_from(s: String) -> Result<MaxLenString<N>, &'static str> {
		(s.len() <= N).then_some(MaxLenString(s)).ok_or("string too long")
	}
}

impl<const N: usize> From<MaxLenString<N>> for String {
	fn from(s: MaxLenString<N>) -> String {
		s.0
	}
}

impl<const N: usize> Deref for MaxLenString<N> {
	type Target = str;

	fn deref(&self) -> &str {
		self.0.as_ref()
	}
}

// Copied from egui's implementation for String but modified for the case of this newtype
#[cfg(feature = "client")]
impl<const N: usize> TextBuffer for MaxLenString<N> {
	fn is_mutable(&self) -> bool {
		true
	}

	fn as_str(&self) -> &str {
		&self.0
	}

	fn insert_text(&mut self, mut text: &str, char_index: usize) -> usize {
		// Pops the final character until the length is fine, source: https://users.rust-lang.org/t/string-pop-for-str/75745/3
		while self.0.len() + text.len() > N {
			let Some((i, _ch)) = text.char_indices().next_back() else { return 0; };
			text = &text[..i];
		}

		let byte_idx = text_cursor_state::byte_index_from_char_index(self.as_str(), char_index);

		// Then insert the string
		self.0.insert_str(byte_idx, text);

		assert!(self.0.len() <= N);

		text.chars().count()
	}

	fn delete_char_range(&mut self, char_range: Range<usize>) {
		assert!(char_range.start <= char_range.end);

		// Get both byte indices
		let byte_start = text_cursor_state::byte_index_from_char_index(self.0.as_str(), char_range.start);
		let byte_end = text_cursor_state::byte_index_from_char_index(self.0.as_str(), char_range.end);

		// Then drain all characters within this range
		self.0.drain(byte_start..byte_end);
	}
}
