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(Clone, Copy, Debug, PartialEq, Eq, Deserialize, Serialize)]
113#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
114pub struct ChatMessageId(Uuid);
115
116impl ChatMessageId {
117 #[must_use]
118 pub fn new() -> Self {
119 Self(Uuid::now_v7())
120 }
121}
122
123impl Default for ChatMessageId {
124 fn default() -> Self {
125 Self::new()
126 }
127}
128
129#[derive(Clone, Copy, Debug, EnumIs, Deserialize, Serialize)]
130#[derive_const(Default)]
131#[serde(rename_all = "kebab-case")]
132#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
133pub enum ChatMessageKind {
134 #[default]
135 Default,
136}
137
138#[derive(Clone, Debug, Default, Deserialize, Serialize)]
139#[serde(tag = "kind", rename_all = "kebab-case")]
140#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
141pub enum ChatMessageAuthor {
142 #[default]
143 System,
144 Player {
145 id: PlayerId,
146 },
147}
148
149impl From<PlayerId> for ChatMessageAuthor {
150 fn from(id: PlayerId) -> Self {
151 Self::Player { id }
152 }
153}
154
155impl From<&PlayerId> for ChatMessageAuthor {
156 fn from(id: &PlayerId) -> Self {
157 Self::Player { id: id.clone() }
158 }
159}
160
161#[derive(Debug, From, Deserialize, Serialize)]
162#[from(String, &str, Arc<str>, Box<str>, Cow<'_, str>)]
163#[cfg_attr(feature = "typescript", derive(ts_rs::TS))]
164pub struct ChatMessageContent(Arc<str>);
165
166impl Clone for ChatMessageContent {
167 fn clone(&self) -> Self {
168 Self(Arc::clone(&self.0))
169 }
170}