boxmux_lib/model/
common.rs

1use serde_json::Value;
2use std::{collections::HashMap, error::Error, hash::Hash};
3
4use crate::{
5    draw_utils::{get_bg_color, get_fg_color},
6    screen_bounds, screen_height, screen_width,
7    utils::input_bounds_to_bounds,
8    AppContext, AppGraph, Layout, Message, MuxBox,
9};
10use serde::{Deserialize, Serialize};
11
12#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
13pub enum EntityType {
14    AppContext,
15    App,
16    Layout,
17    MuxBox,
18}
19
20// Represents a granular field update
21#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
22pub struct FieldUpdate {
23    pub entity_type: EntityType,   // The type of entity being updated
24    pub entity_id: Option<String>, // The ID of the entity (App, Layout, or MuxBox)
25    pub field_name: String,        // The field name to be updated
26    pub new_value: Value,          // The new value for the field
27}
28
29// The Updatable trait
30pub trait Updatable {
31    // Generate a diff of changes from another instance
32    fn generate_diff(&self, other: &Self) -> Vec<FieldUpdate>;
33
34    // Apply a list of updates to the current instance
35    fn apply_updates(&mut self, updates: Vec<FieldUpdate>);
36}
37
38#[derive(Debug, Deserialize, Serialize, Clone, PartialEq)]
39pub struct Config {
40    pub frame_delay: u64,
41    pub locked: bool, // Disable muxbox resizing and moving when true
42}
43
44impl Hash for Config {
45    fn hash<H: std::hash::Hasher>(&self, state: &mut H) {
46        self.frame_delay.hash(state);
47        self.locked.hash(state);
48    }
49}
50
51impl Default for Config {
52    fn default() -> Self {
53        Config {
54            frame_delay: 30,
55            locked: false, // Default to unlocked (resizable/movable)
56        }
57    }
58}
59
60impl Config {
61    pub fn new(frame_delay: u64) -> Self {
62        let result = Config {
63            frame_delay,
64            locked: false, // Default to unlocked
65        };
66        result.validate();
67        result
68    }
69
70    pub fn new_with_lock(frame_delay: u64, locked: bool) -> Self {
71        let result = Config {
72            frame_delay,
73            locked,
74        };
75        result.validate();
76        result
77    }
78    pub fn validate(&self) {
79        if self.frame_delay == 0 {
80            panic!("Validation error: frame_delay cannot be 0");
81        }
82    }
83}
84
85#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
86pub enum SocketFunction {
87    ReplaceBoxContent {
88        box_id: String,
89        success: bool,
90        content: String,
91    },
92    ReplaceBoxScript {
93        box_id: String,
94        script: Vec<String>,
95    },
96    StopBoxRefresh {
97        box_id: String,
98    },
99    StartBoxRefresh {
100        box_id: String,
101    },
102    ReplaceBox {
103        box_id: String,
104        new_box: MuxBox,
105    },
106    SwitchActiveLayout {
107        layout_id: String,
108    },
109    AddBox {
110        layout_id: String,
111        muxbox: MuxBox,
112    },
113    RemoveBox {
114        box_id: String,
115    },
116    // F0137: Socket PTY Control - Kill and restart PTY processes
117    KillPtyProcess {
118        box_id: String,
119    },
120    RestartPtyProcess {
121        box_id: String,
122    },
123    // F0138: Socket PTY Query - Get PTY status and info
124    QueryPtyStatus {
125        box_id: String,
126    },
127    // F0136: Socket PTY Spawn - Spawn PTY processes via socket commands
128    SpawnPtyProcess {
129        box_id: String,
130        script: Vec<String>,
131        libs: Option<Vec<String>>,
132        redirect_output: Option<String>,
133    },
134    // F0139: Socket PTY Input - Send input to PTY processes remotely
135    SendPtyInput {
136        box_id: String,
137        input: String,
138    },
139}
140
141pub fn run_socket_function(
142    socket_function: SocketFunction,
143    app_context: &AppContext,
144) -> Result<(AppContext, Vec<Message>), Box<dyn Error>> {
145    let app_context = app_context.clone();
146    let mut messages = Vec::new();
147    match socket_function {
148        SocketFunction::ReplaceBoxContent {
149            box_id,
150            success,
151            content,
152        } => {
153            messages.push(Message::MuxBoxOutputUpdate(box_id, success, content));
154        }
155        SocketFunction::ReplaceBoxScript { box_id, script } => {
156            messages.push(Message::MuxBoxScriptUpdate(box_id, script));
157        }
158        SocketFunction::StopBoxRefresh { box_id } => {
159            messages.push(Message::StopBoxRefresh(box_id));
160        }
161        SocketFunction::StartBoxRefresh { box_id } => {
162            messages.push(Message::StartBoxRefresh(box_id));
163        }
164        SocketFunction::ReplaceBox { box_id, new_box } => {
165            messages.push(Message::ReplaceMuxBox(box_id, new_box));
166        }
167        SocketFunction::SwitchActiveLayout { layout_id } => {
168            messages.push(Message::SwitchActiveLayout(layout_id));
169        }
170        SocketFunction::AddBox { layout_id, muxbox } => {
171            messages.push(Message::AddBox(layout_id, muxbox));
172        }
173        SocketFunction::RemoveBox { box_id } => {
174            messages.push(Message::RemoveBox(box_id));
175        }
176        // F0137: Socket PTY Control - Kill and restart PTY processes
177        SocketFunction::KillPtyProcess { box_id } => {
178            if let Some(pty_manager) = &app_context.pty_manager {
179                match pty_manager.kill_pty_process(&box_id) {
180                    Ok(_) => {
181                        messages.push(Message::MuxBoxOutputUpdate(
182                            box_id.clone(),
183                            true,
184                            format!("PTY process killed for box {}", box_id),
185                        ));
186                    }
187                    Err(err) => {
188                        messages.push(Message::MuxBoxOutputUpdate(
189                            box_id.clone(),
190                            false,
191                            format!("Failed to kill PTY process: {}", err),
192                        ));
193                    }
194                }
195            } else {
196                messages.push(Message::MuxBoxOutputUpdate(
197                    box_id.clone(),
198                    false,
199                    "PTY manager not available".to_string(),
200                ));
201            }
202        }
203        SocketFunction::RestartPtyProcess { box_id } => {
204            if let Some(pty_manager) = &app_context.pty_manager {
205                match pty_manager.restart_pty_process(&box_id) {
206                    Ok(_) => {
207                        messages.push(Message::MuxBoxOutputUpdate(
208                            box_id.clone(),
209                            true,
210                            format!("PTY process restarted for box {}", box_id),
211                        ));
212                    }
213                    Err(err) => {
214                        messages.push(Message::MuxBoxOutputUpdate(
215                            box_id.clone(),
216                            false,
217                            format!("Failed to restart PTY process: {}", err),
218                        ));
219                    }
220                }
221            } else {
222                messages.push(Message::MuxBoxOutputUpdate(
223                    box_id.clone(),
224                    false,
225                    "PTY manager not available".to_string(),
226                ));
227            }
228        }
229        // F0138: Socket PTY Query - Get PTY status and info
230        SocketFunction::QueryPtyStatus { box_id } => {
231            if let Some(pty_manager) = &app_context.pty_manager {
232                if let Some(info) = pty_manager.get_detailed_process_info(&box_id) {
233                    let status_info = format!(
234                        "PTY Status - Box: {}, PID: {:?}, Status: {:?}, Running: {}, Buffer Lines: {}",
235                        info.muxbox_id, info.process_id, info.status, info.is_running, info.buffer_lines
236                    );
237                    messages.push(Message::MuxBoxOutputUpdate(
238                        box_id.clone(),
239                        true,
240                        status_info,
241                    ));
242                } else {
243                    messages.push(Message::MuxBoxOutputUpdate(
244                        box_id.clone(),
245                        false,
246                        format!("No PTY process found for box {}", box_id),
247                    ));
248                }
249            } else {
250                messages.push(Message::MuxBoxOutputUpdate(
251                    box_id.clone(),
252                    false,
253                    "PTY manager not available".to_string(),
254                ));
255            }
256        }
257        // F0136: Socket PTY Spawn - Spawn PTY processes via socket commands
258        SocketFunction::SpawnPtyProcess {
259            box_id,
260            script,
261            libs,
262            redirect_output,
263        } => {
264            if let Some(pty_manager) = &app_context.pty_manager {
265                // We need to create a temporary message sender for PTY operations
266                // This is a limitation of the socket API - it doesn't have access to the main ThreadManager
267                let (temp_sender, _temp_receiver) = std::sync::mpsc::channel();
268                let temp_uuid = uuid::Uuid::new_v4();
269                
270                let spawn_result = if redirect_output.is_some() {
271                    pty_manager.spawn_pty_script_with_redirect(
272                        box_id.clone(),
273                        &script,
274                        libs,
275                        temp_sender,
276                        temp_uuid,
277                        redirect_output,
278                    )
279                } else {
280                    pty_manager.spawn_pty_script(
281                        box_id.clone(),
282                        &script,
283                        libs,
284                        temp_sender,
285                        temp_uuid,
286                    )
287                };
288                
289                match spawn_result {
290                    Ok(_) => {
291                        messages.push(Message::MuxBoxOutputUpdate(
292                            box_id.clone(),
293                            true,
294                            format!("PTY process spawned successfully for box {}", box_id),
295                        ));
296                    }
297                    Err(err) => {
298                        messages.push(Message::MuxBoxOutputUpdate(
299                            box_id.clone(),
300                            false,
301                            format!("Failed to spawn PTY process: {}", err),
302                        ));
303                    }
304                }
305            } else {
306                messages.push(Message::MuxBoxOutputUpdate(
307                    box_id.clone(),
308                    false,
309                    "PTY manager not available".to_string(),
310                ));
311            }
312        }
313        // F0139: Socket PTY Input - Send input to PTY processes remotely
314        SocketFunction::SendPtyInput { box_id, input } => {
315            if let Some(pty_manager) = &app_context.pty_manager {
316                match pty_manager.send_input(&box_id, &input) {
317                    Ok(_) => {
318                        messages.push(Message::MuxBoxOutputUpdate(
319                            box_id.clone(),
320                            true,
321                            format!("Input sent successfully to PTY process for box {}", box_id),
322                        ));
323                    }
324                    Err(err) => {
325                        messages.push(Message::MuxBoxOutputUpdate(
326                            box_id.clone(),
327                            false,
328                            format!("Failed to send input to PTY process: {}", err),
329                        ));
330                    }
331                }
332            } else {
333                messages.push(Message::MuxBoxOutputUpdate(
334                    box_id.clone(),
335                    false,
336                    "PTY manager not available".to_string(),
337                ));
338            }
339        }
340    }
341    Ok((app_context, messages))
342}
343
344#[derive(Clone, PartialEq, Debug)]
345pub struct Cell {
346    pub fg_color: String,
347    pub bg_color: String,
348    pub ch: char,
349}
350
351#[derive(Debug, Clone)]
352pub struct ScreenBuffer {
353    pub width: usize,
354    pub height: usize,
355    pub buffer: Vec<Vec<Cell>>,
356}
357
358impl Default for ScreenBuffer {
359    fn default() -> Self {
360        Self::new()
361    }
362}
363
364impl ScreenBuffer {
365    pub fn new() -> Self {
366        let default_cell = Cell {
367            fg_color: get_fg_color("white"),
368            bg_color: get_bg_color("black"),
369            ch: ' ',
370        };
371        let width = screen_width();
372        let height = screen_height();
373        let buffer = vec![vec![default_cell; width]; height];
374        ScreenBuffer {
375            width,
376            height,
377            buffer,
378        }
379    }
380
381    pub fn new_custom(width: usize, height: usize) -> Self {
382        let default_cell = Cell {
383            fg_color: get_fg_color("white"),
384            bg_color: get_bg_color("black"),
385            ch: ' ',
386        };
387        let buffer = vec![vec![default_cell; width]; height];
388        ScreenBuffer {
389            width,
390            height,
391            buffer,
392        }
393    }
394
395    pub fn clear(&mut self) {
396        let default_cell = Cell {
397            fg_color: get_fg_color("white"),
398            bg_color: get_bg_color("black"),
399            ch: ' ',
400        };
401        self.buffer = vec![vec![default_cell; self.width]; self.height];
402    }
403
404    pub fn update(&mut self, x: usize, y: usize, cell: Cell) {
405        if x < self.width && y < self.height {
406            self.buffer[y][x] = cell;
407        }
408    }
409
410    pub fn get(&self, x: usize, y: usize) -> Option<&Cell> {
411        if x < self.width && y < self.height {
412            Some(&self.buffer[y][x])
413        } else {
414            None
415        }
416    }
417
418    pub fn resize(&mut self, width: usize, height: usize) {
419        // First handle shrinking the buffer if necessary
420        if height < self.height {
421            self.buffer.truncate(height);
422        }
423        if width < self.width {
424            for row in &mut self.buffer {
425                row.truncate(width);
426            }
427        }
428
429        // Now handle expanding the buffer if necessary
430        if height > self.height {
431            let default_row = vec![
432                Cell {
433                    fg_color: get_fg_color("white"),
434                    bg_color: get_bg_color("black"),
435                    ch: ' ',
436                };
437                width
438            ];
439
440            self.buffer.resize_with(height, || default_row.clone());
441        }
442        if width > self.width {
443            for row in &mut self.buffer {
444                row.resize_with(width, || Cell {
445                    fg_color: get_fg_color("white"),
446                    bg_color: get_bg_color("black"),
447                    ch: ' ',
448                });
449            }
450        }
451
452        // Update the dimensions
453        self.width = width;
454        self.height = height;
455    }
456}
457
458#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq)]
459pub struct InputBounds {
460    pub x1: String,
461    pub y1: String,
462    pub x2: String,
463    pub y2: String,
464}
465
466impl InputBounds {
467    pub fn to_bounds(&self, parent_bounds: &Bounds) -> Bounds {
468        input_bounds_to_bounds(self, parent_bounds)
469    }
470}
471
472#[derive(Debug, Deserialize, Serialize, Clone)]
473pub struct Bounds {
474    pub x1: usize,
475    pub y1: usize,
476    pub x2: usize,
477    pub y2: usize,
478}
479
480impl PartialEq for Bounds {
481    fn eq(&self, other: &Self) -> bool {
482        self.x1 == other.x1 && self.y1 == other.y1 && self.x2 == other.x2 && self.y2 == other.y2
483    }
484}
485
486impl Eq for Bounds {}
487
488#[derive(Debug, Deserialize, Serialize, Clone, PartialEq, Hash, Eq, Default)]
489pub enum Anchor {
490    TopLeft,
491    TopRight,
492    BottomLeft,
493    BottomRight,
494    #[default]
495    Center,
496    CenterTop,
497    CenterBottom,
498    CenterLeft,
499    CenterRight,
500}
501
502impl Bounds {
503    pub fn new(x1: usize, y1: usize, x2: usize, y2: usize) -> Self {
504        Bounds { x1, y1, x2, y2 }
505    }
506
507    pub fn validate(&self) {
508        if self.x1 > self.x2 {
509            panic!(
510                "Validation error: x1 ({}) is greater than x2 ({})",
511                self.x1, self.x2
512            );
513        }
514        if self.y1 > self.y2 {
515            panic!(
516                "Validation error: y1 ({}) is greater than y2 ({})",
517                self.y1, self.y2
518            );
519        }
520    }
521
522    pub fn width(&self) -> usize {
523        // For inclusive coordinate bounds, width is x2 - x1 + 1
524        self.x2.saturating_sub(self.x1).saturating_add(1)
525    }
526
527    pub fn height(&self) -> usize {
528        // For inclusive coordinate bounds, height is y2 - y1 + 1
529        self.y2.saturating_sub(self.y1).saturating_add(1)
530    }
531
532    pub fn to_string(&self) -> String {
533        format!("({}, {}), ({}, {})", self.x1, self.y1, self.x2, self.y2)
534    }
535
536    pub fn extend(&mut self, horizontal_amount: usize, vertical_amount: usize, anchor: Anchor) {
537        match anchor {
538            Anchor::TopLeft => {
539                self.x1 = self.x1.saturating_sub(horizontal_amount);
540                self.y1 = self.y1.saturating_sub(vertical_amount);
541            }
542            Anchor::TopRight => {
543                self.x2 += horizontal_amount;
544                self.y1 = self.y1.saturating_sub(vertical_amount);
545            }
546            Anchor::BottomLeft => {
547                self.x1 = self.x1.saturating_sub(horizontal_amount);
548                self.y2 += vertical_amount;
549            }
550            Anchor::BottomRight => {
551                self.x2 += horizontal_amount;
552                self.y2 += vertical_amount;
553            }
554            Anchor::Center => {
555                let half_horizontal = horizontal_amount / 2;
556                let half_vertical = vertical_amount / 2;
557                self.x1 = self.x1.saturating_sub(half_horizontal);
558                self.y1 = self.y1.saturating_sub(half_vertical);
559                self.x2 += half_horizontal;
560                self.y2 += half_vertical;
561            }
562            Anchor::CenterTop => {
563                let half_horizontal = horizontal_amount / 2;
564                self.x1 = self.x1.saturating_sub(half_horizontal);
565                self.x2 += half_horizontal;
566                self.y1 = self.y1.saturating_sub(vertical_amount);
567            }
568            Anchor::CenterBottom => {
569                let half_horizontal = horizontal_amount / 2;
570                self.x1 = self.x1.saturating_sub(half_horizontal);
571                self.x2 += half_horizontal;
572                self.y2 += vertical_amount;
573            }
574            Anchor::CenterLeft => {
575                let half_vertical = vertical_amount / 2;
576                self.x1 = self.x1.saturating_sub(horizontal_amount);
577                self.y1 = self.y1.saturating_sub(half_vertical);
578                self.y2 += half_vertical;
579            }
580            Anchor::CenterRight => {
581                let half_vertical = vertical_amount / 2;
582                self.x2 += horizontal_amount;
583                self.y1 = self.y1.saturating_sub(half_vertical);
584                self.y2 += half_vertical;
585            }
586        }
587        self.validate();
588    }
589
590    pub fn contract(&mut self, horizontal_amount: usize, vertical_amount: usize, anchor: Anchor) {
591        match anchor {
592            Anchor::TopLeft => {
593                self.x1 += horizontal_amount;
594                self.y1 += vertical_amount;
595            }
596            Anchor::TopRight => {
597                self.x2 = self.x2.saturating_sub(horizontal_amount);
598                self.y1 += vertical_amount;
599            }
600            Anchor::BottomLeft => {
601                self.x1 += horizontal_amount;
602                self.y2 = self.y2.saturating_sub(vertical_amount);
603            }
604            Anchor::BottomRight => {
605                self.x2 = self.x2.saturating_sub(horizontal_amount);
606                self.y2 = self.y2.saturating_sub(vertical_amount);
607            }
608            Anchor::Center => {
609                let half_horizontal = horizontal_amount / 2;
610                let half_vertical = vertical_amount / 2;
611                self.x1 += half_horizontal;
612                self.y1 += half_vertical;
613                self.x2 = self.x2.saturating_sub(half_horizontal);
614                self.y2 = self.y2.saturating_sub(half_vertical);
615            }
616            Anchor::CenterTop => {
617                let half_horizontal = horizontal_amount / 2;
618                self.x1 += half_horizontal;
619                self.x2 = self.x2.saturating_sub(half_horizontal);
620                self.y1 += vertical_amount;
621            }
622            Anchor::CenterBottom => {
623                let half_horizontal = horizontal_amount / 2;
624                self.x1 += half_horizontal;
625                self.x2 = self.x2.saturating_sub(half_horizontal);
626                self.y2 = self.y2.saturating_sub(vertical_amount);
627            }
628            Anchor::CenterLeft => {
629                let half_vertical = vertical_amount / 2;
630                self.x1 += horizontal_amount;
631                self.y1 += half_vertical;
632                self.y2 = self.y2.saturating_sub(half_vertical);
633            }
634            Anchor::CenterRight => {
635                let half_vertical = vertical_amount / 2;
636                self.x2 = self.x2.saturating_sub(horizontal_amount);
637                self.y1 += half_vertical;
638                self.y2 = self.y2.saturating_sub(half_vertical);
639            }
640        }
641        self.validate();
642    }
643
644    pub fn move_to(&mut self, x: usize, y: usize, anchor: Anchor) {
645        match anchor {
646            Anchor::TopLeft => {
647                let width = self.width();
648                let height = self.height();
649                self.x1 = x;
650                self.y1 = y;
651                self.x2 = x + width;
652                self.y2 = y + height;
653            }
654            Anchor::TopRight => {
655                let width = self.width();
656                let height = self.height();
657                self.x2 = x;
658                self.y1 = y;
659                self.x1 = x - width;
660                self.y2 = y + height;
661            }
662            Anchor::BottomLeft => {
663                let width = self.width();
664                let height = self.height();
665                self.x1 = x;
666                self.y2 = y;
667                self.x2 = x + width;
668                self.y1 = y - height;
669            }
670            Anchor::BottomRight => {
671                let width = self.width();
672                let height = self.height();
673                self.x2 = x;
674                self.y2 = y;
675                self.x1 = x - width;
676                self.y1 = y - height;
677            }
678            Anchor::Center => {
679                let width = self.width();
680                let height = self.height();
681                let half_width = width / 2;
682                let half_height = height / 2;
683                self.x1 = x - half_width;
684                self.y1 = y - half_height;
685                self.x2 = x + half_width;
686                self.y2 = y + half_height;
687            }
688            Anchor::CenterTop => {
689                let width = self.width();
690                let height = self.height();
691                let half_width = width / 2;
692                self.x1 = x - half_width;
693                self.x2 = x + half_width;
694                self.y1 = y;
695                self.y2 = y + height;
696            }
697            Anchor::CenterBottom => {
698                let width = self.width();
699                let height = self.height();
700                let half_width = width / 2;
701                self.x1 = x - half_width;
702                self.x2 = x + half_width;
703                self.y2 = y;
704                self.y1 = y - height;
705            }
706            Anchor::CenterLeft => {
707                let width = self.width();
708                let height = self.height();
709                let half_height = height / 2;
710                self.x1 = x;
711                self.x2 = x + width;
712                self.y1 = y - half_height;
713                self.y2 = y + half_height;
714            }
715            Anchor::CenterRight => {
716                let width = self.width();
717                let height = self.height();
718                let half_height = height / 2;
719                self.x2 = x;
720                self.x1 = x - width;
721                self.y1 = y - half_height;
722                self.y2 = y + half_height;
723            }
724        }
725        self.validate();
726    }
727
728    pub fn move_by(&mut self, dx: isize, dy: isize) {
729        self.x1 = (self.x1 as isize + dx) as usize;
730        self.y1 = (self.y1 as isize + dy) as usize;
731        self.x2 = (self.x2 as isize + dx) as usize;
732        self.y2 = (self.y2 as isize + dy) as usize;
733        self.validate();
734    }
735
736    pub fn contains(&self, x: usize, y: usize) -> bool {
737        x >= self.x1 && x < self.x2 && y >= self.y1 && y < self.y2
738    }
739
740    pub fn contains_bounds(&self, other: &Bounds) -> bool {
741        self.contains(other.x1, other.y1) && self.contains(other.x2, other.y2)
742    }
743
744    pub fn intersects(&self, other: &Bounds) -> bool {
745        self.contains(other.x1, other.y1)
746            || self.contains(other.x2, other.y2)
747            || self.contains(other.x1, other.y2)
748            || self.contains(other.x2, other.y1)
749    }
750
751    pub fn intersection(&self, other: &Bounds) -> Option<Bounds> {
752        if self.intersects(other) {
753            Some(Bounds {
754                x1: self.x1.max(other.x1),
755                y1: self.y1.max(other.y1),
756                x2: self.x2.min(other.x2),
757                y2: self.y2.min(other.y2),
758            })
759        } else {
760            None
761        }
762    }
763
764    pub fn union(&self, other: &Bounds) -> Bounds {
765        Bounds {
766            x1: self.x1.min(other.x1),
767            y1: self.y1.min(other.y1),
768            x2: self.x2.max(other.x2),
769            y2: self.y2.max(other.y2),
770        }
771    }
772
773    pub fn translate(&self, dx: isize, dy: isize) -> Bounds {
774        Bounds {
775            x1: (self.x1 as isize + dx) as usize,
776            y1: (self.y1 as isize + dy) as usize,
777            x2: (self.x2 as isize + dx) as usize,
778            y2: (self.y2 as isize + dy) as usize,
779        }
780    }
781
782    pub fn center(&self) -> (usize, usize) {
783        ((self.x1 + self.x2) / 2, (self.y1 + self.y2) / 2)
784    }
785
786    pub fn center_x(&self) -> usize {
787        (self.x1 + self.x2) / 2
788    }
789
790    pub fn center_y(&self) -> usize {
791        (self.y1 + self.y2) / 2
792    }
793
794    pub fn top_left(&self) -> (usize, usize) {
795        (self.x1, self.y1)
796    }
797
798    pub fn top_right(&self) -> (usize, usize) {
799        (self.x2, self.y1)
800    }
801
802    pub fn bottom_left(&self) -> (usize, usize) {
803        (self.x1, self.y2)
804    }
805
806    pub fn bottom_right(&self) -> (usize, usize) {
807        (self.x2, self.y2)
808    }
809
810    pub fn top(&self) -> usize {
811        self.y1
812    }
813
814    pub fn bottom(&self) -> usize {
815        self.y2
816    }
817
818    pub fn left(&self) -> usize {
819        self.x1
820    }
821
822    pub fn right(&self) -> usize {
823        self.x2
824    }
825
826    pub fn center_top(&self) -> (usize, usize) {
827        ((self.x1 + self.x2) / 2, self.y1)
828    }
829
830    pub fn center_bottom(&self) -> (usize, usize) {
831        ((self.x1 + self.x2) / 2, self.y2)
832    }
833
834    pub fn center_left(&self) -> (usize, usize) {
835        (self.x1, (self.y1 + self.y2) / 2)
836    }
837
838    pub fn center_right(&self) -> (usize, usize) {
839        (self.x2, (self.y1 + self.y2) / 2)
840    }
841}
842
843pub fn calculate_initial_bounds(app_graph: &AppGraph, layout: &Layout) -> HashMap<String, Bounds> {
844    let mut bounds_map = HashMap::new();
845
846    fn dfs(
847        app_graph: &AppGraph,
848        layout_id: &str,
849        muxbox: &MuxBox,
850        parent_bounds: Bounds,
851        bounds_map: &mut HashMap<String, Bounds>,
852    ) {
853        let bounds = muxbox.absolute_bounds(Some(&parent_bounds));
854        bounds_map.insert(muxbox.id.clone(), bounds.clone());
855
856        if let Some(children) = &muxbox.children {
857            for child in children {
858                dfs(app_graph, layout_id, child, bounds.clone(), bounds_map);
859            }
860        }
861    }
862
863    let root_bounds = screen_bounds();
864    if let Some(children) = &layout.children {
865        for muxbox in children {
866            dfs(
867                app_graph,
868                &layout.id,
869                muxbox,
870                root_bounds.clone(),
871                &mut bounds_map,
872            );
873        }
874    }
875
876    bounds_map
877}
878
879pub fn adjust_bounds_with_constraints(
880    layout: &Layout,
881    mut bounds_map: HashMap<String, Bounds>,
882) -> HashMap<String, Bounds> {
883    fn apply_constraints(muxbox: &MuxBox, bounds: &mut Bounds) {
884        if let Some(min_width) = muxbox.min_width {
885            if bounds.width() < min_width {
886                bounds.extend(min_width - bounds.width(), 0, muxbox.anchor.clone());
887            }
888        }
889        if let Some(min_height) = muxbox.min_height {
890            if bounds.height() < min_height {
891                bounds.extend(0, min_height - bounds.height(), muxbox.anchor.clone());
892            }
893        }
894        if let Some(max_width) = muxbox.max_width {
895            if bounds.width() > max_width {
896                bounds.contract(bounds.width() - max_width, 0, muxbox.anchor.clone());
897            }
898        }
899        if let Some(max_height) = muxbox.max_height {
900            if bounds.height() > max_height {
901                bounds.contract(0, bounds.height() - max_height, muxbox.anchor.clone());
902            }
903        }
904    }
905
906    fn dfs(muxbox: &MuxBox, bounds_map: &mut HashMap<String, Bounds>) -> Bounds {
907        let mut bounds = bounds_map.remove(&muxbox.id).unwrap();
908        apply_constraints(muxbox, &mut bounds);
909        bounds_map.insert(muxbox.id.clone(), bounds.clone());
910
911        if let Some(children) = &muxbox.children {
912            for child in children {
913                let child_bounds = dfs(child, bounds_map);
914                bounds.x2 = bounds.x2.max(child_bounds.x2);
915                bounds.y2 = bounds.y2.max(child_bounds.y2);
916            }
917        }
918
919        bounds
920    }
921
922    fn revalidate_children(
923        muxbox: &MuxBox,
924        bounds_map: &mut HashMap<String, Bounds>,
925        parent_bounds: &Bounds,
926    ) {
927        if let Some(children) = &muxbox.children {
928            for child in children {
929                if let Some(child_bounds) = bounds_map.get_mut(&child.id) {
930                    // Ensure child bounds are within parent bounds
931                    if child_bounds.x2 > parent_bounds.x2 {
932                        child_bounds.x2 = parent_bounds.x2;
933                    }
934                    if child_bounds.y2 > parent_bounds.y2 {
935                        child_bounds.y2 = parent_bounds.y2;
936                    }
937                    if child_bounds.x1 < parent_bounds.x1 {
938                        child_bounds.x1 = parent_bounds.x1;
939                    }
940                    if child_bounds.y1 < parent_bounds.y1 {
941                        child_bounds.y1 = parent_bounds.y1;
942                    }
943                }
944                revalidate_children(child, bounds_map, parent_bounds);
945            }
946        }
947    }
948
949    if let Some(children) = &layout.children {
950        for muxbox in children {
951            let parent_bounds = dfs(muxbox, &mut bounds_map);
952            revalidate_children(muxbox, &mut bounds_map, &parent_bounds);
953        }
954    }
955
956    bounds_map
957}
958
959pub fn calculate_bounds_map(app_graph: &AppGraph, layout: &Layout) -> HashMap<String, Bounds> {
960    let bounds_map = calculate_initial_bounds(app_graph, layout);
961    adjust_bounds_with_constraints(layout, bounds_map)
962}
963
964use std::io::{Read, Write};
965use std::os::unix::net::UnixStream;
966
967pub fn send_json_to_socket(socket_path: &str, json: &str) -> Result<String, Box<dyn Error>> {
968    let mut stream = UnixStream::connect(socket_path)?;
969    stream.write_all(json.as_bytes())?;
970    let mut response = String::new();
971    stream.read_to_string(&mut response)?;
972    Ok(response)
973}
974
975#[cfg(test)]
976mod tests {
977    use super::*;
978
979    // === Config Tests ===
980
981    /// Tests that Config::new() creates a valid configuration with the specified frame delay.
982    /// This test demonstrates how to create a Config with proper validation.
983    #[test]
984    fn test_config_new_valid_frame_delay() {
985        let config = Config::new(60);
986        assert_eq!(config.frame_delay, 60);
987    }
988
989    /// Tests that Config::new() panics when frame_delay is zero.
990    /// This test demonstrates Config validation for invalid frame delays.
991    #[test]
992    #[should_panic(expected = "Validation error: frame_delay cannot be 0")]
993    fn test_config_new_zero_frame_delay_panics() {
994        Config::new(0);
995    }
996
997    /// Tests that Config::default() creates a configuration with default values.
998    /// This test demonstrates the default configuration settings.
999    #[test]
1000    fn test_config_default() {
1001        let config = Config::default();
1002        assert_eq!(config.frame_delay, 30);
1003    }
1004
1005    /// Tests that Config::validate() correctly identifies invalid configurations.
1006    /// This test demonstrates Config validation behavior.
1007    #[test]
1008    #[should_panic(expected = "Validation error: frame_delay cannot be 0")]
1009    fn test_config_validate_zero_frame_delay() {
1010        let config = Config {
1011            frame_delay: 0,
1012            locked: false,
1013        };
1014        config.validate();
1015    }
1016
1017    /// Tests that Config::validate() passes for valid configurations.
1018    /// This test demonstrates successful Config validation.
1019    #[test]
1020    fn test_config_validate_valid() {
1021        let config = Config {
1022            frame_delay: 16,
1023            locked: false,
1024        };
1025        config.validate(); // Should not panic
1026    }
1027
1028    /// Tests that Config implements Hash consistently.
1029    /// This test demonstrates that Configs with the same values hash to the same value.
1030    #[test]
1031    fn test_config_hash_consistency() {
1032        let config1 = Config::new(30);
1033        let config2 = Config::new(30);
1034        let config3 = Config::new(60);
1035
1036        use std::collections::hash_map::DefaultHasher;
1037        use std::hash::{Hash, Hasher};
1038
1039        let mut hasher1 = DefaultHasher::new();
1040        let mut hasher2 = DefaultHasher::new();
1041        let mut hasher3 = DefaultHasher::new();
1042
1043        config1.hash(&mut hasher1);
1044        config2.hash(&mut hasher2);
1045        config3.hash(&mut hasher3);
1046
1047        assert_eq!(hasher1.finish(), hasher2.finish());
1048        assert_ne!(hasher1.finish(), hasher3.finish());
1049    }
1050
1051    // === Bounds Tests ===
1052
1053    /// Tests that Bounds::new() creates bounds with correct coordinates.
1054    /// This test demonstrates basic Bounds construction.
1055    #[test]
1056    fn test_bounds_new() {
1057        let bounds = Bounds::new(10, 20, 100, 200);
1058        assert_eq!(bounds.x1, 10);
1059        assert_eq!(bounds.y1, 20);
1060        assert_eq!(bounds.x2, 100);
1061        assert_eq!(bounds.y2, 200);
1062    }
1063
1064    /// Tests that Bounds::validate() panics for invalid x coordinates.
1065    /// This test demonstrates Bounds validation for x-coordinate ordering.
1066    #[test]
1067    #[should_panic(expected = "Validation error: x1 (100) is greater than x2 (50)")]
1068    fn test_bounds_validate_invalid_x_coordinates() {
1069        let bounds = Bounds::new(100, 20, 50, 200);
1070        bounds.validate();
1071    }
1072
1073    /// Tests that Bounds::validate() panics for invalid y coordinates.
1074    /// This test demonstrates Bounds validation for y-coordinate ordering.
1075    #[test]
1076    #[should_panic(expected = "Validation error: y1 (200) is greater than y2 (100)")]
1077    fn test_bounds_validate_invalid_y_coordinates() {
1078        let bounds = Bounds::new(10, 200, 100, 100);
1079        bounds.validate();
1080    }
1081
1082    /// Tests that Bounds::validate() passes for valid bounds.
1083    /// This test demonstrates successful Bounds validation.
1084    #[test]
1085    fn test_bounds_validate_valid() {
1086        let bounds = Bounds::new(10, 20, 100, 200);
1087        bounds.validate(); // Should not panic
1088    }
1089
1090    /// Tests that Bounds::width() calculates width correctly.
1091    /// This test demonstrates the width calculation feature.
1092    #[test]
1093    fn test_bounds_width() {
1094        let bounds = Bounds::new(10, 20, 100, 200);
1095        assert_eq!(bounds.width(), 90);
1096    }
1097
1098    /// Tests that Bounds::height() calculates height correctly.
1099    /// This test demonstrates the height calculation feature.
1100    #[test]
1101    fn test_bounds_height() {
1102        let bounds = Bounds::new(10, 20, 100, 200);
1103        assert_eq!(bounds.height(), 180);
1104    }
1105
1106    /// Tests that Bounds::width() handles edge case where x1 equals x2.
1107    /// This test demonstrates edge case handling in width calculation.
1108    #[test]
1109    fn test_bounds_width_zero() {
1110        let bounds = Bounds::new(50, 20, 50, 200);
1111        assert_eq!(bounds.width(), 0);
1112    }
1113
1114    /// Tests that Bounds::height() handles edge case where y1 equals y2.
1115    /// This test demonstrates edge case handling in height calculation.
1116    #[test]
1117    fn test_bounds_height_zero() {
1118        let bounds = Bounds::new(10, 50, 100, 50);
1119        assert_eq!(bounds.height(), 0);
1120    }
1121
1122    /// Tests that Bounds::contains() correctly identifies points within bounds.
1123    /// This test demonstrates the point containment feature.
1124    #[test]
1125    fn test_bounds_contains() {
1126        let bounds = Bounds::new(10, 20, 100, 200);
1127        assert!(bounds.contains(50, 100));
1128        assert!(bounds.contains(10, 20)); // Edge case: top-left corner
1129        assert!(!bounds.contains(100, 200)); // Edge case: bottom-right corner (exclusive)
1130        assert!(!bounds.contains(5, 100)); // Outside left
1131        assert!(!bounds.contains(150, 100)); // Outside right
1132        assert!(!bounds.contains(50, 10)); // Outside top
1133        assert!(!bounds.contains(50, 250)); // Outside bottom
1134    }
1135
1136    /// Tests that Bounds::contains_bounds() correctly identifies bounds containment.
1137    /// This test demonstrates the bounds containment feature.
1138    #[test]
1139    fn test_bounds_contains_bounds() {
1140        let outer = Bounds::new(10, 20, 100, 200);
1141        let inner = Bounds::new(30, 40, 80, 180);
1142        let overlapping = Bounds::new(5, 15, 50, 100);
1143
1144        assert!(outer.contains_bounds(&inner));
1145        assert!(!outer.contains_bounds(&overlapping));
1146    }
1147
1148    /// Tests that Bounds::intersects() correctly identifies intersecting bounds.
1149    /// This test demonstrates the bounds intersection detection feature.
1150    #[test]
1151    fn test_bounds_intersects() {
1152        let bounds1 = Bounds::new(10, 20, 100, 200);
1153        let bounds2 = Bounds::new(50, 100, 150, 250); // Overlapping
1154        let bounds3 = Bounds::new(200, 300, 250, 350); // Non-overlapping
1155
1156        assert!(bounds1.intersects(&bounds2));
1157        assert!(!bounds1.intersects(&bounds3));
1158    }
1159
1160    /// Tests that Bounds::intersection() returns correct intersection bounds.
1161    /// This test demonstrates the bounds intersection calculation feature.
1162    #[test]
1163    fn test_bounds_intersection() {
1164        let bounds1 = Bounds::new(10, 20, 100, 200);
1165        let bounds2 = Bounds::new(50, 100, 150, 250);
1166        let bounds3 = Bounds::new(200, 300, 250, 350);
1167
1168        let intersection = bounds1.intersection(&bounds2);
1169        assert!(intersection.is_some());
1170        let intersection = intersection.unwrap();
1171        assert_eq!(intersection.x1, 50);
1172        assert_eq!(intersection.y1, 100);
1173        assert_eq!(intersection.x2, 100);
1174        assert_eq!(intersection.y2, 200);
1175
1176        assert!(bounds1.intersection(&bounds3).is_none());
1177    }
1178
1179    /// Tests that Bounds::union() returns correct union bounds.
1180    /// This test demonstrates the bounds union calculation feature.
1181    #[test]
1182    fn test_bounds_union() {
1183        let bounds1 = Bounds::new(10, 20, 100, 200);
1184        let bounds2 = Bounds::new(50, 100, 150, 250);
1185
1186        let union = bounds1.union(&bounds2);
1187        assert_eq!(union.x1, 10);
1188        assert_eq!(union.y1, 20);
1189        assert_eq!(union.x2, 150);
1190        assert_eq!(union.y2, 250);
1191    }
1192
1193    /// Tests that Bounds::translate() correctly translates bounds.
1194    /// This test demonstrates the bounds translation feature.
1195    #[test]
1196    fn test_bounds_translate() {
1197        let bounds = Bounds::new(10, 20, 100, 200);
1198        let translated = bounds.translate(5, -10);
1199        assert_eq!(translated.x1, 15);
1200        assert_eq!(translated.y1, 10);
1201        assert_eq!(translated.x2, 105);
1202        assert_eq!(translated.y2, 190);
1203    }
1204
1205    /// Tests that Bounds::center() returns correct center point.
1206    /// This test demonstrates the center calculation feature.
1207    #[test]
1208    fn test_bounds_center() {
1209        let bounds = Bounds::new(10, 20, 100, 200);
1210        let center = bounds.center();
1211        assert_eq!(center, (55, 110));
1212    }
1213
1214    /// Tests that Bounds::center_x() returns correct x center.
1215    /// This test demonstrates the x-center calculation feature.
1216    #[test]
1217    fn test_bounds_center_x() {
1218        let bounds = Bounds::new(10, 20, 100, 200);
1219        assert_eq!(bounds.center_x(), 55);
1220    }
1221
1222    /// Tests that Bounds::center_y() returns correct y center.
1223    /// This test demonstrates the y-center calculation feature.
1224    #[test]
1225    fn test_bounds_center_y() {
1226        let bounds = Bounds::new(10, 20, 100, 200);
1227        assert_eq!(bounds.center_y(), 110);
1228    }
1229
1230    /// Tests that Bounds::extend() correctly extends bounds in all directions.
1231    /// This test demonstrates the bounds extension feature with Center anchor.
1232    #[test]
1233    fn test_bounds_extend_center() {
1234        let mut bounds = Bounds::new(50, 50, 100, 100);
1235        bounds.extend(20, 10, Anchor::Center);
1236        assert_eq!(bounds.x1, 40);
1237        assert_eq!(bounds.y1, 45);
1238        assert_eq!(bounds.x2, 110);
1239        assert_eq!(bounds.y2, 105);
1240    }
1241
1242    /// Tests that Bounds::extend() correctly extends bounds with TopLeft anchor.
1243    /// This test demonstrates the bounds extension feature with TopLeft anchor.
1244    #[test]
1245    fn test_bounds_extend_top_left() {
1246        let mut bounds = Bounds::new(50, 50, 100, 100);
1247        bounds.extend(20, 10, Anchor::TopLeft);
1248        assert_eq!(bounds.x1, 30);
1249        assert_eq!(bounds.y1, 40);
1250        assert_eq!(bounds.x2, 100);
1251        assert_eq!(bounds.y2, 100);
1252    }
1253
1254    /// Tests that Bounds::contract() correctly contracts bounds.
1255    /// This test demonstrates the bounds contraction feature.
1256    #[test]
1257    fn test_bounds_contract_center() {
1258        let mut bounds = Bounds::new(50, 50, 100, 100);
1259        bounds.contract(10, 20, Anchor::Center);
1260        assert_eq!(bounds.x1, 55);
1261        assert_eq!(bounds.y1, 60);
1262        assert_eq!(bounds.x2, 95);
1263        assert_eq!(bounds.y2, 90);
1264    }
1265
1266    /// Tests that Bounds::move_to() correctly moves bounds to new position.
1267    /// This test demonstrates the bounds movement feature.
1268    #[test]
1269    fn test_bounds_move_to() {
1270        let mut bounds = Bounds::new(10, 20, 60, 70);
1271        bounds.move_to(100, 150, Anchor::TopLeft);
1272        assert_eq!(bounds.x1, 100);
1273        assert_eq!(bounds.y1, 150);
1274        assert_eq!(bounds.x2, 150);
1275        assert_eq!(bounds.y2, 200);
1276    }
1277
1278    /// Tests that Bounds::move_by() correctly moves bounds by offset.
1279    /// This test demonstrates the bounds offset movement feature.
1280    #[test]
1281    fn test_bounds_move_by() {
1282        let mut bounds = Bounds::new(10, 20, 60, 70);
1283        bounds.move_by(5, -10);
1284        assert_eq!(bounds.x1, 15);
1285        assert_eq!(bounds.y1, 10);
1286        assert_eq!(bounds.x2, 65);
1287        assert_eq!(bounds.y2, 60);
1288    }
1289
1290    /// Tests various anchor point getters.
1291    /// This test demonstrates the anchor point calculation features.
1292    #[test]
1293    fn test_bounds_anchor_points() {
1294        let bounds = Bounds::new(10, 20, 100, 200);
1295
1296        assert_eq!(bounds.top_left(), (10, 20));
1297        assert_eq!(bounds.top_right(), (100, 20));
1298        assert_eq!(bounds.bottom_left(), (10, 200));
1299        assert_eq!(bounds.bottom_right(), (100, 200));
1300        assert_eq!(bounds.center_top(), (55, 20));
1301        assert_eq!(bounds.center_bottom(), (55, 200));
1302        assert_eq!(bounds.center_left(), (10, 110));
1303        assert_eq!(bounds.center_right(), (100, 110));
1304        assert_eq!(bounds.top(), 20);
1305        assert_eq!(bounds.bottom(), 200);
1306        assert_eq!(bounds.left(), 10);
1307        assert_eq!(bounds.right(), 100);
1308    }
1309
1310    /// Tests that Bounds::to_string() formats bounds correctly.
1311    /// This test demonstrates the bounds string formatting feature.
1312    #[test]
1313    fn test_bounds_to_string() {
1314        let bounds = Bounds::new(10, 20, 100, 200);
1315        assert_eq!(bounds.to_string(), "(10, 20), (100, 200)");
1316    }
1317
1318    // === InputBounds Tests ===
1319
1320    /// Tests that InputBounds::to_bounds() converts percentage strings to absolute bounds.
1321    /// This test demonstrates the InputBounds to Bounds conversion feature.
1322    #[test]
1323    fn test_input_bounds_to_bounds() {
1324        let input_bounds = InputBounds {
1325            x1: "25%".to_string(),
1326            y1: "50%".to_string(),
1327            x2: "75%".to_string(),
1328            y2: "100%".to_string(),
1329        };
1330        let parent_bounds = Bounds::new(0, 0, 100, 200);
1331        let bounds = input_bounds.to_bounds(&parent_bounds);
1332
1333        assert_eq!(bounds.x1, 25);
1334        assert_eq!(bounds.y1, 100);
1335        assert_eq!(bounds.x2, 74); // 75% of 0-99 range = 74
1336        assert_eq!(bounds.y2, 199); // 100% of 0-199 range = 199
1337    }
1338
1339    // === Anchor Tests ===
1340
1341    /// Tests that Anchor::default() returns Center.
1342    /// This test demonstrates the default anchor behavior.
1343    #[test]
1344    fn test_anchor_default() {
1345        let anchor = Anchor::default();
1346        assert_eq!(anchor, Anchor::Center);
1347    }
1348
1349    // === ScreenBuffer Tests ===
1350
1351    /// Tests that ScreenBuffer::new_custom() creates a buffer with specified dimensions.
1352    /// This test demonstrates how to create a custom-sized screen buffer.
1353    #[test]
1354    fn test_screenbuffer_new() {
1355        let screen_buffer = ScreenBuffer::new_custom(5, 5);
1356        assert_eq!(screen_buffer.width, 5);
1357        assert_eq!(screen_buffer.height, 5);
1358        assert_eq!(screen_buffer.buffer.len(), 5);
1359        assert_eq!(screen_buffer.buffer[0].len(), 5);
1360    }
1361
1362    /// Tests that ScreenBuffer::clear() resets all cells to default values.
1363    /// This test demonstrates the screen buffer clearing feature.
1364    #[test]
1365    fn test_screenbuffer_clear() {
1366        let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1367        let test_cell = Cell {
1368            fg_color: String::from("red"),
1369            bg_color: String::from("blue"),
1370            ch: 'X',
1371        };
1372        screen_buffer.update(2, 2, test_cell.clone());
1373        screen_buffer.clear();
1374        for row in screen_buffer.buffer.iter() {
1375            for cell in row.iter() {
1376                assert_eq!(cell.fg_color, get_fg_color("white"));
1377                assert_eq!(cell.bg_color, get_bg_color("black"));
1378                assert_eq!(cell.ch, ' ');
1379            }
1380        }
1381    }
1382
1383    /// Tests that ScreenBuffer::update() correctly updates a cell.
1384    /// This test demonstrates the screen buffer cell update feature.
1385    #[test]
1386    fn test_screenbuffer_update() {
1387        let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1388        let test_cell = Cell {
1389            fg_color: String::from("red"),
1390            bg_color: String::from("blue"),
1391            ch: 'X',
1392        };
1393        screen_buffer.update(2, 2, test_cell.clone());
1394        assert_eq!(screen_buffer.get(2, 2).unwrap(), &test_cell);
1395    }
1396
1397    /// Tests that ScreenBuffer::get() returns correct cell references.
1398    /// This test demonstrates the screen buffer cell retrieval feature.
1399    #[test]
1400    fn test_screenbuffer_get() {
1401        let screen_buffer = ScreenBuffer::new_custom(5, 5);
1402        assert!(screen_buffer.get(6, 6).is_none());
1403        assert!(screen_buffer.get(3, 3).is_some());
1404    }
1405
1406    /// Tests that ScreenBuffer::update() ignores out-of-bounds coordinates.
1407    /// This test demonstrates bounds checking in screen buffer updates.
1408    #[test]
1409    fn test_screenbuffer_update_out_of_bounds() {
1410        let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1411        let test_cell = Cell {
1412            fg_color: String::from("red"),
1413            bg_color: String::from("blue"),
1414            ch: 'X',
1415        };
1416        screen_buffer.update(10, 10, test_cell); // Should not panic
1417        assert!(screen_buffer.get(10, 10).is_none());
1418    }
1419
1420    /// Tests that ScreenBuffer::resize() correctly resizes the buffer.
1421    /// This test demonstrates the screen buffer resizing feature.
1422    #[test]
1423    fn test_screenbuffer_resize() {
1424        let mut screen_buffer = ScreenBuffer::new_custom(5, 5);
1425        screen_buffer.resize(10, 8);
1426        assert_eq!(screen_buffer.width, 10);
1427        assert_eq!(screen_buffer.height, 8);
1428        assert_eq!(screen_buffer.buffer.len(), 8);
1429        assert_eq!(screen_buffer.buffer[0].len(), 10);
1430    }
1431
1432    /// Tests that ScreenBuffer::resize() handles shrinking correctly.
1433    /// This test demonstrates the screen buffer shrinking feature.
1434    #[test]
1435    fn test_screenbuffer_resize_shrink() {
1436        let mut screen_buffer = ScreenBuffer::new_custom(10, 10);
1437        screen_buffer.resize(5, 5);
1438        assert_eq!(screen_buffer.width, 5);
1439        assert_eq!(screen_buffer.height, 5);
1440        assert_eq!(screen_buffer.buffer.len(), 5);
1441        assert_eq!(screen_buffer.buffer[0].len(), 5);
1442    }
1443
1444    // === Helper Functions ===
1445
1446    /// Creates a test app context with a valid layout for testing.
1447    /// This helper ensures tests have a valid app context with layouts.
1448    fn create_test_app_context() -> AppContext {
1449        let current_dir = std::env::current_dir().expect("Failed to get current directory");
1450        let dashboard_path = current_dir.join("layouts/tests.yaml");
1451        let app = crate::load_app_from_yaml(dashboard_path.to_str().unwrap())
1452            .expect("Failed to load app");
1453        AppContext::new(app, Config::default())
1454    }
1455
1456    // === SocketFunction Tests ===
1457
1458    /// Tests that run_socket_function() correctly handles ReplaceBoxContent.
1459    /// This test demonstrates socket function message processing.
1460    #[test]
1461    fn test_run_socket_function_replace_muxbox_content() {
1462        let app_context = create_test_app_context();
1463        let socket_function = SocketFunction::ReplaceBoxContent {
1464            box_id: "test_muxbox".to_string(),
1465            success: true,
1466            content: "Test content".to_string(),
1467        };
1468
1469        let result = run_socket_function(socket_function, &app_context);
1470        assert!(result.is_ok());
1471
1472        let (_, messages) = result.unwrap();
1473        assert_eq!(messages.len(), 1);
1474        match &messages[0] {
1475            crate::Message::MuxBoxOutputUpdate(muxbox_id, success, content) => {
1476                assert_eq!(muxbox_id, "test_muxbox");
1477                assert_eq!(*success, true);
1478                assert_eq!(content, "Test content");
1479            }
1480            _ => panic!("Expected MuxBoxOutputUpdate message"),
1481        }
1482    }
1483
1484    /// Tests that run_socket_function() correctly handles SwitchActiveLayout.
1485    /// This test demonstrates socket function layout switching.
1486    #[test]
1487    fn test_run_socket_function_switch_active_layout() {
1488        let app_context = create_test_app_context();
1489        let socket_function = SocketFunction::SwitchActiveLayout {
1490            layout_id: "new_layout".to_string(),
1491        };
1492
1493        let result = run_socket_function(socket_function, &app_context);
1494        assert!(result.is_ok());
1495
1496        let (_, messages) = result.unwrap();
1497        assert_eq!(messages.len(), 1);
1498        match &messages[0] {
1499            crate::Message::SwitchActiveLayout(layout_id) => {
1500                assert_eq!(layout_id, "new_layout");
1501            }
1502            _ => panic!("Expected SwitchActiveLayout message"),
1503        }
1504    }
1505
1506    // === Cell Tests ===
1507
1508    /// Tests that Cell implements Clone and PartialEq correctly.
1509    /// This test demonstrates Cell trait implementations.
1510    #[test]
1511    fn test_cell_clone_and_eq() {
1512        let cell1 = Cell {
1513            fg_color: "red".to_string(),
1514            bg_color: "blue".to_string(),
1515            ch: 'X',
1516        };
1517        let cell2 = cell1.clone();
1518        assert_eq!(cell1, cell2);
1519
1520        let cell3 = Cell {
1521            fg_color: "green".to_string(),
1522            bg_color: "blue".to_string(),
1523            ch: 'X',
1524        };
1525        assert_ne!(cell1, cell3);
1526    }
1527
1528    /// Test send_json_to_socket function
1529    #[test]
1530    fn test_send_json_to_socket_function() {
1531        use std::os::unix::net::UnixListener;
1532        use std::thread;
1533        use std::time::Duration;
1534
1535        let socket_path = "/tmp/test_send_json.sock";
1536        let _ = std::fs::remove_file(socket_path);
1537
1538        // Start a simple test server
1539        let server_socket_path = socket_path.to_string();
1540        let server_handle = thread::spawn(move || {
1541            match UnixListener::bind(&server_socket_path) {
1542                Ok(listener) => {
1543                    // Set a timeout to prevent hanging
1544                    if let Some(Ok(mut stream)) = listener.incoming().next() {
1545                        let mut buffer = Vec::new();
1546                        let mut temp_buffer = [0; 1024];
1547
1548                        // Read data in chunks to avoid hanging on read_to_string
1549                        match stream.read(&mut temp_buffer) {
1550                            Ok(n) => {
1551                                buffer.extend_from_slice(&temp_buffer[..n]);
1552                                let _ = stream.write_all(b"Test Response");
1553                                String::from_utf8_lossy(&buffer).to_string()
1554                            }
1555                            Err(_) => String::new(),
1556                        }
1557                    } else {
1558                        String::new()
1559                    }
1560                }
1561                Err(_) => String::new(),
1562            }
1563        });
1564
1565        // Give server time to start
1566        thread::sleep(Duration::from_millis(100));
1567
1568        // Test send_json_to_socket
1569        let test_json = r#"{"test": "message"}"#;
1570        let result = send_json_to_socket(socket_path, test_json);
1571
1572        // The test is successful if either:
1573        // 1. The connection succeeds and we get the expected response
1574        // 2. The connection fails (which can happen in CI environments)
1575        match result {
1576            Ok(response) => {
1577                assert_eq!(response, "Test Response");
1578
1579                // Verify server received the correct message
1580                let received_message = server_handle.join().unwrap();
1581                assert_eq!(received_message, test_json);
1582            }
1583            Err(_) => {
1584                // Connection failed - this can happen in CI environments
1585                // The important thing is that the function doesn't panic
1586                let _ = server_handle.join();
1587            }
1588        }
1589
1590        // Clean up
1591        let _ = std::fs::remove_file(socket_path);
1592    }
1593}