theater_cli/output/
formatters.rs

1use chrono::DateTime;
2use serde_json::Value;
3use theater::ChainEvent;
4
5use crate::error::CliResult;
6use crate::output::{OutputFormat, OutputManager};
7use crate::utils::event_display::display_single_event;
8
9/// Actor list formatter
10#[derive(Debug, serde::Serialize)]
11pub struct ActorList {
12    pub actors: Vec<(String, String)>,
13}
14
15impl OutputFormat for ActorList {
16    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
17        if self.actors.is_empty() {
18            output.info("No actors are currently running")?;
19        } else {
20            output.info(&format!("Running actors: {}", self.actors.len()))?;
21            for (id, name) in &self.actors {
22                println!(
23                    "  {} {}",
24                    output.theme().accent().apply_to(id),
25                    output.theme().muted().apply_to(name)
26                );
27            }
28        }
29        Ok(())
30    }
31
32    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
33        if self.actors.is_empty() {
34            output.info("No actors are currently running")?;
35        } else {
36            println!("{}", output.theme().highlight().apply_to("Running Actors"));
37            println!("{}", "─".repeat(40));
38
39            for (i, (id, name)) in self.actors.iter().enumerate() {
40                println!(
41                    "{}. {} {}",
42                    i + 1,
43                    output.theme().accent().apply_to(id),
44                    output.theme().muted().apply_to(&format!("({})", name))
45                );
46            }
47            println!();
48            output.info(&format!("Total: {} actors", self.actors.len()))?;
49        }
50        Ok(())
51    }
52
53    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
54        if self.actors.is_empty() {
55            output.info("No actors are currently running")?;
56            return Ok(());
57        }
58
59        let headers = vec!["ID", "Name"];
60        let rows: Vec<Vec<String>> = self
61            .actors
62            .iter()
63            .map(|(id, name)| vec![id.clone(), name.clone()])
64            .collect();
65
66        output.table(&headers, &rows)?;
67        Ok(())
68    }
69
70    fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
71        if self.actors.is_empty() {
72            output.info("No actors are currently running")?;
73            return Ok(());
74        }
75
76        println!(
77            "{}",
78            output.theme().highlight().apply_to("Detailed Actor List")
79        );
80        println!("{}", "─".repeat(40));
81
82        for (i, (id, name)) in self.actors.iter().enumerate() {
83            println!(
84                "{}. {} - {}",
85                i + 1,
86                output.theme().accent().apply_to(id),
87                output.theme().muted().apply_to(name)
88            );
89        }
90        Ok(())
91    }
92}
93
94/// Actor events formatter
95#[derive(Debug, serde::Serialize)]
96pub struct ActorEvents {
97    pub actor_id: String,
98    pub events: Vec<ChainEvent>,
99}
100
101impl OutputFormat for ActorEvents {
102    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
103        if self.events.is_empty() {
104            output.info(&format!("No events found for actor {}", self.actor_id))?;
105            return Ok(());
106        }
107
108        println!(
109            "{:<12} {:<12} {:<25} {}",
110            "HASH", "PARENT", "EVENT TYPE", "DESCRIPTION"
111        );
112        println!("{}", "─".repeat(100));
113
114        for event in &self.events {
115            display_single_event(event, "compact")?;
116        }
117        Ok(())
118    }
119
120    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
121        if self.events.is_empty() {
122            output.info(&format!("No events found for actor {}", self.actor_id))?;
123            return Ok(());
124        }
125
126        println!(
127            "{}",
128            output
129                .theme()
130                .highlight()
131                .apply_to(&format!("Events for Actor: {}", self.actor_id))
132        );
133        println!("{}", "─".repeat(80));
134
135        for event in self.events.iter() {
136            display_single_event(event, "pretty")?;
137        }
138
139        Ok(())
140    }
141
142    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
143        if self.events.is_empty() {
144            output.info(&format!("No events found for actor {}", self.actor_id))?;
145            return Ok(());
146        }
147
148        let headers = vec!["Timestamp", "Type", "Description"];
149        let rows: Vec<Vec<String>> = self
150            .events
151            .iter()
152            .map(|event| {
153                vec![
154                    format_timestamp(event.timestamp),
155                    event.event_type.clone(),
156                    truncate_string(event.description.as_deref().unwrap_or("No description"), 50),
157                ]
158            })
159            .collect();
160
161        output.table(&headers, &rows)?;
162        Ok(())
163    }
164
165    fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
166        if self.events.is_empty() {
167            output.info(&format!("No events found for actor {}", self.actor_id))?;
168            return Ok(());
169        }
170
171        println!(
172            "{}",
173            output.theme().highlight().apply_to("Detailed Actor Events")
174        );
175        println!("{}", "─".repeat(80));
176
177        for event in self.events.iter() {
178            display_single_event(event, "detailed")?;
179        }
180        Ok(())
181    }
182}
183
184/// Actor state formatter
185#[derive(Debug, serde::Serialize)]
186pub struct ActorState {
187    pub actor_id: String,
188    pub state: Value,
189}
190
191impl OutputFormat for ActorState {
192    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
193        println!(
194            "State for actor {}: {}",
195            output.theme().accent().apply_to(&self.actor_id),
196            self.state
197        );
198        Ok(())
199    }
200
201    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
202        println!(
203            "{}",
204            output
205                .theme()
206                .highlight()
207                .apply_to(&format!("State for Actor: {}", self.actor_id))
208        );
209        println!("{}", "─".repeat(40));
210        println!(
211            "{}",
212            serde_json::to_string_pretty(&self.state)
213                .unwrap_or_else(|_| "Invalid JSON".to_string())
214        );
215        Ok(())
216    }
217
218    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
219        // For state, table format doesn't make much sense, so fall back to pretty
220        self.format_pretty(output)
221    }
222
223    fn format_detailed(&self, output: &OutputManager) -> CliResult<()> {
224        println!(
225            "{}",
226            output.theme().highlight().apply_to("Detailed Actor State")
227        );
228        println!("{}", "─".repeat(40));
229        println!(
230            "{}",
231            serde_json::to_string_pretty(&self.state)
232                .unwrap_or_else(|_| "Invalid JSON".to_string())
233        );
234        Ok(())
235    }
236}
237
238/// Build result formatter
239#[derive(Debug, serde::Serialize)]
240pub struct BuildResult {
241    pub success: bool,
242    pub project_dir: std::path::PathBuf,
243    pub wasm_path: Option<std::path::PathBuf>,
244    pub manifest_exists: bool,
245    pub manifest_path: Option<std::path::PathBuf>,
246    pub build_type: String,
247    pub package_name: String,
248    pub stdout: String,
249    pub stderr: String,
250}
251
252impl OutputFormat for BuildResult {
253    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
254        if self.success {
255            output.success("Build completed successfully")?;
256            if let Some(wasm_path) = &self.wasm_path {
257                println!(
258                    "  Component: {}",
259                    output.theme().accent().apply_to(wasm_path.display())
260                );
261            }
262        } else {
263            output.error("Build failed")?;
264            if !self.stderr.is_empty() {
265                println!("{}", output.theme().muted().apply_to(&self.stderr));
266            }
267        }
268        Ok(())
269    }
270
271    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
272        if self.success {
273            println!(
274                "{} {}",
275                output.theme().success_icon(),
276                output.theme().highlight().apply_to("Build Successful")
277            );
278            println!();
279            println!(
280                "Package: {}",
281                output.theme().accent().apply_to(&self.package_name)
282            );
283            println!(
284                "Build Type: {}",
285                output.theme().muted().apply_to(&self.build_type)
286            );
287
288            if let Some(wasm_path) = &self.wasm_path {
289                println!(
290                    "Component: {}",
291                    output.theme().accent().apply_to(wasm_path.display())
292                );
293            }
294
295            if self.manifest_exists {
296                if let Some(manifest_path) = &self.manifest_path {
297                    println!("\nTo deploy your actor:");
298                    println!(
299                        "  theater start {}",
300                        output.theme().muted().apply_to(manifest_path.display())
301                    );
302                }
303            } else {
304                println!(
305                    "\n{} No manifest.toml found.",
306                    output.theme().warning_icon()
307                );
308                if let Some(wasm_path) = &self.wasm_path {
309                    println!(
310                        "Create one to deploy: theater create-manifest --component-path {}",
311                        output.theme().muted().apply_to(wasm_path.display())
312                    );
313                }
314            }
315
316            if !self.stdout.is_empty() {
317                println!("\nBuild Output:");
318                println!("{}", output.theme().muted().apply_to(&self.stdout));
319            }
320        } else {
321            println!(
322                "{} {}",
323                output.theme().error_icon(),
324                output.theme().error().apply_to("Build Failed")
325            );
326
327            if !self.stderr.is_empty() {
328                println!("\nError Output:");
329                println!("{}", self.stderr);
330            }
331            if !self.stdout.is_empty() {
332                println!("\nBuild Output:");
333                println!("{}", self.stdout);
334            }
335        }
336        Ok(())
337    }
338
339    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
340        let headers = vec!["Property", "Value"];
341        let mut rows = vec![vec![
342            "Status".to_string(),
343            if self.success {
344                "Success".to_string()
345            } else {
346                "Failed".to_string()
347            },
348        ]];
349
350        rows.push(vec!["Package".to_string(), self.package_name.clone()]);
351        rows.push(vec!["Build Type".to_string(), self.build_type.clone()]);
352        rows.push(vec![
353            "Project Dir".to_string(),
354            self.project_dir.display().to_string(),
355        ]);
356
357        if let Some(wasm_path) = &self.wasm_path {
358            rows.push(vec![
359                "Component".to_string(),
360                wasm_path.display().to_string(),
361            ]);
362        }
363
364        rows.push(vec![
365            "Manifest Exists".to_string(),
366            self.manifest_exists.to_string(),
367        ]);
368
369        if let Some(manifest_path) = &self.manifest_path {
370            rows.push(vec![
371                "Manifest Path".to_string(),
372                manifest_path.display().to_string(),
373            ]);
374        }
375
376        output.table(&headers, &rows)?;
377        Ok(())
378    }
379
380    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
381        todo!()
382    }
383}
384
385/// Actor action result formatter (for start, stop, restart actions)
386#[derive(Debug, serde::Serialize)]
387pub struct ActorAction {
388    pub action: String,
389    pub actor_id: String,
390    pub success: bool,
391    pub message: Option<String>,
392}
393
394impl OutputFormat for ActorAction {
395    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
396        if self.success {
397            println!(
398                "{} {} actor: {}",
399                output.theme().success_icon(),
400                self.action
401                    .chars()
402                    .next()
403                    .unwrap()
404                    .to_uppercase()
405                    .collect::<String>()
406                    + &self.action[1..],
407                output.theme().accent().apply_to(&self.actor_id)
408            );
409        } else {
410            println!(
411                "{} Failed to {} actor: {}",
412                output.theme().error_icon(),
413                self.action,
414                output.theme().accent().apply_to(&self.actor_id)
415            );
416            if let Some(msg) = &self.message {
417                println!("{}", output.theme().muted().apply_to(msg));
418            }
419        }
420        Ok(())
421    }
422
423    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
424        if self.success {
425            println!(
426                "{} {}",
427                output.theme().success_icon(),
428                output.theme().highlight().apply_to(&format!(
429                    "Actor {} Successfully",
430                    self.action
431                        .chars()
432                        .next()
433                        .unwrap()
434                        .to_uppercase()
435                        .collect::<String>()
436                        + &self.action[1..]
437                ))
438            );
439            println!(
440                "Actor ID: {}",
441                output.theme().accent().apply_to(&self.actor_id)
442            );
443        } else {
444            println!(
445                "{} {}",
446                output.theme().error_icon(),
447                output
448                    .theme()
449                    .error()
450                    .apply_to(&format!("Failed to {} Actor", self.action))
451            );
452            println!(
453                "Actor ID: {}",
454                output.theme().accent().apply_to(&self.actor_id)
455            );
456            if let Some(msg) = &self.message {
457                println!("Error: {}", msg);
458            }
459        }
460        Ok(())
461    }
462
463    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
464        let headers = vec!["Property", "Value"];
465        let mut rows = vec![
466            vec!["Action".to_string(), self.action.clone()],
467            vec!["Actor ID".to_string(), self.actor_id.clone()],
468            vec![
469                "Status".to_string(),
470                if self.success {
471                    "Success".to_string()
472                } else {
473                    "Failed".to_string()
474                },
475            ],
476        ];
477
478        if let Some(msg) = &self.message {
479            rows.push(vec!["Message".to_string(), msg.clone()]);
480        }
481
482        output.table(&headers, &rows)?;
483        Ok(())
484    }
485
486    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
487        todo!()
488    }
489}
490
491/// Component update result formatter
492#[derive(Debug, serde::Serialize)]
493pub struct ComponentUpdate {
494    pub actor_id: String,
495    pub component: String,
496    pub success: bool,
497    pub message: Option<String>,
498}
499
500impl OutputFormat for ComponentUpdate {
501    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
502        if self.success {
503            println!(
504                "{} Updated actor: {} with component: {}",
505                output.theme().success_icon(),
506                output.theme().accent().apply_to(&self.actor_id),
507                output.theme().accent().apply_to(&self.component)
508            );
509        } else {
510            println!(
511                "{} Failed to update actor: {}",
512                output.theme().error_icon(),
513                output.theme().accent().apply_to(&self.actor_id)
514            );
515            if let Some(msg) = &self.message {
516                println!("{}", output.theme().muted().apply_to(msg));
517            }
518        }
519        Ok(())
520    }
521
522    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
523        if self.success {
524            println!(
525                "{} {}",
526                output.theme().success_icon(),
527                output
528                    .theme()
529                    .highlight()
530                    .apply_to("Component Updated Successfully")
531            );
532            println!(
533                "Actor ID: {}",
534                output.theme().accent().apply_to(&self.actor_id)
535            );
536            println!(
537                "New Component: {}",
538                output.theme().accent().apply_to(&self.component)
539            );
540        } else {
541            println!(
542                "{} {}",
543                output.theme().error_icon(),
544                output
545                    .theme()
546                    .error()
547                    .apply_to("Failed to Update Component")
548            );
549            println!(
550                "Actor ID: {}",
551                output.theme().accent().apply_to(&self.actor_id)
552            );
553            println!(
554                "Component: {}",
555                output.theme().accent().apply_to(&self.component)
556            );
557            if let Some(msg) = &self.message {
558                println!("Error: {}", msg);
559            }
560        }
561        Ok(())
562    }
563
564    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
565        let headers = vec!["Property", "Value"];
566        let mut rows = vec![
567            vec!["Actor ID".to_string(), self.actor_id.clone()],
568            vec!["Component".to_string(), self.component.clone()],
569            vec![
570                "Status".to_string(),
571                if self.success {
572                    "Success".to_string()
573                } else {
574                    "Failed".to_string()
575                },
576            ],
577        ];
578
579        if let Some(msg) = &self.message {
580            rows.push(vec!["Message".to_string(), msg.clone()]);
581        }
582
583        output.table(&headers, &rows)?;
584        Ok(())
585    }
586    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
587        todo!()
588    }
589}
590
591/// Message sent result formatter
592#[derive(Debug, serde::Serialize)]
593pub struct MessageSent {
594    pub actor_id: String,
595    pub message: String,
596    pub success: bool,
597}
598
599impl OutputFormat for MessageSent {
600    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
601        if self.success {
602            println!(
603                "{} Message sent to actor: {}",
604                output.theme().success_icon(),
605                output.theme().accent().apply_to(&self.actor_id)
606            );
607        } else {
608            println!(
609                "{} Failed to send message to actor: {}",
610                output.theme().error_icon(),
611                output.theme().accent().apply_to(&self.actor_id)
612            );
613        }
614        Ok(())
615    }
616
617    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
618        if self.success {
619            println!(
620                "{} {}",
621                output.theme().success_icon(),
622                output
623                    .theme()
624                    .highlight()
625                    .apply_to("Message Sent Successfully")
626            );
627            println!(
628                "Actor ID: {}",
629                output.theme().accent().apply_to(&self.actor_id)
630            );
631            println!(
632                "Message: {}",
633                output
634                    .theme()
635                    .muted()
636                    .apply_to(&truncate_string(&self.message, 100))
637            );
638        } else {
639            println!(
640                "{} {}",
641                output.theme().error_icon(),
642                output.theme().error().apply_to("Failed to Send Message")
643            );
644            println!(
645                "Actor ID: {}",
646                output.theme().accent().apply_to(&self.actor_id)
647            );
648        }
649        Ok(())
650    }
651
652    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
653        let headers = vec!["Property", "Value"];
654        let rows = vec![
655            vec!["Actor ID".to_string(), self.actor_id.clone()],
656            vec!["Message".to_string(), truncate_string(&self.message, 100)],
657            vec![
658                "Status".to_string(),
659                if self.success {
660                    "Sent".to_string()
661                } else {
662                    "Failed".to_string()
663                },
664            ],
665        ];
666
667        output.table(&headers, &rows)?;
668        Ok(())
669    }
670    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
671        todo!()
672    }
673}
674
675/// Message response formatter (for request/response)
676#[derive(Debug, serde::Serialize)]
677pub struct MessageResponse {
678    pub actor_id: String,
679    pub request: String,
680    pub response: String,
681}
682
683impl OutputFormat for MessageResponse {
684    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
685        println!(
686            "{} Response from actor: {}",
687            output.theme().success_icon(),
688            output.theme().accent().apply_to(&self.actor_id)
689        );
690        println!("{}", self.response);
691        Ok(())
692    }
693
694    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
695        println!(
696            "{} {}",
697            output.theme().success_icon(),
698            output.theme().highlight().apply_to("Response Received")
699        );
700        println!(
701            "Actor ID: {}",
702            output.theme().accent().apply_to(&self.actor_id)
703        );
704        println!(
705            "Request: {}",
706            output
707                .theme()
708                .muted()
709                .apply_to(&truncate_string(&self.request, 100))
710        );
711        println!("Response:");
712        println!("{}", self.response);
713        Ok(())
714    }
715
716    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
717        let headers = vec!["Property", "Value"];
718        let rows = vec![
719            vec!["Actor ID".to_string(), self.actor_id.clone()],
720            vec!["Request".to_string(), truncate_string(&self.request, 100)],
721            vec!["Response".to_string(), truncate_string(&self.response, 100)],
722        ];
723
724        output.table(&headers, &rows)?;
725        Ok(())
726    }
727    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
728        todo!()
729    }
730}
731
732/// Stored actor list formatter
733#[derive(Debug, serde::Serialize)]
734pub struct StoredActorList {
735    pub actor_ids: Vec<String>,
736    pub chains_dir: String,
737    pub directory_exists: bool,
738}
739
740impl OutputFormat for StoredActorList {
741    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
742        if !self.directory_exists {
743            output.info(&format!(
744                "No stored actors found. Chains directory does not exist: {}",
745                self.chains_dir
746            ))?;
747        } else if self.actor_ids.is_empty() {
748            output.info("No stored actors found")?;
749        } else {
750            output.info(&format!("Stored actors: {}", self.actor_ids.len()))?;
751            for actor_id in &self.actor_ids {
752                println!("  {}", output.theme().accent().apply_to(actor_id));
753            }
754        }
755        Ok(())
756    }
757
758    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
759        if !self.directory_exists {
760            println!(
761                "{} {}",
762                output.theme().info_icon(),
763                output
764                    .theme()
765                    .highlight()
766                    .apply_to("No Stored Actors Found")
767            );
768            println!(
769                "Chains directory does not exist: {}",
770                output.theme().muted().apply_to(&self.chains_dir)
771            );
772        } else {
773            println!(
774                "{} {}",
775                output.theme().info_icon(),
776                output
777                    .theme()
778                    .highlight()
779                    .apply_to(&format!("Stored Actors ({})", self.actor_ids.len()))
780            );
781            println!(
782                "Directory: {}",
783                output.theme().muted().apply_to(&self.chains_dir)
784            );
785            println!("{}", "─".repeat(40));
786
787            if self.actor_ids.is_empty() {
788                println!("No stored actors found.");
789            } else {
790                for (i, actor_id) in self.actor_ids.iter().enumerate() {
791                    println!("{}. {}", i + 1, output.theme().accent().apply_to(actor_id));
792                }
793            }
794        }
795        Ok(())
796    }
797
798    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
799        if !self.directory_exists || self.actor_ids.is_empty() {
800            self.format_pretty(output)?;
801            return Ok(());
802        }
803
804        let headers = vec!["#", "Actor ID"];
805        let rows: Vec<Vec<String>> = self
806            .actor_ids
807            .iter()
808            .enumerate()
809            .map(|(i, id)| vec![(i + 1).to_string(), id.clone()])
810            .collect();
811
812        output.table(&headers, &rows)?;
813        Ok(())
814    }
815    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
816        todo!()
817    }
818}
819
820/// Actor logs formatter
821#[derive(Debug, serde::Serialize)]
822pub struct ActorLogs {
823    pub actor_id: String,
824    pub events: Vec<ChainEvent>,
825    pub follow_mode: bool,
826    pub lines_limit: usize,
827}
828
829impl OutputFormat for ActorLogs {
830    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
831        println!(
832            "{} Logs for actor: {}",
833            output.theme().info_icon(),
834            output.theme().accent().apply_to(&self.actor_id)
835        );
836
837        if self.events.is_empty() {
838            println!("  No logs found.");
839        } else {
840            for event in &self.events {
841                if let Ok(json_data) = serde_json::from_slice::<serde_json::Value>(&event.data) {
842                    if let Some(message) = json_data.get("message").and_then(|m| m.as_str()) {
843                        println!("[{}] {}", format_timestamp(event.timestamp), message);
844                    }
845                }
846            }
847        }
848        Ok(())
849    }
850
851    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
852        println!(
853            "{} {}",
854            output.theme().info_icon(),
855            output
856                .theme()
857                .highlight()
858                .apply_to(&format!("Logs for Actor: {}", self.actor_id))
859        );
860
861        if self.lines_limit > 0 {
862            println!("Showing last {} lines", self.lines_limit);
863        }
864
865        println!("{}", "─".repeat(80));
866
867        if self.events.is_empty() {
868            println!("No logs found.");
869        } else {
870            for event in &self.events {
871                let timestamp = format_timestamp(event.timestamp);
872                if let Ok(json_data) = serde_json::from_slice::<serde_json::Value>(&event.data) {
873                    if let Some(message) = json_data.get("message").and_then(|m| m.as_str()) {
874                        println!(
875                            "{} {}",
876                            output.theme().muted().apply_to(&timestamp),
877                            message
878                        );
879                    }
880                }
881            }
882        }
883
884        if self.follow_mode {
885            println!();
886            output.info("Following logs in real-time. Press Ctrl+C to exit.")?;
887        }
888
889        Ok(())
890    }
891
892    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
893        if self.events.is_empty() {
894            output.info(&format!("No logs found for actor: {}", self.actor_id))?;
895            return Ok(());
896        }
897
898        let headers = vec!["Timestamp", "Message"];
899        let rows: Vec<Vec<String>> = self
900            .events
901            .iter()
902            .filter_map(|event| {
903                serde_json::from_slice::<serde_json::Value>(&event.data)
904                    .ok()
905                    .and_then(|json_data| {
906                        json_data
907                            .get("message")
908                            .and_then(|m| m.as_str())
909                            .map(|message| {
910                                vec![
911                                    format_timestamp(event.timestamp),
912                                    truncate_string(message, 80),
913                                ]
914                            })
915                    })
916            })
917            .collect();
918
919        output.table(&headers, &rows)?;
920        Ok(())
921    }
922    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
923        todo!()
924    }
925}
926
927/// Server information formatter
928#[derive(Debug, serde::Serialize)]
929pub struct ServerInfo {
930    pub info: Value,
931}
932
933impl OutputFormat for ServerInfo {
934    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
935        if let Some(version) = self.info.get("version") {
936            println!(
937                "Theater Server {}",
938                output.theme().accent().apply_to(version)
939            );
940        }
941        if let Some(uptime) = self.info.get("uptime") {
942            println!("Uptime: {}", output.theme().muted().apply_to(uptime));
943        }
944        Ok(())
945    }
946
947    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
948        println!(
949            "{}",
950            output.theme().highlight().apply_to("Server Information")
951        );
952        println!("{}", "─".repeat(40));
953        println!(
954            "{}",
955            serde_json::to_string_pretty(&self.info).unwrap_or_else(|_| "Invalid JSON".to_string())
956        );
957        Ok(())
958    }
959
960    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
961        // Convert JSON object to key-value table
962        if let Value::Object(map) = &self.info {
963            let headers = vec!["Property", "Value"];
964            let rows: Vec<Vec<String>> = map
965                .iter()
966                .map(|(key, value)| {
967                    vec![
968                        key.clone(),
969                        match value {
970                            Value::String(s) => s.clone(),
971                            _ => value.to_string(),
972                        },
973                    ]
974                })
975                .collect();
976            output.table(&headers, &rows)?;
977        } else {
978            self.format_pretty(output)?;
979        }
980        Ok(())
981    }
982    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
983        todo!()
984    }
985}
986
987/// Actor inspection formatter
988#[derive(Debug, serde::Serialize)]
989pub struct ActorInspection {
990    pub id: theater::id::TheaterId,
991    pub status: String,
992    pub state: Option<serde_json::Value>,
993    pub events: Vec<theater::ChainEvent>,
994    pub metrics: Option<serde_json::Value>,
995    pub detailed: bool,
996}
997
998impl OutputFormat for ActorInspection {
999    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1000        println!(
1001            "{} - {}",
1002            output.theme().accent().apply_to(&self.id.to_string()),
1003            output.theme().muted().apply_to(&self.status)
1004        );
1005        if let Some(ref state) = self.state {
1006            let state_str =
1007                serde_json::to_string(state).unwrap_or_else(|_| "Invalid JSON".to_string());
1008            if state_str.len() > 100 {
1009                println!("State: {} bytes", state_str.len());
1010            } else {
1011                println!("State: {}", truncate_string(&state_str, 100));
1012            }
1013        } else {
1014            println!("State: null");
1015        }
1016        println!("Events: {}", self.events.len());
1017        Ok(())
1018    }
1019
1020    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1021        use std::time::Duration;
1022
1023        println!(
1024            "{}",
1025            output.theme().highlight().apply_to("ACTOR INFORMATION")
1026        );
1027        println!("{}", "─".repeat(50));
1028        println!(
1029            "ID: {}",
1030            output.theme().accent().apply_to(&self.id.to_string())
1031        );
1032        println!("Status: {}", output.theme().muted().apply_to(&self.status));
1033
1034        // Calculate uptime if we have events
1035        if let Some(first_event) = self.events.first() {
1036            let now = chrono::Utc::now().timestamp() as u64;
1037            let uptime = Duration::from_secs(now.saturating_sub(first_event.timestamp));
1038            println!(
1039                "Uptime: {}",
1040                crate::utils::formatting::format_duration(uptime)
1041            );
1042        }
1043
1044        println!();
1045        println!("{}", output.theme().highlight().apply_to("STATE"));
1046        println!("{}", "─".repeat(50));
1047        match &self.state {
1048            Some(state_json) => {
1049                let state_str = serde_json::to_string_pretty(state_json)
1050                    .unwrap_or_else(|_| "Invalid JSON".to_string());
1051                if state_str.len() < 1000 || self.detailed {
1052                    println!("{}", state_str);
1053                } else {
1054                    println!("{} bytes of JSON data", state_str.len());
1055                    println!("(Use --detailed to see full state)");
1056                }
1057            }
1058            None => println!("State is null"),
1059        }
1060
1061        println!();
1062        println!("{}", output.theme().highlight().apply_to("EVENTS"));
1063        println!("{}", "─".repeat(50));
1064        println!("Total events: {}", self.events.len());
1065
1066        if !self.events.is_empty() {
1067            println!();
1068            println!("Latest events:");
1069            let start_idx = if self.events.len() > 5 && !self.detailed {
1070                self.events.len() - 5
1071            } else {
1072                0
1073            };
1074
1075            for (i, event) in self.events.iter().enumerate().skip(start_idx) {
1076                println!(
1077                    "{}. {}",
1078                    i + 1,
1079                    crate::utils::formatting::format_event_summary(event)
1080                );
1081            }
1082
1083            if self.events.len() > 5 && !self.detailed {
1084                println!();
1085                println!("(Showing only the last 5 events. Use --detailed to see all.)");
1086            }
1087        }
1088
1089        // Metrics information if available
1090        if let Some(ref metrics) = self.metrics {
1091            println!();
1092            println!("{}", output.theme().highlight().apply_to("METRICS"));
1093            println!("{}", "─".repeat(50));
1094            println!(
1095                "{}",
1096                serde_json::to_string_pretty(metrics)
1097                    .unwrap_or_else(|_| "Invalid JSON".to_string())
1098            );
1099        }
1100
1101        Ok(())
1102    }
1103
1104    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1105        let headers = vec!["Property", "Value"];
1106        let mut rows = vec![
1107            vec!["ID".to_string(), self.id.to_string()],
1108            vec!["Status".to_string(), self.status.clone()],
1109        ];
1110
1111        if let Some(ref state) = self.state {
1112            let state_str =
1113                serde_json::to_string(state).unwrap_or_else(|_| "Invalid JSON".to_string());
1114            rows.push(vec!["State".to_string(), truncate_string(&state_str, 100)]);
1115        } else {
1116            rows.push(vec!["State".to_string(), "null".to_string()]);
1117        }
1118
1119        rows.push(vec!["Events".to_string(), self.events.len().to_string()]);
1120
1121        if let Some(ref metrics) = self.metrics {
1122            let metrics_str =
1123                serde_json::to_string(metrics).unwrap_or_else(|_| "Invalid JSON".to_string());
1124            rows.push(vec![
1125                "Metrics".to_string(),
1126                truncate_string(&metrics_str, 100),
1127            ]);
1128        }
1129
1130        output.table(&headers, &rows)?;
1131        Ok(())
1132    }
1133    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1134        todo!()
1135    }
1136}
1137
1138/// Project creation formatter
1139#[derive(Debug, serde::Serialize)]
1140pub struct ProjectCreated {
1141    pub name: String,
1142    pub template: String,
1143    pub path: std::path::PathBuf,
1144    pub build_instructions: Vec<String>,
1145}
1146
1147impl OutputFormat for ProjectCreated {
1148    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1149        println!(
1150            "{} Created project: {}",
1151            output.theme().success().apply_to("✓"),
1152            output.theme().accent().apply_to(&self.name)
1153        );
1154        println!(
1155            "Path: {}",
1156            output.theme().muted().apply_to(self.path.display())
1157        );
1158        Ok(())
1159    }
1160
1161    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1162        println!(
1163            "{} {}",
1164            output.theme().success().apply_to("✓"),
1165            output
1166                .theme()
1167                .highlight()
1168                .apply_to(&format!("Created new actor project: {}", self.name))
1169        );
1170        println!();
1171        println!(
1172            "Template: {}",
1173            output.theme().accent().apply_to(&self.template)
1174        );
1175        println!(
1176            "Location: {}",
1177            output.theme().muted().apply_to(self.path.display())
1178        );
1179        println!();
1180        println!("{}", output.theme().highlight().apply_to("Next steps:"));
1181        for (i, instruction) in self.build_instructions.iter().enumerate() {
1182            println!(
1183                "  {}. {}",
1184                i + 1,
1185                output.theme().muted().apply_to(instruction)
1186            );
1187        }
1188        Ok(())
1189    }
1190
1191    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1192        let headers = vec!["Property", "Value"];
1193        let rows = vec![
1194            vec!["Name".to_string(), self.name.clone()],
1195            vec!["Template".to_string(), self.template.clone()],
1196            vec!["Path".to_string(), self.path.display().to_string()],
1197        ];
1198        output.table(&headers, &rows)?;
1199
1200        println!();
1201        println!(
1202            "{}",
1203            output.theme().highlight().apply_to("Build Instructions:")
1204        );
1205        for (i, instruction) in self.build_instructions.iter().enumerate() {
1206            println!("  {}. {}", i + 1, instruction);
1207        }
1208        Ok(())
1209    }
1210    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1211        todo!()
1212    }
1213}
1214
1215/// Actor started formatter
1216#[derive(Debug, serde::Serialize)]
1217pub struct ActorStarted {
1218    pub actor_id: String,
1219    pub manifest_path: String,
1220    pub address: String,
1221    pub subscribing: bool,
1222    pub acting_as_parent: bool,
1223}
1224
1225impl OutputFormat for ActorStarted {
1226    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1227        println!(
1228            "{} Actor started: {}",
1229            output.theme().success_icon(),
1230            output.theme().accent().apply_to(&self.actor_id)
1231        );
1232        Ok(())
1233    }
1234
1235    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1236        println!("{}", "─".repeat(45));
1237        println!(
1238            "{} {}",
1239            output.theme().success_icon(),
1240            output.theme().highlight().apply_to("ACTOR STARTED")
1241        );
1242        println!("{}", "─".repeat(45));
1243        println!(
1244            "Actor ID: {}",
1245            output.theme().accent().apply_to(&self.actor_id)
1246        );
1247        println!(
1248            "Manifest: {}",
1249            output.theme().muted().apply_to(&self.manifest_path)
1250        );
1251        println!("Server: {}", output.theme().muted().apply_to(&self.address));
1252
1253        if self.subscribing {
1254            println!(
1255                "Status: {}",
1256                output.theme().info().apply_to("Subscribing to events")
1257            );
1258        }
1259        if self.acting_as_parent {
1260            println!(
1261                "Role: {}",
1262                output.theme().info().apply_to("Acting as parent")
1263            );
1264        }
1265        println!("{}", "─".repeat(45));
1266        Ok(())
1267    }
1268
1269    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1270        let headers = vec!["Property", "Value"];
1271        let rows = vec![
1272            vec!["Actor ID".to_string(), self.actor_id.clone()],
1273            vec!["Manifest".to_string(), self.manifest_path.clone()],
1274            vec!["Server".to_string(), self.address.clone()],
1275            vec!["Subscribing".to_string(), self.subscribing.to_string()],
1276            vec![
1277                "Acting as Parent".to_string(),
1278                self.acting_as_parent.to_string(),
1279            ],
1280        ];
1281        output.table(&headers, &rows)?;
1282        Ok(())
1283    }
1284    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1285        todo!()
1286    }
1287}
1288
1289/// Event subscription formatter
1290#[derive(Debug, serde::Serialize)]
1291pub struct EventSubscription {
1292    pub actor_id: theater::id::TheaterId,
1293    pub address: String,
1294    pub event_type_filter: Option<String>,
1295    pub limit: usize,
1296    pub timeout: u64,
1297    pub format: String,
1298    pub show_history: bool,
1299    pub history_limit: usize,
1300    pub detailed: bool,
1301    pub events_received: usize,
1302    pub subscription_id: Option<String>,
1303    pub is_active: bool,
1304}
1305
1306impl OutputFormat for EventSubscription {
1307    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1308        if self.is_active {
1309            println!(
1310                "{} Subscribed to: {}",
1311                output.theme().success_icon(),
1312                output.theme().accent().apply_to(&self.actor_id.to_string())
1313            );
1314            if let Some(filter) = &self.event_type_filter {
1315                println!("  Filter: {}", output.theme().muted().apply_to(filter));
1316            }
1317        } else {
1318            println!(
1319                "{} Subscription ended: {} events received",
1320                output.theme().info_icon(),
1321                output.theme().accent().apply_to(&self.events_received)
1322            );
1323        }
1324        Ok(())
1325    }
1326
1327    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1328        if self.is_active {
1329            println!(
1330                "{} {}",
1331                output.theme().info_icon(),
1332                output.theme().highlight().apply_to(&format!(
1333                    "Subscribing to events for actor: {}",
1334                    self.actor_id
1335                ))
1336            );
1337
1338            if let Some(filter) = &self.event_type_filter {
1339                println!(
1340                    "{} {}",
1341                    output.theme().info_icon(),
1342                    output
1343                        .theme()
1344                        .highlight()
1345                        .apply_to(&format!("Filtering events by type: {}", filter))
1346                );
1347            }
1348
1349            println!();
1350            println!("Server: {}", output.theme().muted().apply_to(&self.address));
1351            println!("Format: {}", output.theme().muted().apply_to(&self.format));
1352
1353            if self.limit > 0 {
1354                println!(
1355                    "Limit: {} events",
1356                    output.theme().muted().apply_to(&self.limit)
1357                );
1358            }
1359
1360            if self.timeout > 0 {
1361                println!(
1362                    "Timeout: {} seconds",
1363                    output.theme().muted().apply_to(&self.timeout)
1364                );
1365            }
1366
1367            if self.show_history {
1368                let history_desc = if self.history_limit > 0 {
1369                    format!("Last {} events", self.history_limit)
1370                } else {
1371                    "All historical events".to_string()
1372                };
1373                println!(
1374                    "History: {}",
1375                    output.theme().muted().apply_to(&history_desc)
1376                );
1377            }
1378
1379            if let Some(subscription_id) = &self.subscription_id {
1380                println!(
1381                    "Subscription ID: {}",
1382                    output.theme().muted().apply_to(subscription_id)
1383                );
1384            }
1385
1386            println!();
1387        } else {
1388            println!("{} Subscription ended", output.theme().success_icon());
1389            println!(
1390                "Events received: {}",
1391                output.theme().accent().apply_to(&self.events_received)
1392            );
1393        }
1394        Ok(())
1395    }
1396
1397    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1398        let headers = vec!["Property", "Value"];
1399        let mut rows = vec![
1400            vec!["Actor ID".to_string(), self.actor_id.to_string()],
1401            vec!["Server".to_string(), self.address.clone()],
1402            vec!["Format".to_string(), self.format.clone()],
1403            vec![
1404                "Status".to_string(),
1405                if self.is_active {
1406                    "Active".to_string()
1407                } else {
1408                    "Ended".to_string()
1409                },
1410            ],
1411            vec![
1412                "Events Received".to_string(),
1413                self.events_received.to_string(),
1414            ],
1415        ];
1416
1417        if let Some(filter) = &self.event_type_filter {
1418            rows.push(vec!["Event Filter".to_string(), filter.clone()]);
1419        }
1420
1421        if self.limit > 0 {
1422            rows.push(vec!["Limit".to_string(), self.limit.to_string()]);
1423        }
1424
1425        if self.timeout > 0 {
1426            rows.push(vec![
1427                "Timeout".to_string(),
1428                format!("{} seconds", self.timeout),
1429            ]);
1430        }
1431
1432        if let Some(subscription_id) = &self.subscription_id {
1433            rows.push(vec!["Subscription ID".to_string(), subscription_id.clone()]);
1434        }
1435
1436        output.table(&headers, &rows)?;
1437        Ok(())
1438    }
1439    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1440        todo!()
1441    }
1442}
1443
1444/// Server started formatter
1445#[derive(Debug, serde::Serialize)]
1446pub struct ServerStarted {
1447    pub address: std::net::SocketAddr,
1448    pub log_level: String,
1449    pub log_filter: Option<String>,
1450    pub log_dir: String,
1451    pub log_path: std::path::PathBuf,
1452    pub log_stdout: bool,
1453}
1454
1455impl OutputFormat for ServerStarted {
1456    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1457        println!(
1458            "{} Theater server starting on {}",
1459            output.theme().success_icon(),
1460            output.theme().accent().apply_to(&self.address)
1461        );
1462        println!(
1463            "  Logs: {}",
1464            output.theme().muted().apply_to(self.log_path.display())
1465        );
1466        Ok(())
1467    }
1468
1469    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1470        println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1471        println!(
1472            "{} {}",
1473            output.theme().success_icon(),
1474            output
1475                .theme()
1476                .highlight()
1477                .apply_to("THEATER SERVER STARTING")
1478        );
1479        println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1480        println!();
1481        println!(
1482            "Address: {}",
1483            output.theme().accent().apply_to(&self.address)
1484        );
1485        println!(
1486            "Log Level: {}",
1487            output.theme().muted().apply_to(&self.log_level)
1488        );
1489
1490        if let Some(filter) = &self.log_filter {
1491            println!("Log Filter: {}", output.theme().muted().apply_to(filter));
1492        }
1493
1494        println!(
1495            "Log Directory: {}",
1496            output.theme().muted().apply_to(&self.log_dir)
1497        );
1498        println!(
1499            "Log File: {}",
1500            output.theme().muted().apply_to(self.log_path.display())
1501        );
1502
1503        if self.log_stdout {
1504            println!(
1505                "Console Logging: {}",
1506                output.theme().success().apply_to("Enabled")
1507            );
1508        }
1509
1510        println!();
1511        println!("{}", output.theme().highlight().apply_to("─".repeat(50)));
1512        println!();
1513        println!(
1514            "{} Server is running. Press Ctrl+C to stop.",
1515            output.theme().info_icon()
1516        );
1517        println!();
1518        Ok(())
1519    }
1520
1521    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1522        let headers = vec!["Property", "Value"];
1523        let mut rows = vec![
1524            vec!["Address".to_string(), self.address.to_string()],
1525            vec!["Log Level".to_string(), self.log_level.clone()],
1526            vec!["Log Directory".to_string(), self.log_dir.clone()],
1527            vec!["Log File".to_string(), self.log_path.display().to_string()],
1528            vec!["Console Logging".to_string(), self.log_stdout.to_string()],
1529        ];
1530
1531        if let Some(filter) = &self.log_filter {
1532            rows.push(vec!["Custom Filter".to_string(), filter.clone()]);
1533        }
1534
1535        output.table(&headers, &rows)?;
1536        Ok(())
1537    }
1538    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1539        todo!()
1540    }
1541}
1542
1543/// Channel opened formatter
1544#[derive(Debug, serde::Serialize)]
1545pub struct ChannelOpened {
1546    pub actor_id: theater::id::TheaterId,
1547    pub channel_id: String,
1548    pub address: String,
1549    pub initial_message_size: usize,
1550    pub is_interactive: bool,
1551}
1552
1553impl OutputFormat for ChannelOpened {
1554    fn format_compact(&self, output: &OutputManager) -> CliResult<()> {
1555        println!(
1556            "{} Channel opened to: {}",
1557            output.theme().success_icon(),
1558            output.theme().accent().apply_to(&self.actor_id.to_string())
1559        );
1560        println!(
1561            "  Channel ID: {}",
1562            output.theme().muted().apply_to(&self.channel_id)
1563        );
1564        Ok(())
1565    }
1566
1567    fn format_pretty(&self, output: &OutputManager) -> CliResult<()> {
1568        println!(
1569            "{} {}",
1570            output.theme().success_icon(),
1571            output
1572                .theme()
1573                .highlight()
1574                .apply_to(&format!("Opening channel to actor: {}", self.actor_id))
1575        );
1576        println!();
1577        println!(
1578            "Channel ID: {}",
1579            output.theme().accent().apply_to(&self.channel_id)
1580        );
1581        println!("Server: {}", output.theme().muted().apply_to(&self.address));
1582        println!(
1583            "Initial Message: {} bytes",
1584            output.theme().muted().apply_to(&self.initial_message_size)
1585        );
1586
1587        if self.is_interactive {
1588            println!();
1589            println!(
1590                "{} {}",
1591                output.theme().success_icon(),
1592                output
1593                    .theme()
1594                    .highlight()
1595                    .apply_to("Channel opened successfully")
1596            );
1597        }
1598        Ok(())
1599    }
1600
1601    fn format_table(&self, output: &OutputManager) -> CliResult<()> {
1602        let headers = vec!["Property", "Value"];
1603        let rows = vec![
1604            vec!["Actor ID".to_string(), self.actor_id.to_string()],
1605            vec!["Channel ID".to_string(), self.channel_id.clone()],
1606            vec!["Server".to_string(), self.address.clone()],
1607            vec![
1608                "Initial Message Size".to_string(),
1609                format!("{} bytes", self.initial_message_size),
1610            ],
1611            vec![
1612                "Interactive Mode".to_string(),
1613                self.is_interactive.to_string(),
1614            ],
1615        ];
1616        output.table(&headers, &rows)?;
1617        Ok(())
1618    }
1619    fn format_detailed(&self, _output: &OutputManager) -> CliResult<()> {
1620        todo!()
1621    }
1622}
1623
1624// Helper functions
1625
1626fn format_timestamp(timestamp: u64) -> String {
1627    match DateTime::from_timestamp(timestamp as i64, 0) {
1628        Some(dt) => dt.format("%Y-%m-%d %H:%M:%S UTC").to_string(),
1629        None => timestamp.to_string(),
1630    }
1631}
1632
1633fn truncate_string(s: &str, max_len: usize) -> String {
1634    if s.len() <= max_len {
1635        s.to_string()
1636    } else {
1637        format!("{}…", &s[..max_len.saturating_sub(1)])
1638    }
1639}
1640
1641#[cfg(test)]
1642mod tests {
1643    use super::*;
1644
1645    #[test]
1646    fn test_truncate_string() {
1647        assert_eq!(truncate_string("hello", 10), "hello");
1648        assert_eq!(truncate_string("hello world", 5), "hell…");
1649    }
1650}