1use crate::player::PlayerId;
5use bon::Builder;
6use derive_more::From;
7use jiff::Zoned;
8use serde::{Deserialize, Serialize};
9use std::borrow::Cow;
10use std::collections::VecDeque;
11use std::num::NonZeroUsize;
12use std::sync::Arc;
13use strum::EnumIs;
14use uuid::Uuid;
15
16#[derive(Clone, Debug, Deserialize, Serialize)]
17#[derive_const(Default)]
18#[serde(rename_all = "camelCase")]
19#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
20pub struct Chat {
21 history: ChatHistory,
22}
23
24impl Chat {
25 #[inline]
26 pub fn history(&self) -> ChatHistory {
27 self.history.clone()
28 }
29
30 pub(crate) fn push(&mut self, message: ChatMessage) {
31 self.history.push(message);
32 }
33}
34
35#[derive(Clone, Debug, Deserialize, Serialize)]
36#[serde(rename_all = "camelCase")]
37#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
38pub struct ChatHistory {
39 #[cfg_attr(feature = "typescript", ts(as = "Vec<ChatMessage>"))]
40 queue: VecDeque<ChatMessage>,
41 size: NonZeroUsize,
42}
43
44impl ChatHistory {
45 pub const MIN: NonZeroUsize = NonZeroUsize::new(100).unwrap();
46 pub const MAX: NonZeroUsize = NonZeroUsize::new(500).unwrap();
47
48 const fn new(size: usize) -> Self {
49 let size = size.clamp(Self::MIN.get(), Self::MAX.get());
50 let size = unsafe { NonZeroUsize::new_unchecked(size) };
51 Self { queue: VecDeque::new(), size }
52 }
53
54 fn push(&mut self, message: ChatMessage) {
55 self.prune();
56 self.queue.push_back(message);
57 }
58
59 fn prune(&mut self) {
60 let size = self.size.get();
61 let len = self.queue.len();
62 if len >= size {
63 self.queue.drain(..=(len - size));
64 }
65 }
66}
67
68impl const Default for ChatHistory {
69 fn default() -> Self {
70 Self::new(Self::MIN.get())
71 }
72}
73
74#[derive(Builder, Clone, Debug, Deserialize, Serialize)]
75#[serde(rename_all = "camelCase")]
76#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
77pub struct ChatMessage {
78 #[builder(start_fn, into)]
79 content: ChatMessageContent,
80
81 #[builder(skip)]
82 id: ChatMessageId,
83
84 #[builder(default)]
85 kind: ChatMessageKind,
86
87 #[builder(default, into)]
88 author: ChatMessageAuthor,
89
90 #[builder(default = Zoned::now())]
91 time: Zoned,
92}
93
94impl ChatMessage {
95 #[inline]
96 pub fn id(&self) -> ChatMessageId {
97 self.id
98 }
99
100 #[inline]
101 pub fn kind(&self) -> ChatMessageKind {
102 self.kind
103 }
104}
105
106impl From<ChatMessage> for ChatMessageAuthor {
107 fn from(message: ChatMessage) -> Self {
108 message.author
109 }
110}
111
112#[derive(
113 Clone, Copy, Debug, derive_more::Display, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize,
114)]
115#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
116pub struct ChatMessageId(Uuid);
117
118impl ChatMessageId {
119 #[must_use]
120 pub fn new() -> Self {
121 Self(Uuid::now_v7())
122 }
123}
124
125impl Default for ChatMessageId {
126 fn default() -> Self {
127 Self::new()
128 }
129}
130
131#[derive(Clone, Copy, Debug, EnumIs, Deserialize, Serialize)]
132#[derive_const(Default)]
133#[serde(rename_all = "kebab-case")]
134#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
135pub enum ChatMessageKind {
136 #[default]
137 Default,
138}
139
140#[derive(Clone, Debug, Default, Deserialize, Serialize)]
141#[serde(tag = "kind", rename_all = "kebab-case")]
142#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
143pub enum ChatMessageAuthor {
144 #[default]
145 System,
146 Player {
147 id: PlayerId,
148 },
149}
150
151impl From<PlayerId> for ChatMessageAuthor {
152 fn from(id: PlayerId) -> Self {
153 Self::Player { id }
154 }
155}
156
157impl From<&PlayerId> for ChatMessageAuthor {
158 fn from(id: &PlayerId) -> Self {
159 Self::Player { id: id.clone() }
160 }
161}
162
163#[derive(Debug, From, Deserialize, Serialize)]
164#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
165#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
166pub struct ChatMessageContent(Arc<str>);
167
168impl Clone for ChatMessageContent {
169 fn clone(&self) -> Self {
170 Self(Arc::clone(&self.0))
171 }
172}