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