1use ratatui::Frame;
2use ratatui::layout::Rect;
3use ratatui::style::{Color, Modifier, Style};
4use ratatui::text::{Line, Span};
5use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph};
6
7use crate::app::{App, ChannelEditField, InputMode, McpState, Pane};
8
9pub(crate) fn render_list(app: &App, frame: &mut Frame<'_>, area: Rect) {
10 let title = if let InputMode::Search(ref buf) = app.input_mode {
11 format!(" Search: {buf}▎ ")
12 } else if !app.search_filter.is_empty() {
13 format!(" Channels [filter: {}] ", app.search_filter)
14 } else {
15 " Channels ".to_string()
16 };
17
18 let block = Block::default()
19 .title(title)
20 .borders(Borders::ALL)
21 .border_style(super::border_style(app, Pane::Main));
22
23 if let McpState::Loaded { image, .. } = &app.mcp {
24 let channels = image.channels();
25 let used = app.filtered_channels();
26 let items: Vec<ListItem<'_>> = used
27 .iter()
28 .map(|&i| {
29 let entry = channels.get(i);
30 let name = entry.as_ref().map(|e| e.name.clone()).unwrap_or_default();
31 let freq = entry
32 .as_ref()
33 .map(|e| format!("{:.3}", e.flash.rx_frequency.as_mhz()))
34 .unwrap_or_default();
35 ListItem::new(Line::from(vec![
36 Span::styled(format!("{i:>4}: "), Style::default().fg(Color::DarkGray)),
37 Span::styled(format!("{name:<12}"), Style::default().fg(Color::White)),
38 Span::styled(format!(" {freq}"), Style::default().fg(Color::Cyan)),
39 ]))
40 })
41 .collect();
42
43 let mut list_state = ListState::default();
44 list_state.select(Some(
45 app.channel_list_index.min(items.len().saturating_sub(1)),
46 ));
47
48 let list = List::new(items)
49 .block(block)
50 .highlight_style(
51 Style::default()
52 .fg(Color::Black)
53 .bg(Color::Cyan)
54 .add_modifier(Modifier::BOLD),
55 )
56 .highlight_symbol("▸ ");
57
58 frame.render_stateful_widget(list, area, &mut list_state);
59 } else {
60 let msg = " No MCP data loaded.\n Press [m] then [r] to read from radio.";
61 frame.render_widget(Paragraph::new(msg).block(block), area);
62 }
63}
64
65pub(crate) fn render_detail(app: &App, frame: &mut Frame<'_>, area: Rect) {
66 let block = Block::default()
67 .title(" Detail ")
68 .borders(Borders::ALL)
69 .border_style(super::border_style(app, Pane::Detail));
70
71 match &app.mcp {
72 McpState::Loaded { image, .. } => {
73 let channels = image.channels();
74 let used = app.filtered_channels();
75 if let Some(&ch_num) = used.get(app.channel_list_index)
76 && let Some(entry) = channels.get(ch_num)
77 {
78 let fc = &entry.flash;
79
80 let tone_info = if fc.tone_enabled {
82 format!("CTCSS TX {}", fc.tone_code.index())
83 } else if fc.ctcss_enabled {
84 format!("CTCSS {}/{}", fc.tone_code.index(), fc.ctcss_code.index())
85 } else if fc.dtcs_enabled {
86 format!("DCS {:03}", u16::from(fc.dcs_code.index()))
87 } else {
88 "None".to_string()
89 };
90
91 let duplex_info = match fc.duplex {
93 kenwood_thd75::types::FlashDuplex::Simplex => "Simplex".to_string(),
94 kenwood_thd75::types::FlashDuplex::Plus => {
95 format!("+{:.3} MHz", fc.tx_offset.as_mhz())
96 }
97 kenwood_thd75::types::FlashDuplex::Minus => {
98 format!("-{:.3} MHz", fc.tx_offset.as_mhz())
99 }
100 };
101
102 let mut lines = vec![
103 Line::from(vec![
104 Span::styled(" Channel: ", Style::default().fg(Color::DarkGray)),
105 Span::styled(format!("{ch_num}"), Style::default().fg(Color::White)),
106 ]),
107 Line::from(vec![
108 Span::styled(" Name: ", Style::default().fg(Color::DarkGray)),
109 Span::styled(
110 entry.name.clone(),
111 Style::default()
112 .fg(Color::Cyan)
113 .add_modifier(Modifier::BOLD),
114 ),
115 ]),
116 Line::from(""),
117 Line::from(vec![
118 Span::styled(" RX: ", Style::default().fg(Color::DarkGray)),
119 Span::styled(
120 format!("{:.6} MHz", fc.rx_frequency.as_mhz()),
121 Style::default().fg(Color::Green),
122 ),
123 ]),
124 Line::from(vec![
125 Span::styled(" Duplex: ", Style::default().fg(Color::DarkGray)),
126 Span::styled(duplex_info, Style::default().fg(Color::Yellow)),
127 ]),
128 Line::from(vec![
129 Span::styled(" Mode: ", Style::default().fg(Color::DarkGray)),
130 Span::styled(format!("{}", fc.mode), Style::default().fg(Color::White)),
131 ]),
132 Line::from(vec![
133 Span::styled(" Tone: ", Style::default().fg(Color::DarkGray)),
134 Span::styled(tone_info, Style::default().fg(Color::White)),
135 ]),
136 ];
137
138 lines.push(Line::from(""));
139 if app.channel_edit_mode {
140 lines.push(Line::from(Span::styled(
141 " ── Edit Mode ──",
142 Style::default()
143 .fg(Color::Yellow)
144 .add_modifier(Modifier::BOLD),
145 )));
146
147 let fields = [
148 ChannelEditField::Frequency,
149 ChannelEditField::Name,
150 ChannelEditField::Mode,
151 ChannelEditField::ToneMode,
152 ChannelEditField::ToneFreq,
153 ChannelEditField::Duplex,
154 ChannelEditField::Offset,
155 ];
156 for field in fields {
157 let marker = if field == app.channel_edit_field {
158 "\u{25b8} "
159 } else {
160 " "
161 };
162 let color = if field == app.channel_edit_field {
163 Color::Cyan
164 } else {
165 Color::DarkGray
166 };
167 lines.push(Line::from(Span::styled(
168 format!(" {marker}{:<12}", field.label()),
169 Style::default().fg(color),
170 )));
171 }
172
173 if !app.channel_edit_buffer.is_empty() {
174 lines.push(Line::from(""));
175 lines.push(Line::from(vec![
176 Span::styled(" Input: ", Style::default().fg(Color::DarkGray)),
177 Span::styled(
178 format!("{}\u{258e}", app.channel_edit_buffer),
179 Style::default().fg(Color::White),
180 ),
181 ]));
182 }
183
184 lines.push(Line::from(""));
185 lines.push(Line::from(Span::styled(
186 " Tab=next Enter=apply Esc=cancel",
187 Style::default().fg(Color::DarkGray),
188 )));
189 } else {
190 lines.push(Line::from(vec![Span::styled(
191 format!(
192 " [Enter] Tune Band {} [e] Edit",
193 if app.target_band == kenwood_thd75::types::Band::B {
194 "B"
195 } else {
196 "A"
197 }
198 ),
199 Style::default().fg(Color::DarkGray),
200 )]));
201 }
202 frame.render_widget(Paragraph::new(lines).block(block), area);
203 return;
204 }
205 frame.render_widget(Paragraph::new(" No channel selected").block(block), area);
206 }
207 _ => {
208 frame.render_widget(Paragraph::new("").block(block), area);
209 }
210 }
211}