Skip to main content

rusticity_term/cfn/
actions.rs

1use crate::app::App;
2use crate::common::{CyclicEnum, InputFocus, PageSize};
3use crate::ui::cfn::{
4    filtered_change_sets, filtered_cloudformation_stacks, filtered_events, filtered_outputs,
5    filtered_parameters, filtered_resources, DetailTab as CfnDetailTab,
6    EventsView as CfnEventsView, State as CfnStateConstants, STATUS_FILTER, VIEW_NESTED,
7};
8
9// ── Filter / reset ────────────────────────────────────────────────────────────
10
11pub fn apply_filter_reset(app: &mut App) {
12    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
13    {
14        app.cfn_state.parameters.reset();
15    } else if app.cfn_state.current_stack.is_some()
16        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
17    {
18        app.cfn_state.outputs.reset();
19    } else if app.cfn_state.current_stack.is_some()
20        && app.cfn_state.detail_tab == CfnDetailTab::Events
21    {
22        app.cfn_state.events.reset();
23    } else if app.cfn_state.current_stack.is_some()
24        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
25    {
26        app.cfn_state.change_sets.reset();
27    } else {
28        app.cfn_state.table.reset();
29    }
30}
31
32pub fn reset_on_service_select(app: &mut App) {
33    app.cfn_state.table.reset();
34}
35
36pub fn is_pagination_focused(app: &App) -> bool {
37    app.cfn_state.input_focus == InputFocus::Pagination
38}
39
40pub fn filter_char_push(app: &mut App, c: char) {
41    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
42    {
43        if app.cfn_state.parameters_input_focus == InputFocus::Filter {
44            app.cfn_state.parameters.filter.push(c);
45            app.cfn_state.parameters.selected = 0;
46        }
47    } else if app.cfn_state.current_stack.is_some()
48        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
49    {
50        if app.cfn_state.outputs_input_focus == InputFocus::Filter {
51            app.cfn_state.outputs.filter.push(c);
52            app.cfn_state.outputs.selected = 0;
53        }
54    } else if app.cfn_state.current_stack.is_some()
55        && app.cfn_state.detail_tab == CfnDetailTab::Resources
56    {
57        if app.cfn_state.resources_input_focus == InputFocus::Filter {
58            app.cfn_state.resources.filter.push(c);
59            app.cfn_state.resources.selected = 0;
60        }
61    } else if app.cfn_state.current_stack.is_some()
62        && app.cfn_state.detail_tab == CfnDetailTab::Events
63    {
64        if app.cfn_state.events_input_focus == InputFocus::Filter {
65            app.cfn_state.events.filter.push(c);
66            app.cfn_state.events.selected = 0;
67        }
68    } else if app.cfn_state.current_stack.is_some()
69        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
70    {
71        if app.cfn_state.change_sets_input_focus == InputFocus::Filter {
72            app.cfn_state.change_sets.filter.push(c);
73            app.cfn_state.change_sets.selected = 0;
74        }
75    } else if app.cfn_state.input_focus == InputFocus::Filter {
76        app.cfn_state.table.filter.push(c);
77        app.cfn_state.table.selected = 0;
78    }
79}
80
81pub fn filter_char_pop(app: &mut App) {
82    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
83    {
84        if app.cfn_state.parameters_input_focus == InputFocus::Filter {
85            app.cfn_state.parameters.filter.pop();
86            app.cfn_state.parameters.selected = 0;
87        }
88    } else if app.cfn_state.current_stack.is_some()
89        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
90    {
91        if app.cfn_state.outputs_input_focus == InputFocus::Filter {
92            app.cfn_state.outputs.filter.pop();
93            app.cfn_state.outputs.selected = 0;
94        }
95    } else if app.cfn_state.current_stack.is_some()
96        && app.cfn_state.detail_tab == CfnDetailTab::Resources
97    {
98        if app.cfn_state.resources_input_focus == InputFocus::Filter {
99            app.cfn_state.resources.filter.pop();
100            app.cfn_state.resources.selected = 0;
101        }
102    } else if app.cfn_state.current_stack.is_some()
103        && app.cfn_state.detail_tab == CfnDetailTab::Events
104    {
105        if app.cfn_state.events_input_focus == InputFocus::Filter {
106            app.cfn_state.events.filter.pop();
107            app.cfn_state.events.selected = 0;
108        }
109    } else if app.cfn_state.current_stack.is_some()
110        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
111    {
112        if app.cfn_state.change_sets_input_focus == InputFocus::Filter {
113            app.cfn_state.change_sets.filter.pop();
114            app.cfn_state.change_sets.selected = 0;
115        }
116    } else if app.cfn_state.input_focus == InputFocus::Filter {
117        app.cfn_state.table.filter.pop();
118        app.cfn_state.table.selected = 0;
119    }
120}
121
122pub fn start_filter(app: &mut App) {
123    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
124    {
125        app.cfn_state.parameters_input_focus = InputFocus::Filter;
126    } else if app.cfn_state.current_stack.is_some()
127        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
128    {
129        app.cfn_state.outputs_input_focus = InputFocus::Filter;
130    } else if app.cfn_state.current_stack.is_some()
131        && app.cfn_state.detail_tab == CfnDetailTab::Events
132    {
133        app.cfn_state.events_input_focus = InputFocus::Filter;
134    } else if app.cfn_state.current_stack.is_some()
135        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
136    {
137        app.cfn_state.change_sets_input_focus = InputFocus::Filter;
138    } else {
139        app.cfn_state.input_focus = InputFocus::Filter;
140    }
141}
142
143pub fn next_filter_focus(app: &mut App) {
144    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
145    {
146        app.cfn_state.parameters_input_focus = app
147            .cfn_state
148            .parameters_input_focus
149            .next(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
150    } else if app.cfn_state.current_stack.is_some()
151        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
152    {
153        app.cfn_state.outputs_input_focus = app
154            .cfn_state
155            .outputs_input_focus
156            .next(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
157    } else if app.cfn_state.current_stack.is_some()
158        && app.cfn_state.detail_tab == CfnDetailTab::Resources
159    {
160        app.cfn_state.resources_input_focus = app
161            .cfn_state
162            .resources_input_focus
163            .next(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
164    } else if app.cfn_state.current_stack.is_some()
165        && app.cfn_state.detail_tab == CfnDetailTab::Events
166    {
167        app.cfn_state.events_input_focus = app
168            .cfn_state
169            .events_input_focus
170            .next(&CfnStateConstants::EVENTS_FILTER_CONTROLS);
171    } else if app.cfn_state.current_stack.is_some()
172        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
173    {
174        app.cfn_state.change_sets_input_focus = app
175            .cfn_state
176            .change_sets_input_focus
177            .next(&CfnStateConstants::CHANGE_SETS_FILTER_CONTROLS);
178    } else {
179        app.cfn_state.input_focus = app
180            .cfn_state
181            .input_focus
182            .next(&CfnStateConstants::FILTER_CONTROLS);
183    }
184}
185
186pub fn prev_filter_focus(app: &mut App) {
187    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
188    {
189        app.cfn_state.parameters_input_focus = app
190            .cfn_state
191            .parameters_input_focus
192            .prev(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
193    } else if app.cfn_state.current_stack.is_some()
194        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
195    {
196        app.cfn_state.outputs_input_focus = app
197            .cfn_state
198            .outputs_input_focus
199            .prev(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
200    } else if app.cfn_state.current_stack.is_some()
201        && app.cfn_state.detail_tab == CfnDetailTab::Resources
202    {
203        app.cfn_state.resources_input_focus = app
204            .cfn_state
205            .resources_input_focus
206            .prev(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
207    } else if app.cfn_state.current_stack.is_some()
208        && app.cfn_state.detail_tab == CfnDetailTab::Events
209    {
210        app.cfn_state.events_input_focus = app
211            .cfn_state
212            .events_input_focus
213            .prev(&CfnStateConstants::EVENTS_FILTER_CONTROLS);
214    } else if app.cfn_state.current_stack.is_some()
215        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
216    {
217        app.cfn_state.change_sets_input_focus = app
218            .cfn_state
219            .change_sets_input_focus
220            .prev(&CfnStateConstants::CHANGE_SETS_FILTER_CONTROLS);
221    } else {
222        app.cfn_state.input_focus = app
223            .cfn_state
224            .input_focus
225            .prev(&CfnStateConstants::FILTER_CONTROLS);
226    }
227}
228
229pub fn toggle_filter_checkbox(app: &mut App) {
230    match app.cfn_state.input_focus {
231        STATUS_FILTER => {
232            app.cfn_state.status_filter = app.cfn_state.status_filter.next();
233            app.cfn_state.table.reset();
234        }
235        VIEW_NESTED => {
236            app.cfn_state.view_nested = !app.cfn_state.view_nested;
237            app.cfn_state.table.reset();
238        }
239        _ => {}
240    }
241}
242
243pub fn block_column_selector(app: &App) -> bool {
244    app.cfn_state.current_stack.is_some()
245        && (app.cfn_state.detail_tab == CfnDetailTab::Template
246            || app.cfn_state.detail_tab == CfnDetailTab::GitSync)
247}
248
249// ── Navigation ────────────────────────────────────────────────────────────────
250
251pub fn next_item(app: &mut App) {
252    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Template {
253        scroll_down_template(app);
254    } else if app.cfn_state.current_stack.is_some()
255        && app.cfn_state.detail_tab == CfnDetailTab::Parameters
256    {
257        let filtered = filtered_parameters(app);
258        app.cfn_state.parameters.next_item(filtered.len());
259    } else if app.cfn_state.current_stack.is_some()
260        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
261    {
262        let filtered = filtered_outputs(app);
263        app.cfn_state.outputs.next_item(filtered.len());
264    } else if app.cfn_state.current_stack.is_some()
265        && app.cfn_state.detail_tab == CfnDetailTab::Resources
266    {
267        let filtered = filtered_resources(app);
268        app.cfn_state.resources.next_item(filtered.len());
269    } else if app.cfn_state.current_stack.is_some()
270        && app.cfn_state.detail_tab == CfnDetailTab::Events
271    {
272        let filtered = filtered_events(app);
273        app.cfn_state.events.next_item(filtered.len());
274    } else if app.cfn_state.current_stack.is_some()
275        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
276    {
277        let filtered = filtered_change_sets(app);
278        app.cfn_state.change_sets.next_item(filtered.len());
279    } else {
280        let filtered = filtered_cloudformation_stacks(app);
281        app.cfn_state.table.next_item(filtered.len());
282    }
283}
284
285pub fn next_item_filter_input(app: &mut App) {
286    if app.cfn_state.input_focus == STATUS_FILTER {
287        app.cfn_state.status_filter = app.cfn_state.status_filter.next();
288        app.cfn_state.table.reset();
289    }
290}
291
292pub fn prev_item(app: &mut App) {
293    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Template {
294        scroll_up_template(app);
295    } else if app.cfn_state.current_stack.is_some()
296        && app.cfn_state.detail_tab == CfnDetailTab::Parameters
297    {
298        app.cfn_state.parameters.prev_item();
299    } else if app.cfn_state.current_stack.is_some()
300        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
301    {
302        app.cfn_state.outputs.prev_item();
303    } else if app.cfn_state.current_stack.is_some()
304        && app.cfn_state.detail_tab == CfnDetailTab::Resources
305    {
306        app.cfn_state.resources.prev_item();
307    } else if app.cfn_state.current_stack.is_some()
308        && app.cfn_state.detail_tab == CfnDetailTab::Events
309    {
310        app.cfn_state.events.prev_item();
311    } else if app.cfn_state.current_stack.is_some()
312        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
313    {
314        app.cfn_state.change_sets.prev_item();
315    } else {
316        app.cfn_state.table.prev_item();
317    }
318}
319
320pub fn prev_item_filter_input(app: &mut App) {
321    if app.cfn_state.input_focus == STATUS_FILTER {
322        app.cfn_state.status_filter = app.cfn_state.status_filter.prev();
323        app.cfn_state.table.reset();
324    }
325}
326
327pub fn page_down_filter_input(app: &mut App) {
328    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
329    {
330        let page_size = app.cfn_state.parameters.page_size.value();
331        let filtered_count = filtered_parameters(app).len();
332        app.cfn_state.parameters_input_focus.handle_page_down(
333            &mut app.cfn_state.parameters.selected,
334            &mut app.cfn_state.parameters.scroll_offset,
335            page_size,
336            filtered_count,
337        );
338    } else if app.cfn_state.current_stack.is_some()
339        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
340    {
341        let page_size = app.cfn_state.outputs.page_size.value();
342        let filtered_count = filtered_outputs(app).len();
343        app.cfn_state.outputs_input_focus.handle_page_down(
344            &mut app.cfn_state.outputs.selected,
345            &mut app.cfn_state.outputs.scroll_offset,
346            page_size,
347            filtered_count,
348        );
349    } else if app.cfn_state.current_stack.is_some()
350        && app.cfn_state.detail_tab == CfnDetailTab::Resources
351    {
352        let page_size = app.cfn_state.resources.page_size.value();
353        let filtered_count = filtered_resources(app).len();
354        app.cfn_state.resources_input_focus.handle_page_down(
355            &mut app.cfn_state.resources.selected,
356            &mut app.cfn_state.resources.scroll_offset,
357            page_size,
358            filtered_count,
359        );
360    } else if app.cfn_state.current_stack.is_some()
361        && app.cfn_state.detail_tab == CfnDetailTab::Events
362    {
363        let page_size = app.cfn_state.events.page_size.value();
364        let filtered_count = filtered_events(app).len();
365        app.cfn_state.events_input_focus.handle_page_down(
366            &mut app.cfn_state.events.selected,
367            &mut app.cfn_state.events.scroll_offset,
368            page_size,
369            filtered_count,
370        );
371    } else if app.cfn_state.current_stack.is_some()
372        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
373    {
374        let page_size = app.cfn_state.change_sets.page_size.value();
375        let filtered_count = filtered_change_sets(app).len();
376        app.cfn_state.change_sets_input_focus.handle_page_down(
377            &mut app.cfn_state.change_sets.selected,
378            &mut app.cfn_state.change_sets.scroll_offset,
379            page_size,
380            filtered_count,
381        );
382    } else {
383        let page_size = app.cfn_state.table.page_size.value();
384        let filtered_count = filtered_cloudformation_stacks(app).len();
385        app.cfn_state.input_focus.handle_page_down(
386            &mut app.cfn_state.table.selected,
387            &mut app.cfn_state.table.scroll_offset,
388            page_size,
389            filtered_count,
390        );
391    }
392}
393
394pub fn page_down_normal(app: &mut App) {
395    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
396    {
397        let filtered = filtered_parameters(app);
398        app.cfn_state.parameters.page_down(filtered.len());
399    } else if app.cfn_state.current_stack.is_some()
400        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
401    {
402        let filtered = filtered_outputs(app);
403        app.cfn_state.outputs.page_down(filtered.len());
404    } else if app.cfn_state.current_stack.is_some()
405        && app.cfn_state.detail_tab == CfnDetailTab::Resources
406    {
407        let filtered = filtered_resources(app);
408        app.cfn_state.resources.page_down(filtered.len());
409    } else if app.cfn_state.current_stack.is_some()
410        && app.cfn_state.detail_tab == CfnDetailTab::Events
411    {
412        let filtered = filtered_events(app);
413        app.cfn_state.events.page_down(filtered.len());
414    } else if app.cfn_state.current_stack.is_some()
415        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
416    {
417        let filtered = filtered_change_sets(app);
418        app.cfn_state.change_sets.page_down(filtered.len());
419    } else {
420        let filtered = filtered_cloudformation_stacks(app);
421        app.cfn_state.table.page_down(filtered.len());
422    }
423}
424
425pub fn page_up_filter_input(app: &mut App) {
426    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
427    {
428        let page_size = app.cfn_state.parameters.page_size.value();
429        app.cfn_state.parameters_input_focus.handle_page_up(
430            &mut app.cfn_state.parameters.selected,
431            &mut app.cfn_state.parameters.scroll_offset,
432            page_size,
433        );
434    } else if app.cfn_state.current_stack.is_some()
435        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
436    {
437        let page_size = app.cfn_state.outputs.page_size.value();
438        app.cfn_state.outputs_input_focus.handle_page_up(
439            &mut app.cfn_state.outputs.selected,
440            &mut app.cfn_state.outputs.scroll_offset,
441            page_size,
442        );
443    } else if app.cfn_state.current_stack.is_some()
444        && app.cfn_state.detail_tab == CfnDetailTab::Resources
445    {
446        let page_size = app.cfn_state.resources.page_size.value();
447        app.cfn_state.resources_input_focus.handle_page_up(
448            &mut app.cfn_state.resources.selected,
449            &mut app.cfn_state.resources.scroll_offset,
450            page_size,
451        );
452    } else if app.cfn_state.current_stack.is_some()
453        && app.cfn_state.detail_tab == CfnDetailTab::Events
454    {
455        let page_size = app.cfn_state.events.page_size.value();
456        app.cfn_state.events_input_focus.handle_page_up(
457            &mut app.cfn_state.events.selected,
458            &mut app.cfn_state.events.scroll_offset,
459            page_size,
460        );
461    } else if app.cfn_state.current_stack.is_some()
462        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
463    {
464        let page_size = app.cfn_state.change_sets.page_size.value();
465        app.cfn_state.change_sets_input_focus.handle_page_up(
466            &mut app.cfn_state.change_sets.selected,
467            &mut app.cfn_state.change_sets.scroll_offset,
468            page_size,
469        );
470    } else {
471        let page_size = app.cfn_state.table.page_size.value();
472        app.cfn_state.input_focus.handle_page_up(
473            &mut app.cfn_state.table.selected,
474            &mut app.cfn_state.table.scroll_offset,
475            page_size,
476        );
477    }
478}
479
480pub fn page_up_normal(app: &mut App) {
481    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::Parameters
482    {
483        app.cfn_state.parameters.page_up();
484    } else if app.cfn_state.current_stack.is_some()
485        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
486    {
487        app.cfn_state.outputs.page_up();
488    } else if app.cfn_state.current_stack.is_some()
489        && app.cfn_state.detail_tab == CfnDetailTab::Resources
490    {
491        app.cfn_state.resources.page_up();
492    } else if app.cfn_state.current_stack.is_some()
493        && app.cfn_state.detail_tab == CfnDetailTab::Events
494    {
495        app.cfn_state.events.page_up();
496    } else if app.cfn_state.current_stack.is_some()
497        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
498    {
499        app.cfn_state.change_sets.page_up();
500    } else {
501        app.cfn_state.table.page_up();
502    }
503}
504
505pub fn scroll_up_template(app: &mut App) {
506    app.cfn_state.template_scroll = app.cfn_state.template_scroll.saturating_sub(1);
507}
508
509pub fn scroll_down_template(app: &mut App) {
510    let lines = app.cfn_state.template_body.lines().count();
511    let max_scroll = lines.saturating_sub(1);
512    app.cfn_state.template_scroll = (app.cfn_state.template_scroll + 1).min(max_scroll);
513}
514
515pub fn scroll_up_template_fast(app: &mut App) {
516    app.cfn_state.template_scroll = app.cfn_state.template_scroll.saturating_sub(10);
517}
518
519pub fn scroll_down_template_fast(app: &mut App) {
520    let lines = app.cfn_state.template_body.lines().count();
521    let max_scroll = lines.saturating_sub(1);
522    app.cfn_state.template_scroll = (app.cfn_state.template_scroll + 10).min(max_scroll);
523}
524
525pub fn expand_row(app: &mut App) {
526    if app.cfn_state.current_stack.is_none() {
527        app.cfn_state.table.toggle_expand();
528    } else if app.cfn_state.detail_tab == CfnDetailTab::Parameters {
529        app.cfn_state.parameters.toggle_expand();
530    } else if app.cfn_state.detail_tab == CfnDetailTab::Outputs {
531        app.cfn_state.outputs.toggle_expand();
532    } else if app.cfn_state.detail_tab == CfnDetailTab::Resources {
533        app.cfn_state.resources.toggle_expand();
534    } else if app.cfn_state.detail_tab == CfnDetailTab::Events {
535        app.cfn_state.events.toggle_expand();
536    } else if app.cfn_state.detail_tab == CfnDetailTab::ChangeSets {
537        app.cfn_state.change_sets.toggle_expand();
538    }
539}
540
541pub fn prev_pane(app: &mut App) {
542    if app.cfn_state.current_stack.is_none() {
543        app.cfn_state.table.collapse();
544    } else if app.cfn_state.detail_tab == CfnDetailTab::Parameters {
545        app.cfn_state.parameters.collapse();
546    } else if app.cfn_state.detail_tab == CfnDetailTab::Outputs {
547        app.cfn_state.outputs.collapse();
548    } else if app.cfn_state.detail_tab == CfnDetailTab::Resources {
549        app.cfn_state.resources.collapse();
550    } else if app.cfn_state.detail_tab == CfnDetailTab::Events {
551        app.cfn_state.events.collapse();
552    } else if app.cfn_state.detail_tab == CfnDetailTab::ChangeSets {
553        app.cfn_state.change_sets.collapse();
554    }
555}
556
557pub fn collapse_row(app: &mut App) {
558    if app.cfn_state.current_stack.is_some() {
559        match app.cfn_state.detail_tab {
560            CfnDetailTab::Resources => app.cfn_state.resources.collapse(),
561            CfnDetailTab::Parameters => app.cfn_state.parameters.collapse(),
562            CfnDetailTab::Outputs => app.cfn_state.outputs.collapse(),
563            CfnDetailTab::Events => app.cfn_state.events.collapse(),
564            CfnDetailTab::ChangeSets => app.cfn_state.change_sets.collapse(),
565            _ => {}
566        }
567    } else {
568        app.cfn_state.table.collapse();
569    }
570}
571
572pub fn go_to_page(app: &mut App, page: usize) {
573    let filtered_count = filtered_cloudformation_stacks(app).len();
574    app.cfn_state.table.goto_page(page, filtered_count);
575}
576
577// ── Actions ───────────────────────────────────────────────────────────────────
578
579pub fn select_item(app: &mut App) {
580    if app.cfn_state.current_stack.is_none() {
581        let filtered = filtered_cloudformation_stacks(app);
582        if let Some(stack) = app.cfn_state.table.get_selected(&filtered) {
583            let stack_name = stack.name.clone();
584            let mut tags = stack.tags.clone();
585            tags.sort_by(|a, b| a.0.cmp(&b.0));
586            app.cfn_state.current_stack = Some(stack_name);
587            app.cfn_state.tags.items = tags;
588            app.cfn_state.tags.reset();
589            app.cfn_state.table.loading = true;
590            app.update_current_tab_breadcrumb();
591        }
592    }
593}
594
595pub fn go_back(app: &mut App) {
596    app.cfn_state.current_stack = None;
597    app.update_current_tab_breadcrumb();
598}
599
600pub fn next_detail_tab(app: &mut App) {
601    app.cfn_state.detail_tab = app.cfn_state.detail_tab.next();
602}
603
604pub fn prev_detail_tab(app: &mut App) {
605    app.cfn_state.detail_tab = app.cfn_state.detail_tab.prev();
606}
607
608/// Toggle between Table and Timeline view on the Events tab (bound to Tab key).
609pub fn toggle_events_view(app: &mut App) {
610    app.cfn_state.events_view = match app.cfn_state.events_view {
611        CfnEventsView::Table => CfnEventsView::Timeline,
612        CfnEventsView::Timeline => CfnEventsView::Table,
613    };
614}
615
616pub fn yank(app: &App) {
617    use crate::app::copy_to_clipboard;
618    if let Some(stack_name) = &app.cfn_state.current_stack {
619        // On Template tab — copy the full template body
620        if app.cfn_state.detail_tab == CfnDetailTab::Template {
621            if !app.cfn_state.template_body.is_empty() {
622                copy_to_clipboard(&app.cfn_state.template_body);
623            }
624            return;
625        }
626        if let Some(stack) = app
627            .cfn_state
628            .table
629            .items
630            .iter()
631            .find(|s| &s.name == stack_name)
632        {
633            copy_to_clipboard(&stack.stack_id);
634        }
635    } else {
636        let filtered = filtered_cloudformation_stacks(app);
637        if let Some(stack) = app.cfn_state.table.get_selected(&filtered) {
638            copy_to_clipboard(&stack.stack_id);
639        }
640    }
641}
642
643// ── Column preferences ────────────────────────────────────────────────────────
644
645pub fn toggle_column(app: &mut App) {
646    let idx = app.column_selector_index;
647    if app.cfn_state.current_stack.is_some() && app.cfn_state.detail_tab == CfnDetailTab::StackInfo
648    {
649        // Tags: page size only
650        if idx == 4 {
651            app.cfn_state.tags.page_size = PageSize::Ten;
652        } else if idx == 5 {
653            app.cfn_state.tags.page_size = PageSize::TwentyFive;
654        } else if idx == 6 {
655            app.cfn_state.tags.page_size = PageSize::Fifty;
656        } else if idx == 7 {
657            app.cfn_state.tags.page_size = PageSize::OneHundred;
658        }
659    } else if app.cfn_state.current_stack.is_some()
660        && app.cfn_state.detail_tab == CfnDetailTab::Parameters
661    {
662        if idx > 0 && idx <= app.cfn_parameter_column_ids.len() {
663            if let Some(col) = app.cfn_parameter_column_ids.get(idx - 1) {
664                if let Some(pos) = app
665                    .cfn_parameter_visible_column_ids
666                    .iter()
667                    .position(|c| c == col)
668                {
669                    if app.cfn_parameter_visible_column_ids.len() > 1 {
670                        app.cfn_parameter_visible_column_ids.remove(pos);
671                    }
672                } else {
673                    app.cfn_parameter_visible_column_ids.push(col);
674                }
675            }
676        } else if idx == app.cfn_parameter_column_ids.len() + 3 {
677            app.cfn_state.parameters.page_size = PageSize::Ten;
678        } else if idx == app.cfn_parameter_column_ids.len() + 4 {
679            app.cfn_state.parameters.page_size = PageSize::TwentyFive;
680        } else if idx == app.cfn_parameter_column_ids.len() + 5 {
681            app.cfn_state.parameters.page_size = PageSize::Fifty;
682        } else if idx == app.cfn_parameter_column_ids.len() + 6 {
683            app.cfn_state.parameters.page_size = PageSize::OneHundred;
684        }
685    } else if app.cfn_state.current_stack.is_some()
686        && app.cfn_state.detail_tab == CfnDetailTab::Outputs
687    {
688        if idx > 0 && idx <= app.cfn_output_column_ids.len() {
689            if let Some(col) = app.cfn_output_column_ids.get(idx - 1) {
690                if let Some(pos) = app
691                    .cfn_output_visible_column_ids
692                    .iter()
693                    .position(|c| c == col)
694                {
695                    if app.cfn_output_visible_column_ids.len() > 1 {
696                        app.cfn_output_visible_column_ids.remove(pos);
697                    }
698                } else {
699                    app.cfn_output_visible_column_ids.push(col);
700                }
701            }
702        } else if idx == app.cfn_output_column_ids.len() + 3 {
703            app.cfn_state.outputs.page_size = PageSize::Ten;
704        } else if idx == app.cfn_output_column_ids.len() + 4 {
705            app.cfn_state.outputs.page_size = PageSize::TwentyFive;
706        } else if idx == app.cfn_output_column_ids.len() + 5 {
707            app.cfn_state.outputs.page_size = PageSize::Fifty;
708        } else if idx == app.cfn_output_column_ids.len() + 6 {
709            app.cfn_state.outputs.page_size = PageSize::OneHundred;
710        }
711    } else if app.cfn_state.current_stack.is_some()
712        && app.cfn_state.detail_tab == CfnDetailTab::Resources
713    {
714        if idx > 0 && idx <= app.cfn_resource_column_ids.len() {
715            if let Some(col) = app.cfn_resource_column_ids.get(idx - 1) {
716                if let Some(pos) = app
717                    .cfn_resource_visible_column_ids
718                    .iter()
719                    .position(|c| c == col)
720                {
721                    if app.cfn_resource_visible_column_ids.len() > 1 {
722                        app.cfn_resource_visible_column_ids.remove(pos);
723                    }
724                } else {
725                    app.cfn_resource_visible_column_ids.push(col);
726                }
727            }
728        } else if idx == app.cfn_resource_column_ids.len() + 3 {
729            app.cfn_state.resources.page_size = PageSize::Ten;
730        } else if idx == app.cfn_resource_column_ids.len() + 4 {
731            app.cfn_state.resources.page_size = PageSize::TwentyFive;
732        } else if idx == app.cfn_resource_column_ids.len() + 5 {
733            app.cfn_state.resources.page_size = PageSize::Fifty;
734        } else if idx == app.cfn_resource_column_ids.len() + 6 {
735            app.cfn_state.resources.page_size = PageSize::OneHundred;
736        }
737    } else if app.cfn_state.current_stack.is_some()
738        && app.cfn_state.detail_tab == CfnDetailTab::Events
739    {
740        if idx > 0 && idx <= app.cfn_event_column_ids.len() {
741            if let Some(col) = app.cfn_event_column_ids.get(idx - 1) {
742                if let Some(pos) = app
743                    .cfn_event_visible_column_ids
744                    .iter()
745                    .position(|c| c == col)
746                {
747                    if app.cfn_event_visible_column_ids.len() > 1 {
748                        app.cfn_event_visible_column_ids.remove(pos);
749                    }
750                } else {
751                    app.cfn_event_visible_column_ids.push(col);
752                }
753            }
754        } else if idx == app.cfn_event_column_ids.len() + 3 {
755            app.cfn_state.events.page_size = PageSize::Ten;
756        } else if idx == app.cfn_event_column_ids.len() + 4 {
757            app.cfn_state.events.page_size = PageSize::TwentyFive;
758        } else if idx == app.cfn_event_column_ids.len() + 5 {
759            app.cfn_state.events.page_size = PageSize::Fifty;
760        } else if idx == app.cfn_event_column_ids.len() + 6 {
761            app.cfn_state.events.page_size = PageSize::OneHundred;
762        }
763    } else if app.cfn_state.current_stack.is_some()
764        && app.cfn_state.detail_tab == CfnDetailTab::ChangeSets
765    {
766        if idx > 0 && idx <= app.cfn_change_set_column_ids.len() {
767            if let Some(col) = app.cfn_change_set_column_ids.get(idx - 1) {
768                if let Some(pos) = app
769                    .cfn_change_set_visible_column_ids
770                    .iter()
771                    .position(|c| c == col)
772                {
773                    if app.cfn_change_set_visible_column_ids.len() > 1 {
774                        app.cfn_change_set_visible_column_ids.remove(pos);
775                    }
776                } else {
777                    app.cfn_change_set_visible_column_ids.push(col);
778                }
779            }
780        } else if idx == app.cfn_change_set_column_ids.len() + 3 {
781            app.cfn_state.change_sets.page_size = PageSize::Ten;
782        } else if idx == app.cfn_change_set_column_ids.len() + 4 {
783            app.cfn_state.change_sets.page_size = PageSize::TwentyFive;
784        } else if idx == app.cfn_change_set_column_ids.len() + 5 {
785            app.cfn_state.change_sets.page_size = PageSize::Fifty;
786        } else if idx == app.cfn_change_set_column_ids.len() + 6 {
787            app.cfn_state.change_sets.page_size = PageSize::OneHundred;
788        }
789    } else if app.cfn_state.current_stack.is_none() {
790        if idx > 0 && idx <= app.cfn_column_ids.len() {
791            if let Some(col) = app.cfn_column_ids.get(idx - 1) {
792                if let Some(pos) = app.cfn_visible_column_ids.iter().position(|c| c == col) {
793                    if app.cfn_visible_column_ids.len() > 1 {
794                        app.cfn_visible_column_ids.remove(pos);
795                    }
796                } else {
797                    app.cfn_visible_column_ids.push(*col);
798                }
799            }
800        } else if idx == app.cfn_column_ids.len() + 3 {
801            app.cfn_state.table.page_size = PageSize::Ten;
802        } else if idx == app.cfn_column_ids.len() + 4 {
803            app.cfn_state.table.page_size = PageSize::TwentyFive;
804        } else if idx == app.cfn_column_ids.len() + 5 {
805            app.cfn_state.table.page_size = PageSize::Fifty;
806        } else if idx == app.cfn_column_ids.len() + 6 {
807            app.cfn_state.table.page_size = PageSize::OneHundred;
808        }
809    }
810}
811
812pub fn next_preferences(app: &mut App) {
813    let cols = column_count(app);
814    let page_size_idx = cols + 2;
815    if app.column_selector_index < page_size_idx {
816        app.column_selector_index = page_size_idx;
817    } else {
818        app.column_selector_index = 0;
819    }
820}
821
822pub fn prev_preferences(app: &mut App) {
823    let cols = column_count(app);
824    let page_size_idx = cols + 2;
825    if app.column_selector_index >= page_size_idx {
826        app.column_selector_index = 0;
827    } else {
828        app.column_selector_index = page_size_idx;
829    }
830}
831
832pub fn column_selector_max(app: &App) -> usize {
833    column_count(app) + 6
834}
835
836pub fn column_count(app: &App) -> usize {
837    if app.cfn_state.current_stack.is_some() {
838        match app.cfn_state.detail_tab {
839            CfnDetailTab::Parameters => app.cfn_parameter_column_ids.len(),
840            CfnDetailTab::Outputs => app.cfn_output_column_ids.len(),
841            CfnDetailTab::Resources => app.cfn_resource_column_ids.len(),
842            CfnDetailTab::Events => app.cfn_event_column_ids.len(),
843            CfnDetailTab::ChangeSets => app.cfn_change_set_column_ids.len(),
844            _ => 0,
845        }
846    } else {
847        app.cfn_column_ids.len()
848    }
849}
850
851// ── Breadcrumb / console URL ──────────────────────────────────────────────────
852
853pub fn breadcrumb(app: &App) -> Vec<String> {
854    let mut parts = vec!["CloudFormation".to_string()];
855    if let Some(stack_name) = &app.cfn_state.current_stack {
856        parts.push(stack_name.clone());
857    } else {
858        parts.push("Stacks".to_string());
859    }
860    parts
861}
862
863pub fn console_url(app: &App) -> String {
864    use crate::cfn;
865    if let Some(stack_name) = &app.cfn_state.current_stack {
866        if let Some(stack) = app
867            .cfn_state
868            .table
869            .items
870            .iter()
871            .find(|s| &s.name == stack_name)
872        {
873            return cfn::console_url_stack_detail_with_tab(
874                &app.config.region,
875                &stack.stack_id,
876                &app.cfn_state.detail_tab,
877            );
878        }
879    }
880    cfn::console_url_stacks(&app.config.region)
881}