Skip to main content

txtx_addon_kit/types/
frontend.rs

1use std::{borrow::BorrowMut, collections::BTreeMap, fmt::Display};
2
3use crate::{
4    constants::ACTION_ITEM_BEGIN_FLOW,
5    types::{stores::AddonDefaults, types::RunbookCompleteAdditionalInfo},
6};
7
8use super::{
9    block_id::BlockId,
10    diagnostics::Diagnostic,
11    types::{Type, Value},
12    ConstructDid, Did,
13};
14use serde::Serialize;
15use uuid::Uuid;
16
17#[derive(Debug, Clone, Serialize, Deserialize)]
18pub enum BlockEvent {
19    Action(Block),
20    Clear,
21    UpdateActionItems(Vec<NormalizedActionItemRequestUpdate>),
22    RunbookCompleted(Vec<RunbookCompleteAdditionalInfo>),
23    Exit,
24    LogEvent(LogEvent),
25    Modal(Block),
26    Error(Block),
27}
28
29#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq, PartialOrd, Ord)]
30pub enum LogLevel {
31    Trace,
32    Debug,
33    Info,
34    Warn,
35    Error,
36}
37impl ToString for LogLevel {
38    fn to_string(&self) -> String {
39        match self {
40            LogLevel::Trace => "trace".to_string(),
41            LogLevel::Debug => "debug".to_string(),
42            LogLevel::Info => "info".to_string(),
43            LogLevel::Warn => "warn".to_string(),
44            LogLevel::Error => "error".to_string(),
45        }
46    }
47}
48impl From<&str> for LogLevel {
49    fn from(s: &str) -> Self {
50        match s.to_lowercase().as_str() {
51            "trace" => LogLevel::Trace,
52            "debug" => LogLevel::Debug,
53            "info" => LogLevel::Info,
54            "warn" => LogLevel::Warn,
55            "error" => LogLevel::Error,
56            _ => LogLevel::Info,
57        }
58    }
59}
60
61impl From<String> for LogLevel {
62    fn from(s: String) -> Self {
63        LogLevel::from(s.as_str())
64    }
65}
66
67impl LogLevel {
68    pub fn should_log(&self, level: &LogLevel) -> bool {
69        level >= self
70    }
71}
72
73#[derive(Debug, Clone, Serialize, Deserialize)]
74#[serde(tag = "type", content = "log")]
75pub enum LogEvent {
76    Static(StaticLogEvent),
77    Transient(TransientLogEvent),
78}
79
80impl LogEvent {
81    pub fn typing(&self) -> String {
82        match self {
83            LogEvent::Static(_) => "Static".into(),
84            LogEvent::Transient(_) => "Transient".into(),
85        }
86    }
87    pub fn uuid(&self) -> Uuid {
88        match self {
89            LogEvent::Static(event) => event.uuid,
90            LogEvent::Transient(event) => event.uuid,
91        }
92    }
93    pub fn status(&self) -> Option<String> {
94        match self {
95            LogEvent::Static(_) => None,
96            LogEvent::Transient(event) => Some(event.status()),
97        }
98    }
99    pub fn summary(&self) -> String {
100        match self {
101            LogEvent::Static(event) => event.details.summary.clone(),
102            LogEvent::Transient(event) => event.summary(),
103        }
104    }
105    pub fn message(&self) -> String {
106        match self {
107            LogEvent::Static(event) => event.details.message.clone(),
108            LogEvent::Transient(event) => event.message(),
109        }
110    }
111
112    pub fn level(&self) -> LogLevel {
113        match self {
114            LogEvent::Static(event) => event.level.clone(),
115            LogEvent::Transient(event) => event.level.clone(),
116        }
117    }
118
119    pub fn namespace(&self) -> &str {
120        match self {
121            LogEvent::Static(event) => &event.namespace,
122            LogEvent::Transient(event) => &event.namespace,
123        }
124    }
125}
126
127#[derive(Debug, Clone, Serialize, Deserialize)]
128#[serde(rename_all = "camelCase")]
129pub struct StaticLogEvent {
130    pub level: LogLevel,
131    pub uuid: Uuid,
132    pub details: LogDetails,
133    pub namespace: String,
134}
135
136#[derive(Debug, Clone, Serialize, Deserialize)]
137#[serde(rename_all = "camelCase")]
138pub struct LogDetails {
139    pub message: String,
140    pub summary: String,
141}
142
143#[derive(Debug, Clone, Serialize, Deserialize)]
144#[serde(tag = "status", content = "details")]
145pub enum TransientLogEventStatus {
146    Pending(LogDetails),
147    Success(LogDetails),
148    Failure(LogDetails),
149}
150
151#[derive(Debug, Clone, Serialize, Deserialize)]
152#[serde(rename_all = "camelCase")]
153pub struct TransientLogEvent {
154    pub level: LogLevel,
155    pub uuid: Uuid,
156    pub status: TransientLogEventStatus,
157    pub namespace: String,
158}
159
160impl TransientLogEvent {
161    pub fn summary(&self) -> String {
162        match &self.status {
163            TransientLogEventStatus::Pending(log_details) => log_details.summary.clone(),
164            TransientLogEventStatus::Success(log_details) => log_details.summary.clone(),
165            TransientLogEventStatus::Failure(log_details) => log_details.summary.clone(),
166        }
167    }
168    pub fn message(&self) -> String {
169        match &self.status {
170            TransientLogEventStatus::Pending(log_details) => log_details.message.clone(),
171            TransientLogEventStatus::Success(log_details) => log_details.message.clone(),
172            TransientLogEventStatus::Failure(log_details) => log_details.message.clone(),
173        }
174    }
175    pub fn status(&self) -> String {
176        match &self.status {
177            TransientLogEventStatus::Pending(_) => "Pending".into(),
178            TransientLogEventStatus::Success(_) => "Success".into(),
179            TransientLogEventStatus::Failure(_) => "Failure".into(),
180        }
181    }
182}
183
184impl TransientLogEvent {
185    pub fn pending_info(
186        uuid: Uuid,
187        summary: impl ToString,
188        message: impl ToString,
189        namespace: impl ToString,
190    ) -> Self {
191        TransientLogEvent {
192            level: LogLevel::Info,
193            uuid,
194            status: TransientLogEventStatus::Pending(LogDetails {
195                message: message.to_string(),
196                summary: summary.to_string(),
197            }),
198            namespace: namespace.to_string(),
199        }
200    }
201
202    pub fn success_info(
203        uuid: Uuid,
204        summary: impl ToString,
205        message: impl ToString,
206        namespace: impl ToString,
207    ) -> Self {
208        TransientLogEvent {
209            level: LogLevel::Info,
210            uuid,
211            status: TransientLogEventStatus::Success(LogDetails {
212                message: message.to_string(),
213                summary: summary.to_string(),
214            }),
215            namespace: namespace.to_string(),
216        }
217    }
218
219    pub fn failure_info(
220        uuid: Uuid,
221        summary: impl ToString,
222        message: impl ToString,
223        namespace: impl ToString,
224    ) -> Self {
225        TransientLogEvent {
226            level: LogLevel::Error,
227            uuid,
228            status: TransientLogEventStatus::Failure(LogDetails {
229                message: message.to_string(),
230                summary: summary.to_string(),
231            }),
232            namespace: namespace.to_string(),
233        }
234    }
235}
236
237pub struct LogDispatcher {
238    uuid: Uuid,
239    namespace: String,
240    tx: channel::Sender<BlockEvent>,
241}
242impl LogDispatcher {
243    pub fn new(uuid: Uuid, namespace: &str, tx: &channel::Sender<BlockEvent>) -> Self {
244        LogDispatcher { uuid, namespace: format!("txtx::{}", namespace), tx: tx.clone() }
245    }
246
247    fn log_static(&self, level: LogLevel, summary: impl ToString, message: impl ToString) {
248        let _ = self.tx.try_send(BlockEvent::static_log(
249            level,
250            self.uuid,
251            self.namespace.clone(),
252            summary,
253            message,
254        ));
255    }
256
257    pub fn trace(&self, summary: impl ToString, message: impl ToString) {
258        self.log_static(LogLevel::Trace, summary, message);
259    }
260
261    pub fn debug(&self, summary: impl ToString, message: impl ToString) {
262        self.log_static(LogLevel::Debug, summary, message);
263    }
264
265    pub fn info(&self, summary: impl ToString, message: impl ToString) {
266        self.log_static(LogLevel::Info, summary, message);
267    }
268
269    pub fn warn(&self, summary: impl ToString, message: impl ToString) {
270        self.log_static(LogLevel::Warn, summary, message);
271    }
272
273    pub fn error(&self, summary: impl ToString, message: impl ToString) {
274        self.log_static(LogLevel::Error, summary, message);
275    }
276
277    pub fn pending_info(&self, summary: impl ToString, message: impl ToString) {
278        let _ = self.tx.try_send(BlockEvent::LogEvent(LogEvent::Transient(
279            TransientLogEvent::pending_info(self.uuid, summary, message, &self.namespace),
280        )));
281    }
282
283    pub fn success_info(&self, summary: impl ToString, message: impl ToString) {
284        let _ = self.tx.try_send(BlockEvent::LogEvent(LogEvent::Transient(
285            TransientLogEvent::success_info(self.uuid, summary, message, &self.namespace),
286        )));
287    }
288
289    pub fn failure_info(&self, summary: impl ToString, message: impl ToString) {
290        let _ = self.tx.try_send(BlockEvent::LogEvent(LogEvent::Transient(
291            TransientLogEvent::failure_info(self.uuid, summary, message, &self.namespace),
292        )));
293    }
294    pub fn failure_with_diag(
295        &self,
296        summary: impl ToString,
297        message: impl ToString,
298        diag: &Diagnostic,
299    ) {
300        let summary = summary.to_string();
301        self.failure_info(&summary, message);
302        self.error(summary, diag.to_string());
303    }
304}
305
306impl BlockEvent {
307    pub fn static_log(
308        level: LogLevel,
309        uuid: Uuid,
310        namespace: String,
311        summary: impl ToString,
312        message: impl ToString,
313    ) -> Self {
314        BlockEvent::LogEvent(LogEvent::Static(StaticLogEvent {
315            level,
316            uuid,
317            details: LogDetails { message: message.to_string(), summary: summary.to_string() },
318            namespace,
319        }))
320    }
321
322    pub fn transient_log(event: TransientLogEvent) -> Self {
323        BlockEvent::LogEvent(LogEvent::Transient(event))
324    }
325    pub fn as_block(&self) -> Option<&Block> {
326        match &self {
327            BlockEvent::Action(ref block) => Some(block),
328            _ => None,
329        }
330    }
331
332    pub fn as_modal(&self) -> Option<&Block> {
333        match &self {
334            BlockEvent::Modal(ref block) => Some(block),
335            _ => None,
336        }
337    }
338
339    pub fn expect_block(&self) -> &Block {
340        match &self {
341            BlockEvent::Action(ref block) => block,
342            _ => unreachable!("block expected"),
343        }
344    }
345
346    pub fn expect_modal(&self) -> &Block {
347        match &self {
348            BlockEvent::Modal(ref block) => block,
349            _ => unreachable!("block expected"),
350        }
351    }
352
353    pub fn expect_updated_action_items(&self) -> &Vec<NormalizedActionItemRequestUpdate> {
354        match &self {
355            BlockEvent::UpdateActionItems(ref updates) => updates,
356            _ => unreachable!("block expected"),
357        }
358    }
359
360    pub fn expect_runbook_completed(&self) {
361        match &self {
362            BlockEvent::RunbookCompleted(_) => {}
363            _ => unreachable!("block expected"),
364        }
365    }
366
367    pub fn expect_log_event(&self) -> &LogEvent {
368        match &self {
369            BlockEvent::LogEvent(ref log_event) => log_event,
370            _ => unreachable!("log event expected"),
371        }
372    }
373
374    pub fn new_modal(title: &str, description: &str, groups: Vec<ActionGroup>) -> Block {
375        Block {
376            uuid: Uuid::new_v4(),
377            panel: Panel::new_modal_panel(title, description, groups),
378            visible: false,
379        }
380    }
381}
382
383pub enum RunbookExecutionState {
384    RunbookGenesis,
385    RunbookGlobalsUpdated,
386}
387
388#[derive(Debug, Clone, Serialize, Deserialize)]
389#[serde(rename_all = "camelCase")]
390pub struct Block {
391    pub uuid: Uuid,
392    #[serde(flatten)]
393    pub panel: Panel,
394    pub visible: bool,
395}
396
397impl Block {
398    pub fn new(uuid: &Uuid, panel: Panel) -> Self {
399        Block { uuid: uuid.clone(), panel, visible: true }
400    }
401
402    pub fn apply_action_item_updates(&mut self, update: NormalizedActionItemRequestUpdate) -> bool {
403        let mut did_update = false;
404        match self.panel.borrow_mut() {
405            Panel::ActionPanel(panel) => {
406                for group in panel.groups.iter_mut() {
407                    let group_did_update = group.apply_action_item_updates(&update);
408                    if group_did_update {
409                        did_update = true;
410                    }
411                }
412            }
413            Panel::ModalPanel(panel) => {
414                for group in panel.groups.iter_mut() {
415                    let group_did_update = group.apply_action_item_updates(&update);
416                    if group_did_update {
417                        did_update = true;
418                    }
419                }
420            }
421            _ => {}
422        };
423        did_update
424    }
425}
426
427#[derive(Debug, Clone, Serialize, Deserialize)]
428/// Note: though the `action_status` field is optional, it is required for many functions. I kep it like this
429/// because I like the `.set_status` pattern :-)
430pub struct NormalizedActionItemRequestUpdate {
431    pub id: BlockId,
432    pub action_status: Option<ActionItemStatus>,
433    pub action_type: Option<ActionItemRequestType>,
434}
435
436#[derive(Debug, Clone, Serialize)]
437pub struct ActionItemRequestUpdate {
438    pub id: ActionItemRequestUpdateIdentifier,
439    pub action_status: Option<ActionItemStatus>,
440    pub action_type: Option<ActionItemRequestType>,
441}
442
443#[derive(Debug, Clone, Serialize)]
444pub enum ActionItemRequestUpdateIdentifier {
445    Id(BlockId),
446    ConstructDidWithKey((ConstructDid, String)),
447}
448
449impl ActionItemRequestUpdate {
450    pub fn from_id(id: &BlockId) -> Self {
451        ActionItemRequestUpdate {
452            id: ActionItemRequestUpdateIdentifier::Id(id.clone()),
453            action_status: None,
454            action_type: None,
455        }
456    }
457    pub fn from_context(construct_did: &ConstructDid, internal_key: &str) -> Self {
458        ActionItemRequestUpdate {
459            id: ActionItemRequestUpdateIdentifier::ConstructDidWithKey((
460                construct_did.clone(),
461                internal_key.to_string(),
462            )),
463            action_status: None,
464            action_type: None,
465        }
466    }
467    ///
468    /// Compares `new_item` and `existing_item`, returning an `ActionItemRequestUpdate` if
469    /// the ids are the same and either the mutable properties of the type or that status have been updated.
470    ///
471    pub fn from_diff(
472        new_item: &ActionItemRequest,
473        existing_item: &ActionItemRequest,
474    ) -> Option<Self> {
475        let id_match = new_item.id == existing_item.id;
476        let status_match = new_item.action_status == existing_item.action_status;
477        let type_diff = ActionItemRequestType::diff_mutable_properties(
478            &new_item.action_type,
479            &existing_item.action_type,
480        );
481        if !id_match || (status_match && type_diff.is_none()) {
482            return None;
483        }
484        let mut update = ActionItemRequestUpdate::from_id(&new_item.id);
485        if !status_match {
486            update.set_status(new_item.action_status.clone());
487        }
488        if let Some(new_type) = type_diff {
489            update.set_type(new_type);
490        }
491        Some(update)
492    }
493
494    pub fn set_status(&mut self, new_status: ActionItemStatus) -> Self {
495        self.action_status = Some(new_status);
496        self.clone()
497    }
498
499    pub fn set_type(&mut self, new_type: ActionItemRequestType) -> Self {
500        self.action_type = Some(new_type);
501        self.clone()
502    }
503
504    pub fn normalize(
505        &self,
506        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
507    ) -> Option<NormalizedActionItemRequestUpdate> {
508        for (_, action) in action_item_requests.iter() {
509            match &self.id {
510                ActionItemRequestUpdateIdentifier::Id(id) => {
511                    if action.id.eq(id) {
512                        return Some(NormalizedActionItemRequestUpdate {
513                            id: id.clone(),
514                            action_status: self.action_status.clone(),
515                            action_type: self.action_type.clone(),
516                        });
517                    }
518                }
519                ActionItemRequestUpdateIdentifier::ConstructDidWithKey((
520                    construct_did,
521                    internal_key,
522                )) => {
523                    let Some(ref action_construct_did) = action.construct_did else {
524                        continue;
525                    };
526                    if action_construct_did.eq(construct_did)
527                        && action.internal_key.eq(internal_key)
528                    {
529                        return Some(NormalizedActionItemRequestUpdate {
530                            id: action.id.clone(),
531                            action_status: self.action_status.clone(),
532                            action_type: self.action_type.clone(),
533                        });
534                    }
535                }
536            }
537        }
538        None
539    }
540}
541
542impl Display for Block {
543    fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
544        writeln!(f, "Block {} {{", self.uuid)?;
545        match &self.panel {
546            Panel::ActionPanel(panel) => {
547                writeln!(f, "  title: {}", panel.title)?;
548                for group in panel.groups.iter() {
549                    writeln!(f, "  group: {} {{", group.title)?;
550                    for sub_group in group.sub_groups.iter() {
551                        writeln!(f, "    sub_group: {{")?;
552                        for item in sub_group.action_items.iter() {
553                            writeln!(f, "          title: {:?}", item.construct_instance_name)?;
554                            writeln!(f, "          consctruct: {:?}", item.construct_did)?;
555                            writeln!(f, "          status: {:?}", item.action_status)?;
556                            writeln!(f, "          action: {:?}", item.action_type)?;
557                            writeln!(f, "      }}")?;
558                        }
559                        writeln!(f, "    }}")?;
560                    }
561                    writeln!(f, "  }}")?;
562                }
563                writeln!(f, "}}")
564            }
565            Panel::ModalPanel(panel) => {
566                writeln!(f, "  title: {}", panel.title)?;
567                for group in panel.groups.iter() {
568                    writeln!(f, "  group: {} {{", group.title)?;
569                    for sub_group in group.sub_groups.iter() {
570                        writeln!(f, "    sub_group: {{")?;
571                        for item in sub_group.action_items.iter() {
572                            writeln!(f, "          title: {:?}", item.construct_instance_name)?;
573                            writeln!(f, "          consctruct: {:?}", item.construct_did)?;
574                            writeln!(f, "          status: {:?}", item.action_status)?;
575                            writeln!(f, "          action: {:?}", item.action_type)?;
576                            writeln!(f, "      }}")?;
577                        }
578                        writeln!(f, "    }}")?;
579                    }
580                    writeln!(f, "  }}")?;
581                }
582                writeln!(f, "}}")
583            }
584
585            _ => {
586                writeln!(f, "?????")
587            }
588        }
589    }
590}
591
592#[derive(Debug, Clone, Serialize, Deserialize)]
593#[serde(tag = "type", content = "panel")]
594pub enum Panel {
595    ActionPanel(ActionPanelData),
596    ModalPanel(ModalPanelData),
597    ErrorPanel(ErrorPanelData),
598}
599
600impl Panel {
601    pub fn new_action_panel(title: &str, description: &str, groups: Vec<ActionGroup>) -> Self {
602        Panel::ActionPanel(ActionPanelData {
603            title: title.to_string(),
604            description: description.to_string(),
605            groups,
606        })
607    }
608
609    pub fn new_modal_panel(title: &str, description: &str, groups: Vec<ActionGroup>) -> Self {
610        Panel::ModalPanel(ModalPanelData {
611            title: title.to_string(),
612            description: description.to_string(),
613            groups,
614        })
615    }
616
617    pub fn as_action_panel(&self) -> Option<&ActionPanelData> {
618        match &self {
619            Panel::ActionPanel(ref data) => Some(data),
620            _ => None,
621        }
622    }
623
624    pub fn as_modal_panel(&self) -> Option<&ModalPanelData> {
625        match &self {
626            Panel::ModalPanel(ref data) => Some(data),
627            _ => None,
628        }
629    }
630
631    pub fn expect_action_panel(&self) -> &ActionPanelData {
632        match &self {
633            Panel::ActionPanel(ref data) => data,
634            _ => panic!("expected action panel, got {:?}", self),
635        }
636    }
637
638    pub fn expect_modal_panel(&self) -> &ModalPanelData {
639        match &self {
640            Panel::ModalPanel(ref data) => data,
641            _ => panic!("expected action panel, got {:?}", self),
642        }
643    }
644
645    pub fn expect_modal_panel_mut(&mut self) -> &mut ModalPanelData {
646        match self {
647            Panel::ModalPanel(ref mut data) => data,
648            _ => panic!("expected action panel, got {:?}", self),
649        }
650    }
651}
652
653#[derive(Debug, Clone, Serialize, Deserialize)]
654#[serde(rename_all = "camelCase")]
655pub struct ActionPanelData {
656    pub title: String,
657    pub description: String,
658    pub groups: Vec<ActionGroup>,
659}
660
661impl ActionPanelData {
662    pub fn compile_actions_to_item_updates(
663        &self,
664        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
665    ) -> Vec<ActionItemRequestUpdate> {
666        let mut updates = vec![];
667        for group in self.groups.iter() {
668            let mut group_updates = group.compile_actions_to_item_updates(&action_item_requests);
669            updates.append(&mut group_updates);
670        }
671        updates
672    }
673
674    pub fn filter_existing_action_items(
675        &mut self,
676        existing_requests: &Vec<&mut ActionItemRequest>,
677    ) -> &mut Self {
678        let mut group_idx_to_remove = vec![];
679        for (i, group) in self.groups.iter_mut().enumerate() {
680            group.filter_existing_action_items(&existing_requests);
681            if group.sub_groups.is_empty() {
682                group_idx_to_remove.push(i);
683            }
684        }
685        group_idx_to_remove.iter().rev().for_each(|i| {
686            self.groups.remove(*i);
687        });
688        self
689    }
690}
691
692#[derive(Debug, Clone, Serialize, Deserialize)]
693#[serde(rename_all = "camelCase")]
694pub struct ModalPanelData {
695    pub title: String,
696    pub description: String,
697    pub groups: Vec<ActionGroup>,
698}
699
700impl ModalPanelData {
701    pub fn compile_actions_to_item_updates(
702        &self,
703        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
704    ) -> Vec<ActionItemRequestUpdate> {
705        let mut updates = vec![];
706        for group in self.groups.iter() {
707            let mut group_updates = group.compile_actions_to_item_updates(&action_item_requests);
708            updates.append(&mut group_updates);
709        }
710        updates
711    }
712
713    pub fn filter_existing_action_items(
714        &mut self,
715        existing_requests: &Vec<&mut ActionItemRequest>,
716    ) -> &mut Self {
717        let mut group_idx_to_remove = vec![];
718        for (i, group) in self.groups.iter_mut().enumerate() {
719            group.filter_existing_action_items(&existing_requests);
720            if group.sub_groups.is_empty() {
721                group_idx_to_remove.push(i);
722            }
723        }
724        group_idx_to_remove.iter().rev().for_each(|i| {
725            self.groups.remove(*i);
726        });
727        self
728    }
729}
730
731#[derive(Debug, Clone, Serialize, Deserialize)]
732#[serde(rename_all = "camelCase")]
733pub struct ErrorPanelData {
734    pub title: String,
735    pub description: String,
736    pub groups: Vec<ActionGroup>,
737}
738
739impl ErrorPanelData {
740    pub fn from_diagnostics(diagnostics: &Vec<Diagnostic>) -> Self {
741        let mut diag_actions = vec![];
742        for (i, diag) in diagnostics.iter().enumerate() {
743            let mut action = ActionItemRequestType::DisplayErrorLog(DisplayErrorLogRequest {
744                diagnostic: diag.clone(),
745            })
746            .to_request("", "diagnostic")
747            .with_status(ActionItemStatus::Error(diag.clone()));
748
749            action.index = (i + 1) as u16;
750            diag_actions.push(action);
751        }
752        ErrorPanelData {
753            title: "EXECUTION ERROR".into(),
754            description: "Review the following execution errors and restart the runbook.".into(),
755            groups: vec![ActionGroup {
756                title: "".into(),
757                sub_groups: vec![ActionSubGroup {
758                    title: None,
759                    action_items: diag_actions,
760                    allow_batch_completion: false,
761                }],
762            }],
763        }
764    }
765}
766
767#[derive(Debug, Clone, Serialize, Deserialize)]
768#[serde(rename_all = "camelCase")]
769pub struct ActionGroup {
770    pub title: String,
771    pub sub_groups: Vec<ActionSubGroup>,
772}
773
774impl ActionGroup {
775    pub fn new(title: &str, sub_groups: Vec<ActionSubGroup>) -> Self {
776        ActionGroup { title: title.to_string(), sub_groups }
777    }
778    pub fn contains_validate_modal_item(&self) -> bool {
779        for sub_group in self.sub_groups.iter() {
780            for item in sub_group.action_items.iter() {
781                if let ActionItemRequestType::ValidateModal = item.action_type {
782                    return true;
783                }
784            }
785        }
786        false
787    }
788
789    pub fn apply_action_item_updates(
790        &mut self,
791        update: &NormalizedActionItemRequestUpdate,
792    ) -> bool {
793        let mut did_update = false;
794        for sub_group in self.sub_groups.iter_mut() {
795            for action in sub_group.action_items.iter_mut() {
796                if action.id == update.id {
797                    if let Some(action_status) = update.action_status.clone() {
798                        if action.action_status != action_status {
799                            action.action_status = action_status;
800                            did_update = true;
801                        }
802                    }
803                    if let Some(action_type) = update.action_type.clone() {
804                        if action.action_type != action_type {
805                            action.action_type = action_type;
806                            did_update = true;
807                        }
808                    }
809                }
810            }
811        }
812        did_update
813    }
814
815    pub fn compile_actions_to_item_updates(
816        &self,
817        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
818    ) -> Vec<ActionItemRequestUpdate> {
819        let mut updates = vec![];
820        for sub_group in self.sub_groups.iter() {
821            let mut sub_group_updates =
822                sub_group.compile_actions_to_item_updates(&action_item_requests);
823            updates.append(&mut sub_group_updates);
824        }
825        updates
826    }
827
828    pub fn filter_existing_action_items(
829        &mut self,
830        existing_requests: &Vec<&mut ActionItemRequest>,
831    ) -> &mut Self {
832        let mut sub_group_idx_to_remove = vec![];
833        for (i, sub_group) in self.sub_groups.iter_mut().enumerate() {
834            sub_group.filter_existing_action_items(&existing_requests);
835            if sub_group.action_items.is_empty() {
836                sub_group_idx_to_remove.push(i);
837            }
838        }
839        sub_group_idx_to_remove.iter().rev().for_each(|i| {
840            self.sub_groups.remove(*i);
841        });
842        self
843    }
844}
845
846#[derive(Debug, Clone, Serialize, Deserialize)]
847#[serde(rename_all = "camelCase")]
848pub struct ActionSubGroup {
849    pub title: Option<String>,
850    pub action_items: Vec<ActionItemRequest>,
851    pub allow_batch_completion: bool,
852}
853
854impl ActionSubGroup {
855    pub fn new(
856        title: Option<String>,
857        action_items: Vec<ActionItemRequest>,
858        allow_batch_completion: bool,
859    ) -> Self {
860        ActionSubGroup { title, action_items, allow_batch_completion }
861    }
862
863    pub fn contains_validate_modal_item(&self) -> bool {
864        for item in self.action_items.iter() {
865            if let ActionItemRequestType::ValidateModal = item.action_type {
866                return true;
867            }
868        }
869        false
870    }
871
872    pub fn compile_actions_to_item_updates(
873        &self,
874        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
875    ) -> Vec<ActionItemRequestUpdate> {
876        let mut updates = vec![];
877        for new_item in self.action_items.iter() {
878            if let Some(existing_item) = action_item_requests.get(&new_item.id) {
879                if let Some(update) = ActionItemRequestUpdate::from_diff(new_item, existing_item) {
880                    updates.push(update);
881                };
882            };
883        }
884        updates
885    }
886
887    pub fn filter_existing_action_items(
888        &mut self,
889        existing_requests: &Vec<&mut ActionItemRequest>,
890    ) -> &mut Self {
891        let mut action_item_idx_to_remove = vec![];
892        for (i, new_item) in self.action_items.iter().enumerate() {
893            for existing_item in existing_requests.iter() {
894                if existing_item.id.eq(&new_item.id) {
895                    if let None = ActionItemRequestUpdate::from_diff(new_item, existing_item) {
896                        action_item_idx_to_remove.push(i);
897                    };
898                }
899            }
900        }
901        action_item_idx_to_remove.iter().rev().for_each(|i| {
902            self.action_items.remove(*i);
903        });
904        self
905    }
906}
907
908#[derive(Debug, Clone, Serialize, Deserialize)]
909#[serde(rename_all = "camelCase")]
910pub struct ActionItemRequest {
911    pub id: BlockId,
912    pub construct_did: Option<ConstructDid>,
913    pub index: u16,
914    pub construct_instance_name: String,
915    pub meta_description: Option<String>,
916    pub description: Option<String>,
917    pub markdown: Option<String>,
918    pub action_status: ActionItemStatus,
919    pub action_type: ActionItemRequestType,
920    pub internal_key: String,
921}
922
923impl ActionItemRequest {
924    fn new(
925        construct_instance_name: &str,
926        internal_key: &str,
927        action_type: ActionItemRequestType,
928    ) -> Self {
929        let mut req = ActionItemRequest {
930            id: BlockId::new("empty".as_bytes()),
931            construct_did: None,
932            index: 0,
933            construct_instance_name: construct_instance_name.to_string(),
934            description: None,
935            meta_description: None,
936            markdown: None,
937            action_status: ActionItemStatus::Todo,
938            action_type,
939            internal_key: internal_key.to_string(),
940        };
941        req.recompute_id();
942        req
943    }
944    pub fn with_description(mut self, description: &str) -> Self {
945        self.description = Some(description.to_string());
946        self.recompute_id();
947        self
948    }
949    pub fn with_some_description(mut self, description: Option<String>) -> Self {
950        self.description = description;
951        self.recompute_id();
952        self
953    }
954    pub fn with_meta_description(mut self, meta_description: &str) -> Self {
955        self.meta_description = Some(meta_description.to_string());
956        self.recompute_id();
957        self
958    }
959    pub fn with_some_meta_description(mut self, meta_description: Option<String>) -> Self {
960        self.meta_description = meta_description;
961        self
962    }
963    pub fn with_some_markdown(mut self, markdown: Option<String>) -> Self {
964        self.markdown = markdown;
965        self
966    }
967    pub fn with_construct_did(mut self, construct_did: &ConstructDid) -> Self {
968        self.construct_did = Some(construct_did.clone());
969        self.recompute_id();
970        self
971    }
972    pub fn with_status(mut self, action_status: ActionItemStatus) -> Self {
973        self.action_status = action_status;
974        self
975    }
976    pub fn recompute_id(&mut self) {
977        let data = format!(
978            "{}-{}-{}-{}-{}",
979            self.construct_instance_name,
980            self.description.clone().unwrap_or("".into()),
981            self.internal_key,
982            self.construct_did.as_ref().and_then(|did| Some(did.to_string())).unwrap_or("".into()),
983            self.action_type.get_block_id_string()
984        );
985        self.id = BlockId::new(data.as_bytes());
986    }
987}
988
989pub enum ChecklistActionResultProvider {
990    TermConsole,
991    LocalWebConsole,
992    RemoteWebConsole,
993}
994
995#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
996#[serde(tag = "status", content = "data")]
997pub enum ActionItemStatus {
998    Blocked,
999    Todo,
1000    Success(Option<String>),
1001    InProgress(String),
1002    Error(Diagnostic),
1003    Warning(Diagnostic),
1004}
1005
1006#[derive(Debug, Clone, Serialize)]
1007#[serde(rename_all = "camelCase")]
1008pub struct UpdateConstructData {
1009    pub construct_did: ConstructDid,
1010    pub action_item_update: ActionItemRequestUpdate,
1011    pub internal_key: String,
1012}
1013
1014#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1015#[serde(rename_all = "camelCase")]
1016pub struct OpenModalData {
1017    pub modal_uuid: Uuid,
1018    pub title: String,
1019}
1020
1021#[derive(Clone, Debug)]
1022pub enum ActionType {
1023    UpdateActionItemRequest(ActionItemRequestUpdate),
1024    AppendSubGroup(ActionSubGroup),
1025    AppendGroup(ActionGroup),
1026    AppendItem(ActionItemRequest, Option<String>, Option<String>),
1027    NewBlock(ActionPanelData),
1028    NewModal(Block),
1029}
1030
1031#[derive(Clone, Debug)]
1032pub struct Actions {
1033    pub store: Vec<ActionType>,
1034}
1035
1036impl Actions {
1037    pub fn none() -> Actions {
1038        Actions { store: vec![] }
1039    }
1040
1041    pub fn has_pending_actions(&self) -> bool {
1042        for item in self.store.iter() {
1043            match item {
1044                ActionType::AppendSubGroup(_)
1045                | ActionType::AppendGroup(_)
1046                | ActionType::AppendItem(_, _, _) => return true,
1047                ActionType::NewBlock(_) => return true,
1048                ActionType::NewModal(_) => return true,
1049                ActionType::UpdateActionItemRequest(data) => {
1050                    match data.action_status.clone().unwrap() {
1051                        ActionItemStatus::Success(_) => continue,
1052                        _ => return true,
1053                    }
1054                }
1055            }
1056        }
1057        false
1058    }
1059
1060    pub fn append(&mut self, actions: &mut Actions) {
1061        self.store.append(&mut actions.store);
1062    }
1063
1064    pub fn push_modal(&mut self, block: Block) {
1065        self.store.push(ActionType::NewModal(block));
1066    }
1067
1068    pub fn push_group(&mut self, title: &str, action_items: Vec<ActionItemRequest>) {
1069        self.store.push(ActionType::AppendGroup(ActionGroup {
1070            sub_groups: vec![ActionSubGroup {
1071                title: None,
1072                action_items,
1073                allow_batch_completion: false,
1074            }],
1075            title: title.to_string(),
1076        }));
1077    }
1078
1079    pub fn push_sub_group(&mut self, title: Option<String>, action_items: Vec<ActionItemRequest>) {
1080        if !action_items.is_empty() {
1081            self.store.push(ActionType::AppendSubGroup(ActionSubGroup {
1082                title,
1083                action_items,
1084                allow_batch_completion: false,
1085            }));
1086        }
1087    }
1088    pub fn push_action_item_update(&mut self, update: ActionItemRequestUpdate) {
1089        self.store.push(ActionType::UpdateActionItemRequest(update))
1090    }
1091
1092    pub fn push_panel(&mut self, title: &str, description: &str) {
1093        self.store.push(ActionType::NewBlock(ActionPanelData {
1094            title: title.to_string(),
1095            description: description.to_string(), //todo, make optional
1096            groups: vec![],
1097        }))
1098    }
1099
1100    pub fn push_begin_flow_panel(
1101        &mut self,
1102        flow_index: usize,
1103        total_flows_count: usize,
1104        flow_name: &str,
1105        flow_description: &Option<String>,
1106    ) {
1107        self.store.push(ActionType::NewBlock(ActionPanelData {
1108            title: "Flow Execution".to_string(),
1109            description: "".to_string(),
1110            groups: vec![ActionGroup {
1111                title: "".to_string(),
1112                sub_groups: vec![ActionSubGroup {
1113                    title: None,
1114                    action_items: vec![ActionItemRequestType::BeginFlow(FlowBlockData {
1115                        index: flow_index,
1116                        total_flows: total_flows_count,
1117                        name: flow_name.to_string(),
1118                        description: flow_description.clone(),
1119                    })
1120                    .to_request("", ACTION_ITEM_BEGIN_FLOW)
1121                    .with_status(ActionItemStatus::Success(None))],
1122                    allow_batch_completion: false,
1123                }],
1124            }],
1125        }))
1126    }
1127
1128    pub fn new_panel(title: &str, description: &str) -> Actions {
1129        let store = vec![ActionType::NewBlock(ActionPanelData {
1130            title: title.to_string(),
1131            description: description.to_string(), //todo, make optional
1132            groups: vec![],
1133        })];
1134        Actions { store }
1135    }
1136
1137    pub fn new_group_of_items(title: &str, action_items: Vec<ActionItemRequest>) -> Actions {
1138        let store = vec![ActionType::AppendGroup(ActionGroup {
1139            sub_groups: vec![ActionSubGroup {
1140                title: None,
1141                action_items,
1142                allow_batch_completion: false,
1143            }],
1144            title: title.to_string(),
1145        })];
1146        Actions { store }
1147    }
1148
1149    pub fn new_sub_group_of_items(
1150        title: Option<String>,
1151        action_items: Vec<ActionItemRequest>,
1152    ) -> Actions {
1153        let store = vec![ActionType::AppendSubGroup(ActionSubGroup {
1154            title,
1155            action_items,
1156            allow_batch_completion: false,
1157        })];
1158        Actions { store }
1159    }
1160
1161    pub fn append_item(
1162        item: ActionItemRequest,
1163        group_title: Option<&str>,
1164        panel_title: Option<&str>,
1165    ) -> Actions {
1166        let store = vec![ActionType::AppendItem(
1167            item,
1168            group_title.map(|t| t.to_string()),
1169            panel_title.map(|t| t.to_string()),
1170        )];
1171        Actions { store }
1172    }
1173
1174    pub fn get_new_action_item_requests(&self) -> Vec<&ActionItemRequest> {
1175        let mut new_action_item_requests = vec![];
1176        for item in self.store.iter() {
1177            match item {
1178                ActionType::AppendSubGroup(data) => {
1179                    for item in data.action_items.iter() {
1180                        new_action_item_requests.push(item);
1181                    }
1182                }
1183                ActionType::AppendGroup(data) => {
1184                    for subgroup in data.sub_groups.iter() {
1185                        for item in subgroup.action_items.iter() {
1186                            new_action_item_requests.push(item);
1187                        }
1188                    }
1189                }
1190                ActionType::AppendItem(item, _, _) => {
1191                    new_action_item_requests.push(item);
1192                }
1193                ActionType::NewBlock(data) => {
1194                    for group in data.groups.iter() {
1195                        for subgroup in group.sub_groups.iter() {
1196                            for item in subgroup.action_items.iter() {
1197                                new_action_item_requests.push(item);
1198                            }
1199                        }
1200                    }
1201                }
1202                ActionType::NewModal(data) => {
1203                    for group in data.panel.expect_modal_panel().groups.iter() {
1204                        for subgroup in group.sub_groups.iter() {
1205                            for item in subgroup.action_items.iter() {
1206                                new_action_item_requests.push(item);
1207                            }
1208                        }
1209                    }
1210                }
1211                ActionType::UpdateActionItemRequest(_) => continue,
1212            }
1213        }
1214        new_action_item_requests
1215    }
1216
1217    pub fn compile_actions_to_block_events(
1218        &mut self,
1219        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
1220    ) -> Vec<BlockEvent> {
1221        let mut blocks = vec![];
1222        let mut current_panel_data =
1223            ActionPanelData { title: "".to_string(), description: "".to_string(), groups: vec![] };
1224        let mut index = 0;
1225        let mut current_modal: Option<Block> = None;
1226        let mut updates = vec![];
1227        for item in self.store.iter_mut() {
1228            match item {
1229                ActionType::AppendItem(item, group_title, panel_title) => {
1230                    item.index = index;
1231                    index += 1;
1232                    match current_modal {
1233                        None => {
1234                            if current_panel_data.groups.len() > 0 {
1235                                let Some(group) = current_panel_data.groups.last_mut() else {
1236                                    continue;
1237                                };
1238                                if group.sub_groups.len() > 0 {
1239                                    // if the last sub group has no action items, don't push a new group, just replace it
1240                                    let Some(sub_group) = group.sub_groups.last_mut() else {
1241                                        continue;
1242                                    };
1243                                    if sub_group.action_items.is_empty() {
1244                                        *sub_group = ActionSubGroup {
1245                                            title: None,
1246                                            action_items: vec![item.clone()],
1247                                            allow_batch_completion: true,
1248                                        };
1249                                        continue;
1250                                    }
1251                                }
1252                                group
1253                                    .sub_groups
1254                                    .last_mut()
1255                                    .unwrap()
1256                                    .action_items
1257                                    .push(item.clone());
1258                            } else {
1259                                current_panel_data.groups.push(ActionGroup {
1260                                    title: group_title.as_ref().unwrap_or(&"".into()).into(),
1261                                    sub_groups: vec![ActionSubGroup {
1262                                        title: None,
1263                                        action_items: vec![item.clone()],
1264                                        allow_batch_completion: true,
1265                                    }],
1266                                });
1267                            }
1268                            if let Some(panel_title) = panel_title {
1269                                current_panel_data.title = panel_title.to_string();
1270                            }
1271                        }
1272                        Some(ref mut modal) => {
1273                            if modal.panel.expect_modal_panel().groups.len() > 0 {
1274                                let Some(group) =
1275                                    modal.panel.expect_modal_panel_mut().groups.last_mut()
1276                                else {
1277                                    continue;
1278                                };
1279                                if group.sub_groups.len() > 0 {
1280                                    // if the last sub group has no action items, don't push a new group, just replace it
1281                                    let Some(sub_group) = group.sub_groups.last_mut() else {
1282                                        continue;
1283                                    };
1284                                    if sub_group.action_items.is_empty() {
1285                                        *sub_group = ActionSubGroup {
1286                                            title: None,
1287                                            action_items: vec![item.clone()],
1288                                            allow_batch_completion: true,
1289                                        };
1290                                        continue;
1291                                    }
1292                                }
1293                                group.sub_groups.push(ActionSubGroup {
1294                                    title: None,
1295                                    action_items: vec![item.clone()],
1296                                    allow_batch_completion: true,
1297                                });
1298                            } else {
1299                                modal.panel.expect_modal_panel_mut().groups.push(ActionGroup {
1300                                    title: group_title.as_ref().unwrap_or(&"".into()).into(),
1301                                    sub_groups: vec![ActionSubGroup {
1302                                        title: None,
1303                                        action_items: vec![item.clone()],
1304                                        allow_batch_completion: true,
1305                                    }],
1306                                });
1307                            }
1308                            if let ActionItemRequestType::ValidateModal = item.action_type {
1309                                blocks.push(BlockEvent::Modal(modal.clone()));
1310                                current_modal = None;
1311                            }
1312                        }
1313                    }
1314                }
1315                ActionType::AppendSubGroup(data) => {
1316                    for item in data.action_items.iter_mut() {
1317                        item.index = index;
1318                        index += 1;
1319                    }
1320                    match current_modal {
1321                        None => {
1322                            if current_panel_data.groups.len() > 0 {
1323                                let Some(group) = current_panel_data.groups.last_mut() else {
1324                                    continue;
1325                                };
1326                                if group.sub_groups.len() > 0 {
1327                                    // if the last sub group has no action items, don't push a new group, just replace it
1328                                    let Some(sub_group) = group.sub_groups.last_mut() else {
1329                                        continue;
1330                                    };
1331                                    if sub_group.action_items.is_empty() {
1332                                        *sub_group = data.clone();
1333                                        continue;
1334                                    }
1335                                }
1336                                group.sub_groups.push(data.clone());
1337                            } else {
1338                                current_panel_data.groups.push(ActionGroup {
1339                                    title: "".to_string(),
1340                                    sub_groups: vec![data.clone()],
1341                                });
1342                            }
1343                        }
1344                        Some(ref mut modal) => {
1345                            if modal.panel.expect_modal_panel().groups.len() > 0 {
1346                                let Some(group) =
1347                                    modal.panel.expect_modal_panel_mut().groups.last_mut()
1348                                else {
1349                                    continue;
1350                                };
1351                                if group.sub_groups.len() > 0 {
1352                                    // if the last sub group has no action items, don't push a new group, just replace it
1353                                    let Some(sub_group) = group.sub_groups.last_mut() else {
1354                                        continue;
1355                                    };
1356                                    if sub_group.action_items.is_empty() {
1357                                        *sub_group = data.clone();
1358                                        continue;
1359                                    }
1360                                }
1361                                group.sub_groups.push(data.clone());
1362                            } else {
1363                                modal.panel.expect_modal_panel_mut().groups.push(ActionGroup {
1364                                    title: "".to_string(),
1365                                    sub_groups: vec![data.clone()],
1366                                });
1367                            }
1368                            if data.contains_validate_modal_item() {
1369                                blocks.push(BlockEvent::Modal(modal.clone()));
1370                                current_modal = None;
1371                            }
1372                        }
1373                    }
1374                }
1375                ActionType::AppendGroup(data) => {
1376                    for subgroup in data.sub_groups.iter_mut() {
1377                        for item in subgroup.action_items.iter_mut() {
1378                            item.index = index;
1379                            index += 1;
1380                        }
1381                    }
1382                    match current_modal {
1383                        None => {
1384                            current_panel_data.groups.push(data.clone());
1385                        }
1386                        Some(ref mut modal) => {
1387                            modal.panel.expect_modal_panel_mut().groups.push(data.clone());
1388                            if data.contains_validate_modal_item() {
1389                                blocks.push(BlockEvent::Modal(modal.clone()));
1390                                current_modal = None;
1391                            }
1392                        }
1393                    }
1394                }
1395                ActionType::NewBlock(data) => {
1396                    if current_panel_data.groups.len() >= 1 {
1397                        blocks.push(BlockEvent::Action(Block {
1398                            uuid: Uuid::new_v4(),
1399                            panel: Panel::ActionPanel(current_panel_data.clone()),
1400                            visible: true,
1401                        }));
1402                    }
1403                    current_panel_data = data.clone();
1404                }
1405                ActionType::NewModal(data) => {
1406                    current_modal = Some(data.clone());
1407                }
1408                ActionType::UpdateActionItemRequest(data) => {
1409                    if let Some(update) = data.normalize(&action_item_requests) {
1410                        updates.push(update);
1411                    }
1412                }
1413            }
1414        }
1415        if !updates.is_empty() {
1416            blocks.push(BlockEvent::UpdateActionItems(updates));
1417        }
1418        if current_panel_data.groups.len() > 0 {
1419            blocks.push(BlockEvent::Action(Block {
1420                uuid: Uuid::new_v4(),
1421                panel: Panel::ActionPanel(current_panel_data.clone()),
1422                visible: true,
1423            }));
1424        }
1425        blocks
1426    }
1427
1428    pub fn compile_actions_to_item_updates(
1429        &self,
1430        action_item_requests: &BTreeMap<BlockId, ActionItemRequest>,
1431    ) -> Vec<ActionItemRequestUpdate> {
1432        let mut updates = vec![];
1433
1434        for item in self.store.iter() {
1435            match item {
1436                ActionType::AppendSubGroup(sub_group) => {
1437                    let mut sub_group_updates =
1438                        sub_group.compile_actions_to_item_updates(&action_item_requests);
1439                    updates.append(&mut sub_group_updates);
1440                }
1441                ActionType::AppendGroup(group) => {
1442                    let mut group_updates =
1443                        group.compile_actions_to_item_updates(&action_item_requests);
1444                    updates.append(&mut group_updates);
1445                }
1446                ActionType::AppendItem(new_item, _, _) => {
1447                    if let Some(existing_item) = action_item_requests.get(&new_item.id) {
1448                        if let Some(update) =
1449                            ActionItemRequestUpdate::from_diff(new_item, existing_item)
1450                        {
1451                            updates.push(update);
1452                        };
1453                    };
1454                }
1455                ActionType::NewBlock(action_panel_data) => {
1456                    let mut block_updates =
1457                        action_panel_data.compile_actions_to_item_updates(&action_item_requests);
1458                    updates.append(&mut block_updates);
1459                }
1460                ActionType::NewModal(modal) => match &modal.panel {
1461                    Panel::ActionPanel(action_panel_data) => {
1462                        let mut block_updates = action_panel_data
1463                            .compile_actions_to_item_updates(&action_item_requests);
1464                        updates.append(&mut block_updates);
1465                    }
1466                    Panel::ModalPanel(modal_panel_data) => {
1467                        let mut block_updates =
1468                            modal_panel_data.compile_actions_to_item_updates(&action_item_requests);
1469                        updates.append(&mut block_updates);
1470                    }
1471                    _ => {}
1472                },
1473                ActionType::UpdateActionItemRequest(update) => updates.push(update.clone()),
1474            }
1475        }
1476
1477        updates
1478    }
1479
1480    pub fn filter_existing_action_items(
1481        &mut self,
1482        existing_requests: &Option<&Vec<&mut ActionItemRequest>>,
1483    ) -> &mut Self {
1484        let Some(existing_requests) = existing_requests else {
1485            return self;
1486        };
1487
1488        let mut idx_to_remove = vec![];
1489        for (i, item) in self.store.iter_mut().enumerate() {
1490            match item {
1491                ActionType::UpdateActionItemRequest(_) => {}
1492                ActionType::AppendSubGroup(sub_group) => {
1493                    sub_group.filter_existing_action_items(&existing_requests);
1494                    if sub_group.action_items.is_empty() {
1495                        idx_to_remove.push(i);
1496                    }
1497                }
1498                ActionType::AppendGroup(group) => {
1499                    group.filter_existing_action_items(&existing_requests);
1500                    if group.sub_groups.is_empty() {
1501                        idx_to_remove.push(i);
1502                    }
1503                }
1504                ActionType::AppendItem(new_item, _, _) => {
1505                    for existing_item in existing_requests.iter() {
1506                        if existing_item.id.eq(&new_item.id) {
1507                            if let None =
1508                                ActionItemRequestUpdate::from_diff(new_item, existing_item)
1509                            {
1510                                idx_to_remove.push(i);
1511                            };
1512                        }
1513                    }
1514                }
1515                ActionType::NewBlock(block) => {
1516                    block.filter_existing_action_items(&existing_requests);
1517                    if block.groups.is_empty() {
1518                        idx_to_remove.push(i);
1519                    }
1520                }
1521                ActionType::NewModal(_) => {}
1522            }
1523        }
1524        idx_to_remove.iter().rev().for_each(|i| {
1525            self.store.remove(*i);
1526        });
1527
1528        self
1529    }
1530}
1531
1532#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1533#[serde(tag = "type", content = "data")]
1534pub enum ActionItemRequestType {
1535    ReviewInput(ReviewInputRequest),
1536    ProvideInput(ProvideInputRequest),
1537    PickInputOption(PickInputOptionRequest),
1538    ProvidePublicKey(ProvidePublicKeyRequest),
1539    ProvideSignedTransaction(ProvideSignedTransactionRequest),
1540    VerifyThirdPartySignature(VerifyThirdPartySignatureRequest),
1541    ProvideSignedMessage(ProvideSignedMessageRequest),
1542    SendTransaction(SendTransactionRequest),
1543    DisplayOutput(DisplayOutputRequest),
1544    DisplayErrorLog(DisplayErrorLogRequest),
1545    OpenModal(OpenModalData),
1546    ValidateBlock(ValidateBlockData),
1547    ValidateModal,
1548    BeginFlow(FlowBlockData),
1549}
1550
1551impl ActionItemRequestType {
1552    pub fn to_request(
1553        self,
1554        construct_instance_name: &str,
1555        internal_key: &str,
1556    ) -> ActionItemRequest {
1557        ActionItemRequest::new(construct_instance_name, internal_key, self)
1558    }
1559    pub fn as_review_input(&self) -> Option<&ReviewInputRequest> {
1560        match &self {
1561            ActionItemRequestType::ReviewInput(value) => Some(value),
1562            _ => None,
1563        }
1564    }
1565    pub fn as_provide_input(&self) -> Option<&ProvideInputRequest> {
1566        match &self {
1567            ActionItemRequestType::ProvideInput(value) => Some(value),
1568            _ => None,
1569        }
1570    }
1571    pub fn as_pick_input(&self) -> Option<&PickInputOptionRequest> {
1572        match &self {
1573            ActionItemRequestType::PickInputOption(value) => Some(value),
1574            _ => None,
1575        }
1576    }
1577    pub fn as_provide_public_key(&self) -> Option<&ProvidePublicKeyRequest> {
1578        match &self {
1579            ActionItemRequestType::ProvidePublicKey(value) => Some(value),
1580            _ => None,
1581        }
1582    }
1583    pub fn as_provide_signed_tx(&self) -> Option<&ProvideSignedTransactionRequest> {
1584        match &self {
1585            ActionItemRequestType::ProvideSignedTransaction(value) => Some(value),
1586            _ => None,
1587        }
1588    }
1589    pub fn as_verify_third_party_signature(&self) -> Option<&VerifyThirdPartySignatureRequest> {
1590        match &self {
1591            ActionItemRequestType::VerifyThirdPartySignature(value) => Some(value),
1592            _ => None,
1593        }
1594    }
1595    pub fn as_sign_tx(&self) -> Option<&SendTransactionRequest> {
1596        match &self {
1597            ActionItemRequestType::SendTransaction(value) => Some(value),
1598            _ => None,
1599        }
1600    }
1601    pub fn as_provide_signed_msg(&self) -> Option<&ProvideSignedMessageRequest> {
1602        match &self {
1603            ActionItemRequestType::ProvideSignedMessage(value) => Some(value),
1604            _ => None,
1605        }
1606    }
1607    pub fn as_display_output(&self) -> Option<&DisplayOutputRequest> {
1608        match &self {
1609            ActionItemRequestType::DisplayOutput(value) => Some(value),
1610            _ => None,
1611        }
1612    }
1613    pub fn as_display_err(&self) -> Option<&DisplayErrorLogRequest> {
1614        match &self {
1615            ActionItemRequestType::DisplayErrorLog(value) => Some(value),
1616            _ => None,
1617        }
1618    }
1619    pub fn as_open_modal(&self) -> Option<&OpenModalData> {
1620        match &self {
1621            ActionItemRequestType::OpenModal(value) => Some(value),
1622            _ => None,
1623        }
1624    }
1625
1626    ///
1627    /// Serialize the immutable properties of the type to be used for an `ActionItemRequest`'s `BlockId`.
1628    ///
1629    pub fn get_block_id_string(&self) -> String {
1630        match self {
1631            ActionItemRequestType::ReviewInput(val) => {
1632                format!("ReviewInput({}-{})", val.input_name, val.force_execution)
1633            }
1634            ActionItemRequestType::ProvideInput(val) => format!(
1635                "ProvideInput({}-{})",
1636                val.input_name,
1637                serde_json::to_string(&val.typing).unwrap() //todo: make to_string prop?
1638            ),
1639            ActionItemRequestType::PickInputOption(_) => format!("PickInputOption"),
1640            ActionItemRequestType::ProvidePublicKey(val) => format!(
1641                "ProvidePublicKey({}-{}-{})",
1642                val.check_expectation_action_uuid
1643                    .as_ref()
1644                    .and_then(|u| Some(u.to_string()))
1645                    .unwrap_or("None".to_string()),
1646                val.namespace,
1647                val.network_id
1648            ),
1649            ActionItemRequestType::ProvideSignedTransaction(val) => {
1650                format!(
1651                    "ProvideSignedTransaction({}-{}-{}-{})",
1652                    val.check_expectation_action_uuid
1653                        .as_ref()
1654                        .and_then(|u| Some(u.to_string()))
1655                        .unwrap_or("None".to_string()),
1656                    val.signer_uuid.to_string(),
1657                    val.namespace,
1658                    val.network_id
1659                )
1660            }
1661            ActionItemRequestType::VerifyThirdPartySignature(val) => {
1662                format!(
1663                    "VerifyThirdPartySignature({}-{}-{})",
1664                    val.check_expectation_action_uuid
1665                        .as_ref()
1666                        .and_then(|u| Some(u.to_string()))
1667                        .unwrap_or("None".to_string()),
1668                    val.namespace,
1669                    val.network_id
1670                )
1671            }
1672            ActionItemRequestType::SendTransaction(val) => {
1673                format!(
1674                    "SendTransaction({}-{}-{}-{})",
1675                    val.check_expectation_action_uuid
1676                        .as_ref()
1677                        .and_then(|u| Some(u.to_string()))
1678                        .unwrap_or("None".to_string()),
1679                    val.signer_uuid.to_string(),
1680                    val.namespace,
1681                    val.network_id
1682                )
1683            }
1684            ActionItemRequestType::ProvideSignedMessage(val) => format!(
1685                "ProvideSignedMessage({}-{}-{}-{})",
1686                val.check_expectation_action_uuid
1687                    .as_ref()
1688                    .and_then(|u| Some(u.to_string()))
1689                    .unwrap_or("None".to_string()),
1690                val.signer_uuid.to_string(),
1691                val.namespace,
1692                val.network_id
1693            ),
1694            ActionItemRequestType::DisplayOutput(val) => format!(
1695                "DisplayOutput({}-{}-{})",
1696                val.name,
1697                val.description.clone().unwrap_or("None".to_string()),
1698                val.value.to_string()
1699            ),
1700            ActionItemRequestType::DisplayErrorLog(val) => {
1701                format!("DisplayErrorLog({})", val.diagnostic.to_string())
1702            }
1703            ActionItemRequestType::OpenModal(val) => {
1704                format!("OpenModal({}-{})", val.modal_uuid, val.title)
1705            }
1706            ActionItemRequestType::ValidateBlock(val) => {
1707                format!("ValidateBlock({})", val.internal_idx.to_string())
1708            }
1709            ActionItemRequestType::ValidateModal => format!("ValidateModal"),
1710            ActionItemRequestType::BeginFlow(val) => {
1711                format!("BeginFlow({}-{})", val.index, val.name)
1712            }
1713        }
1714    }
1715
1716    ///
1717    /// Compares all properties of `new_type` against `existing_type` to determine if any of the mutable properties
1718    /// of the type have been updated. Returns `Some(new_type)` if only mutable properties were updated, returns `None`
1719    /// otherwise.
1720    ///
1721    pub fn diff_mutable_properties(
1722        new_type: &ActionItemRequestType,
1723        existing_item: &ActionItemRequestType,
1724    ) -> Option<ActionItemRequestType> {
1725        match new_type {
1726            ActionItemRequestType::ReviewInput(new) => {
1727                let Some(existing) = existing_item.as_review_input() else {
1728                    unreachable!("cannot change action item request type")
1729                };
1730                if new.value != existing.value {
1731                    if new.input_name != existing.input_name {
1732                        unreachable!("cannot change review input request input_name")
1733                    }
1734                    if new.force_execution != existing.force_execution {
1735                        unreachable!("cannot change review input request force_execution")
1736                    }
1737                    Some(new_type.clone())
1738                } else {
1739                    None
1740                }
1741            }
1742            ActionItemRequestType::ProvideInput(new) => {
1743                let Some(existing) = existing_item.as_provide_input() else {
1744                    unreachable!("cannot change action item request type")
1745                };
1746                if new.default_value != existing.default_value {
1747                    if new.input_name != existing.input_name {
1748                        unreachable!("cannot change provide input request input_name")
1749                    }
1750                    if new.typing != existing.typing {
1751                        unreachable!("cannot change provide input request typing")
1752                    }
1753                    Some(new_type.clone())
1754                } else {
1755                    None
1756                }
1757            }
1758            ActionItemRequestType::PickInputOption(_) => {
1759                let Some(_) = existing_item.as_pick_input() else {
1760                    unreachable!("cannot change action item request type")
1761                };
1762                Some(new_type.clone())
1763            }
1764            ActionItemRequestType::ProvidePublicKey(new) => {
1765                let Some(existing) = existing_item.as_provide_public_key() else {
1766                    unreachable!("cannot change action item request type")
1767                };
1768                if new.message != existing.message {
1769                    if new.check_expectation_action_uuid != existing.check_expectation_action_uuid {
1770                        unreachable!("cannot change provide public key request check_expectation_action_uuid");
1771                    }
1772                    if new.namespace != existing.namespace {
1773                        unreachable!("cannot change provide public key request namespace");
1774                    }
1775                    if new.network_id != existing.network_id {
1776                        unreachable!("cannot change provide public key request network_id");
1777                    }
1778                    Some(new_type.clone())
1779                } else {
1780                    None
1781                }
1782            }
1783            ActionItemRequestType::ProvideSignedTransaction(new) => {
1784                let Some(existing) = existing_item.as_provide_signed_tx() else {
1785                    unreachable!("cannot change action item request type")
1786                };
1787                if new.payload != existing.payload || new.skippable != existing.skippable {
1788                    if new.check_expectation_action_uuid != existing.check_expectation_action_uuid {
1789                        unreachable!(
1790                            "cannot change provide signed tx request check_expectation_action_uuid"
1791                        );
1792                    }
1793                    if new.signer_uuid != existing.signer_uuid {
1794                        unreachable!("cannot change provide signed tx request signer_uuid");
1795                    }
1796                    if new.namespace != existing.namespace {
1797                        unreachable!("cannot change provide signed tx request namespace");
1798                    }
1799                    if new.network_id != existing.network_id {
1800                        unreachable!("cannot change provide signed tx request network_id");
1801                    }
1802                    if new.only_approval_needed != existing.only_approval_needed {
1803                        unreachable!(
1804                            "cannot change provide signed tx request only_approval_needed"
1805                        );
1806                    }
1807                    Some(new_type.clone())
1808                } else {
1809                    None
1810                }
1811            }
1812            ActionItemRequestType::VerifyThirdPartySignature(new) => {
1813                let Some(existing) = existing_item.as_verify_third_party_signature() else {
1814                    unreachable!("cannot change action item request type")
1815                };
1816                if new.url != existing.url || new.payload != existing.payload {
1817                    if new.check_expectation_action_uuid != existing.check_expectation_action_uuid {
1818                        unreachable!(
1819                            "cannot change verify third party signature request check_expectation_action_uuid"
1820                        );
1821                    }
1822                    if new.signer_uuid != existing.signer_uuid {
1823                        unreachable!(
1824                            "cannot change verify third party signature request signer_uuid"
1825                        );
1826                    }
1827                    if new.namespace != existing.namespace {
1828                        unreachable!(
1829                            "cannot change verify third party signature request namespace"
1830                        );
1831                    }
1832                    if new.network_id != existing.network_id {
1833                        unreachable!(
1834                            "cannot change verify third party signature request network_id"
1835                        );
1836                    }
1837                    Some(new_type.clone())
1838                } else {
1839                    None
1840                }
1841            }
1842            ActionItemRequestType::SendTransaction(new) => {
1843                let Some(existing) = existing_item.as_sign_tx() else {
1844                    unreachable!("cannot change action item request type")
1845                };
1846                if new.payload != existing.payload {
1847                    if new.check_expectation_action_uuid != existing.check_expectation_action_uuid {
1848                        unreachable!(
1849                            "cannot change provide signed tx request check_expectation_action_uuid"
1850                        );
1851                    }
1852                    if new.signer_uuid != existing.signer_uuid {
1853                        unreachable!("cannot change provide signed tx request signer_uuid");
1854                    }
1855                    if new.namespace != existing.namespace {
1856                        unreachable!("cannot change provide signed tx request namespace");
1857                    }
1858                    if new.network_id != existing.network_id {
1859                        unreachable!("cannot change provide signed tx request network_id");
1860                    }
1861                    Some(new_type.clone())
1862                } else {
1863                    None
1864                }
1865            }
1866            ActionItemRequestType::ProvideSignedMessage(new) => {
1867                let Some(existing) = existing_item.as_provide_signed_msg() else {
1868                    unreachable!("cannot change action item request type")
1869                };
1870                if new.message != existing.message {
1871                    if new.check_expectation_action_uuid != existing.check_expectation_action_uuid {
1872                        unreachable!(
1873                            "cannot change provide signed msg request check_expectation_action_uuid"
1874                        );
1875                    }
1876                    if new.signer_uuid != existing.signer_uuid {
1877                        unreachable!("cannot change provide signed msg request signer_uuid");
1878                    }
1879                    if new.namespace != existing.namespace {
1880                        unreachable!("cannot change provide signed msg request namespace");
1881                    }
1882                    if new.network_id != existing.network_id {
1883                        unreachable!("cannot change provide signed msg request network_id");
1884                    }
1885                    Some(new_type.clone())
1886                } else {
1887                    None
1888                }
1889            }
1890            ActionItemRequestType::DisplayOutput(_) => None,
1891            ActionItemRequestType::DisplayErrorLog(_) => None,
1892            ActionItemRequestType::OpenModal(_) => None,
1893            ActionItemRequestType::ValidateBlock(_) => None,
1894            ActionItemRequestType::ValidateModal => None,
1895            ActionItemRequestType::BeginFlow(_) => None,
1896        }
1897    }
1898}
1899
1900#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1901#[serde(rename_all = "camelCase")]
1902pub struct ReviewInputRequest {
1903    pub input_name: String,
1904    pub value: Value,
1905    pub force_execution: bool,
1906}
1907
1908impl ReviewInputRequest {
1909    pub fn new(input_name: &str, value: &Value) -> Self {
1910        ReviewInputRequest {
1911            input_name: input_name.to_string(),
1912            value: value.clone(),
1913            force_execution: false,
1914        }
1915    }
1916    pub fn force_execution(&mut self) -> &mut Self {
1917        self.force_execution = true;
1918        self
1919    }
1920    pub fn to_action_type(&self) -> ActionItemRequestType {
1921        ActionItemRequestType::ReviewInput(self.clone())
1922    }
1923}
1924
1925#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1926#[serde(rename_all = "camelCase")]
1927pub struct ProvideInputRequest {
1928    pub default_value: Option<Value>,
1929    pub input_name: String,
1930    pub typing: Type,
1931}
1932
1933#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1934#[serde(rename_all = "camelCase")]
1935pub struct DisplayOutputRequest {
1936    pub name: String,
1937    pub description: Option<String>,
1938    pub value: Value,
1939}
1940
1941#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1942#[serde(rename_all = "camelCase")]
1943pub struct DisplayErrorLogRequest {
1944    pub diagnostic: Diagnostic,
1945}
1946
1947#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1948#[serde(rename_all = "camelCase")]
1949pub struct ValidateBlockData {
1950    /// internal index used to differential one validate block instance from another
1951    internal_idx: usize,
1952}
1953impl ValidateBlockData {
1954    pub fn new(internal_idx: usize) -> Self {
1955        ValidateBlockData { internal_idx }
1956    }
1957}
1958
1959#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1960#[serde(rename_all = "camelCase")]
1961pub struct FlowBlockData {
1962    index: usize,
1963    total_flows: usize,
1964    name: String,
1965    description: Option<String>,
1966}
1967
1968#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1969#[serde(rename_all = "camelCase")]
1970pub struct PickInputOptionRequest {
1971    pub options: Vec<InputOption>,
1972    pub selected: InputOption,
1973}
1974#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1975#[serde(rename_all = "camelCase")]
1976pub struct InputOption {
1977    pub value: String,
1978    pub displayed_value: String,
1979}
1980
1981impl InputOption {
1982    pub fn default() -> Self {
1983        InputOption { value: String::new(), displayed_value: String::new() }
1984    }
1985}
1986
1987#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1988#[serde(rename_all = "camelCase")]
1989pub struct ProvidePublicKeyRequest {
1990    pub check_expectation_action_uuid: Option<ConstructDid>,
1991    pub message: String,
1992    pub namespace: String,
1993    pub network_id: String,
1994}
1995
1996#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
1997#[serde(rename_all = "camelCase")]
1998pub struct ProvideSignedTransactionRequest {
1999    pub check_expectation_action_uuid: Option<ConstructDid>,
2000    pub signer_uuid: ConstructDid,
2001    pub expected_signer_address: Option<String>,
2002    pub skippable: bool,
2003    pub only_approval_needed: bool,
2004    pub payload: Value,
2005    pub formatted_payload: Option<Value>,
2006    pub namespace: String,
2007    pub network_id: String,
2008}
2009
2010impl ProvideSignedTransactionRequest {
2011    pub fn new(signer_uuid: &Did, payload: &Value, namespace: &str, network_id: &str) -> Self {
2012        ProvideSignedTransactionRequest {
2013            signer_uuid: ConstructDid(signer_uuid.clone()),
2014            check_expectation_action_uuid: None,
2015            expected_signer_address: None,
2016            skippable: false,
2017            payload: payload.clone(),
2018            formatted_payload: None,
2019            namespace: namespace.to_string(),
2020            network_id: network_id.to_string(),
2021            only_approval_needed: false,
2022        }
2023    }
2024
2025    pub fn skippable(&mut self, is_skippable: bool) -> &mut Self {
2026        self.skippable = is_skippable;
2027        self
2028    }
2029
2030    pub fn only_approval_needed(&mut self) -> &mut Self {
2031        self.only_approval_needed = true;
2032        self
2033    }
2034
2035    pub fn check_expectation_action_uuid(&mut self, uuid: &ConstructDid) -> &mut Self {
2036        self.check_expectation_action_uuid = Some(uuid.clone());
2037        self
2038    }
2039
2040    pub fn expected_signer_address(&mut self, address: Option<&str>) -> &mut Self {
2041        self.expected_signer_address = address.and_then(|a| Some(a.to_string()));
2042        self
2043    }
2044
2045    pub fn formatted_payload(&mut self, display_payload: Option<&Value>) -> &mut Self {
2046        self.formatted_payload = display_payload.cloned();
2047        self
2048    }
2049
2050    pub fn to_action_type(&self) -> ActionItemRequestType {
2051        ActionItemRequestType::ProvideSignedTransaction(self.clone())
2052    }
2053}
2054
2055#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2056#[serde(rename_all = "camelCase")]
2057pub struct VerifyThirdPartySignatureRequest {
2058    pub check_expectation_action_uuid: Option<ConstructDid>,
2059    pub signer_uuid: ConstructDid,
2060    pub namespace: String,
2061    pub network_id: String,
2062    pub signer_name: String,
2063    pub third_party_name: String,
2064    pub url: String,
2065    pub payload: Value,
2066    pub formatted_payload: Option<Value>,
2067}
2068
2069impl VerifyThirdPartySignatureRequest {
2070    pub fn new(
2071        signer_uuid: &Did,
2072        url: &str,
2073        signer_name: &str,
2074        third_party_name: &str,
2075        payload: &Value,
2076        namespace: &str,
2077        network_id: &str,
2078    ) -> Self {
2079        VerifyThirdPartySignatureRequest {
2080            signer_uuid: ConstructDid(signer_uuid.clone()),
2081            check_expectation_action_uuid: None,
2082            signer_name: signer_name.to_string(),
2083            third_party_name: third_party_name.to_string(),
2084            url: url.to_string(),
2085            namespace: namespace.to_string(),
2086            network_id: network_id.to_string(),
2087            payload: payload.clone(),
2088            formatted_payload: None,
2089        }
2090    }
2091
2092    pub fn check_expectation_action_uuid(&mut self, uuid: &ConstructDid) -> &mut Self {
2093        self.check_expectation_action_uuid = Some(uuid.clone());
2094        self
2095    }
2096
2097    pub fn formatted_payload(&mut self, display_payload: Option<&Value>) -> &mut Self {
2098        self.formatted_payload = display_payload.cloned();
2099        self
2100    }
2101
2102    pub fn to_action_type(&self) -> ActionItemRequestType {
2103        ActionItemRequestType::VerifyThirdPartySignature(self.clone())
2104    }
2105}
2106
2107#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2108#[serde(rename_all = "camelCase")]
2109pub struct SendTransactionRequest {
2110    pub check_expectation_action_uuid: Option<ConstructDid>,
2111    pub signer_uuid: ConstructDid,
2112    pub expected_signer_address: Option<String>,
2113    pub payload: Value,
2114    pub formatted_payload: Option<Value>,
2115    pub namespace: String,
2116    pub network_id: String,
2117}
2118
2119impl SendTransactionRequest {
2120    pub fn new(signer_uuid: &Did, payload: &Value, namespace: &str, network_id: &str) -> Self {
2121        SendTransactionRequest {
2122            signer_uuid: ConstructDid(signer_uuid.clone()),
2123            check_expectation_action_uuid: None,
2124            expected_signer_address: None,
2125            payload: payload.clone(),
2126            formatted_payload: None,
2127            namespace: namespace.to_string(),
2128            network_id: network_id.to_string(),
2129        }
2130    }
2131
2132    pub fn check_expectation_action_uuid(&mut self, uuid: &ConstructDid) -> &mut Self {
2133        self.check_expectation_action_uuid = Some(uuid.clone());
2134        self
2135    }
2136
2137    pub fn expected_signer_address(&mut self, address: Option<&str>) -> &mut Self {
2138        self.expected_signer_address = address.and_then(|a| Some(a.to_string()));
2139        self
2140    }
2141
2142    pub fn formatted_payload(&mut self, display_payload: Option<&Value>) -> &mut Self {
2143        self.formatted_payload = display_payload.cloned();
2144        self
2145    }
2146
2147    pub fn to_action_type(&self) -> ActionItemRequestType {
2148        ActionItemRequestType::SendTransaction(self.clone())
2149    }
2150}
2151
2152#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
2153#[serde(rename_all = "camelCase")]
2154pub struct ProvideSignedMessageRequest {
2155    pub check_expectation_action_uuid: Option<ConstructDid>,
2156    pub signer_uuid: ConstructDid,
2157    pub message: Value,
2158    pub namespace: String,
2159    pub network_id: String,
2160}
2161
2162#[derive(Debug, Clone, Serialize, Deserialize)]
2163#[serde(rename_all = "camelCase")]
2164pub struct ActionItemResponse {
2165    pub action_item_id: BlockId,
2166    #[serde(flatten)]
2167    pub payload: ActionItemResponseType,
2168}
2169
2170#[derive(Debug, Clone, Serialize, Deserialize)]
2171#[serde(tag = "type", content = "data")]
2172pub enum ActionItemResponseType {
2173    ReviewInput(ReviewedInputResponse),
2174    ProvideInput(ProvidedInputResponse),
2175    PickInputOption(String),
2176    ProvidePublicKey(ProvidePublicKeyResponse),
2177    ProvideSignedMessage(ProvideSignedMessageResponse),
2178    ProvideSignedTransaction(ProvideSignedTransactionResponse),
2179    VerifyThirdPartySignature(VerifyThirdPartySignatureResponse),
2180    SendTransaction(SendTransactionResponse),
2181    ValidateBlock,
2182    ValidateModal,
2183}
2184
2185impl ActionItemResponseType {
2186    pub fn is_validate_panel(&self) -> bool {
2187        match &self {
2188            ActionItemResponseType::ValidateBlock => true,
2189            _ => false,
2190        }
2191    }
2192}
2193
2194#[derive(Debug, Clone, Serialize, Deserialize)]
2195#[serde(rename_all = "camelCase")]
2196pub struct ReviewedInputResponse {
2197    pub input_name: String,
2198    pub value_checked: bool,
2199    pub force_execution: bool,
2200}
2201
2202#[derive(Debug, Clone, Serialize, Deserialize)]
2203#[serde(rename_all = "camelCase")]
2204pub struct ProvidedInputResponse {
2205    pub input_name: String,
2206    pub updated_value: Value,
2207}
2208
2209#[derive(Debug, Clone, Serialize, Deserialize)]
2210#[serde(rename_all = "camelCase")]
2211pub struct ProvidePublicKeyResponse {
2212    pub public_key: String,
2213}
2214
2215#[derive(Debug, Clone, Serialize, Deserialize)]
2216#[serde(rename_all = "camelCase")]
2217pub struct ProvideSignedMessageResponse {
2218    pub signed_message_bytes: String,
2219    pub signer_uuid: ConstructDid,
2220}
2221
2222#[derive(Debug, Clone, Serialize, Deserialize)]
2223#[serde(rename_all = "camelCase")]
2224pub struct ProvideSignedTransactionResponse {
2225    pub signed_transaction_bytes: Option<String>,
2226    pub signature_approved: Option<bool>,
2227    pub signer_uuid: ConstructDid,
2228}
2229#[derive(Debug, Clone, Serialize, Deserialize)]
2230#[serde(rename_all = "camelCase")]
2231pub struct VerifyThirdPartySignatureResponse {
2232    pub signer_uuid: ConstructDid,
2233    pub signature_complete: bool,
2234}
2235#[derive(Debug, Clone, Serialize, Deserialize)]
2236#[serde(rename_all = "camelCase")]
2237pub struct SendTransactionResponse {
2238    pub transaction_hash: String,
2239    pub signer_uuid: ConstructDid,
2240}
2241
2242#[derive(Debug, Clone, Serialize, Deserialize)]
2243#[serde(rename_all = "camelCase")]
2244pub struct DiscoveryResponse {
2245    pub needs_credentials: bool,
2246    pub client_type: ClientType,
2247}
2248
2249#[derive(Debug, Clone, Serialize, Deserialize)]
2250pub enum ClientType {
2251    Operator,
2252    Participant,
2253}
2254
2255#[derive(Clone, Debug, Serialize, Deserialize)]
2256#[serde(rename_all = "camelCase")]
2257pub struct SupervisorAddonData {
2258    pub addon_name: String,
2259    pub rpc_api_url: Option<String>,
2260}
2261
2262impl SupervisorAddonData {
2263    pub fn new(addon_name: &str, addon_defaults: &AddonDefaults) -> Self {
2264        let rpc_api_url = addon_defaults.store.get_string("rpc_api_url").map(|s| s.to_string());
2265        Self { addon_name: addon_name.to_string(), rpc_api_url }
2266    }
2267}