Skip to main content

boxmux_lib/
thread_manager.rs

1use crate::model::app::AppContext;
2// T0325: ExecuteChoice cleanup - keeping Choice import for ChoiceScriptRunner
3use crate::{FieldUpdate, MuxBox, Updatable};
4use bincode;
5use log::error;
6use std::collections::hash_map::DefaultHasher;
7use std::collections::HashMap;
8use std::hash::{Hash, Hasher};
9use std::sync::mpsc::{self, Sender};
10use std::thread;
11use uuid::Uuid;
12
13#[derive(Debug, Clone, PartialEq, Eq)]
14pub enum Message {
15    Exit,
16    Terminate,
17    Pause,
18    Start,
19    NextMuxBox(),
20    PreviousMuxBox(),
21    ScrollMuxBoxDown(),
22    ScrollMuxBoxUp(),
23    ScrollMuxBoxLeft(),
24    ScrollMuxBoxRight(),
25    ScrollMuxBoxPageUp(),
26    ScrollMuxBoxPageDown(),
27    ScrollMuxBoxPageLeft(),
28    ScrollMuxBoxPageRight(),
29    ScrollMuxBoxToBeginning(), // Home key - scroll to beginning horizontally
30    ScrollMuxBoxToEnd(),       // End key - scroll to end horizontally
31    ScrollMuxBoxToTop(),       // Ctrl+Home - scroll to top vertically
32    ScrollMuxBoxToBottom(),    // Ctrl+End - scroll to bottom vertically
33    CopyFocusedMuxBoxContent(),
34    Resize,
35    RedrawMuxBox(String),
36    RedrawApp,
37    RedrawAppDiff, // Redraw entire app using diff-based rendering (no screen clear)
38    MuxBoxEventRefresh(String),
39    MuxBoxScriptUpdate(String, Vec<String>),
40    ReplaceMuxBox(String, MuxBox),
41    StopBoxRefresh(String),
42    StartBoxRefresh(String),
43    SwitchActiveLayout(String),
44    KeyPress(String),
45    ExecuteHotKeyChoice(String),
46    MouseClick(u16, u16),                          // x, y coordinates
47    MouseMove(u16, u16), // x, y coordinates - mouse movement for hover detection
48    MouseDragStart(u16, u16), // x, y coordinates - start drag
49    MouseDrag(u16, u16), // x, y coordinates - continue drag
50    MouseDragEnd(u16, u16), // x, y coordinates - end drag
51    // Mouse wheel scroll, carrying the cursor position so it scrolls the box
52    // UNDER the pointer (hovered), regardless of which box has focus.
53    MouseScrollUp(u16, u16),
54    MouseScrollDown(u16, u16),
55    MouseScrollLeft(u16, u16),
56    MouseScrollRight(u16, u16),
57    MuxBoxBorderDrag(String, u16, u16), // muxbox_id, x, y coordinates - resize muxbox
58    MuxBoxResizeComplete(String), // muxbox_id - save changes to YAML
59    MuxBoxMove(String, u16, u16), // muxbox_id, x, y coordinates - move muxbox
60    MuxBoxMoveComplete(String), // muxbox_id - save position changes to YAML
61    SaveYamlState,       // F0200: Trigger complete YAML state persistence
62    SaveActiveLayout(String), // F0200: Save active layout to YAML
63    SaveMuxBoxContent(String, String), // F0200: Save muxbox content to YAML
64    SaveMuxBoxScroll(String, usize, usize), // F0200: Save muxbox scroll position
65    PTYInput(String, String), // muxbox_id, input_text
66    PTYInputWithModes(String, String, bool, bool), // F0309: muxbox_id, input_text, cursor_key_mode, keypad_mode
67    PTYMouseEvent(
68        String,
69        crossterm::event::MouseEventKind,
70        u16,
71        u16,
72        crossterm::event::KeyModifiers,
73    ), // F0310: muxbox_id, kind, column, row, modifiers
74    ExternalMessage(String),
75    AddBox(String, MuxBox),
76    RemoveBox(String),
77    // F0203: Multi-Stream Input Tabs messages
78    SwitchTab(String, usize),       // muxbox_id, tab_index
79    ScrollTabsLeft(String),         // muxbox_id - scroll tabs left
80    ScrollTabsRight(String),        // muxbox_id - scroll tabs right
81    SwitchToStream(String, String), // muxbox_id, stream_id
82    AddStream(String, crate::model::common::StreamSource), // muxbox_id, stream
83    RemoveStream(String, String),   // muxbox_id, stream_id
84    CloseTab(String, String), // muxbox_id, stream_id - F0219: Close button for redirected tabs
85    UpdateStreamContent(String, String, String), // muxbox_id, stream_id, content
86    // T0309: UNIFIED EXECUTION ARCHITECTURE - New message types for unified execution system
87    ExecuteScriptMessage(crate::model::common::ExecuteScript), // Universal script execution entry point
88    StreamUpdateMessage(crate::model::common::StreamUpdate),   // Universal stream content updates
89    SourceActionMessage(crate::model::common::SourceAction),   // Source lifecycle management
90}
91
92impl Hash for Message {
93    fn hash<H: Hasher>(&self, state: &mut H) {
94        match self {
95            Message::Exit => "exit".hash(state),
96            Message::Terminate => "terminate".hash(state),
97            Message::NextMuxBox() => "next_muxbox".hash(state),
98            Message::PreviousMuxBox() => "previous_muxbox".hash(state),
99            Message::Resize => "resize".hash(state),
100            Message::RedrawMuxBox(muxbox_id) => {
101                "redraw_muxbox".hash(state);
102                muxbox_id.hash(state);
103            }
104            Message::RedrawApp => "redraw_app".hash(state),
105            Message::RedrawAppDiff => "redraw_app_diff".hash(state),
106            Message::SwitchActiveLayout(layout_id) => {
107                "switch_active_layout".hash(state);
108                layout_id.hash(state);
109            }
110            Message::MuxBoxEventRefresh(muxbox_id) => {
111                "muxbox_event_refresh".hash(state);
112                muxbox_id.hash(state);
113            }
114            Message::ScrollMuxBoxDown() => "scroll_muxbox_down".hash(state),
115            Message::ScrollMuxBoxUp() => "scroll_muxbox_up".hash(state),
116            Message::ScrollMuxBoxLeft() => "scroll_muxbox_left".hash(state),
117            Message::ScrollMuxBoxRight() => "scroll_muxbox_right".hash(state),
118            Message::ScrollMuxBoxPageUp() => "scroll_muxbox_page_up".hash(state),
119            Message::ScrollMuxBoxPageDown() => "scroll_muxbox_page_down".hash(state),
120            Message::ScrollMuxBoxPageLeft() => "scroll_muxbox_page_left".hash(state),
121            Message::ScrollMuxBoxPageRight() => "scroll_muxbox_page_right".hash(state),
122            Message::ScrollMuxBoxToBeginning() => "scroll_muxbox_to_beginning".hash(state),
123            Message::ScrollMuxBoxToEnd() => "scroll_muxbox_to_end".hash(state),
124            Message::ScrollMuxBoxToTop() => "scroll_muxbox_to_top".hash(state),
125            Message::ScrollMuxBoxToBottom() => "scroll_muxbox_to_bottom".hash(state),
126            Message::CopyFocusedMuxBoxContent() => "copy_focused_muxbox_content".hash(state),
127            Message::MuxBoxScriptUpdate(muxbox_id, script) => {
128                "muxbox_script_update".hash(state);
129                muxbox_id.hash(state);
130                script.hash(state);
131            }
132            Message::ReplaceMuxBox(muxbox_id, muxbox) => {
133                "replace_muxbox".hash(state);
134                muxbox_id.hash(state);
135                muxbox.hash(state);
136            }
137            Message::KeyPress(pressed_key) => {
138                "key_press".hash(state);
139                pressed_key.hash(state);
140            }
141            Message::ExecuteHotKeyChoice(choice_id) => {
142                "execute_hot_key_choice".hash(state);
143                choice_id.hash(state);
144            }
145            Message::MouseClick(x, y) => {
146                "mouse_click".hash(state);
147                x.hash(state);
148                y.hash(state);
149            }
150            Message::MouseMove(x, y) => {
151                "mouse_move".hash(state);
152                x.hash(state);
153                y.hash(state);
154            }
155            Message::MouseDragStart(x, y) => {
156                "mouse_drag_start".hash(state);
157                x.hash(state);
158                y.hash(state);
159            }
160            Message::MouseDrag(x, y) => {
161                "mouse_drag".hash(state);
162                x.hash(state);
163                y.hash(state);
164            }
165            Message::MouseScrollUp(x, y) => {
166                "mouse_scroll_up".hash(state);
167                x.hash(state);
168                y.hash(state);
169            }
170            Message::MouseScrollDown(x, y) => {
171                "mouse_scroll_down".hash(state);
172                x.hash(state);
173                y.hash(state);
174            }
175            Message::MouseScrollLeft(x, y) => {
176                "mouse_scroll_left".hash(state);
177                x.hash(state);
178                y.hash(state);
179            }
180            Message::MouseScrollRight(x, y) => {
181                "mouse_scroll_right".hash(state);
182                x.hash(state);
183                y.hash(state);
184            }
185            Message::MouseDragEnd(x, y) => {
186                "mouse_drag_end".hash(state);
187                x.hash(state);
188                y.hash(state);
189            }
190            Message::MuxBoxBorderDrag(muxbox_id, x, y) => {
191                "muxbox_border_drag".hash(state);
192                muxbox_id.hash(state);
193                x.hash(state);
194                y.hash(state);
195            }
196            Message::MuxBoxResizeComplete(muxbox_id) => {
197                "muxbox_resize_complete".hash(state);
198                muxbox_id.hash(state);
199            }
200            Message::MuxBoxMove(muxbox_id, x, y) => {
201                "muxbox_move".hash(state);
202                muxbox_id.hash(state);
203                x.hash(state);
204                y.hash(state);
205            }
206            Message::MuxBoxMoveComplete(muxbox_id) => {
207                "muxbox_move_complete".hash(state);
208                muxbox_id.hash(state);
209            }
210            Message::SaveYamlState => "save_yaml_state".hash(state),
211            Message::SaveActiveLayout(layout_id) => {
212                "save_active_layout".hash(state);
213                layout_id.hash(state);
214            }
215            Message::SaveMuxBoxContent(muxbox_id, content) => {
216                "save_muxbox_content".hash(state);
217                muxbox_id.hash(state);
218                content.hash(state);
219            }
220            Message::SaveMuxBoxScroll(muxbox_id, x, y) => {
221                "save_muxbox_scroll".hash(state);
222                muxbox_id.hash(state);
223                x.hash(state);
224                y.hash(state);
225            }
226            Message::PTYInput(muxbox_id, input) => {
227                "pty_input".hash(state);
228                muxbox_id.hash(state);
229                input.hash(state);
230            }
231            Message::PTYInputWithModes(muxbox_id, input, cursor_key_mode, keypad_mode) => {
232                "pty_input_with_modes".hash(state);
233                muxbox_id.hash(state);
234                input.hash(state);
235                cursor_key_mode.hash(state);
236                keypad_mode.hash(state);
237            }
238            Message::PTYMouseEvent(muxbox_id, kind, column, row, modifiers) => {
239                "pty_mouse_event".hash(state);
240                muxbox_id.hash(state);
241                // Hash the discriminant of MouseEventKind enum
242                std::mem::discriminant(kind).hash(state);
243                column.hash(state);
244                row.hash(state);
245                // Hash KeyModifiers as bits
246                modifiers.bits().hash(state);
247            }
248            Message::Pause => "pause".hash(state),
249            Message::ExternalMessage(msg) => {
250                "external_message".hash(state);
251                msg.hash(state);
252            }
253            Message::Start => "start".hash(state),
254            Message::StopBoxRefresh(box_id) => {
255                "stop_box_refresh".hash(state);
256                box_id.hash(state);
257            }
258            Message::StartBoxRefresh(box_id) => {
259                "start_box_refresh".hash(state);
260                box_id.hash(state);
261            }
262            Message::AddBox(box_id, muxbox) => {
263                "add_box".hash(state);
264                box_id.hash(state);
265                muxbox.hash(state);
266            }
267            Message::RemoveBox(box_id) => {
268                "remove_box".hash(state);
269                box_id.hash(state);
270            }
271            // F0203: Multi-Stream Input Tabs hash implementations
272            Message::SwitchTab(muxbox_id, tab_index) => {
273                "switch_tab".hash(state);
274                muxbox_id.hash(state);
275                tab_index.hash(state);
276            }
277            Message::ScrollTabsLeft(muxbox_id) => {
278                "scroll_tabs_left".hash(state);
279                muxbox_id.hash(state);
280            }
281            Message::ScrollTabsRight(muxbox_id) => {
282                "scroll_tabs_right".hash(state);
283                muxbox_id.hash(state);
284            }
285            Message::SwitchToStream(muxbox_id, stream_id) => {
286                "switch_to_stream".hash(state);
287                muxbox_id.hash(state);
288                stream_id.hash(state);
289            }
290            Message::AddStream(muxbox_id, stream) => {
291                "add_stream".hash(state);
292                muxbox_id.hash(state);
293                stream.hash(state);
294            }
295            Message::RemoveStream(muxbox_id, stream_id) => {
296                "remove_stream".hash(state);
297                muxbox_id.hash(state);
298                stream_id.hash(state);
299            }
300            Message::CloseTab(muxbox_id, stream_id) => {
301                "close_tab".hash(state);
302                muxbox_id.hash(state);
303                stream_id.hash(state);
304            }
305            Message::UpdateStreamContent(muxbox_id, stream_id, content) => {
306                "update_stream_content".hash(state);
307                muxbox_id.hash(state);
308                stream_id.hash(state);
309                content.hash(state);
310            }
311            // T0309: UNIFIED EXECUTION ARCHITECTURE - Hash implementations for new message types
312            Message::ExecuteScriptMessage(execute_script) => {
313                "execute_script_message".hash(state);
314                execute_script.hash(state);
315            }
316            Message::StreamUpdateMessage(stream_update) => {
317                "stream_update_message".hash(state);
318                stream_update.hash(state);
319            }
320            Message::SourceActionMessage(source_action) => {
321                "source_action_message".hash(state);
322                source_action.hash(state);
323            }
324        }
325    }
326}
327
328pub trait Runnable: Send + 'static {
329    fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>>;
330    fn receive_updates(&mut self) -> (AppContext, Vec<Message>);
331    fn process(&mut self, app_context: AppContext, messages: Vec<Message>);
332
333    fn update_app_context(&mut self, app_context: AppContext);
334    fn set_uuid(&mut self, uuid: Uuid);
335    fn get_uuid(&self) -> Uuid;
336    fn set_app_context_sender(
337        &mut self,
338        app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
339    );
340    fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>);
341    fn set_app_context_receiver(
342        &mut self,
343        app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
344    );
345    fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>);
346    fn get_app_context(&self) -> &AppContext;
347    fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>;
348    fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>>;
349
350    fn send_app_context_update(&self, old_app_context: AppContext);
351    fn send_message(&self, msg: Message);
352}
353
354#[derive(Debug, Clone, PartialEq, Eq)]
355pub enum RunnableState {
356    Created,
357    Running,
358    Paused,
359    Terminated,
360}
361
362pub struct RunnableImpl {
363    pub app_context: AppContext,
364    uuid: Uuid,
365    running_state: RunnableState,
366    app_context_sender: Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>,
367    message_sender: Option<mpsc::Sender<(Uuid, Message)>>,
368    app_context_receiver: Option<mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>>,
369    message_receiver: Option<mpsc::Receiver<(Uuid, Message)>>,
370}
371
372impl RunnableImpl {
373    pub fn new(app_context: AppContext) -> Self {
374        RunnableImpl {
375            app_context,
376            uuid: Uuid::new_v4(),
377            running_state: RunnableState::Created,
378            app_context_sender: None,
379            message_sender: None,
380            app_context_receiver: None,
381            message_receiver: None,
382        }
383    }
384
385    pub fn get_message_sender(&self) -> Option<&mpsc::Sender<(Uuid, Message)>> {
386        self.message_sender.as_ref()
387    }
388
389    pub fn get_message_sender_option_ref(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
390        &self.message_sender
391    }
392
393    pub fn _run(
394        &mut self,
395        process_fn: &mut dyn FnMut(&mut Self, AppContext, Vec<Message>) -> (bool, AppContext),
396    ) -> Result<bool, Box<dyn std::error::Error>> {
397        let (updated_app_context, new_messages) = self.receive_updates();
398        let original_app_context = updated_app_context.clone();
399        let mut should_continue = true;
400        for message in new_messages.iter() {
401            match message {
402                Message::Exit => {
403                    self.running_state = RunnableState::Terminated;
404                    return Ok(false);
405                }
406                Message::Terminate => {
407                    self.running_state = RunnableState::Terminated;
408                    return Ok(false);
409                }
410                Message::Pause => {
411                    self.running_state = RunnableState::Paused;
412                }
413                Message::Start => {
414                    self.running_state = RunnableState::Running;
415                }
416                _ => {}
417            }
418        }
419
420        //keep app_context in sync even if not running
421        if updated_app_context != self.app_context {
422            self.app_context = updated_app_context;
423        }
424
425        log::trace!(
426            "RunnableImpl _run: state={:?}, messages={}",
427            self.running_state,
428            new_messages.len()
429        );
430        if self.running_state == RunnableState::Running {
431            log::trace!(
432                "RunnableImpl calling process function with {} messages",
433                new_messages.len()
434            );
435            let (process_should_continue, result_app_context) =
436                process_fn(self, self.app_context.clone(), new_messages);
437            if result_app_context != original_app_context {
438                self.app_context = result_app_context;
439                self.send_app_context_update(original_app_context);
440            }
441            should_continue = process_should_continue;
442            log::trace!(
443                "RunnableImpl process function returned should_continue={}",
444                should_continue
445            );
446        } else {
447            log::debug!(
448                "RunnableImpl NOT calling process function - state is {:?}",
449                self.running_state
450            );
451        }
452
453        if !should_continue {
454            return Ok(false);
455        }
456        Ok(true)
457    }
458}
459
460impl Runnable for RunnableImpl {
461    fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
462        let mut app_context_updates = Vec::new();
463        let mut new_messages = Vec::new();
464
465        if let Some(ref app_context_receiver) = self.app_context_receiver {
466            while let Ok((_, received_field_updates)) = app_context_receiver.try_recv() {
467                if !received_field_updates.is_empty() {
468                    log::trace!(
469                        "Received app_context update: {:?} in thread {}",
470                        received_field_updates,
471                        self.uuid
472                    );
473                    app_context_updates = received_field_updates;
474                }
475            }
476        }
477
478        let mut updated_app_context = self.app_context.clone();
479        updated_app_context.apply_updates(app_context_updates);
480
481        if let Some(ref message_receiver) = self.message_receiver {
482            while let Ok((_, message)) = message_receiver.try_recv() {
483                new_messages.push(message);
484            }
485        }
486
487        (updated_app_context, new_messages)
488    }
489
490    fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
491        // Default implementation: update app context and handle basic messages
492        self.update_app_context(app_context);
493
494        // Process any messages that need handling
495        for message in messages {
496            match message {
497                Message::Terminate => {
498                    self.running_state = RunnableState::Terminated;
499                }
500                Message::Pause => {
501                    self.running_state = RunnableState::Paused;
502                }
503                Message::Start => {
504                    self.running_state = RunnableState::Running;
505                }
506                Message::ExecuteScriptMessage(execute_script) => {
507                    log::info!("ThreadManager processing ExecuteScript for target_box_id: {}, execution_mode: {:?}", 
508                               execute_script.target_box_id, execute_script.execution_mode);
509
510                    self.handle_execute_script(execute_script);
511                }
512                _ => {
513                    // Other messages are handled by specific implementations
514                }
515            }
516        }
517    }
518
519    fn update_app_context(&mut self, app_context: AppContext) {
520        let old_app_context = self.app_context.clone();
521        self.app_context = app_context;
522        self.send_app_context_update(old_app_context);
523    }
524
525    fn set_uuid(&mut self, uuid: Uuid) {
526        self.uuid = uuid;
527    }
528
529    fn get_uuid(&self) -> Uuid {
530        self.uuid
531    }
532
533    fn set_app_context_sender(&mut self, app_context_sender: Sender<(Uuid, Vec<FieldUpdate>)>) {
534        self.app_context_sender = Some(app_context_sender);
535    }
536
537    fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
538        self.message_sender = Some(message_sender);
539    }
540
541    fn set_app_context_receiver(
542        &mut self,
543        app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
544    ) {
545        self.app_context_receiver = Some(app_context_receiver);
546    }
547
548    fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
549        self.message_receiver = Some(message_receiver);
550    }
551
552    fn get_app_context(&self) -> &AppContext {
553        &self.app_context
554    }
555
556    fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
557        &self.app_context_sender
558    }
559
560    fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
561        &self.message_sender
562    }
563
564    fn send_app_context_update(&self, old_app_context: AppContext) {
565        if let Some(ref app_context_sender) = self.get_app_context_sender() {
566            if let Err(e) = app_context_sender.send((
567                self.get_uuid(),
568                self.get_app_context().generate_diff(&old_app_context),
569            )) {
570                error!("Failed to send update to main thread: {}", e);
571            }
572        }
573    }
574
575    fn send_message(&self, msg: Message) {
576        if let Some(message_sender) = self.get_message_sender() {
577            if let Err(e) = message_sender.send((self.get_uuid(), msg)) {
578                error!("Failed to send message to main thread: {}", e);
579            }
580        }
581    }
582
583    fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
584        // This Runnable implementation doesn't need a run loop - it's handled by ThreadManager
585        // Return false to indicate no continuous processing needed
586        Ok(false)
587    }
588}
589
590impl RunnableImpl {
591    fn handle_execute_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
592        use crate::model::common::ExecutionMode;
593
594        log::info!(
595            "T0315 FIXED: ThreadManager properly handling ExecuteScript for target_box: {}",
596            execute_script.target_box_id
597        );
598
599        // Use UpdateStreamContent to create/update the output stream
600        match execute_script.execution_mode {
601            ExecutionMode::Immediate => {
602                log::info!("T0315: Immediate execution - running script synchronously");
603                self.execute_immediate_script(execute_script);
604            }
605            ExecutionMode::Thread => {
606                log::info!("T0315: Thread execution - dispatching to thread pool");
607                self.execute_threaded_script(execute_script);
608            }
609            ExecutionMode::Pty => {
610                // REMOVED DEAD CODE: PTY execution now properly routed to PTYManager, never reaches ThreadManager
611                panic!("PTY ExecuteScript reached ThreadManager - this indicates a critical routing bug that should never happen");
612            }
613        }
614    }
615
616    fn execute_immediate_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
617
618        // Run the script synchronously
619        let output = crate::utils::detached_command("sh")
620            .arg("-c")
621            .arg(execute_script.script.join(" "))
622            // Detached from the terminal (own session, null stdin) so it can never
623            // reset boxmux's raw mode / mouse tracking; stdout/stderr are captured.
624            .output();
625
626        let content = match output {
627            Ok(output) => {
628                let stdout = String::from_utf8_lossy(&output.stdout);
629                let stderr = String::from_utf8_lossy(&output.stderr);
630                if stderr.is_empty() {
631                    stdout.to_string()
632                } else {
633                    format!("{}\n{}", stdout, stderr)
634                }
635            }
636            Err(e) => {
637                format!("Error executing script: {}", e)
638            }
639        };
640
641        // Use stream_id from ExecuteScript (already registered in source registry)
642        let stream_id = execute_script.stream_id.clone();
643
644        // Send result via StreamUpdate with target_box_id for auto-creation
645        // REDIRECT FIX: Use redirect destination if specified
646        let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
647            log::info!(
648                "THREADMANAGER REDIRECT FIX UNKNOWN: Using redirect destination: {} (was {})",
649                redirect_to,
650                execute_script.target_box_id
651            );
652            redirect_to.clone()
653        } else {
654            log::info!(
655                "THREADMANAGER REDIRECT FIX UNKNOWN: No redirect, using source box: {}",
656                execute_script.target_box_id
657            );
658            execute_script.target_box_id.clone()
659        };
660
661        let stream_update = crate::model::common::StreamUpdate {
662            stream_id: stream_id.clone(),
663            target_box_id,
664            content_update: content,
665            source_state: crate::model::common::SourceState::Batch(
666                crate::model::common::BatchSourceState {
667                    task_id: stream_id.clone(),
668                    queue_wait_time: std::time::Duration::from_millis(0),
669                    execution_time: std::time::Duration::from_millis(50), // Immediate scripts are very fast
670                    exit_code: Some(0),
671                    status: crate::model::common::BatchStatus::Completed,
672                },
673            ),
674            execution_mode: execute_script.execution_mode,
675        };
676
677        self.send_message(Message::StreamUpdateMessage(stream_update));
678    }
679
680    fn execute_threaded_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
681        use std::thread;
682
683        log::info!("T0315: Thread execution - spawning background thread for script");
684
685        // Let PTYManager generate and hold its own stream ID - remove ThreadManager stream ID generation
686        let target_box_id = execute_script.target_box_id.clone();
687        let script = execute_script.script.clone();
688        let execution_mode = execute_script.execution_mode.clone();
689        let stream_id = execute_script.stream_id.clone();
690
691        // Send initial "started" update using stream_id from ExecuteScript
692        let start_update = crate::model::common::StreamUpdate {
693            stream_id: stream_id.clone(),
694            target_box_id: target_box_id.clone(),
695            content_update: format!("Starting thread execution: {}\n", script.join(" ")),
696            source_state: crate::model::common::SourceState::Thread(
697                crate::model::common::ThreadSourceState {
698                    thread_id: "pending".to_string(),
699                    execution_time: std::time::Duration::from_millis(0),
700                    exit_code: None,
701                    status: crate::model::common::ExecutionThreadStatus::Running,
702                },
703            ),
704            execution_mode: execution_mode.clone(),
705        };
706
707        // TODO: Need to send this message back to DrawLoop somehow
708        // For now, just log it
709        log::info!(
710            "ThreadManager would send StreamUpdate message: {:?}",
711            start_update
712        );
713
714        // Create a channel to receive messages from the spawned thread
715        let (_thread_sender, _thread_receiver) =
716            std::sync::mpsc::channel::<crate::model::common::StreamUpdate>();
717
718        // Store the receiver so we can poll it later (simplified approach)
719        // TODO: This is not ideal - should use a proper async mechanism
720
721        // Spawn background thread for actual execution
722        thread::spawn(move || {
723            let thread_id = format!("{:?}", thread::current().id());
724
725            // Execute the script
726            let output = crate::utils::detached_command("sh")
727                .arg("-c")
728                .arg(script.join(" "))
729                .output();
730
731            let (content, exit_code, status) = match output {
732                Ok(output) => {
733                    let stdout = String::from_utf8_lossy(&output.stdout);
734                    let stderr = String::from_utf8_lossy(&output.stderr);
735                    let content = if stderr.is_empty() {
736                        stdout.to_string()
737                    } else {
738                        format!("{}\n{}", stdout, stderr)
739                    };
740                    let exit_code = output.status.code();
741                    let status = if output.status.success() {
742                        crate::model::common::ExecutionThreadStatus::Completed
743                    } else {
744                        crate::model::common::ExecutionThreadStatus::Failed(
745                            "Script execution failed".to_string(),
746                        )
747                    };
748                    (content, exit_code, status)
749                }
750                Err(e) => (
751                    format!("Error executing script: {}", e),
752                    Some(1),
753                    crate::model::common::ExecutionThreadStatus::Failed(format!(
754                        "Execution error: {}",
755                        e
756                    )),
757                ),
758            };
759
760            // Send final result back to ThreadManager
761            let final_update = crate::model::common::StreamUpdate {
762                stream_id,
763                target_box_id,
764                content_update: content,
765                source_state: crate::model::common::SourceState::Thread(
766                    crate::model::common::ThreadSourceState {
767                        thread_id,
768                        execution_time: std::time::Duration::from_millis(100), // approximate
769                        exit_code,
770                        status,
771                    },
772                ),
773                execution_mode,
774            };
775
776            // TODO: Need to send this back to DrawLoop
777            log::info!(
778                "Thread execution completed, would send StreamUpdate: {:?}",
779                final_update
780            );
781        });
782    }
783}
784
785#[derive(Debug)]
786pub struct ThreadManager {
787    threads: HashMap<Uuid, thread::JoinHandle<()>>,
788    app_context_senders: HashMap<Uuid, mpsc::Sender<(Uuid, Vec<FieldUpdate>)>>,
789    app_context_receivers: HashMap<Uuid, mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>>,
790    message_senders: HashMap<Uuid, mpsc::Sender<(Uuid, Message)>>,
791    message_receivers: HashMap<Uuid, mpsc::Receiver<(Uuid, Message)>>,
792    app_context: AppContext,
793}
794
795impl ThreadManager {
796    pub fn new(app_context: AppContext) -> Self {
797        ThreadManager {
798            threads: HashMap::new(),
799            app_context_senders: HashMap::new(),
800            message_senders: HashMap::new(),
801            app_context_receivers: HashMap::new(),
802            message_receivers: HashMap::new(),
803            app_context,
804        }
805    }
806
807    pub fn stop(&self) {
808        self.send_message_to_all_threads((Uuid::new_v4(), Message::Exit));
809    }
810
811    pub fn pause(&self) {
812        self.send_message_to_all_threads((Uuid::new_v4(), Message::Pause));
813    }
814
815    pub fn run(&mut self) {
816        for message_sender in self.message_senders.values() {
817            if let Err(e) = message_sender.send((Uuid::new_v4(), Message::Start)) {
818                error!("Failed to send start message to thread: {}", e);
819            }
820        }
821        let mut should_continue: bool = true;
822        while should_continue {
823            let mut has_updates = false;
824
825            // Handle app_context updates
826            for reciever in self.app_context_receivers.values() {
827                if let Ok((uuid, app_context_updates)) = reciever.try_recv() {
828                    if app_context_updates.is_empty() {
829                        // log::trace!("No updates received from thread {}", uuid);
830                        continue;
831                    } else {
832                        let app_context_updates_size_in_bytes =
833                            bincode::serialize(&app_context_updates)
834                                .unwrap_or_default()
835                                .len();
836                        log::trace!(
837                            "Received {} updates from thread {} with total size {} bytes. Will relay to all other threads.",
838                            app_context_updates.len(),
839                            uuid,
840                            app_context_updates_size_in_bytes
841                        );
842                    }
843
844                    let original_app_context = self.app_context.clone();
845
846                    // log::trace!(
847                    //     "Sending app_context update to all threads: {:?}",
848                    //     app_context_updates
849                    // );
850                    self.app_context.app.apply_updates(app_context_updates);
851                    self.send_app_context_update_to_all_threads((
852                        uuid,
853                        self.app_context.generate_diff(&original_app_context),
854                    ));
855                    has_updates = true;
856                }
857            }
858
859            // Handle messages - collect first to avoid borrow conflicts
860            let mut messages_to_process = Vec::new();
861            for reciever in self.message_receivers.values() {
862                if let Ok((uuid, received_msg)) = reciever.try_recv() {
863                    messages_to_process.push((uuid, received_msg));
864                }
865            }
866
867            // Process collected messages
868            for (uuid, received_msg) in messages_to_process {
869                // log::info!("Received message from thread {}: {:?}", uuid, received_msg);
870                match received_msg {
871                    Message::Exit => {
872                        self.send_message_to_all_threads((Uuid::new_v4(), Message::Terminate));
873                        should_continue = false;
874                    }
875                    Message::ExecuteScriptMessage(execute_script) => {
876                        log::info!("ThreadManager processing ExecuteScript from thread {}: target_box_id={}, execution_mode={:?}", 
877                                   uuid, execute_script.target_box_id, execute_script.execution_mode);
878
879                        // Handle ExecuteScript directly in ThreadManager, don't broadcast
880                        self.handle_execute_script(execute_script);
881                        has_updates = true;
882                    }
883                    _ => {
884                        // For all other messages, broadcast to all threads
885                        self.send_message_to_all_threads((uuid, received_msg));
886                        has_updates = true;
887                    }
888                }
889            }
890
891            // Sleep only if there were no updates to process
892            if !has_updates {
893                std::thread::sleep(std::time::Duration::from_millis(
894                    self.app_context.config.frame_delay,
895                ));
896            }
897        }
898    }
899
900    pub fn spawn_thread<R: Runnable + 'static>(&mut self, mut runnable: R) -> Uuid {
901        let uuid = Uuid::new_v4();
902        let (s_tm_t_s, s_tm_t_r) = mpsc::channel::<(Uuid, Vec<FieldUpdate>)>();
903        let (s_t_tm_s, s_t_tm_r) = mpsc::channel::<(Uuid, Vec<FieldUpdate>)>();
904        let (m_tm_t_s, m_tm_t_r) = mpsc::channel::<(Uuid, Message)>();
905        let (m_t_tm_s, m_t_tm_r) = mpsc::channel::<(Uuid, Message)>();
906
907        runnable.set_uuid(uuid);
908        runnable.set_app_context_sender(s_t_tm_s);
909        runnable.set_message_sender(m_t_tm_s);
910        runnable.set_app_context_receiver(s_tm_t_r);
911        runnable.set_message_receiver(m_tm_t_r);
912
913        self.app_context_senders.insert(uuid, s_tm_t_s);
914        self.message_senders.insert(uuid, m_tm_t_s);
915        self.app_context_receivers.insert(uuid, s_t_tm_r);
916        self.message_receivers.insert(uuid, m_t_tm_r);
917
918        let runnable_class_name = std::any::type_name::<R>();
919        let thread_name = format!("{}_{}", runnable_class_name, uuid);
920
921        let handle = thread::Builder::new()
922            .name(thread_name)
923            .spawn(move || {
924                let mut continue_running = true;
925                while continue_running {
926                    let result = runnable.run();
927                    if let Err(e) = result {
928                        error!("Runnable encountered an error: {}", e);
929                        continue_running = false;
930                    } else if let Ok(should_continue) = result {
931                        continue_running = should_continue;
932                        if !continue_running {
933                            log::trace!("Stopping thread as directed by run method");
934                        }
935                    }
936                }
937            })
938            .unwrap();
939
940        self.threads.insert(uuid, handle);
941
942        log::trace!("Thread spawned: {}", uuid);
943
944        uuid
945    }
946
947    pub fn send_app_context_update_to_thread(&self, field_updates: Vec<FieldUpdate>, uuid: Uuid) {
948        if let Some(sender) = self.app_context_senders.get(&uuid) {
949            if let Err(e) = sender.send((uuid, field_updates)) {
950                error!("Failed to send data to thread: {}", e);
951            }
952        }
953    }
954
955    pub fn send_app_context_update_to_all_threads(&self, field_updates: (Uuid, Vec<FieldUpdate>)) {
956        for (&uuid, sender) in &self.app_context_senders {
957            if uuid != field_updates.0 {
958                if let Err(e) = sender.send(field_updates.clone()) {
959                    error!("Failed to send update to thread: {}", e);
960                }
961            } else {
962                log::trace!("Skipping sending update to thread: {}", uuid);
963            }
964        }
965    }
966
967    pub fn send_message_to_thread(&self, msg: (Uuid, Message), uuid: Uuid) {
968        if let Some(sender) = self.message_senders.get(&uuid) {
969            log::debug!("ThreadManager found message sender for thread: {}", uuid);
970            if let Err(e) = sender.send(msg) {
971                log::error!("Failed to send message to thread {}: {}", uuid, e);
972            } else {
973                log::debug!(
974                    "ThreadManager successfully sent message to thread: {}",
975                    uuid
976                );
977            }
978        } else {
979            log::error!(
980                "ThreadManager could not find message sender for thread: {}",
981                uuid
982            );
983        }
984    }
985
986    pub fn send_message_to_all_threads(&self, msg: (Uuid, Message)) {
987        for (&uuid, sender) in &self.message_senders {
988            if uuid != msg.0 {
989                if let Err(e) = sender.send(msg.clone()) {
990                    error!("Failed to send message to thread: {}", e);
991                }
992            }
993        }
994    }
995
996    pub fn join_threads(&mut self) {
997        for handle in self.threads.drain() {
998            if let Err(e) = handle.1.join() {
999                error!("Failed to join thread: {:?}", e);
1000            }
1001        }
1002    }
1003
1004    pub fn get_hash<T: Hash>(&self, t: &T) -> u64 {
1005        let mut hasher = DefaultHasher::new();
1006        t.hash(&mut hasher);
1007        hasher.finish()
1008    }
1009
1010    pub fn remove_thread(&mut self, uuid: Uuid) {
1011        if let Some(handle) = self.threads.remove(&uuid) {
1012            if let Err(e) = handle.join() {
1013                error!("Failed to join thread: {:?}", e);
1014            }
1015        }
1016        let msg = (Uuid::new_v4(), Message::Exit);
1017        self.send_message_to_thread(msg, uuid);
1018        self.app_context_senders.remove(&uuid);
1019        self.message_senders.remove(&uuid);
1020    }
1021
1022    fn handle_execute_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1023        use crate::model::common::ExecutionMode;
1024
1025        log::info!(
1026            "T0315 FIXED: ThreadManager properly handling ExecuteScript for target_box: {}",
1027            execute_script.target_box_id
1028        );
1029
1030        // Use UpdateStreamContent to create/update the output stream
1031        match execute_script.execution_mode {
1032            ExecutionMode::Immediate => {
1033                log::info!("T0315: Immediate execution - running script synchronously");
1034                self.execute_immediate_script(execute_script);
1035            }
1036            ExecutionMode::Thread => {
1037                log::info!("T0315: Thread execution - dispatching to thread pool");
1038                self.execute_threaded_script(execute_script);
1039            }
1040            ExecutionMode::Pty => {
1041                // REMOVED DEAD CODE: PTY execution now properly routed to PTYManager, never reaches ThreadManager
1042                panic!("PTY ExecuteScript reached ThreadManager - this indicates a critical routing bug that should never happen");
1043            }
1044        }
1045    }
1046
1047    fn execute_immediate_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1048
1049        // Run the script synchronously
1050        let output = crate::utils::detached_command("sh")
1051            .arg("-c")
1052            .arg(execute_script.script.join(" "))
1053            // Detached from the terminal (own session, null stdin) so it can never
1054            // reset boxmux's raw mode / mouse tracking; stdout/stderr are captured.
1055            .output();
1056
1057        let content = match output {
1058            Ok(output) => {
1059                let stdout = String::from_utf8_lossy(&output.stdout);
1060                let stderr = String::from_utf8_lossy(&output.stderr);
1061                if stderr.is_empty() {
1062                    stdout.to_string()
1063                } else {
1064                    format!("{}\n{}", stdout, stderr)
1065                }
1066            }
1067            Err(e) => {
1068                format!("Error executing script: {}", e)
1069            }
1070        };
1071
1072        // SOURCE OBJECT ARCHITECTURE: Use stream_id from ExecuteScript (from source object)
1073        let stream_id = execute_script.stream_id.clone();
1074
1075        // Send result via StreamUpdate with target_box_id for auto-creation
1076        // REDIRECT FIX: Use redirect destination if specified
1077        let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
1078            log::info!(
1079                "THREADMANAGER REDIRECT FIX IMMEDIATE: Using redirect destination: {} (was {})",
1080                redirect_to,
1081                execute_script.target_box_id
1082            );
1083            redirect_to.clone()
1084        } else {
1085            log::info!(
1086                "THREADMANAGER REDIRECT FIX IMMEDIATE: No redirect, using source box: {}",
1087                execute_script.target_box_id
1088            );
1089            execute_script.target_box_id.clone()
1090        };
1091
1092        let stream_update = crate::model::common::StreamUpdate {
1093            stream_id,
1094            target_box_id,
1095            content_update: content,
1096            source_state: crate::model::common::SourceState::Batch(
1097                crate::model::common::BatchSourceState {
1098                    task_id: "immediate".to_string(),
1099                    queue_wait_time: std::time::Duration::from_millis(0),
1100                    execution_time: std::time::Duration::from_millis(50), // Immediate scripts are very fast
1101                    exit_code: Some(0),
1102                    status: crate::model::common::BatchStatus::Completed,
1103                },
1104            ),
1105            execution_mode: execute_script.execution_mode,
1106        };
1107
1108        // Broadcast StreamUpdate to all threads for processing
1109        self.send_message_to_all_threads((
1110            uuid::Uuid::new_v4(),
1111            Message::StreamUpdateMessage(stream_update),
1112        ));
1113    }
1114
1115    fn execute_threaded_script(&mut self, execute_script: crate::model::common::ExecuteScript) {
1116        log::info!("T0315: Thread execution - using existing thread pool infrastructure");
1117
1118        // SOURCE OBJECT ARCHITECTURE: Use stream_id from ExecuteScript (from source object)
1119        let stream_id = execute_script.stream_id.clone();
1120
1121        // Use existing utils::run_script_with_pty_and_redirect for Thread execution
1122        let libs = if execute_script.libs.is_empty() {
1123            None
1124        } else {
1125            Some(execute_script.libs.clone())
1126        };
1127
1128        // REDIRECT FIX: Use redirect destination if specified
1129        let target_box_id = if let Some(ref redirect_to) = execute_script.redirect_output {
1130            log::info!(
1131                "THREADMANAGER REDIRECT FIX THREAD: Using redirect destination: {} (was {})",
1132                redirect_to,
1133                execute_script.target_box_id
1134            );
1135            redirect_to.clone()
1136        } else {
1137            log::info!(
1138                "THREADMANAGER REDIRECT FIX THREAD: No redirect, using source box: {}",
1139                execute_script.target_box_id
1140            );
1141            execute_script.target_box_id.clone()
1142        };
1143        let script = execute_script.script.clone();
1144        let execution_mode = execute_script.execution_mode.clone();
1145        let redirect_target = execute_script.redirect_output.clone();
1146        let message_senders = self.message_senders.clone();
1147        let thread_manager_uuid = uuid::Uuid::new_v4();
1148
1149        // Spawn thread using existing infrastructure pattern
1150        std::thread::spawn(move || {
1151            let result = crate::utils::run_script_with_pty_and_redirect(
1152                libs,
1153                &script,
1154                &execution_mode,
1155                None, // No PTY manager for Thread mode
1156                None, // No muxbox_id needed for Thread mode
1157                None, // No message sender needed - we'll send result directly
1158                redirect_target,
1159            );
1160
1161            let (content, is_success) = match result {
1162                Ok(output) => (output, true),
1163                Err(e) => (format!("Thread execution error: {}", e), false),
1164            };
1165
1166            // Send result via StreamUpdate
1167            let final_update = crate::model::common::StreamUpdate {
1168                stream_id,
1169                target_box_id,
1170                content_update: content,
1171                source_state: crate::model::common::SourceState::Thread(
1172                    crate::model::common::ThreadSourceState {
1173                        thread_id: format!("{:?}", std::thread::current().id()),
1174                        execution_time: std::time::Duration::from_millis(100), // approximate
1175                        exit_code: Some(if is_success { 0 } else { 1 }),       // Success=0, Error=1
1176                        status: crate::model::common::ExecutionThreadStatus::Completed,
1177                    },
1178                ),
1179                execution_mode,
1180            };
1181
1182            // Send to all threads via message senders
1183            for (uuid, sender) in message_senders.iter() {
1184                if let Err(e) = sender.send((
1185                    thread_manager_uuid,
1186                    Message::StreamUpdateMessage(final_update.clone()),
1187                )) {
1188                    log::error!(
1189                        "Failed to send thread execution result to thread {}: {}",
1190                        uuid,
1191                        e
1192                    );
1193                }
1194            }
1195        });
1196    }
1197}
1198
1199#[macro_export]
1200macro_rules! create_runnable {
1201    ($name:ident, $init_body:expr, $process_body:expr) => {
1202        pub struct $name {
1203            inner: RunnableImpl,
1204        }
1205
1206        impl $name {
1207            pub fn new(app_context: AppContext) -> Self {
1208                $name {
1209                    inner: RunnableImpl::new(app_context),
1210                }
1211            }
1212        }
1213
1214        impl Runnable for $name {
1215            fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
1216                // Call the init block before the loop
1217                {
1218                    let inner = &mut self.inner;
1219                    let app_context = inner.app_context.clone();
1220                    let messages = Vec::new();
1221                    let init_result = $init_body(inner, app_context, messages);
1222                    if !init_result {
1223                        return Ok(false);
1224                    }
1225                }
1226                self.inner._run(&mut |inner, app_context, messages| {
1227                    $process_body(inner, app_context, messages)
1228                })
1229            }
1230
1231            fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
1232                self.inner.receive_updates()
1233            }
1234
1235            fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
1236                self.inner.process(app_context, messages)
1237            }
1238
1239            fn update_app_context(&mut self, app_context: AppContext) {
1240                self.inner.update_app_context(app_context)
1241            }
1242
1243            fn set_uuid(&mut self, uuid: Uuid) {
1244                self.inner.set_uuid(uuid)
1245            }
1246
1247            fn get_uuid(&self) -> Uuid {
1248                self.inner.get_uuid()
1249            }
1250
1251            fn set_app_context_sender(
1252                &mut self,
1253                app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
1254            ) {
1255                self.inner.set_app_context_sender(app_context_sender)
1256            }
1257
1258            fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
1259                self.inner.set_message_sender(message_sender)
1260            }
1261
1262            fn set_app_context_receiver(
1263                &mut self,
1264                app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
1265            ) {
1266                self.inner.set_app_context_receiver(app_context_receiver)
1267            }
1268
1269            fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
1270                self.inner.set_message_receiver(message_receiver)
1271            }
1272
1273            fn get_app_context(&self) -> &AppContext {
1274                self.inner.get_app_context()
1275            }
1276
1277            fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
1278                self.inner.get_app_context_sender()
1279            }
1280
1281            fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
1282                self.inner.get_message_sender_option_ref()
1283            }
1284
1285            fn send_app_context_update(&self, old_app_context: AppContext) {
1286                self.inner.send_app_context_update(old_app_context)
1287            }
1288
1289            fn send_message(&self, msg: Message) {
1290                self.inner.send_message(msg)
1291            }
1292        }
1293    };
1294}
1295
1296// T312: ChoiceExecutionRunnable - Convert choice execution to Runnable pattern
1297create_runnable!(
1298    ChoiceExecutionRunnable,
1299    |inner: &mut RunnableImpl, _app_context: AppContext, _messages: Vec<Message>| -> bool {
1300        // Initialize - no setup needed for choice execution
1301        log::debug!(
1302            "ChoiceExecutionRunnable initialization: state={:?}",
1303            inner.running_state
1304        );
1305        inner.running_state = RunnableState::Running;
1306        log::debug!("ChoiceExecutionRunnable set state to Running");
1307        true
1308    },
1309    |_inner: &mut RunnableImpl,
1310     app_context: AppContext,
1311     messages: Vec<Message>|
1312     -> (bool, AppContext) {
1313        // Debug: Always log to see if processing function is called
1314        let message_count = messages.len();
1315        log::debug!(
1316            "ChoiceExecutionRunnable processing function called with {} messages",
1317            message_count
1318        );
1319        for message in messages {
1320            // T0325: ExecuteChoice message removed - Phase 5 cleanup complete
1321            // All message handling now goes through unified ExecuteScript architecture
1322            {
1323                log::debug!(
1324                    "ChoiceExecutionRunnable: Unhandled message type: {:?}",
1325                    message
1326                );
1327            }
1328        }
1329
1330        // T0325: ExecuteChoice message removed - no choice execution logic needed
1331        // All execution now flows through unified ExecuteScript architecture
1332        let should_continue = true; // Continue message processing
1333        log::debug!(
1334            "ChoiceExecutionRunnable: messages={}, should_continue={}",
1335            message_count,
1336            should_continue
1337        );
1338        (should_continue, app_context)
1339    }
1340);
1341
1342// ChoiceExecutionRunnable implementation removed - using unified message system
1343
1344#[macro_export]
1345macro_rules! create_runnable_with_dynamic_input {
1346    ($name:ident, $vec_fn:expr, $init_body:expr, $process_body:expr) => {
1347        pub struct $name {
1348            inner: RunnableImpl,
1349            vec_fn: Box<dyn Fn() -> Vec<String> + Send>,
1350        }
1351
1352        impl $name {
1353            pub fn new(
1354                app_context: AppContext,
1355                vec_fn: Box<dyn Fn() -> Vec<String> + Send>,
1356            ) -> Self {
1357                $name {
1358                    inner: RunnableImpl::new(app_context),
1359                    vec_fn,
1360                }
1361            }
1362        }
1363
1364        impl Runnable for $name {
1365            fn run(&mut self) -> Result<bool, Box<dyn std::error::Error>> {
1366                // Call the init block before the loop
1367                {
1368                    let inner = &mut self.inner;
1369                    let app_context = inner.app_context.clone();
1370                    let messages = Vec::new();
1371                    let vec = (self.vec_fn)();
1372                    let init_result = $init_body(inner, app_context, messages, vec);
1373                    if !init_result {
1374                        return Ok(false);
1375                    }
1376                }
1377                self.inner._run(&mut |inner, app_context, messages| {
1378                    let vec = (self.vec_fn)();
1379                    $process_body(inner, app_context, messages, vec)
1380                })
1381            }
1382
1383            fn receive_updates(&mut self) -> (AppContext, Vec<Message>) {
1384                self.inner.receive_updates()
1385            }
1386
1387            fn process(&mut self, app_context: AppContext, messages: Vec<Message>) {
1388                self.inner.process(app_context.clone(), messages.clone());
1389            }
1390
1391            fn update_app_context(&mut self, app_context: AppContext) {
1392                self.inner.update_app_context(app_context)
1393            }
1394
1395            fn set_uuid(&mut self, uuid: Uuid) {
1396                self.inner.set_uuid(uuid)
1397            }
1398
1399            fn get_uuid(&self) -> Uuid {
1400                self.inner.get_uuid()
1401            }
1402
1403            fn set_app_context_sender(
1404                &mut self,
1405                app_context_sender: mpsc::Sender<(Uuid, Vec<FieldUpdate>)>,
1406            ) {
1407                self.inner.set_app_context_sender(app_context_sender)
1408            }
1409
1410            fn set_message_sender(&mut self, message_sender: mpsc::Sender<(Uuid, Message)>) {
1411                self.inner.set_message_sender(message_sender)
1412            }
1413
1414            fn set_app_context_receiver(
1415                &mut self,
1416                app_context_receiver: mpsc::Receiver<(Uuid, Vec<FieldUpdate>)>,
1417            ) {
1418                self.inner.set_app_context_receiver(app_context_receiver)
1419            }
1420
1421            fn set_message_receiver(&mut self, message_receiver: mpsc::Receiver<(Uuid, Message)>) {
1422                self.inner.set_message_receiver(message_receiver)
1423            }
1424
1425            fn get_app_context(&self) -> &AppContext {
1426                self.inner.get_app_context()
1427            }
1428
1429            fn get_app_context_sender(&self) -> &Option<mpsc::Sender<(Uuid, Vec<FieldUpdate>)>> {
1430                self.inner.get_app_context_sender()
1431            }
1432
1433            fn get_message_sender(&self) -> &Option<mpsc::Sender<(Uuid, Message)>> {
1434                self.inner.get_message_sender_option_ref()
1435            }
1436
1437            fn send_app_context_update(&self, old_app_context: AppContext) {
1438                self.inner.send_app_context_update(old_app_context)
1439            }
1440
1441            fn send_message(&self, msg: Message) {
1442                self.inner.send_message(msg)
1443            }
1444        }
1445    };
1446}
1447
1448pub fn run_script_in_thread(
1449    app_context: AppContext,
1450    manager: &mut ThreadManager,
1451    muxbox_id: String,
1452    choice_id: String,
1453) -> Uuid {
1454    let vec_fn = move || vec![muxbox_id.clone(), choice_id.clone()];
1455
1456    create_runnable_with_dynamic_input!(
1457        ChoiceScriptRunner,
1458        Box::new(vec_fn),
1459        |_inner: &mut RunnableImpl,
1460         _app_context: AppContext,
1461         _messages: Vec<Message>,
1462         _vec: Vec<String>|
1463         -> bool { true },
1464        |inner: &mut RunnableImpl,
1465         app_context: AppContext,
1466         _messages: Vec<Message>,
1467         vec: Vec<String>|
1468         -> (bool, AppContext) {
1469            let mut app_context_unwrapped = app_context.clone();
1470            let app_graph = app_context_unwrapped.app.generate_graph();
1471            let libs = app_context_unwrapped.app.libs.clone();
1472            let muxbox = app_context_unwrapped
1473                .app
1474                .get_muxbox_by_id_mut(&vec[0])
1475                .unwrap();
1476            let choice = muxbox
1477                .choices
1478                .as_mut()
1479                .unwrap()
1480                .iter_mut()
1481                .find(|c| c.id == vec[1])
1482                .unwrap();
1483            // T0330: Remove legacy thread/pty fields - use ExecutionMode
1484            let execution_mode = &choice.execution_mode;
1485            let pty_manager = app_context_unwrapped.pty_manager.as_ref();
1486            let message_sender = inner
1487                .get_message_sender()
1488                .map(|sender| (sender.clone(), inner.get_uuid()));
1489
1490            match crate::utils::run_script_with_pty(
1491                libs,
1492                choice.script.clone().unwrap().as_ref(),
1493                execution_mode, // F0228: Use ExecutionMode directly
1494                pty_manager.map(|arc| arc.as_ref()),
1495                Some(choice.id.clone()),
1496                message_sender,
1497            ) {
1498                Ok(output) => {
1499                    // Create stream_id for proper stream targeting
1500                    let stream_id = format!("{}_{}", choice.id, execution_mode.as_stream_suffix());
1501                    // T0328: Replace MuxBoxOutputUpdate with StreamUpdateMessage
1502                    let stream_update = crate::model::common::StreamUpdate {
1503                        stream_id,
1504                        target_box_id: vec[0].clone(),
1505                        content_update: output,
1506                        source_state: crate::model::common::SourceState::Thread(
1507                            crate::model::common::ThreadSourceState {
1508                                thread_id: format!("{:?}", std::thread::current().id()),
1509                                execution_time: std::time::Duration::from_millis(0),
1510                                exit_code: Some(0),
1511                                status: crate::model::common::ExecutionThreadStatus::Completed,
1512                            },
1513                        ),
1514                        execution_mode: execution_mode.clone(),
1515                    };
1516                    inner.send_message(Message::StreamUpdateMessage(stream_update))
1517                }
1518                Err(e) => {
1519                    let stream_id = format!("{}_{}", choice.id, execution_mode.as_stream_suffix());
1520                    // T0328: Replace MuxBoxOutputUpdate with StreamUpdateMessage
1521                    let stream_update = crate::model::common::StreamUpdate {
1522                        stream_id,
1523                        target_box_id: vec[0].clone(),
1524                        content_update: e.to_string(),
1525                        source_state: crate::model::common::SourceState::Thread(
1526                            crate::model::common::ThreadSourceState {
1527                                thread_id: format!("{:?}", std::thread::current().id()),
1528                                execution_time: std::time::Duration::from_millis(0),
1529                                exit_code: Some(1),
1530                                status: crate::model::common::ExecutionThreadStatus::Failed(
1531                                    e.to_string(),
1532                                ),
1533                            },
1534                        ),
1535                        execution_mode: execution_mode.clone(),
1536                    };
1537                    inner.send_message(Message::StreamUpdateMessage(stream_update))
1538                }
1539            }
1540            std::thread::sleep(std::time::Duration::from_millis(
1541                muxbox.calc_refresh_interval(&app_context, &app_graph),
1542            ));
1543            (false, app_context_unwrapped)
1544        }
1545    );
1546
1547    let choice_refresh_loop = ChoiceScriptRunner::new(app_context.clone(), Box::new(vec_fn));
1548    manager.spawn_thread(choice_refresh_loop)
1549}
1550
1551// create_runnable!(
1552//     ExampleRunnable,z
1553//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| -> bool {
1554//         // Initialization block
1555//         info!("Initializing ExampleRunnable with app_context: {:?}", app_context);
1556//         inner.update_app_context(app_context);
1557//         true // Initialization complete, continue running
1558//     },
1559//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| -> bool {
1560//         // Processing block
1561//         info!("Processing in ExampleRunnable with app_context: {:?} and messages: {:?}", app_context, messages);
1562
1563//         for message in messages {
1564//             match message {
1565//                 Message::Exit => return false, // Stop running
1566//                 Message::NextMuxBox(muxbox_id) => {
1567//                     info!("Next muxbox: {}", muxbox_id);
1568//                     // Handle NextMuxBox logic
1569//                 },
1570//                 Message::PreviousMuxBox(muxbox_id) => {
1571//                     info!("Previous muxbox: {}", muxbox_id);
1572//                     // Handle PreviousMuxBox logic
1573//                 },
1574//                 _ => {
1575//                     info!("Unhandled message: {:?}", message);
1576//                     // Handle other messages
1577//                 },
1578//             }
1579//         }
1580
1581//         true // Continue running
1582//     }
1583// );
1584
1585// create_runnable!(
1586//     TestRunnableOne,
1587//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1588//         info!("TestRunnableOne initialization");
1589//         true  // Assuming initialization is always successful
1590//     },
1591//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1592//         info!("TestRunnableOne received app_context: {:?}", app_context);
1593//         for message in messages.iter() {
1594//             info!("TestRunnableOne received message: {:?}", message);
1595//         }
1596//         info!("TestRunnableOne running with data: {:?}", inner.get_app_context());
1597//         inner.send_message(Message::RedrawApp);
1598//         false  // Intentionally stopping the thread for demonstration
1599//     }
1600// );
1601
1602// create_runnable!(
1603//     TestRunnableTwo,
1604//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1605//         info!("TestRunnableOne initialization");
1606//         true  // Assuming initialization is always successful
1607//     },
1608//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1609//         info!("TestRunnableOne received app_context: {:?}", app_context);
1610//         for message in messages.iter() {
1611//             info!("TestRunnableOne received message: {:?}", message);
1612//         }
1613//         info!("TestRunnableOne running with data: {:?}", inner.get_app_context());
1614//         inner.send_message(Message::RedrawApp);
1615//         false  // Intentionally stopping the thread for demonstration
1616//     }
1617// );
1618
1619// create_runnable!(
1620//     TestRunnableThree,
1621//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1622//         info!("TestRunnableOne initialization");
1623//         true  // Assuming initialization is always successful
1624//     },
1625//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1626//         info!("TestRunnableOne received app_context: {:?}", app_context);
1627//         for message in messages.iter() {
1628//             info!("TestRunnableOne received message: {:?}", message);
1629//         }
1630//         info!("TestRunnableOne running with data: {:?}", inner.get_app_context());
1631//         inner.send_message(Message::RedrawApp);
1632//         false  // Intentionally stopping the thread for demonstration
1633//     }
1634// );
1635
1636// create_runnable!(
1637//     TestRunnableTwo,
1638//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message>| {
1639//         info!("TestRunnableTwo initialization");
1640//         true  // Initialization success
1641//     },
1642//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message)| {
1643//         info!("TestRunnableTwo received app_context: {:?}", app_context);
1644//         for message in messages.iter() {
1645//             info!("TestRunnableTwo received message: {:?}", message);
1646//         }
1647//         info!("TestRunnableTwo running with data: {:?}", inner.get_app_context());
1648//         inner.send_message(Message::ReplaceMuxBox("MuxBox2".to_string()));
1649//         true  // Continue running
1650//     }
1651// );
1652
1653// create_runnable!(
1654//     TestRunnableThree,
1655//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message)| {
1656//         info!("TestRunnableThree initialization");
1657//         true  // Initialization success
1658//     },
1659//     |inner: &mut RunnableImpl, app_context: AppContext, messages: Vec<Message)| {
1660//         info!("TestRunnableThree received app_context: {:?}", app_context);
1661//         for message in messages.iter() {
1662//             info!("TestRunnableThree received message: {:?}", message);
1663//         }
1664//         info!("TestRunnableThree running with data: {:?}", inner.get_app_context());
1665//         inner.send_message(Message::MuxBoxEventEnter("MuxBox3".to_string()));
1666//         true  // Continue running
1667//     }
1668// );
1669
1670// #[cfg(test)]
1671// mod tests {
1672//     use crate::App;
1673
1674//     use super::*;
1675//     use std::sync::mpsc::TryRecvError;
1676
1677//     #[test]
1678//     fn test_message_delivery() {
1679//         let app_context = AppContext::new(App::new());
1680//         let mut manager = ThreadManager::new(app_context.clone());
1681//         let uuid1 = manager.spawn_thread(TestRunnableOne::new(app_context.clone()));
1682//         let uuid2 = manager.spawn_thread(TestRunnableTwo::new(app_context.clone()));
1683//         let uuid3 = manager.spawn_thread(TestRunnableThree::new(app_context.clone()));
1684
1685//         let data = AppContext::new(App::new());
1686//         manager.send_app_context_update_to_thread(data.clone(), uuid1);
1687//         manager.send_app_context_update_to_thread(data.clone(), uuid2);
1688//         manager.send_app_context_update_to_thread(data.clone(), uuid3);
1689
1690//         manager.send_message_to_all_threads((uuid1, Message::NextMuxBox("MuxBox1".to_string())));
1691
1692//         // Run the manager's loop in a separate thread to allow message handling
1693//         let manager = manager;
1694//         // let manager_clone = Arc::clone(&manager);
1695
1696//         let handle = thread::spawn(move || {
1697//             manager_clone.run();
1698//         });
1699
1700//         // Give the threads some time to process the messages
1701//         thread::sleep(std::time::Duration::from_secs(1));
1702
1703//         // Ensure that each runnable received the message
1704//         let runnables = manager.runnables.clone();
1705//         for (_, runnable) in runnables.iter() {
1706//             let mut runnable = runnable.lock().unwrap();
1707//             let (_, messages) = runnable.receive_updates();
1708//             assert!(messages.iter().any(|msg| matches!(msg, Message::NextMuxBox(muxbox_id) if muxbox_id == "MuxBox1")));
1709//         }
1710//         manager.stop();
1711//         handle.join().unwrap();
1712//     }
1713
1714//     #[test]
1715//     fn test_state_update_propagation() {
1716//         let app_context = AppContext::new(App::new());
1717//         let mut manager = ThreadManager::new(app_context.clone());
1718//         let uuid1 = manager.spawn_thread(TestRunnableOne::new(app_context.clone()));
1719//         let uuid2 = manager.spawn_thread(TestRunnableTwo::new(app_context.clone()));
1720//         let uuid3 = manager.spawn_thread(TestRunnableThree::new(app_context.clone()));
1721
1722//         let data = AppContext::new(App::new());
1723//         manager.send_app_context_update_to_thread(data.clone(), uuid1);
1724
1725//         // Run the manager's loop in a separate thread to allow app_context handling
1726//         let manager = Arc::new(manager);
1727//         let manager_clone = Arc::clone(&manager);
1728
1729//         let handle = thread::spawn(move || {
1730//             manager_clone.run();
1731//         });
1732
1733//         // Give the threads some time to process the app_context update
1734//         thread::sleep(std::time::Duration::from_secs(1));
1735
1736//         // Ensure that the app_context was propagated to all runnables
1737//         let runnables = manager.runnables.clone();
1738//         for (_, runnable) in runnables.iter() {
1739//             let mut runnable = runnable.lock().unwrap();
1740//             let (app_context, _) = runnable.receive_updates();
1741//             assert_eq!(app_context, data);
1742//         }
1743//         manager.stop();
1744//         handle.join().unwrap();
1745//     }
1746
1747//     #[test]
1748//     fn test_concurrent_message_handling() {
1749//         let app_context = AppContext::new(App::new());
1750//         let mut manager = ThreadManager::new(app_context.clone());
1751//         let uuid1 = manager.spawn_thread(TestRunnableOne::new(app_context.clone()));
1752//         let uuid2 = manager.spawn_thread(TestRunnableTwo::new(app_context.clone()));
1753//         let uuid3 = manager.spawn_thread(TestRunnableThree::new(app_context.clone()));
1754
1755//         let data = AppContext::new(App::new());
1756//         manager.send_app_context_update_to_thread(data.clone(), uuid1);
1757//         manager.send_app_context_update_to_thread(data.clone(), uuid2);
1758//         manager.send_app_context_update_to_thread(data.clone(), uuid3);
1759
1760//         manager.send_message_to_all_threads((uuid1, Message::RedrawApp));
1761//         manager.send_message_to_all_threads((uuid2, Message::ReplaceMuxBox("MuxBox2".to_string())));
1762//         manager.send_message_to_all_threads((uuid3, Message::MuxBoxEventEnter("MuxBox3".to_string())));
1763
1764//         // Run the manager's loop in a separate thread to allow message handling
1765//         let manager = Arc::new(manager);
1766//         let manager_clone = Arc::clone(&manager);
1767
1768//         let handle = thread::spawn(move || {
1769//             manager_clone.run();
1770//         });
1771
1772//         // Give the threads some time to process the messages
1773//         thread::sleep(std::time::Duration::from_secs(1));
1774
1775//         // Ensure that each runnable received the messages
1776//         let runnables = manager.runnables.clone();
1777//         for (_, runnable) in runnables.iter() {
1778//             let mut runnable = runnable.lock().unwrap();
1779//             let (_, messages) = runnable.receive_updates();
1780//             assert!(messages.iter().any(|msg| matches!(msg, Message::RedrawApp)));
1781//             assert!(messages.iter().any(|msg| matches!(msg, Message::ReplaceMuxBox(muxbox_id) if muxbox_id == "MuxBox2")));
1782//             assert!(messages.iter().any(|msg| matches!(msg, Message::MuxBoxEventEnter(muxbox_id) if muxbox_id == "MuxBox3")));
1783//         }
1784//         manager.stop();
1785//         handle.join().unwrap();
1786//     }
1787
1788//     #[test]
1789//     fn test_message_delivery_once() {
1790//         let app_context = AppContext::new(App::new());
1791//         let mut manager = ThreadManager::new(app_context.clone());
1792//         let uuid1 = manager.spawn_thread(TestRunnableOne::new(app_context.clone()));
1793//         let uuid2 = manager.spawn_thread(TestRunnableTwo::new(app_context.clone()));
1794//         let uuid3 = manager.spawn_thread(TestRunnableThree::new(app_context.clone()));
1795
1796//         let data = AppContext::new(App::new());
1797//         manager.send_app_context_update_to_thread(data.clone(), uuid1);
1798//         manager.send_app_context_update_to_thread(data.clone(), uuid2);
1799//         manager.send_app_context_update_to_thread(data.clone(), uuid3);
1800
1801//         manager.send_message_to_all_threads((uuid1, Message::RedrawApp));
1802
1803//         // Run the manager's loop in a separate thread to allow message handling
1804//         let manager = Arc::new(manager);
1805//         let manager_clone = Arc::clone(&manager);
1806
1807//         let handle = thread::spawn(move || {
1808//             manager_clone.run();
1809//         });
1810
1811//         // Give the threads some time to process the messages
1812//         thread::sleep(std::time::Duration::from_secs(1));
1813
1814//         // Ensure that the message was delivered only once
1815//         let runnables = manager.runnables.clone();
1816//         for (_, runnable) in runnables.iter() {
1817//             let mut runnable = runnable.lock().unwrap();
1818//             let (_, messages) = runnable.receive_updates();
1819//             let count = messages.iter().filter(|msg| matches!(msg, Message::RedrawApp)).count();
1820//             assert_eq!(count, 1);
1821//         }
1822//         manager.stop();
1823//         handle.join().unwrap();
1824//     }
1825// }
1826
1827#[cfg(test)]
1828mod tests {
1829    use super::*;
1830    use crate::model::app::App;
1831    use crate::model::common::{Config, EntityType, FieldUpdate};
1832    use crate::model::layout::Layout;
1833    use crate::model::muxbox::MuxBox;
1834    use serde_json::Value;
1835    use std::sync::mpsc;
1836
1837    // Helper function to create test AppContext
1838    fn create_test_app_context() -> AppContext {
1839        let mut app = App::new();
1840        let layout = create_test_layout("test_layout");
1841        app.layouts.push(layout);
1842        let config = Config::default();
1843        AppContext::new(app, config)
1844    }
1845
1846    // Helper function to create test MuxBox
1847    fn create_test_muxbox(id: &str) -> MuxBox {
1848        MuxBox {
1849            id: id.to_string(),
1850            position: crate::model::common::InputBounds {
1851                x1: "0%".to_string(),
1852                y1: "0%".to_string(),
1853                x2: "100%".to_string(),
1854                y2: "100%".to_string(),
1855            },
1856            ..Default::default()
1857        }
1858    }
1859
1860    // Helper function to create test Layout
1861    fn create_test_layout(id: &str) -> Layout {
1862        Layout {
1863            id: id.to_string(),
1864            ..Default::default()
1865        }
1866    }
1867
1868    // Helper function to create test FieldUpdate
1869    fn create_test_field_update(
1870        entity_type: EntityType,
1871        entity_id: &str,
1872        field_name: &str,
1873        value: Value,
1874    ) -> FieldUpdate {
1875        FieldUpdate {
1876            entity_type,
1877            entity_id: Some(entity_id.to_string()),
1878            field_name: field_name.to_string(),
1879            new_value: value,
1880        }
1881    }
1882
1883    /// Tests that Message enum implements Hash correctly for different variants.
1884    /// This test demonstrates the message hashing feature.
1885    #[test]
1886    fn test_message_hash() {
1887        let mut hasher1 = DefaultHasher::new();
1888        let mut hasher2 = DefaultHasher::new();
1889
1890        let msg1 = Message::Exit;
1891        let msg2 = Message::Exit;
1892        msg1.hash(&mut hasher1);
1893        msg2.hash(&mut hasher2);
1894        assert_eq!(hasher1.finish(), hasher2.finish());
1895
1896        let msg3 = Message::RedrawMuxBox("muxbox1".to_string());
1897        let msg4 = Message::RedrawMuxBox("muxbox1".to_string());
1898        let mut hasher3 = DefaultHasher::new();
1899        let mut hasher4 = DefaultHasher::new();
1900        msg3.hash(&mut hasher3);
1901        msg4.hash(&mut hasher4);
1902        assert_eq!(hasher3.finish(), hasher4.finish());
1903    }
1904
1905    /// Tests that Message enum implements PartialEq correctly.
1906    /// This test demonstrates the message equality comparison feature.
1907    #[test]
1908    fn test_message_equality() {
1909        assert_eq!(Message::Exit, Message::Exit);
1910        assert_eq!(Message::Terminate, Message::Terminate);
1911        assert_eq!(
1912            Message::RedrawMuxBox("muxbox1".to_string()),
1913            Message::RedrawMuxBox("muxbox1".to_string())
1914        );
1915        assert_ne!(
1916            Message::RedrawMuxBox("muxbox1".to_string()),
1917            Message::RedrawMuxBox("muxbox2".to_string())
1918        );
1919        assert_ne!(Message::Exit, Message::Terminate);
1920    }
1921
1922    /// Tests that Message enum correctly hashes different message types.
1923    /// This test demonstrates the message type differentiation feature.
1924    #[test]
1925    fn test_message_hash_different_types() {
1926        let msg1 = Message::KeyPress("a".to_string());
1927        let msg2 = Message::KeyPress("a".to_string());
1928        let msg3 = Message::KeyPress("b".to_string());
1929
1930        let mut hasher1 = DefaultHasher::new();
1931        let mut hasher2 = DefaultHasher::new();
1932        let mut hasher3 = DefaultHasher::new();
1933
1934        msg1.hash(&mut hasher1);
1935        msg2.hash(&mut hasher2);
1936        msg3.hash(&mut hasher3);
1937
1938        assert_eq!(hasher1.finish(), hasher2.finish());
1939        assert_ne!(hasher1.finish(), hasher3.finish());
1940    }
1941
1942    /// Tests that RunnableState enum implements Clone and PartialEq.
1943    /// This test demonstrates the runnable state management feature.
1944    #[test]
1945    fn test_runnable_state() {
1946        let state1 = RunnableState::Created;
1947        let state2 = state1.clone();
1948        assert_eq!(state1, state2);
1949
1950        let state3 = RunnableState::Running;
1951        assert_ne!(state1, state3);
1952
1953        let state4 = RunnableState::Paused;
1954        let state5 = RunnableState::Terminated;
1955        assert_ne!(state4, state5);
1956    }
1957
1958    /// Tests that RunnableImpl can be created with an AppContext.
1959    /// This test demonstrates the runnable implementation creation feature.
1960    #[test]
1961    fn test_runnable_impl_new() {
1962        let app_context = create_test_app_context();
1963        let runnable = RunnableImpl::new(app_context.clone());
1964
1965        assert_eq!(runnable.get_app_context(), &app_context);
1966        assert_eq!(runnable.running_state, RunnableState::Created);
1967        assert!(runnable.app_context_sender.is_none());
1968        assert!(runnable.message_sender.is_none());
1969        assert!(runnable.app_context_receiver.is_none());
1970        assert!(runnable.message_receiver.is_none());
1971    }
1972
1973    /// Tests that RunnableImpl can set and get UUID.
1974    /// This test demonstrates the runnable UUID management feature.
1975    #[test]
1976    fn test_runnable_impl_uuid() {
1977        let app_context = create_test_app_context();
1978        let mut runnable = RunnableImpl::new(app_context);
1979
1980        let original_uuid = runnable.get_uuid();
1981        let new_uuid = Uuid::new_v4();
1982        runnable.set_uuid(new_uuid);
1983
1984        assert_eq!(runnable.get_uuid(), new_uuid);
1985        assert_ne!(runnable.get_uuid(), original_uuid);
1986    }
1987
1988    /// Tests that RunnableImpl can set and get senders and receivers.
1989    /// This test demonstrates the runnable channel management feature.
1990    #[test]
1991    fn test_runnable_impl_channels() {
1992        let app_context = create_test_app_context();
1993        let mut runnable = RunnableImpl::new(app_context);
1994
1995        let (app_context_sender, app_context_receiver) = mpsc::channel();
1996        let (message_sender, message_receiver) = mpsc::channel();
1997
1998        runnable.set_app_context_sender(app_context_sender);
1999        runnable.set_message_sender(message_sender);
2000        runnable.set_app_context_receiver(app_context_receiver);
2001        runnable.set_message_receiver(message_receiver);
2002
2003        assert!(runnable.get_app_context_sender().is_some());
2004        assert!(runnable.get_message_sender().is_some());
2005        assert!(runnable.app_context_receiver.is_some());
2006        assert!(runnable.message_receiver.is_some());
2007    }
2008
2009    /// Tests that RunnableImpl can update app context.
2010    /// This test demonstrates the app context update feature.
2011    #[test]
2012    fn test_runnable_impl_update_app_context() {
2013        let app_context = create_test_app_context();
2014        let mut runnable = RunnableImpl::new(app_context);
2015
2016        let mut new_app_context = create_test_app_context();
2017        new_app_context.config.frame_delay = 100;
2018
2019        runnable.update_app_context(new_app_context.clone());
2020        assert_eq!(runnable.get_app_context(), &new_app_context);
2021    }
2022
2023    /// Tests that RunnableImpl can receive updates from channels.
2024    /// This test demonstrates the message receiving feature.
2025    #[test]
2026    fn test_runnable_impl_receive_updates() {
2027        let app_context = create_test_app_context();
2028        let mut runnable = RunnableImpl::new(app_context);
2029
2030        let (app_context_sender, app_context_receiver) = mpsc::channel();
2031        let (message_sender, message_receiver) = mpsc::channel();
2032
2033        runnable.set_app_context_receiver(app_context_receiver);
2034        runnable.set_message_receiver(message_receiver);
2035
2036        // Send test data
2037        let field_update = create_test_field_update(
2038            EntityType::App,
2039            "test",
2040            "field",
2041            Value::String("value".to_string()),
2042        );
2043        app_context_sender
2044            .send((Uuid::new_v4(), vec![field_update]))
2045            .unwrap();
2046        message_sender
2047            .send((Uuid::new_v4(), Message::RedrawApp))
2048            .unwrap();
2049
2050        let (updated_app_context, messages) = runnable.receive_updates();
2051        assert_eq!(messages.len(), 1);
2052        assert_eq!(messages[0], Message::RedrawApp);
2053    }
2054
2055    /// Tests that RunnableImpl processes messages correctly.
2056    /// This test demonstrates the message processing feature.
2057    #[test]
2058    fn test_runnable_impl_process_messages() {
2059        let app_context = create_test_app_context();
2060        let mut runnable = RunnableImpl::new(app_context.clone());
2061
2062        let messages = vec![Message::Start, Message::Pause, Message::Terminate];
2063
2064        runnable.process(app_context.clone(), vec![Message::Start]);
2065        assert_eq!(runnable.running_state, RunnableState::Running);
2066
2067        runnable.process(app_context.clone(), vec![Message::Pause]);
2068        assert_eq!(runnable.running_state, RunnableState::Paused);
2069
2070        runnable.process(app_context.clone(), vec![Message::Terminate]);
2071        assert_eq!(runnable.running_state, RunnableState::Terminated);
2072    }
2073
2074    /// Tests that RunnableImpl can send messages through channels.
2075    /// This test demonstrates the message sending feature.
2076    #[test]
2077    fn test_runnable_impl_send_message() {
2078        let app_context = create_test_app_context();
2079        let mut runnable = RunnableImpl::new(app_context);
2080
2081        let (message_sender, message_receiver) = mpsc::channel();
2082        runnable.set_message_sender(message_sender);
2083
2084        runnable.send_message(Message::RedrawApp);
2085
2086        let (uuid, received_message) = message_receiver.recv().unwrap();
2087        assert_eq!(received_message, Message::RedrawApp);
2088        assert_eq!(uuid, runnable.get_uuid());
2089    }
2090
2091    /// Tests that ThreadManager can be created with an AppContext.
2092    /// This test demonstrates the thread manager creation feature.
2093    #[test]
2094    fn test_thread_manager_new() {
2095        let app_context = create_test_app_context();
2096        let manager = ThreadManager::new(app_context.clone());
2097
2098        assert_eq!(manager.app_context, app_context);
2099        assert!(manager.threads.is_empty());
2100        assert!(manager.app_context_senders.is_empty());
2101        assert!(manager.message_senders.is_empty());
2102        assert!(manager.app_context_receivers.is_empty());
2103        assert!(manager.message_receivers.is_empty());
2104    }
2105
2106    /// Tests that ThreadManager can spawn threads.
2107    /// This test demonstrates the thread spawning feature.
2108    #[test]
2109    fn test_thread_manager_spawn_thread() {
2110        let app_context = create_test_app_context();
2111        let mut manager = ThreadManager::new(app_context.clone());
2112        let runnable = RunnableImpl::new(app_context);
2113
2114        let uuid = manager.spawn_thread(runnable);
2115
2116        assert!(manager.threads.contains_key(&uuid));
2117        assert!(manager.app_context_senders.contains_key(&uuid));
2118        assert!(manager.message_senders.contains_key(&uuid));
2119        assert!(manager.app_context_receivers.contains_key(&uuid));
2120        assert!(manager.message_receivers.contains_key(&uuid));
2121
2122        // Clean up
2123        manager.stop();
2124        manager.join_threads();
2125    }
2126
2127    /// Tests that ThreadManager can remove threads.
2128    /// This test demonstrates the thread removal feature.
2129    #[test]
2130    fn test_thread_manager_remove_thread() {
2131        let app_context = create_test_app_context();
2132        let mut manager = ThreadManager::new(app_context.clone());
2133        let runnable = RunnableImpl::new(app_context);
2134
2135        let uuid = manager.spawn_thread(runnable);
2136        assert!(manager.threads.contains_key(&uuid));
2137
2138        manager.remove_thread(uuid);
2139        assert!(!manager.threads.contains_key(&uuid));
2140        assert!(!manager.app_context_senders.contains_key(&uuid));
2141        assert!(!manager.message_senders.contains_key(&uuid));
2142    }
2143
2144    /// Tests that ThreadManager can send messages to specific threads.
2145    /// This test demonstrates the targeted message sending feature.
2146    #[test]
2147    fn test_thread_manager_send_message_to_thread() {
2148        let app_context = create_test_app_context();
2149        let mut manager = ThreadManager::new(app_context.clone());
2150        let runnable = RunnableImpl::new(app_context);
2151
2152        let uuid = manager.spawn_thread(runnable);
2153        let message = (Uuid::new_v4(), Message::RedrawApp);
2154
2155        manager.send_message_to_thread(message, uuid);
2156
2157        // Clean up
2158        manager.stop();
2159        manager.join_threads();
2160    }
2161
2162    /// Tests that ThreadManager can send messages to all threads.
2163    /// This test demonstrates the broadcast message sending feature.
2164    #[test]
2165    fn test_thread_manager_send_message_to_all_threads() {
2166        let app_context = create_test_app_context();
2167        let mut manager = ThreadManager::new(app_context.clone());
2168        let runnable1 = RunnableImpl::new(app_context.clone());
2169        let runnable2 = RunnableImpl::new(app_context.clone());
2170
2171        let uuid1 = manager.spawn_thread(runnable1);
2172        let uuid2 = manager.spawn_thread(runnable2);
2173
2174        let message = (Uuid::new_v4(), Message::RedrawApp);
2175        manager.send_message_to_all_threads(message);
2176
2177        // Clean up
2178        manager.stop();
2179        manager.join_threads();
2180    }
2181
2182    /// Tests that ThreadManager can send app context updates to specific threads.
2183    /// This test demonstrates the targeted app context update feature.
2184    #[test]
2185    fn test_thread_manager_send_app_context_update_to_thread() {
2186        let app_context = create_test_app_context();
2187        let mut manager = ThreadManager::new(app_context.clone());
2188        let runnable = RunnableImpl::new(app_context);
2189
2190        let uuid = manager.spawn_thread(runnable);
2191        let field_update = create_test_field_update(
2192            EntityType::App,
2193            "test",
2194            "field",
2195            Value::String("value".to_string()),
2196        );
2197
2198        manager.send_app_context_update_to_thread(vec![field_update], uuid);
2199
2200        // Clean up
2201        manager.stop();
2202        manager.join_threads();
2203    }
2204
2205    /// Tests that ThreadManager can send app context updates to all threads.
2206    /// This test demonstrates the broadcast app context update feature.
2207    #[test]
2208    fn test_thread_manager_send_app_context_update_to_all_threads() {
2209        let app_context = create_test_app_context();
2210        let mut manager = ThreadManager::new(app_context.clone());
2211        let runnable1 = RunnableImpl::new(app_context.clone());
2212        let runnable2 = RunnableImpl::new(app_context.clone());
2213
2214        let uuid1 = manager.spawn_thread(runnable1);
2215        let uuid2 = manager.spawn_thread(runnable2);
2216
2217        let field_update = create_test_field_update(
2218            EntityType::App,
2219            "test",
2220            "field",
2221            Value::String("value".to_string()),
2222        );
2223        let sender_uuid = Uuid::new_v4();
2224
2225        manager.send_app_context_update_to_all_threads((sender_uuid, vec![field_update]));
2226
2227        // Clean up
2228        manager.stop();
2229        manager.join_threads();
2230    }
2231
2232    /// Tests that ThreadManager can stop all threads.
2233    /// This test demonstrates the thread stopping feature.
2234    #[test]
2235    fn test_thread_manager_stop() {
2236        let app_context = create_test_app_context();
2237        let mut manager = ThreadManager::new(app_context.clone());
2238        let runnable = RunnableImpl::new(app_context);
2239
2240        let uuid = manager.spawn_thread(runnable);
2241        assert!(manager.threads.contains_key(&uuid));
2242
2243        manager.stop();
2244        manager.join_threads();
2245
2246        assert!(manager.threads.is_empty());
2247    }
2248
2249    /// Tests that ThreadManager can pause all threads.
2250    /// This test demonstrates the thread pausing feature.
2251    #[test]
2252    fn test_thread_manager_pause() {
2253        let app_context = create_test_app_context();
2254        let mut manager = ThreadManager::new(app_context.clone());
2255        let runnable = RunnableImpl::new(app_context);
2256
2257        let uuid = manager.spawn_thread(runnable);
2258        manager.pause();
2259
2260        // Clean up
2261        manager.stop();
2262        manager.join_threads();
2263    }
2264
2265    /// Tests that ThreadManager can calculate hash values.
2266    /// This test demonstrates the hash calculation feature.
2267    #[test]
2268    fn test_thread_manager_get_hash() {
2269        let app_context = create_test_app_context();
2270        let manager = ThreadManager::new(app_context);
2271
2272        let test_string = "test";
2273        let hash1 = manager.get_hash(&test_string);
2274        let hash2 = manager.get_hash(&test_string);
2275        assert_eq!(hash1, hash2);
2276
2277        let test_string2 = "different";
2278        let hash3 = manager.get_hash(&test_string2);
2279        assert_ne!(hash1, hash3);
2280    }
2281
2282    /// Tests that ThreadManager can join all threads.
2283    /// This test demonstrates the thread joining feature.
2284    #[test]
2285    fn test_thread_manager_join_threads() {
2286        let app_context = create_test_app_context();
2287        let mut manager = ThreadManager::new(app_context.clone());
2288        let runnable = RunnableImpl::new(app_context);
2289
2290        let uuid = manager.spawn_thread(runnable);
2291        assert!(manager.threads.contains_key(&uuid));
2292
2293        manager.stop();
2294        manager.join_threads();
2295
2296        assert!(manager.threads.is_empty());
2297    }
2298
2299    /// Tests that run_script_in_thread spawns a thread correctly.
2300    /// This test demonstrates the script thread spawning feature.
2301    #[test]
2302    fn test_run_script_in_thread() {
2303        let app_context = create_test_app_context();
2304        let mut manager = ThreadManager::new(app_context.clone());
2305
2306        let muxbox_id = "test_muxbox".to_string();
2307        let choice_id = "test_choice".to_string();
2308
2309        let uuid = run_script_in_thread(app_context, &mut manager, muxbox_id, choice_id);
2310
2311        assert!(manager.threads.contains_key(&uuid));
2312        assert!(manager.app_context_senders.contains_key(&uuid));
2313        assert!(manager.message_senders.contains_key(&uuid));
2314
2315        // Clean up
2316        manager.stop();
2317        manager.join_threads();
2318    }
2319
2320    // T0328: REMOVED test_message_muxbox_output_update - replaced by StreamUpdateMessage tests
2321
2322    /// Tests that Message::MuxBoxScriptUpdate contains correct data.
2323    /// This test demonstrates the muxbox script update message feature.
2324    #[test]
2325    fn test_message_muxbox_script_update() {
2326        let muxbox_id = "test_muxbox".to_string();
2327        let script = vec!["echo 'test'".to_string(), "ls".to_string()];
2328
2329        let message = Message::MuxBoxScriptUpdate(muxbox_id.clone(), script.clone());
2330
2331        match message {
2332            Message::MuxBoxScriptUpdate(id, script_content) => {
2333                assert_eq!(id, muxbox_id);
2334                assert_eq!(script_content, script);
2335            }
2336            _ => panic!("Expected MuxBoxScriptUpdate message"),
2337        }
2338    }
2339
2340    /// Tests that Message::ReplaceMuxBox contains correct data.
2341    /// This test demonstrates the muxbox replacement message feature.
2342    #[test]
2343    fn test_message_replace_muxbox() {
2344        let muxbox_id = "test_muxbox".to_string();
2345        let muxbox = create_test_muxbox("new_muxbox");
2346
2347        let message = Message::ReplaceMuxBox(muxbox_id.clone(), muxbox.clone());
2348
2349        match message {
2350            Message::ReplaceMuxBox(id, new_muxbox) => {
2351                assert_eq!(id, muxbox_id);
2352                assert_eq!(new_muxbox.id, muxbox.id);
2353            }
2354            _ => panic!("Expected ReplaceMuxBox message"),
2355        }
2356    }
2357
2358    /// Tests that Message::SwitchActiveLayout contains correct data.
2359    /// This test demonstrates the layout switching message feature.
2360    #[test]
2361    fn test_message_switch_active_layout() {
2362        let layout_id = "test_layout".to_string();
2363        let message = Message::SwitchActiveLayout(layout_id.clone());
2364
2365        match message {
2366            Message::SwitchActiveLayout(id) => {
2367                assert_eq!(id, layout_id);
2368            }
2369            _ => panic!("Expected SwitchActiveLayout message"),
2370        }
2371    }
2372
2373    /// Tests that Message::KeyPress contains correct data.
2374    /// This test demonstrates the key press message feature.
2375    #[test]
2376    fn test_message_key_press() {
2377        let key = "ctrl+c".to_string();
2378        let message = Message::KeyPress(key.clone());
2379
2380        match message {
2381            Message::KeyPress(pressed_key) => {
2382                assert_eq!(pressed_key, key);
2383            }
2384            _ => panic!("Expected KeyPress message"),
2385        }
2386    }
2387
2388    /// Tests that Message::ExternalMessage contains correct data.
2389    /// This test demonstrates the external message feature.
2390    #[test]
2391    fn test_message_external_message() {
2392        let external_msg = "external command".to_string();
2393        let message = Message::ExternalMessage(external_msg.clone());
2394
2395        match message {
2396            Message::ExternalMessage(msg) => {
2397                assert_eq!(msg, external_msg);
2398            }
2399            _ => panic!("Expected ExternalMessage message"),
2400        }
2401    }
2402
2403    /// Tests that Message::AddBox contains correct data.
2404    /// This test demonstrates the box addition message feature.
2405    #[test]
2406    fn test_message_add_box() {
2407        let box_id = "test_box".to_string();
2408        let muxbox = create_test_muxbox("new_box");
2409
2410        let message = Message::AddBox(box_id.clone(), muxbox.clone());
2411
2412        match message {
2413            Message::AddBox(id, new_muxbox) => {
2414                assert_eq!(id, box_id);
2415                assert_eq!(new_muxbox.id, muxbox.id);
2416            }
2417            _ => panic!("Expected AddBox message"),
2418        }
2419    }
2420
2421    /// Tests that Message::RemoveBox contains correct data.
2422    /// This test demonstrates the box removal message feature.
2423    #[test]
2424    fn test_message_remove_box() {
2425        let box_id = "test_box".to_string();
2426        let message = Message::RemoveBox(box_id.clone());
2427
2428        match message {
2429            Message::RemoveBox(id) => {
2430                assert_eq!(id, box_id);
2431            }
2432            _ => panic!("Expected RemoveBox message"),
2433        }
2434    }
2435
2436    /// Tests that simple messages are created correctly.
2437    /// This test demonstrates the simple message creation feature.
2438    #[test]
2439    fn test_simple_messages() {
2440        let exit_msg = Message::Exit;
2441        let terminate_msg = Message::Terminate;
2442        let pause_msg = Message::Pause;
2443        let start_msg = Message::Start;
2444        let resize_msg = Message::Resize;
2445        let redraw_app_msg = Message::RedrawApp;
2446
2447        // Test that they can be created and compared
2448        assert_eq!(exit_msg, Message::Exit);
2449        assert_eq!(terminate_msg, Message::Terminate);
2450        assert_eq!(pause_msg, Message::Pause);
2451        assert_eq!(start_msg, Message::Start);
2452        assert_eq!(resize_msg, Message::Resize);
2453        assert_eq!(redraw_app_msg, Message::RedrawApp);
2454
2455        // Test that they are different from each other
2456        assert_ne!(exit_msg, terminate_msg);
2457        assert_ne!(pause_msg, start_msg);
2458        assert_ne!(resize_msg, redraw_app_msg);
2459    }
2460
2461    /// Tests that scroll messages are created correctly.
2462    /// This test demonstrates the scroll message creation feature.
2463    #[test]
2464    fn test_scroll_messages() {
2465        let scroll_down = Message::ScrollMuxBoxDown();
2466        let scroll_up = Message::ScrollMuxBoxUp();
2467        let scroll_left = Message::ScrollMuxBoxLeft();
2468        let scroll_right = Message::ScrollMuxBoxRight();
2469        let scroll_page_up = Message::ScrollMuxBoxPageUp();
2470        let scroll_page_down = Message::ScrollMuxBoxPageDown();
2471
2472        assert_eq!(scroll_down, Message::ScrollMuxBoxDown());
2473        assert_eq!(scroll_up, Message::ScrollMuxBoxUp());
2474        assert_eq!(scroll_left, Message::ScrollMuxBoxLeft());
2475        assert_eq!(scroll_right, Message::ScrollMuxBoxRight());
2476        assert_eq!(scroll_page_up, Message::ScrollMuxBoxPageUp());
2477        assert_eq!(scroll_page_down, Message::ScrollMuxBoxPageDown());
2478
2479        assert_ne!(scroll_down, scroll_up);
2480        assert_ne!(scroll_left, scroll_right);
2481    }
2482
2483    /// Tests that navigation messages are created correctly.
2484    /// This test demonstrates the navigation message creation feature.
2485    #[test]
2486    fn test_navigation_messages() {
2487        let next_muxbox = Message::NextMuxBox();
2488        let previous_muxbox = Message::PreviousMuxBox();
2489
2490        assert_eq!(next_muxbox, Message::NextMuxBox());
2491        assert_eq!(previous_muxbox, Message::PreviousMuxBox());
2492        assert_ne!(next_muxbox, previous_muxbox);
2493    }
2494
2495    /// Tests that box refresh messages are created correctly.
2496    /// This test demonstrates the box refresh message feature.
2497    #[test]
2498    fn test_box_refresh_messages() {
2499        let box_id = "test_box".to_string();
2500        let start_refresh = Message::StartBoxRefresh(box_id.clone());
2501        let stop_refresh = Message::StopBoxRefresh(box_id.clone());
2502        let event_refresh = Message::MuxBoxEventRefresh(box_id.clone());
2503
2504        match start_refresh {
2505            Message::StartBoxRefresh(id) => assert_eq!(id, box_id),
2506            _ => panic!("Expected StartBoxRefresh"),
2507        }
2508
2509        match stop_refresh {
2510            Message::StopBoxRefresh(id) => assert_eq!(id, box_id),
2511            _ => panic!("Expected StopBoxRefresh"),
2512        }
2513
2514        match event_refresh {
2515            Message::MuxBoxEventRefresh(id) => assert_eq!(id, box_id),
2516            _ => panic!("Expected MuxBoxEventRefresh"),
2517        }
2518    }
2519
2520    /// Tests that RedrawMuxBox message is created correctly.
2521    /// This test demonstrates the muxbox redraw message feature.
2522    #[test]
2523    fn test_redraw_muxbox_message() {
2524        let muxbox_id = "test_muxbox".to_string();
2525        let redraw_msg = Message::RedrawMuxBox(muxbox_id.clone());
2526
2527        match redraw_msg {
2528            Message::RedrawMuxBox(id) => assert_eq!(id, muxbox_id),
2529            _ => panic!("Expected RedrawMuxBox message"),
2530        }
2531    }
2532}