jsonrpc_debugger/
app.rs

1use ratatui::widgets::TableState;
2use std::collections::HashMap;
3use tokio::sync::{mpsc, oneshot};
4
5#[derive(Debug, Clone)]
6pub struct JsonRpcMessage {
7    pub id: Option<serde_json::Value>,
8    pub method: Option<String>,
9    pub params: Option<serde_json::Value>,
10    pub result: Option<serde_json::Value>,
11    pub error: Option<serde_json::Value>,
12    pub timestamp: std::time::SystemTime,
13    pub direction: MessageDirection,
14    pub transport: TransportType,
15    pub headers: Option<HashMap<String, String>>,
16}
17
18#[derive(Debug, Clone)]
19pub struct JsonRpcExchange {
20    pub id: Option<serde_json::Value>,
21    pub method: Option<String>,
22    pub request: Option<JsonRpcMessage>,
23    pub response: Option<JsonRpcMessage>,
24    #[allow(dead_code)] // Used in UI for duration calculation
25    pub timestamp: std::time::SystemTime,
26    pub transport: TransportType,
27}
28
29#[derive(Debug, Clone)]
30pub enum MessageDirection {
31    Request,
32    Response,
33}
34
35#[derive(Debug, Clone)]
36pub enum TransportType {
37    Http,
38    #[allow(dead_code)] // Used in tests and UI display
39    WebSocket,
40}
41
42#[derive(Debug, Clone, PartialEq)]
43pub enum InputMode {
44    Normal,
45    EditingTarget,
46    FilteringRequests,
47}
48
49#[derive(Debug, Clone, PartialEq)]
50pub enum Focus {
51    MessageList,
52    RequestSection,
53    ResponseSection,
54}
55
56#[derive(Debug, Clone, PartialEq)]
57pub enum AppMode {
58    Normal,       // Regular proxy mode
59    Paused,       // All requests paused
60    Intercepting, // Inspecting a specific request
61}
62
63#[derive(Debug)]
64pub enum ProxyDecision {
65    Allow(Option<serde_json::Value>, Option<HashMap<String, String>>), // Allow with optional modified JSON and headers
66    Block,                                                             // Block the request
67    Complete(serde_json::Value), // Complete with custom response
68}
69
70#[allow(dead_code)]
71pub struct PendingRequest {
72    pub id: String,
73    pub original_request: JsonRpcMessage,
74    pub modified_request: Option<String>, // JSON string for editing
75    pub modified_headers: Option<HashMap<String, String>>, // Modified headers
76    pub decision_sender: oneshot::Sender<ProxyDecision>,
77}
78
79#[allow(dead_code)]
80pub struct App {
81    pub exchanges: Vec<JsonRpcExchange>,
82    pub selected_exchange: usize,
83    pub filter_text: String,
84    pub table_state: TableState,
85    pub details_scroll: usize,
86    pub request_details_scroll: usize,
87    pub response_details_scroll: usize,
88    pub details_tab: usize,
89    pub request_details_tab: usize,
90    pub response_details_tab: usize,
91    pub intercept_details_scroll: usize, // New field for intercept details scrolling
92    pub proxy_config: ProxyConfig,
93    pub is_running: bool,
94    pub message_receiver: Option<mpsc::UnboundedReceiver<JsonRpcMessage>>,
95    pub input_mode: InputMode,
96    pub input_buffer: String,
97    pub app_mode: AppMode,                     // New field
98    pub pending_requests: Vec<PendingRequest>, // New field
99    pub selected_pending: usize,               // New field
100    pub request_editor_buffer: String,         // New field
101    pub focus: Focus,                          // New field for tracking which element is active
102    pub request_tab: usize,                    // 0 = Headers, 1 = Body
103    pub response_tab: usize,                   // 0 = Headers, 1 = Body
104}
105
106#[derive(Debug)]
107#[allow(dead_code)]
108pub struct ProxyConfig {
109    pub listen_port: u16,
110    pub target_url: String,
111    pub transport: TransportType,
112}
113
114impl Default for App {
115    fn default() -> Self {
116        Self::new()
117    }
118}
119
120#[allow(dead_code)]
121impl App {
122    pub fn new() -> Self {
123        let mut table_state = TableState::default();
124        table_state.select(Some(0));
125
126        Self {
127            exchanges: Vec::new(),
128            selected_exchange: 0,
129            filter_text: String::new(),
130            table_state,
131            details_scroll: 0,
132            request_details_scroll: 0,
133            response_details_scroll: 0,
134            details_tab: 0,
135            request_details_tab: 0,
136            response_details_tab: 0,
137            intercept_details_scroll: 0,
138            proxy_config: ProxyConfig {
139                listen_port: 8080,
140                target_url: "".to_string(),
141                transport: TransportType::Http,
142            },
143            is_running: true,
144            message_receiver: None,
145            input_mode: InputMode::Normal,
146            input_buffer: String::new(),
147            app_mode: AppMode::Normal,
148            pending_requests: Vec::new(),
149            selected_pending: 0,
150            request_editor_buffer: String::new(),
151            focus: Focus::MessageList,
152            request_tab: 1,  // Body selected by default
153            response_tab: 1, // Body selected by default
154        }
155    }
156
157    pub fn new_with_receiver(receiver: mpsc::UnboundedReceiver<JsonRpcMessage>) -> Self {
158        let mut table_state = TableState::default();
159        table_state.select(Some(0));
160
161        Self {
162            exchanges: Vec::new(),
163            selected_exchange: 0,
164            filter_text: String::new(),
165            table_state,
166            details_scroll: 0,
167            request_details_scroll: 0,
168            response_details_scroll: 0,
169            details_tab: 0,
170            request_details_tab: 0,
171            response_details_tab: 0,
172            intercept_details_scroll: 0,
173            proxy_config: ProxyConfig {
174                listen_port: 8080,
175                target_url: "".to_string(),
176                transport: TransportType::Http,
177            },
178            is_running: true,
179            message_receiver: Some(receiver),
180            input_mode: InputMode::Normal,
181            input_buffer: String::new(),
182            app_mode: AppMode::Normal,
183            pending_requests: Vec::new(),
184            selected_pending: 0,
185            request_editor_buffer: String::new(),
186            focus: Focus::MessageList,
187            request_tab: 1,  // Body selected by default
188            response_tab: 1, // Body selected by default
189        }
190    }
191
192    pub fn check_for_new_messages(&mut self) {
193        if let Some(receiver) = &mut self.message_receiver {
194            let mut new_messages = Vec::new();
195            while let Ok(message) = receiver.try_recv() {
196                new_messages.push(message);
197            }
198            for message in new_messages {
199                self.add_message(message);
200            }
201        }
202    }
203
204    pub fn add_message(&mut self, mut message: JsonRpcMessage) {
205        // Sanitize message content to prevent UI corruption
206        if let Some(ref mut error) = message.error {
207            if let Some(data) = error.get_mut("data") {
208                if let Some(data_str) = data.as_str() {
209                    let sanitized = data_str
210                        .chars()
211                        .filter(|c| c.is_ascii() && (!c.is_control() || *c == '\n' || *c == '\t'))
212                        .take(500)
213                        .collect::<String>();
214                    *data = serde_json::Value::String(sanitized);
215                }
216            }
217        }
218
219        match message.direction {
220            MessageDirection::Request => {
221                // Create a new exchange for the request
222                let exchange = JsonRpcExchange {
223                    id: message.id.clone(),
224                    method: message.method.clone(),
225                    request: Some(message.clone()),
226                    response: None,
227                    timestamp: message.timestamp,
228                    transport: message.transport.clone(),
229                };
230                self.exchanges.push(exchange);
231            }
232            MessageDirection::Response => {
233                // Find matching request by ID and add response
234                if let Some(exchange) = self
235                    .exchanges
236                    .iter_mut()
237                    .rev()
238                    .find(|e| e.id == message.id && e.response.is_none())
239                {
240                    exchange.response = Some(message);
241                } else {
242                    // No matching request found, create exchange with just response
243                    let exchange = JsonRpcExchange {
244                        id: message.id.clone(),
245                        method: None,
246                        request: None,
247                        response: Some(message.clone()),
248                        timestamp: message.timestamp,
249                        transport: message.transport.clone(),
250                    };
251                    self.exchanges.push(exchange);
252                }
253            }
254        }
255    }
256
257    pub fn get_selected_exchange(&self) -> Option<&JsonRpcExchange> {
258        self.exchanges.get(self.selected_exchange)
259    }
260
261    pub fn select_next(&mut self) {
262        if !self.exchanges.is_empty() {
263            self.selected_exchange = (self.selected_exchange + 1) % self.exchanges.len();
264            self.table_state.select(Some(self.selected_exchange));
265            self.reset_details_scroll();
266            self.request_details_scroll = 0;
267            self.response_details_scroll = 0;
268            self.details_tab = 0;
269            self.request_details_tab = 0;
270            self.response_details_tab = 0;
271        }
272    }
273
274    pub fn select_previous(&mut self) {
275        if !self.exchanges.is_empty() {
276            self.selected_exchange = if self.selected_exchange == 0 {
277                self.exchanges.len() - 1
278            } else {
279                self.selected_exchange - 1
280            };
281            self.table_state.select(Some(self.selected_exchange));
282            self.reset_details_scroll();
283            self.request_details_scroll = 0;
284            self.response_details_scroll = 0;
285            self.details_tab = 0;
286            self.request_details_tab = 0;
287            self.response_details_tab = 0;
288        }
289    }
290
291    pub fn toggle_proxy(&mut self) {
292        self.is_running = !self.is_running;
293    }
294
295    pub fn scroll_details_up(&mut self) {
296        if self.details_scroll > 0 {
297            self.details_scroll -= 1;
298        }
299    }
300
301    pub fn scroll_details_down(&mut self, max_lines: usize, visible_lines: usize) {
302        if max_lines > visible_lines && self.details_scroll < max_lines - visible_lines {
303            self.details_scroll += 1;
304        }
305    }
306
307    pub fn reset_details_scroll(&mut self) {
308        self.details_scroll = 0;
309    }
310
311    // Intercept details scrolling methods
312    pub fn scroll_intercept_details_up(&mut self) {
313        if self.intercept_details_scroll > 0 {
314            self.intercept_details_scroll -= 1;
315        }
316    }
317
318    pub fn scroll_intercept_details_down(&mut self, max_lines: usize, visible_lines: usize) {
319        if max_lines > visible_lines && self.intercept_details_scroll < max_lines - visible_lines {
320            self.intercept_details_scroll += 1;
321        }
322    }
323
324    pub fn reset_intercept_details_scroll(&mut self) {
325        self.intercept_details_scroll = 0;
326    }
327
328    pub fn page_down_intercept_details(&mut self) {
329        let page_size = 10; // Half page
330        self.intercept_details_scroll += page_size;
331    }
332
333    pub fn page_up_intercept_details(&mut self) {
334        let page_size = 10; // Half page
335        self.intercept_details_scroll = self.intercept_details_scroll.saturating_sub(page_size);
336    }
337
338    pub fn goto_top_intercept_details(&mut self) {
339        self.intercept_details_scroll = 0;
340    }
341
342    pub fn goto_bottom_intercept_details(&mut self, max_lines: usize, visible_lines: usize) {
343        if max_lines > visible_lines {
344            self.intercept_details_scroll = max_lines - visible_lines;
345        }
346    }
347
348    // Enhanced details scrolling with vim-style page jumps
349    pub fn page_down_details(&mut self, visible_lines: usize) {
350        let page_size = visible_lines / 2; // Half page
351        self.details_scroll += page_size;
352    }
353
354    pub fn page_up_details(&mut self) {
355        let page_size = 10; // Half page
356        self.details_scroll = self.details_scroll.saturating_sub(page_size);
357    }
358
359    pub fn goto_top_details(&mut self) {
360        self.details_scroll = 0;
361    }
362
363    pub fn goto_bottom_details(&mut self, max_lines: usize, visible_lines: usize) {
364        if max_lines > visible_lines {
365            self.details_scroll = max_lines - visible_lines;
366        }
367    }
368
369    pub fn switch_focus(&mut self) {
370        self.focus = match self.focus {
371            Focus::MessageList => Focus::RequestSection,
372            Focus::RequestSection => Focus::ResponseSection,
373            Focus::ResponseSection => Focus::MessageList,
374        };
375        self.reset_details_scroll();
376        self.request_details_scroll = 0;
377        self.response_details_scroll = 0;
378    }
379
380    pub fn switch_focus_reverse(&mut self) {
381        self.focus = match self.focus {
382            Focus::MessageList => Focus::ResponseSection,
383            Focus::RequestSection => Focus::MessageList,
384            Focus::ResponseSection => Focus::RequestSection,
385        };
386        self.reset_details_scroll();
387        self.request_details_scroll = 0;
388        self.response_details_scroll = 0;
389    }
390
391    pub fn is_message_list_focused(&self) -> bool {
392        matches!(self.focus, Focus::MessageList)
393    }
394
395    pub fn is_request_section_focused(&self) -> bool {
396        matches!(self.focus, Focus::RequestSection)
397    }
398
399    pub fn is_response_section_focused(&self) -> bool {
400        matches!(self.focus, Focus::ResponseSection)
401    }
402
403    pub fn next_request_tab(&mut self) {
404        self.request_tab = 1 - self.request_tab; // Toggle between 0 and 1
405        self.reset_details_scroll();
406    }
407
408    pub fn previous_request_tab(&mut self) {
409        self.request_tab = 1 - self.request_tab; // Toggle between 0 and 1
410        self.reset_details_scroll();
411    }
412
413    pub fn next_response_tab(&mut self) {
414        self.response_tab = 1 - self.response_tab; // Toggle between 0 and 1
415        self.reset_details_scroll();
416    }
417
418    pub fn previous_response_tab(&mut self) {
419        self.response_tab = 1 - self.response_tab; // Toggle between 0 and 1
420        self.reset_details_scroll();
421    }
422
423    // Filtering requests methods
424    pub fn start_filtering_requests(&mut self) {
425        self.input_mode = InputMode::FilteringRequests;
426        self.input_buffer.clear();
427    }
428
429    pub fn cancel_filtering(&mut self) {
430        self.input_mode = InputMode::Normal;
431        self.input_buffer.clear();
432    }
433
434    pub fn apply_filter(&mut self) {
435        self.filter_text = self.input_buffer.clone();
436        self.input_mode = InputMode::Normal;
437        self.input_buffer.clear();
438    }
439
440    // Get content lines for proper scrolling calculations
441    // Target editing methods
442    pub fn start_editing_target(&mut self) {
443        self.input_mode = InputMode::EditingTarget;
444        self.input_buffer.clear();
445    }
446
447    pub fn cancel_editing(&mut self) {
448        self.input_mode = InputMode::Normal;
449        self.input_buffer.clear();
450    }
451
452    pub fn confirm_target_edit(&mut self) {
453        if !self.input_buffer.trim().is_empty() {
454            self.proxy_config.target_url = self.input_buffer.trim().to_string();
455        }
456        self.input_mode = InputMode::Normal;
457        self.input_buffer.clear();
458    }
459
460    pub fn handle_input_char(&mut self, c: char) {
461        if self.input_mode == InputMode::EditingTarget
462            || self.input_mode == InputMode::FilteringRequests
463        {
464            self.input_buffer.push(c);
465        }
466    }
467
468    pub fn handle_backspace(&mut self) {
469        if self.input_mode == InputMode::EditingTarget
470            || self.input_mode == InputMode::FilteringRequests
471        {
472            self.input_buffer.pop();
473        }
474    }
475
476    pub fn get_details_content_lines(&self) -> usize {
477        if let Some(exchange) = self.get_selected_exchange() {
478            let mut line_count = 1; // Transport line
479
480            if exchange.method.is_some() {
481                line_count += 1;
482            }
483            if exchange.id.is_some() {
484                line_count += 1;
485            }
486
487            // Request section
488            line_count += 1; // Blank line before section
489            line_count += 1; // Section header
490            line_count += 1; // Tabs line
491
492            if let Some(request) = &exchange.request {
493                match self.request_details_tab {
494                    0 => match &request.headers {
495                        Some(headers) if !headers.is_empty() => {
496                            line_count += headers.len();
497                        }
498                        Some(_) | None => {
499                            line_count += 1;
500                        }
501                    },
502                    _ => {
503                        let mut request_json = serde_json::Map::new();
504                        request_json.insert(
505                            "jsonrpc".to_string(),
506                            serde_json::Value::String("2.0".to_string()),
507                        );
508                        if let Some(id) = &request.id {
509                            request_json.insert("id".to_string(), id.clone());
510                        }
511                        if let Some(method) = &request.method {
512                            request_json.insert(
513                                "method".to_string(),
514                                serde_json::Value::String(method.clone()),
515                            );
516                        }
517                        if let Some(params) = &request.params {
518                            request_json.insert("params".to_string(), params.clone());
519                        }
520
521                        if let Ok(json_str) =
522                            serde_json::to_string_pretty(&serde_json::Value::Object(request_json))
523                        {
524                            line_count += json_str.lines().count();
525                        }
526                    }
527                }
528            } else {
529                line_count += 1;
530            }
531
532            // Response section
533            line_count += 1; // Blank line before section
534            line_count += 1; // Section header
535            line_count += 1; // Tabs line
536
537            if let Some(response) = &exchange.response {
538                match self.response_details_tab {
539                    0 => match &response.headers {
540                        Some(headers) if !headers.is_empty() => {
541                            line_count += headers.len();
542                        }
543                        Some(_) | None => {
544                            line_count += 1;
545                        }
546                    },
547                    _ => {
548                        let mut response_json = serde_json::Map::new();
549                        response_json.insert(
550                            "jsonrpc".to_string(),
551                            serde_json::Value::String("2.0".to_string()),
552                        );
553                        if let Some(id) = &response.id {
554                            response_json.insert("id".to_string(), id.clone());
555                        }
556                        if let Some(result) = &response.result {
557                            response_json.insert("result".to_string(), result.clone());
558                        }
559                        if let Some(error) = &response.error {
560                            response_json.insert("error".to_string(), error.clone());
561                        }
562
563                        if let Ok(json_str) =
564                            serde_json::to_string_pretty(&serde_json::Value::Object(response_json))
565                        {
566                            line_count += json_str.lines().count();
567                        }
568                    }
569                }
570            } else {
571                line_count += 1;
572            }
573
574            line_count
575        } else {
576            1
577        }
578    }
579
580    pub fn get_request_details_content_lines(&self) -> usize {
581        if let Some(exchange) = self.get_selected_exchange() {
582            let mut line_count = 0;
583
584            // Basic exchange info
585            line_count += 1; // Transport line
586
587            if exchange.method.is_some() {
588                line_count += 1;
589            }
590            if exchange.id.is_some() {
591                line_count += 1;
592            }
593
594            // Request section
595            line_count += 1; // Blank line before section
596            line_count += 1; // Section header
597            line_count += 1; // Tabs line
598
599            if let Some(request) = &exchange.request {
600                match self.request_details_tab {
601                    0 => match &request.headers {
602                        Some(headers) if !headers.is_empty() => {
603                            line_count += headers.len();
604                        }
605                        Some(_) | None => {
606                            line_count += 1;
607                        }
608                    },
609                    _ => {
610                        let mut request_json = serde_json::Map::new();
611                        request_json.insert(
612                            "jsonrpc".to_string(),
613                            serde_json::Value::String("2.0".to_string()),
614                        );
615                        if let Some(id) = &request.id {
616                            request_json.insert("id".to_string(), id.clone());
617                        }
618                        if let Some(method) = &request.method {
619                            request_json.insert(
620                                "method".to_string(),
621                                serde_json::Value::String(method.clone()),
622                            );
623                        }
624                        if let Some(params) = &request.params {
625                            request_json.insert("params".to_string(), params.clone());
626                        }
627
628                        if let Ok(json_str) =
629                            serde_json::to_string_pretty(&serde_json::Value::Object(request_json))
630                        {
631                            line_count += json_str.lines().count();
632                        }
633                    }
634                }
635            } else {
636                line_count += 1;
637            }
638
639            line_count
640        } else {
641            1
642        }
643    }
644
645    pub fn get_response_details_content_lines(&self) -> usize {
646        if let Some(exchange) = self.get_selected_exchange() {
647            let mut line_count = 0;
648
649            // Response section
650            line_count += 1; // Section header
651            line_count += 1; // Tabs line
652
653            if let Some(response) = &exchange.response {
654                match self.response_details_tab {
655                    0 => match &response.headers {
656                        Some(headers) if !headers.is_empty() => {
657                            line_count += headers.len();
658                        }
659                        Some(_) | None => {
660                            line_count += 1;
661                        }
662                    },
663                    _ => {
664                        let mut response_json = serde_json::Map::new();
665                        response_json.insert(
666                            "jsonrpc".to_string(),
667                            serde_json::Value::String("2.0".to_string()),
668                        );
669                        if let Some(id) = &response.id {
670                            response_json.insert("id".to_string(), id.clone());
671                        }
672                        if let Some(result) = &response.result {
673                            response_json.insert("result".to_string(), result.clone());
674                        }
675                        if let Some(error) = &response.error {
676                            response_json.insert("error".to_string(), error.clone());
677                        }
678
679                        if let Ok(json_str) =
680                            serde_json::to_string_pretty(&serde_json::Value::Object(response_json))
681                        {
682                            line_count += json_str.lines().count();
683                        }
684                    }
685                }
686            } else {
687                line_count += 1;
688            }
689
690            line_count
691        } else {
692            1
693        }
694    }
695
696    // Pause/Intercept functionality
697    pub fn toggle_pause_mode(&mut self) {
698        self.app_mode = match self.app_mode {
699            AppMode::Normal => AppMode::Paused,
700            AppMode::Paused => AppMode::Normal,
701            AppMode::Intercepting => AppMode::Normal,
702        };
703    }
704
705    pub fn select_next_pending(&mut self) {
706        if !self.pending_requests.is_empty() {
707            self.selected_pending = (self.selected_pending + 1) % self.pending_requests.len();
708            self.reset_intercept_details_scroll();
709        }
710    }
711
712    pub fn select_previous_pending(&mut self) {
713        if !self.pending_requests.is_empty() {
714            self.selected_pending = if self.selected_pending == 0 {
715                self.pending_requests.len() - 1
716            } else {
717                self.selected_pending - 1
718            };
719            self.reset_intercept_details_scroll();
720        }
721    }
722
723    pub fn get_selected_pending(&self) -> Option<&PendingRequest> {
724        self.pending_requests.get(self.selected_pending)
725    }
726
727    pub fn allow_selected_request(&mut self) {
728        if self.selected_pending < self.pending_requests.len() {
729            let pending = self.pending_requests.remove(self.selected_pending);
730            if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
731                self.selected_pending -= 1;
732            }
733
734            // Send decision to proxy
735            let decision = if let Some(ref modified_json) = pending.modified_request {
736                if let Ok(parsed) = serde_json::from_str::<serde_json::Value>(modified_json) {
737                    ProxyDecision::Allow(Some(parsed), pending.modified_headers.clone())
738                } else {
739                    ProxyDecision::Allow(None, pending.modified_headers.clone())
740                    // Fallback to original if modified JSON is invalid
741                }
742            } else {
743                ProxyDecision::Allow(None, pending.modified_headers.clone()) // Use original request
744            };
745
746            let _ = pending.decision_sender.send(decision);
747        }
748    }
749
750    pub fn block_selected_request(&mut self) {
751        if self.selected_pending < self.pending_requests.len() {
752            let pending = self.pending_requests.remove(self.selected_pending);
753            if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
754                self.selected_pending -= 1;
755            }
756
757            // Send block decision to proxy
758            let _ = pending.decision_sender.send(ProxyDecision::Block);
759        }
760    }
761
762    pub fn resume_all_requests(&mut self) {
763        for pending in self.pending_requests.drain(..) {
764            let _ = pending
765                .decision_sender
766                .send(ProxyDecision::Allow(None, None));
767        }
768        self.selected_pending = 0;
769        self.app_mode = AppMode::Normal;
770    }
771
772    pub fn get_pending_request_json(&self) -> Option<String> {
773        if let Some(pending) = self.get_selected_pending() {
774            // Get the original request JSON and format it nicely
775            let json_value = serde_json::json!({
776                "jsonrpc": "2.0",
777                "method": pending.original_request.method,
778                "params": pending.original_request.params,
779                "id": pending.original_request.id
780            });
781
782            // Pretty print the JSON for editing
783            serde_json::to_string_pretty(&json_value).ok()
784        } else {
785            None
786        }
787    }
788
789    pub fn apply_edited_json(&mut self, edited_json: String) -> Result<(), String> {
790        if self.selected_pending >= self.pending_requests.len() {
791            return Err("No pending request selected".to_string());
792        }
793
794        // Parse the edited JSON
795        let parsed: serde_json::Value =
796            serde_json::from_str(&edited_json).map_err(|e| format!("Invalid JSON: {}", e))?;
797
798        // Validate it's a proper JSON-RPC request
799        if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
800            return Err("Missing or invalid 'jsonrpc' field".to_string());
801        }
802
803        if parsed.get("method").is_none() {
804            return Err("Missing 'method' field".to_string());
805        }
806
807        // Store the modified request
808        self.pending_requests[self.selected_pending].modified_request = Some(edited_json);
809
810        Ok(())
811    }
812
813    pub fn get_pending_request_headers(&self) -> Option<String> {
814        if let Some(pending) = self.get_selected_pending() {
815            // Get headers (modified if available, otherwise original)
816            let headers = pending
817                .modified_headers
818                .as_ref()
819                .or(pending.original_request.headers.as_ref());
820
821            if let Some(headers) = headers {
822                // Format headers as key: value pairs for editing
823                let mut header_lines = Vec::new();
824                for (key, value) in headers {
825                    header_lines.push(format!("{}: {}", key, value));
826                }
827                Some(header_lines.join("\n"))
828            } else {
829                Some(
830                    "# No headers\n# Add headers in the format:\n# header-name: header-value"
831                        .to_string(),
832                )
833            }
834        } else {
835            None
836        }
837    }
838
839    pub fn apply_edited_headers(&mut self, edited_headers: String) -> Result<(), String> {
840        if self.selected_pending >= self.pending_requests.len() {
841            return Err("No pending request selected".to_string());
842        }
843
844        let mut headers = HashMap::new();
845
846        for line in edited_headers.lines() {
847            let line = line.trim();
848
849            // Skip empty lines and comments
850            if line.is_empty() || line.starts_with('#') {
851                continue;
852            }
853
854            // Parse header line (key: value)
855            if let Some(colon_pos) = line.find(':') {
856                let key = line[..colon_pos].trim().to_string();
857                let value = line[colon_pos + 1..].trim().to_string();
858
859                if !key.is_empty() {
860                    headers.insert(key, value);
861                }
862            } else {
863                return Err(format!(
864                    "Invalid header format: '{}'. Use 'key: value' format.",
865                    line
866                ));
867            }
868        }
869
870        // Store the modified headers
871        self.pending_requests[self.selected_pending].modified_headers = Some(headers);
872
873        Ok(())
874    }
875
876    pub fn get_pending_response_template(&self) -> Option<String> {
877        if let Some(pending) = self.get_selected_pending() {
878            // Create a template JSON-RPC response with simple string result
879            let response_template = serde_json::json!({
880                "jsonrpc": "2.0",
881                "id": pending.original_request.id,
882                "result": "custom response"
883            });
884
885            // Pretty print the JSON for editing
886            serde_json::to_string_pretty(&response_template).ok()
887        } else {
888            None
889        }
890    }
891
892    pub fn complete_selected_request(&mut self, response_json: String) -> Result<(), String> {
893        if self.selected_pending >= self.pending_requests.len() {
894            return Err("No pending request selected".to_string());
895        }
896
897        // Parse the response JSON
898        let parsed: serde_json::Value =
899            serde_json::from_str(&response_json).map_err(|e| format!("Invalid JSON: {}", e))?;
900
901        // Validate it's a proper JSON-RPC response
902        if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
903            return Err("Missing or invalid 'jsonrpc' field".to_string());
904        }
905
906        if parsed.get("id").is_none() {
907            return Err("Missing 'id' field".to_string());
908        }
909
910        // Must have either result or error, but not both
911        let has_result = parsed.get("result").is_some();
912        let has_error = parsed.get("error").is_some();
913
914        if !has_result && !has_error {
915            return Err("Response must have either 'result' or 'error' field".to_string());
916        }
917
918        if has_result && has_error {
919            return Err("Response cannot have both 'result' and 'error' fields".to_string());
920        }
921
922        // Remove the pending request and send the completion decision
923        let pending = self.pending_requests.remove(self.selected_pending);
924        if self.selected_pending > 0 && self.selected_pending >= self.pending_requests.len() {
925            self.selected_pending -= 1;
926        }
927
928        let _ = pending
929            .decision_sender
930            .send(ProxyDecision::Complete(parsed));
931
932        Ok(())
933    }
934
935    pub async fn send_new_request(&self, request_json: String) -> Result<(), String> {
936        // Parse the request JSON
937        let parsed: serde_json::Value =
938            serde_json::from_str(&request_json).map_err(|e| format!("Invalid JSON: {}", e))?;
939
940        // Validate it's a proper JSON-RPC request
941        if parsed.get("jsonrpc") != Some(&serde_json::Value::String("2.0".to_string())) {
942            return Err("Missing or invalid 'jsonrpc' field".to_string());
943        }
944
945        if parsed.get("method").is_none() {
946            return Err("Missing 'method' field".to_string());
947        }
948
949        // Check if target URL is empty
950        if self.proxy_config.target_url.trim().is_empty() {
951            return Err("Target URL is not set. Press 't' to set a target URL first.".to_string());
952        }
953
954        let client = reqwest::Client::new();
955
956        // If we're in paused mode, send directly to target to avoid interception
957        // Otherwise, send through proxy for normal logging
958        let url = if matches!(self.app_mode, AppMode::Paused | AppMode::Intercepting) {
959            &self.proxy_config.target_url
960        } else {
961            // Send through proxy for normal logging
962            &format!("http://localhost:{}", self.proxy_config.listen_port)
963        };
964
965        let response = client
966            .post(url)
967            .header("Content-Type", "application/json")
968            .body(request_json)
969            .send()
970            .await
971            .map_err(|e| format!("Failed to send request: {}", e))?;
972
973        if !response.status().is_success() {
974            return Err(format!("Request failed with status: {}", response.status()));
975        }
976
977        Ok(())
978    }
979}