1use sim_codec_bridge::{BridgePacket, BridgeWarrant, content_id_string};
10use sim_kernel::{Error, Expr, Result, Symbol};
11use sim_lib_scene::{Anchor, AnchorSpace, Transform3};
12use sim_lib_view::SurfaceCaps;
13use sim_lib_view_device::{DeviceProfile, DeviceSurfaceCapsExt, GlanceReducer, GlassesClass};
14use sim_value::{access, build};
15
16pub const BRIDGE_WARRANT_REVIEW_MISSION_NAMESPACE: &str = "bridge";
18
19pub const BRIDGE_WARRANT_REVIEW_MISSION_NAME: &str = "packet-review";
21
22pub const VITURE_WARRANT_REVIEW_PANEL_ID: &str = "bridge-warrant-review";
24
25const HALO_GLYPH: &str = "OK/X";
26const REVIEW_TITLE: &str = "BRIDGE/FORGE warrant";
27
28#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30pub enum BridgeGlassesReviewInput {
31 VitureGazeDwellNod,
33 VitureShake,
35 HaloDoubleTap,
37 HaloLongPress,
39}
40
41#[derive(Clone, Copy, Debug, PartialEq, Eq)]
43pub enum WarrantReviewDecision {
44 Approve,
46 Reject,
48}
49
50impl WarrantReviewDecision {
51 fn intent_kind(self) -> &'static str {
52 match self {
53 Self::Approve => "approve",
54 Self::Reject => "reject",
55 }
56 }
57}
58
59pub fn warrant_review_mission() -> Symbol {
61 Symbol::qualified(
62 BRIDGE_WARRANT_REVIEW_MISSION_NAMESPACE,
63 BRIDGE_WARRANT_REVIEW_MISSION_NAME,
64 )
65}
66
67pub fn viture_warrant_review_panel(packet: &BridgePacket) -> Result<Expr> {
69 let context = WarrantReviewContext::new(packet)?;
70 let body = viture_review_card(packet, &context);
71 let panel = sim_lib_scene::panel(
72 VITURE_WARRANT_REVIEW_PANEL_ID,
73 body,
74 Anchor::new(AnchorSpace::Head, "center-front"),
75 Transform3::new([0.0, 0.0, -1.2], [0.0, 0.0, 0.0, 1.0], [1.0, 1.0, 1.0]),
76 );
77 let panel = mark_warrant_scene(panel, "warrant");
78 validate("invalid Viture warrant review panel", &panel)?;
79 Ok(panel)
80}
81
82pub fn viture_warrant_review_scene(packet: &BridgePacket) -> Result<Expr> {
84 let scene = sim_lib_scene::spatial(vec![viture_warrant_review_panel(packet)?]);
85 validate("invalid Viture warrant review scene", &scene)?;
86 Ok(scene)
87}
88
89pub fn halo_warrant_glance_pager(packet: &BridgePacket, profile: &DeviceProfile) -> Result<Expr> {
91 WarrantReviewContext::new(packet)?;
92 let source = halo_warrant_source_scene(packet);
93 validate("invalid Halo warrant source scene", &source)?;
94 let glance = GlanceReducer.reduce(&source, profile)?;
95 validate("invalid Halo warrant glance pager", &glance)?;
96 Ok(glance)
97}
98
99pub fn warrant_review_intent(
101 packet: &BridgePacket,
102 decision: WarrantReviewDecision,
103 origin: sim_lib_intent::Origin,
104) -> Result<Expr> {
105 let context = WarrantReviewContext::new(packet)?;
106 let intent = sim_lib_intent::intent(
107 decision.intent_kind(),
108 origin,
109 vec![
110 ("mission", Expr::Symbol(warrant_review_mission())),
111 ("packet-cid", build::text(context.packet_cid.to_owned())),
112 ("warrant", warrant_expr(context.warrant)),
113 ],
114 );
115 sim_lib_intent::validate_intent(&intent)
116 .map_err(|error| Error::HostError(format!("invalid warrant review Intent: {error}")))?;
117 Ok(intent)
118}
119
120pub fn warrant_review_intent_from_glasses_input(
122 packet: &BridgePacket,
123 input: BridgeGlassesReviewInput,
124 origin: sim_lib_intent::Origin,
125) -> Result<Expr> {
126 warrant_review_intent(packet, decision_for_input(input), origin)
127}
128
129pub(crate) fn glasses_warrant_scene_for_caps(
130 packet: &BridgePacket,
131 caps: &SurfaceCaps,
132) -> Result<Option<Expr>> {
133 if packet.warrant.is_none() {
134 return Ok(None);
135 }
136 let profile = caps.device_profile();
137 match sim_lib_view_device::glasses_class(&profile) {
138 Some(GlassesClass::Stereo6Dof) => viture_warrant_review_scene(packet).map(Some),
139 Some(GlassesClass::MonoHud) => halo_warrant_glance_pager(packet, &profile).map(Some),
140 Some(GlassesClass::DisplayOnly) | None => Ok(None),
141 }
142}
143
144fn decision_for_input(input: BridgeGlassesReviewInput) -> WarrantReviewDecision {
145 match input {
146 BridgeGlassesReviewInput::VitureGazeDwellNod | BridgeGlassesReviewInput::HaloDoubleTap => {
147 WarrantReviewDecision::Approve
148 }
149 BridgeGlassesReviewInput::VitureShake | BridgeGlassesReviewInput::HaloLongPress => {
150 WarrantReviewDecision::Reject
151 }
152 }
153}
154
155struct WarrantReviewContext<'a> {
156 packet_cid: &'a str,
157 warrant: &'a BridgeWarrant,
158}
159
160impl<'a> WarrantReviewContext<'a> {
161 fn new(packet: &'a BridgePacket) -> Result<Self> {
162 Ok(Self {
163 packet_cid: packet.header.cid.as_deref().ok_or_else(|| {
164 Error::Eval("BRIDGE/FORGE warrant review requires a stamped packet".to_owned())
165 })?,
166 warrant: packet.warrant.as_ref().ok_or_else(|| {
167 Error::Eval("BRIDGE/FORGE warrant review requires a packet warrant".to_owned())
168 })?,
169 })
170 }
171}
172
173fn viture_review_card(packet: &BridgePacket, context: &WarrantReviewContext<'_>) -> Expr {
174 sim_lib_scene::node(
175 "box",
176 vec![
177 ("role", build::sym("bridge-warrant-review")),
178 ("title", build::text(REVIEW_TITLE)),
179 ("status", build::sym("warrant")),
180 ("warrant", Expr::Bool(true)),
181 ("bypass-budget", Expr::Bool(true)),
182 ("packet-cid", build::text(context.packet_cid.to_owned())),
183 ("mission", Expr::Symbol(warrant_review_mission())),
184 (
185 "children",
186 build::list(vec![
187 sim_lib_scene::badge("warrant", "FORGE gate"),
188 sim_lib_scene::text_node(format!(
189 "move {} from {}",
190 packet.header.move_kind.as_qualified_str(),
191 packet.header.from
192 )),
193 sim_lib_scene::text_node(format!(
194 "parts {} warrant parts {}",
195 packet.body.len(),
196 context.warrant.parts.len()
197 )),
198 sim_lib_scene::text_node(format!("packet {}", short_cid(context.packet_cid))),
199 decision_button("Approve", WarrantReviewDecision::Approve),
200 decision_button("Reject", WarrantReviewDecision::Reject),
201 ]),
202 ),
203 ],
204 )
205}
206
207fn halo_warrant_source_scene(packet: &BridgePacket) -> Expr {
208 let mut source = sim_lib_scene::node(
209 "stack",
210 vec![
211 ("dir", build::sym("column")),
212 ("title", build::text(REVIEW_TITLE)),
213 ("status", build::sym("critical")),
214 ("warrant", Expr::Bool(true)),
215 ("bypass-budget", Expr::Bool(true)),
216 (
217 "children",
218 build::list(vec![sim_lib_scene::node(
219 "button",
220 vec![
221 ("label", build::text(HALO_GLYPH)),
222 ("target", Expr::Symbol(warrant_review_mission())),
223 ("control", build::sym("warrant-decision")),
224 (
225 "packet-cid",
226 build::text(packet.header.cid.clone().unwrap_or_default()),
227 ),
228 ],
229 )]),
230 ),
231 ],
232 );
233 source = access::set(&source, "pager", build::sym("halo-glance"));
234 source
235}
236
237fn decision_button(label: &str, decision: WarrantReviewDecision) -> Expr {
238 sim_lib_scene::node(
239 "button",
240 vec![
241 ("label", build::text(label)),
242 ("target", Expr::Symbol(warrant_review_mission())),
243 ("control", build::sym(decision.intent_kind())),
244 (
245 "intent-kind",
246 Expr::Symbol(Symbol::qualified("intent", decision.intent_kind())),
247 ),
248 ],
249 )
250}
251
252fn mark_warrant_scene(scene: Expr, status: &str) -> Expr {
253 let scene = access::set(&scene, "status", build::sym(status));
254 let scene = access::set(&scene, "warrant", Expr::Bool(true));
255 let scene = access::set(&scene, "pinned", Expr::Bool(true));
256 access::set(&scene, "bypass-budget", Expr::Bool(true))
257}
258
259fn warrant_expr(warrant: &BridgeWarrant) -> Expr {
260 build::map(vec![
261 ("moves", build::text(content_id_string(&warrant.moves))),
262 ("frames", build::text(content_id_string(&warrant.frames))),
263 (
264 "parts",
265 build::list(
266 warrant
267 .parts
268 .iter()
269 .map(|(kind, cid)| {
270 build::map(vec![
271 ("part-kind", Expr::Symbol(kind.clone())),
272 ("cid", build::text(content_id_string(cid))),
273 ])
274 })
275 .collect(),
276 ),
277 ),
278 ])
279}
280
281fn short_cid(cid: &str) -> &str {
282 cid.char_indices()
283 .nth(28)
284 .map(|(index, _)| &cid[..index])
285 .unwrap_or(cid)
286}
287
288fn validate(context: &str, scene: &Expr) -> Result<()> {
289 sim_lib_scene::validate_scene(scene)
290 .map_err(|error| Error::HostError(format!("{context}: {error}")))
291}