1use serde::{Deserialize, Serialize};
6use std::sync::OnceLock;
7use ts_rs::TS;
8
9use crate::types::PacketType;
10
11#[derive(Debug, Clone, Serialize, Deserialize, TS)]
14#[ts(export)]
15pub struct FieldRule {
16 pub name: String,
17 #[serde(skip_serializing_if = "Option::is_none")]
18 pub wildcard_value: Option<serde_json::Value>,
19}
20
21#[derive(Debug, Clone, Serialize, Deserialize, TS)]
24#[ts(export)]
25#[serde(tag = "kind", rename_all = "lowercase")]
26pub enum Compatibility {
27 Any,
29 Exact,
31 StructFieldWildcard { fields: Vec<FieldRule> },
33}
34
35#[derive(Debug, Clone, Serialize, Deserialize, TS)]
38#[ts(export)]
39pub struct PacketTypeMeta {
40 pub id: String,
42 pub label: String,
44 pub color: String,
46 #[serde(skip_serializing_if = "Option::is_none")]
50 pub display_template: Option<String>,
51 pub compatibility: Compatibility,
53}
54
55pub fn packet_type_registry() -> &'static [PacketTypeMeta] {
59 static REGISTRY: OnceLock<Vec<PacketTypeMeta>> = OnceLock::new();
60 REGISTRY.get_or_init(|| {
61 vec![
62 PacketTypeMeta {
63 id: "Any".into(),
64 label: "Any".into(),
65 color: "#96ceb4".into(),
66 display_template: None,
67 compatibility: Compatibility::Any,
68 },
69 PacketTypeMeta {
70 id: "Binary".into(),
71 label: "Binary".into(),
72 color: "#45b7d1".into(),
73 display_template: None,
74 compatibility: Compatibility::Exact,
75 },
76 PacketTypeMeta {
77 id: "Text".into(),
78 label: "Text".into(),
79 color: "#4ecdc4".into(),
80 display_template: None,
81 compatibility: Compatibility::Exact,
82 },
83 PacketTypeMeta {
84 id: "OpusAudio".into(),
85 label: "Opus Audio".into(),
86 color: "#ff6b6b".into(),
87 display_template: None,
88 compatibility: Compatibility::Exact,
89 },
90 PacketTypeMeta {
91 id: "RawAudio".into(),
92 label: "Raw Audio".into(),
93 color: "#f39c12".into(),
94 display_template: Some(
95 "Raw Audio ({sample_rate|*}Hz, {channels|*}ch, {sample_format})".into(),
96 ),
97 compatibility: Compatibility::StructFieldWildcard {
98 fields: vec![
99 FieldRule {
100 name: "sample_rate".into(),
101 wildcard_value: Some(serde_json::json!(0)),
102 },
103 FieldRule {
104 name: "channels".into(),
105 wildcard_value: Some(serde_json::json!(0)),
106 },
107 FieldRule { name: "sample_format".into(), wildcard_value: None },
108 ],
109 },
110 },
111 PacketTypeMeta {
112 id: "Transcription".into(),
113 label: "Transcription".into(),
114 color: "#9b59b6".into(),
115 display_template: None,
116 compatibility: Compatibility::Exact,
117 },
118 PacketTypeMeta {
119 id: "Custom".into(),
120 label: "Custom".into(),
121 color: "#e67e22".into(),
122 display_template: Some("Custom ({type_id})".into()),
123 compatibility: Compatibility::StructFieldWildcard {
124 fields: vec![FieldRule { name: "type_id".into(), wildcard_value: None }],
125 },
126 },
127 ]
128 })
129}
130
131fn to_variant_and_payload(packet_type: &PacketType) -> (String, Option<serde_json::Value>) {
133 let json = serde_json::to_value(packet_type).unwrap_or(serde_json::Value::Null);
134 match json {
135 serde_json::Value::String(unit) => (unit, None),
136 serde_json::Value::Object(map) => {
137 if map.len() == 1 {
138 if let Some((k, v)) = map.into_iter().next() {
140 (k, Some(v))
141 } else {
142 ("Unknown".to_string(), None)
143 }
144 } else {
145 ("Unknown".to_string(), None)
146 }
147 },
148 _ => ("Unknown".to_string(), None),
149 }
150}
151
152fn find_meta<'a>(registry: &'a [PacketTypeMeta], id: &str) -> Option<&'a PacketTypeMeta> {
154 registry.iter().find(|m| m.id == id)
155}
156
157pub fn can_connect(output: &PacketType, input: &PacketType, registry: &[PacketTypeMeta]) -> bool {
164 let (out_id, out_payload) = to_variant_and_payload(output);
165 let (in_id, in_payload) = to_variant_and_payload(input);
166
167 if out_id == "Any" || in_id == "Any" {
168 return true;
169 }
170 if out_id != in_id {
171 return false;
172 }
173
174 let Some(meta) = find_meta(registry, &out_id) else {
175 return false;
177 };
178
179 match &meta.compatibility {
180 Compatibility::Any | Compatibility::Exact => true,
181 Compatibility::StructFieldWildcard { fields } => {
182 let (Some(out_obj), Some(in_obj)) = (out_payload.as_ref(), in_payload.as_ref()) else {
183 return false;
184 };
185 let Some(out_map) = out_obj.as_object() else {
186 return false;
187 };
188 let Some(in_map) = in_obj.as_object() else {
189 return false;
190 };
191
192 fields.iter().all(|f| {
193 let Some(av) = out_map.get(&f.name) else {
194 return false;
195 };
196 let Some(bv) = in_map.get(&f.name) else {
197 return false;
198 };
199
200 if let Some(wild) = &f.wildcard_value {
202 if av == wild || bv == wild {
203 return true;
204 }
205 }
206
207 av == bv
209 })
210 },
211 }
212}
213
214pub fn can_connect_any(
216 output: &PacketType,
217 inputs: &[PacketType],
218 registry: &[PacketTypeMeta],
219) -> bool {
220 inputs.iter().any(|inp| can_connect(output, inp, registry))
221}