Skip to main content

rusticity_term/iam/
actions.rs

1use crate::app::App;
2use crate::common::{CyclicEnum, InputFocus, PageSize};
3use crate::ui::iam::{
4    filtered_iam_policies, filtered_iam_roles, filtered_iam_users, filtered_last_accessed,
5    filtered_tags as filtered_iam_tags, filtered_user_tags, GroupTab, RoleTab, UserTab,
6    GROUP_FILTER_CONTROLS, HISTORY_FILTER, POLICY_FILTER_CONTROLS, POLICY_TYPE_DROPDOWN,
7    ROLE_FILTER_CONTROLS, USER_LAST_ACCESSED_FILTER_CONTROLS, USER_SIMPLE_FILTER_CONTROLS,
8};
9
10// ── Filter reset ──────────────────────────────────────────────────────────────
11
12pub fn apply_filter_reset_users(app: &mut App) {
13    if app.iam_state.current_user.is_some() {
14        app.iam_state.user_tags.reset();
15        app.iam_state.policies.reset();
16    } else {
17        app.iam_state.users.reset();
18    }
19}
20
21pub fn apply_filter_reset_roles(app: &mut App) {
22    if app.iam_state.current_role.is_some() {
23        app.iam_state.tags.reset();
24        app.iam_state.policies.reset();
25    } else {
26        app.iam_state.roles.reset();
27    }
28}
29
30pub fn apply_filter_reset_groups(app: &mut App) {
31    if app.iam_state.current_group.is_some() {
32        app.iam_state.policies.reset();
33        app.iam_state.group_users.reset();
34    } else {
35        app.iam_state.groups.reset();
36    }
37}
38
39pub fn reset_on_service_select_roles(app: &mut App) {
40    app.iam_state.roles.reset();
41}
42
43pub fn reset_on_service_select_users(app: &mut App) {
44    app.iam_state.users.reset();
45}
46
47pub fn reset_on_service_select_groups(app: &mut App) {
48    app.iam_state.groups.reset();
49}
50
51// ── Filter focus ──────────────────────────────────────────────────────────────
52
53pub fn next_filter_focus_roles(app: &mut App) {
54    if app.iam_state.current_role.is_some() {
55        app.iam_state.policy_input_focus = app
56            .iam_state
57            .policy_input_focus
58            .next(&POLICY_FILTER_CONTROLS);
59    } else {
60        app.iam_state.role_input_focus = app.iam_state.role_input_focus.next(&ROLE_FILTER_CONTROLS);
61    }
62}
63
64pub fn prev_filter_focus_roles(app: &mut App) {
65    if app.iam_state.current_role.is_some() {
66        app.iam_state.policy_input_focus = app
67            .iam_state
68            .policy_input_focus
69            .prev(&POLICY_FILTER_CONTROLS);
70    } else {
71        app.iam_state.role_input_focus = app.iam_state.role_input_focus.prev(&ROLE_FILTER_CONTROLS);
72    }
73}
74
75pub fn next_filter_focus_users(app: &mut App) {
76    if app.iam_state.user_tab == UserTab::Permissions {
77        app.iam_state.policy_input_focus = app
78            .iam_state
79            .policy_input_focus
80            .next(&POLICY_FILTER_CONTROLS);
81    } else if app.iam_state.user_tab == UserTab::LastAccessed {
82        app.iam_state.last_accessed_input_focus = app
83            .iam_state
84            .last_accessed_input_focus
85            .next(&USER_LAST_ACCESSED_FILTER_CONTROLS);
86    } else {
87        app.iam_state.user_input_focus = app
88            .iam_state
89            .user_input_focus
90            .next(&USER_SIMPLE_FILTER_CONTROLS);
91    }
92}
93
94pub fn prev_filter_focus_users(app: &mut App) {
95    if app.iam_state.user_tab == UserTab::Permissions {
96        app.iam_state.policy_input_focus = app
97            .iam_state
98            .policy_input_focus
99            .prev(&POLICY_FILTER_CONTROLS);
100    } else if app.iam_state.user_tab == UserTab::LastAccessed {
101        app.iam_state.last_accessed_input_focus = app
102            .iam_state
103            .last_accessed_input_focus
104            .prev(&USER_LAST_ACCESSED_FILTER_CONTROLS);
105    } else {
106        app.iam_state.user_input_focus = app
107            .iam_state
108            .user_input_focus
109            .prev(&USER_SIMPLE_FILTER_CONTROLS);
110    }
111}
112
113pub fn next_filter_focus_groups(app: &mut App) {
114    app.iam_state.group_input_focus = app.iam_state.group_input_focus.next(&GROUP_FILTER_CONTROLS);
115}
116
117pub fn prev_filter_focus_groups(app: &mut App) {
118    app.iam_state.group_input_focus = app.iam_state.group_input_focus.prev(&GROUP_FILTER_CONTROLS);
119}
120
121pub fn is_pagination_focused_roles(app: &App) -> bool {
122    app.iam_state.current_role.is_none() && app.iam_state.role_input_focus == InputFocus::Pagination
123}
124
125pub fn is_filter_focused_roles(app: &App) -> bool {
126    app.iam_state.current_role.is_none() && app.iam_state.role_input_focus == InputFocus::Filter
127}
128
129pub fn is_filter_focused_policy_view(app: &App) -> bool {
130    app.iam_state.policy_input_focus == InputFocus::Filter
131}
132
133pub fn is_pagination_focused_policy_view(app: &App) -> bool {
134    app.iam_state.policy_input_focus == InputFocus::Pagination
135}
136
137// ── Navigation ────────────────────────────────────────────────────────────────
138
139pub fn next_item_users(app: &mut App) {
140    if app.iam_state.current_user.is_some() {
141        if app.iam_state.user_tab == UserTab::Tags {
142            let filtered = filtered_user_tags(app);
143            if !filtered.is_empty() {
144                app.iam_state.user_tags.next_item(filtered.len());
145            }
146        } else {
147            let filtered = filtered_iam_policies(app);
148            if !filtered.is_empty() {
149                app.iam_state.policies.next_item(filtered.len());
150            }
151        }
152    } else {
153        let filtered = filtered_iam_users(app);
154        if !filtered.is_empty() {
155            app.iam_state.users.next_item(filtered.len());
156        }
157    }
158}
159
160pub fn next_item_roles(app: &mut App) {
161    if app.iam_state.current_role.is_some() {
162        match app.iam_state.role_tab {
163            RoleTab::TrustRelationships => {
164                let lines = app.iam_state.trust_policy_document.lines().count();
165                let max_scroll = lines.saturating_sub(1);
166                app.iam_state.trust_policy_scroll =
167                    (app.iam_state.trust_policy_scroll + 1).min(max_scroll);
168            }
169            RoleTab::RevokeSessions => {
170                app.iam_state.revoke_sessions_scroll =
171                    (app.iam_state.revoke_sessions_scroll + 1).min(19);
172            }
173            RoleTab::Tags => {
174                let filtered = filtered_iam_tags(app);
175                if !filtered.is_empty() {
176                    app.iam_state.tags.next_item(filtered.len());
177                }
178            }
179            RoleTab::LastAccessed => {
180                let filtered = filtered_last_accessed(app);
181                if !filtered.is_empty() {
182                    app.iam_state
183                        .last_accessed_services
184                        .next_item(filtered.len());
185                }
186            }
187            _ => {
188                let filtered = filtered_iam_policies(app);
189                if !filtered.is_empty() {
190                    app.iam_state.policies.next_item(filtered.len());
191                }
192            }
193        }
194    } else {
195        let filtered = filtered_iam_roles(app);
196        if !filtered.is_empty() {
197            app.iam_state.roles.next_item(filtered.len());
198        }
199    }
200}
201
202pub fn next_item_groups(app: &mut App) {
203    if app.iam_state.current_group.is_some() {
204        match app.iam_state.group_tab {
205            GroupTab::Users => {
206                let filtered: Vec<_> = app
207                    .iam_state
208                    .group_users
209                    .items
210                    .iter()
211                    .filter(|u| {
212                        app.iam_state.group_users.filter.is_empty()
213                            || u.user_name
214                                .to_lowercase()
215                                .contains(&app.iam_state.group_users.filter.to_lowercase())
216                    })
217                    .collect();
218                if !filtered.is_empty() {
219                    app.iam_state.group_users.next_item(filtered.len());
220                }
221            }
222            GroupTab::Permissions => {
223                let filtered = filtered_iam_policies(app);
224                if !filtered.is_empty() {
225                    app.iam_state.policies.next_item(filtered.len());
226                }
227            }
228            GroupTab::AccessAdvisor => {
229                let filtered = filtered_last_accessed(app);
230                if !filtered.is_empty() {
231                    app.iam_state
232                        .last_accessed_services
233                        .next_item(filtered.len());
234                }
235            }
236        }
237    } else {
238        let filtered: Vec<_> = app
239            .iam_state
240            .groups
241            .items
242            .iter()
243            .filter(|g| {
244                app.iam_state.groups.filter.is_empty()
245                    || g.group_name
246                        .to_lowercase()
247                        .contains(&app.iam_state.groups.filter.to_lowercase())
248            })
249            .collect();
250        if !filtered.is_empty() {
251            app.iam_state.groups.next_item(filtered.len());
252        }
253    }
254}
255
256pub fn prev_item_users(app: &mut App) {
257    app.iam_state.users.prev_item();
258}
259
260pub fn prev_item_roles(app: &mut App) {
261    if app.iam_state.current_role.is_some() {
262        match app.iam_state.role_tab {
263            RoleTab::TrustRelationships => {
264                app.iam_state.trust_policy_scroll =
265                    app.iam_state.trust_policy_scroll.saturating_sub(1);
266            }
267            RoleTab::RevokeSessions => {
268                app.iam_state.revoke_sessions_scroll =
269                    app.iam_state.revoke_sessions_scroll.saturating_sub(1);
270            }
271            RoleTab::Tags => app.iam_state.tags.prev_item(),
272            RoleTab::LastAccessed => app.iam_state.last_accessed_services.prev_item(),
273            _ => app.iam_state.policies.prev_item(),
274        }
275    } else {
276        app.iam_state.roles.prev_item();
277    }
278}
279
280pub fn prev_item_groups(app: &mut App) {
281    if app.iam_state.current_group.is_some() {
282        match app.iam_state.group_tab {
283            GroupTab::Users => app.iam_state.group_users.prev_item(),
284            GroupTab::Permissions => app.iam_state.policies.prev_item(),
285            GroupTab::AccessAdvisor => app.iam_state.last_accessed_services.prev_item(),
286        }
287    } else {
288        app.iam_state.groups.prev_item();
289    }
290}
291
292pub fn page_down_filter_input_roles(app: &mut App) {
293    let page_size = app.iam_state.roles.page_size.value();
294    let filtered_count = filtered_iam_roles(app).len();
295    app.iam_state.role_input_focus.handle_page_down(
296        &mut app.iam_state.roles.selected,
297        &mut app.iam_state.roles.scroll_offset,
298        page_size,
299        filtered_count,
300    );
301}
302
303pub fn page_down_filter_input_policy_view(app: &mut App) {
304    let page_size = app.iam_state.policies.page_size.value();
305    let filtered_count = filtered_iam_policies(app).len();
306    app.iam_state.policy_input_focus.handle_page_down(
307        &mut app.iam_state.policies.selected,
308        &mut app.iam_state.policies.scroll_offset,
309        page_size,
310        filtered_count,
311    );
312}
313
314pub fn page_down_normal_users(app: &mut App) {
315    let len = filtered_iam_users(app).len();
316    crate::app::nav_page_down(&mut app.iam_state.users.selected, len, 10);
317}
318
319pub fn page_down_normal_roles(app: &mut App) {
320    if app.iam_state.current_role.is_some() {
321        let filtered = filtered_iam_policies(app);
322        if !filtered.is_empty() {
323            app.iam_state.policies.page_down(filtered.len());
324        }
325    } else {
326        let filtered = filtered_iam_roles(app);
327        app.iam_state.roles.page_down(filtered.len());
328    }
329}
330
331pub fn page_down_normal_groups(app: &mut App) {
332    if app.iam_state.current_group.is_some() {
333        match app.iam_state.group_tab {
334            GroupTab::Users => {
335                let filtered: Vec<_> = app
336                    .iam_state
337                    .group_users
338                    .items
339                    .iter()
340                    .filter(|u| {
341                        app.iam_state.group_users.filter.is_empty()
342                            || u.user_name
343                                .to_lowercase()
344                                .contains(&app.iam_state.group_users.filter.to_lowercase())
345                    })
346                    .collect();
347                if !filtered.is_empty() {
348                    app.iam_state.group_users.page_down(filtered.len());
349                }
350            }
351            GroupTab::Permissions => {
352                let filtered = filtered_iam_policies(app);
353                if !filtered.is_empty() {
354                    app.iam_state.policies.page_down(filtered.len());
355                }
356            }
357            GroupTab::AccessAdvisor => {
358                let filtered = filtered_last_accessed(app);
359                if !filtered.is_empty() {
360                    app.iam_state
361                        .last_accessed_services
362                        .page_down(filtered.len());
363                }
364            }
365        }
366    } else {
367        let filtered: Vec<_> = app
368            .iam_state
369            .groups
370            .items
371            .iter()
372            .filter(|g| {
373                app.iam_state.groups.filter.is_empty()
374                    || g.group_name
375                        .to_lowercase()
376                        .contains(&app.iam_state.groups.filter.to_lowercase())
377            })
378            .collect();
379        if !filtered.is_empty() {
380            app.iam_state.groups.page_down(filtered.len());
381        }
382    }
383}
384
385pub fn page_up_filter_input_roles(app: &mut App) {
386    let page_size = app.iam_state.roles.page_size.value();
387    app.iam_state.role_input_focus.handle_page_up(
388        &mut app.iam_state.roles.selected,
389        &mut app.iam_state.roles.scroll_offset,
390        page_size,
391    );
392}
393
394pub fn page_up_filter_input_policy_view(app: &mut App) {
395    let page_size = app.iam_state.policies.page_size.value();
396    app.iam_state.policy_input_focus.handle_page_up(
397        &mut app.iam_state.policies.selected,
398        &mut app.iam_state.policies.scroll_offset,
399        page_size,
400    );
401}
402
403pub fn page_up_normal_users(app: &mut App) {
404    app.iam_state.users.page_up();
405}
406
407pub fn page_up_normal_roles(app: &mut App) {
408    if app.iam_state.current_role.is_some() {
409        app.iam_state.policies.page_up();
410    } else {
411        app.iam_state.roles.page_up();
412    }
413}
414
415pub fn go_to_page_users(app: &mut App, page: usize) {
416    let filtered_count = filtered_iam_users(app).len();
417    app.iam_state.users.goto_page(page, filtered_count);
418}
419
420pub fn go_to_page_roles(app: &mut App, page: usize) {
421    let filtered_count = filtered_iam_roles(app).len();
422    app.iam_state.roles.goto_page(page, filtered_count);
423}
424
425pub fn scroll_up_policy_view(app: &mut App) {
426    app.iam_state.policy_scroll = app.iam_state.policy_scroll.saturating_sub(10);
427}
428
429pub fn scroll_down_policy_view(app: &mut App) {
430    let lines = app.iam_state.policy_document.lines().count();
431    let max_scroll = lines.saturating_sub(1);
432    app.iam_state.policy_scroll = (app.iam_state.policy_scroll + 10).min(max_scroll);
433}
434
435pub fn scroll_up_trust_policy(app: &mut App) {
436    app.iam_state.trust_policy_scroll = app.iam_state.trust_policy_scroll.saturating_sub(10);
437}
438
439pub fn scroll_down_trust_policy(app: &mut App) {
440    let lines = app.iam_state.trust_policy_document.lines().count();
441    let max_scroll = lines.saturating_sub(1);
442    app.iam_state.trust_policy_scroll = (app.iam_state.trust_policy_scroll + 10).min(max_scroll);
443}
444
445pub fn scroll_up_revoke_sessions(app: &mut App) {
446    app.iam_state.revoke_sessions_scroll = app.iam_state.revoke_sessions_scroll.saturating_sub(10);
447}
448
449pub fn scroll_down_revoke_sessions(app: &mut App) {
450    app.iam_state.revoke_sessions_scroll = (app.iam_state.revoke_sessions_scroll + 10).min(19);
451}
452
453pub fn scroll_down_policy_view_one(app: &mut App) {
454    let lines = app.iam_state.policy_document.lines().count();
455    let max_scroll = lines.saturating_sub(1);
456    app.iam_state.policy_scroll = (app.iam_state.policy_scroll + 1).min(max_scroll);
457}
458
459pub fn scroll_up_policy_view_one(app: &mut App) {
460    app.iam_state.policy_scroll = app.iam_state.policy_scroll.saturating_sub(1);
461}
462
463// ── FilterInput next_item (dropdown cycling) ──────────────────────────────────
464
465pub fn next_item_filter_input_users(app: &mut App) {
466    if app.iam_state.user_tab == UserTab::Permissions
467        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
468    {
469        cycle_policy_type_next(app);
470    } else if app.iam_state.user_tab == UserTab::LastAccessed
471        && app.iam_state.last_accessed_input_focus == HISTORY_FILTER
472    {
473        app.iam_state.last_accessed_history_filter =
474            app.iam_state.last_accessed_history_filter.next();
475        app.iam_state.last_accessed_services.reset();
476    }
477}
478
479pub fn prev_item_filter_input_users(app: &mut App) {
480    if app.iam_state.user_tab == UserTab::Permissions
481        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
482    {
483        cycle_policy_type_prev(app);
484    } else if app.iam_state.user_tab == UserTab::LastAccessed
485        && app.iam_state.last_accessed_input_focus == HISTORY_FILTER
486    {
487        app.iam_state.last_accessed_history_filter =
488            app.iam_state.last_accessed_history_filter.prev();
489        app.iam_state.last_accessed_services.reset();
490    }
491}
492
493pub fn next_item_filter_input_roles(app: &mut App) {
494    if app.iam_state.role_tab == RoleTab::Permissions
495        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
496    {
497        cycle_policy_type_next(app);
498    }
499}
500
501pub fn prev_item_filter_input_roles(app: &mut App) {
502    if app.iam_state.role_tab == RoleTab::Permissions
503        && app.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
504    {
505        cycle_policy_type_prev(app);
506    }
507}
508
509fn cycle_policy_type_next(app: &mut App) {
510    let types = ["All types", "AWS managed", "Customer managed"];
511    let current_idx = types
512        .iter()
513        .position(|&t| t == app.iam_state.policy_type_filter)
514        .unwrap_or(0);
515    app.iam_state.policy_type_filter = types[(current_idx + 1) % types.len()].to_string();
516    app.iam_state.policies.reset();
517}
518
519fn cycle_policy_type_prev(app: &mut App) {
520    let types = ["All types", "AWS managed", "Customer managed"];
521    let current_idx = types
522        .iter()
523        .position(|&t| t == app.iam_state.policy_type_filter)
524        .unwrap_or(0);
525    let prev_idx = if current_idx == 0 {
526        types.len() - 1
527    } else {
528        current_idx - 1
529    };
530    app.iam_state.policy_type_filter = types[prev_idx].to_string();
531    app.iam_state.policies.reset();
532}
533
534// ── Expand / collapse ─────────────────────────────────────────────────────────
535
536pub fn expand_row_users(app: &mut App) {
537    if app.iam_state.current_user.is_some() {
538        if app.iam_state.user_tab == UserTab::Tags {
539            if app.iam_state.user_tags.expanded_item != Some(app.iam_state.user_tags.selected) {
540                app.iam_state.user_tags.expanded_item = Some(app.iam_state.user_tags.selected);
541            }
542        } else if app.iam_state.policies.expanded_item != Some(app.iam_state.policies.selected) {
543            app.iam_state.policies.toggle_expand();
544        }
545    } else if !app.iam_state.users.is_expanded() {
546        app.iam_state.users.toggle_expand();
547    }
548}
549
550pub fn expand_row_roles(app: &mut App) {
551    if app.iam_state.current_role.is_some() {
552        match app.iam_state.role_tab {
553            RoleTab::Tags => {
554                if !app.iam_state.tags.is_expanded() {
555                    app.iam_state.tags.expand();
556                }
557            }
558            RoleTab::LastAccessed => {
559                if !app.iam_state.last_accessed_services.is_expanded() {
560                    app.iam_state.last_accessed_services.expand();
561                }
562            }
563            _ => {
564                if !app.iam_state.policies.is_expanded() {
565                    app.iam_state.policies.expand();
566                }
567            }
568        }
569    } else if !app.iam_state.roles.is_expanded() {
570        app.iam_state.roles.expand();
571    }
572}
573
574pub fn expand_row_groups(app: &mut App) {
575    if app.iam_state.current_group.is_some() {
576        match app.iam_state.group_tab {
577            GroupTab::Users => {
578                if !app.iam_state.group_users.is_expanded() {
579                    app.iam_state.group_users.expand();
580                }
581            }
582            GroupTab::Permissions => {
583                if !app.iam_state.policies.is_expanded() {
584                    app.iam_state.policies.expand();
585                }
586            }
587            GroupTab::AccessAdvisor => {
588                if !app.iam_state.last_accessed_services.is_expanded() {
589                    app.iam_state.last_accessed_services.expand();
590                }
591            }
592        }
593    } else if !app.iam_state.groups.is_expanded() {
594        app.iam_state.groups.expand();
595    }
596}
597
598pub fn prev_pane_users(app: &mut App) {
599    if app.iam_state.users.has_expanded_item() {
600        app.iam_state.users.collapse();
601    }
602}
603
604pub fn prev_pane_roles(app: &mut App) {
605    if app.view_mode == crate::app::ViewMode::PolicyView {
606        app.view_mode = crate::app::ViewMode::Detail;
607        app.iam_state.current_policy = None;
608        app.iam_state.policy_document.clear();
609        app.iam_state.policy_scroll = 0;
610    } else if app.iam_state.current_role.is_some() {
611        if app.iam_state.role_tab == RoleTab::Tags && app.iam_state.tags.has_expanded_item() {
612            app.iam_state.tags.collapse();
613        } else if app.iam_state.role_tab == RoleTab::LastAccessed
614            && app.iam_state.last_accessed_services.expanded_item.is_some()
615        {
616            app.iam_state.last_accessed_services.collapse();
617        } else if app.iam_state.policies.has_expanded_item() {
618            app.iam_state.policies.collapse();
619        }
620    } else if app.iam_state.roles.has_expanded_item() {
621        app.iam_state.roles.collapse();
622    }
623}
624
625pub fn prev_pane_groups(app: &mut App) {
626    if app.iam_state.current_group.is_some() {
627        if app.iam_state.group_tab == GroupTab::Users
628            && app.iam_state.group_users.has_expanded_item()
629        {
630            app.iam_state.group_users.collapse();
631        } else if app.iam_state.group_tab == GroupTab::Permissions
632            && app.iam_state.policies.has_expanded_item()
633        {
634            app.iam_state.policies.collapse();
635        } else if app.iam_state.group_tab == GroupTab::AccessAdvisor
636            && app.iam_state.last_accessed_services.expanded_item.is_some()
637        {
638            app.iam_state.last_accessed_services.collapse();
639        }
640    } else if app.iam_state.groups.has_expanded_item() {
641        app.iam_state.groups.collapse();
642    }
643}
644
645pub fn collapse_row_users(app: &mut App) {
646    if app.iam_state.current_user.is_some() {
647        match app.iam_state.user_tab {
648            UserTab::Permissions => app.iam_state.policies.collapse(),
649            UserTab::Groups => app.iam_state.user_group_memberships.collapse(),
650            UserTab::Tags => app.iam_state.user_tags.collapse(),
651            _ => {}
652        }
653    } else {
654        app.iam_state.users.collapse();
655    }
656}
657
658pub fn collapse_row_roles(app: &mut App) {
659    if app.iam_state.current_role.is_some() {
660        match app.iam_state.role_tab {
661            RoleTab::Permissions => app.iam_state.policies.collapse(),
662            RoleTab::Tags => app.iam_state.tags.collapse(),
663            _ => {}
664        }
665    } else {
666        app.iam_state.roles.collapse();
667    }
668}
669
670pub fn collapse_row_groups(app: &mut App) {
671    app.iam_state.groups.collapse();
672}
673
674// ── Go back ───────────────────────────────────────────────────────────────────
675
676pub fn go_back_users(app: &mut App) {
677    app.iam_state.current_user = None;
678    app.iam_state.policies.items.clear();
679    app.iam_state.policies.reset();
680    app.update_current_tab_breadcrumb();
681}
682
683pub fn go_back_groups(app: &mut App) {
684    app.iam_state.current_group = None;
685    app.update_current_tab_breadcrumb();
686}
687
688pub fn go_back_roles(app: &mut App) {
689    if app.view_mode == crate::app::ViewMode::PolicyView {
690        app.view_mode = crate::app::ViewMode::Detail;
691        app.iam_state.current_policy = None;
692        app.iam_state.policy_document.clear();
693        app.iam_state.policy_scroll = 0;
694        app.update_current_tab_breadcrumb();
695    } else if app.iam_state.current_role.is_some() {
696        app.iam_state.current_role = None;
697        app.iam_state.policies.items.clear();
698        app.iam_state.policies.reset();
699        app.update_current_tab_breadcrumb();
700    }
701}
702
703// ── Detail tabs ───────────────────────────────────────────────────────────────
704
705pub fn next_detail_tab_roles(app: &mut App) {
706    app.iam_state.role_tab = app.iam_state.role_tab.next();
707    if app.iam_state.role_tab == RoleTab::Tags {
708        app.iam_state.tags.loading = true;
709    }
710}
711
712pub fn prev_detail_tab_roles(app: &mut App) {
713    app.iam_state.role_tab = app.iam_state.role_tab.prev();
714}
715
716pub fn next_detail_tab_users(app: &mut App) {
717    app.iam_state.user_tab = app.iam_state.user_tab.next();
718    if app.iam_state.user_tab == UserTab::Tags {
719        app.iam_state.user_tags.loading = true;
720    }
721}
722
723pub fn prev_detail_tab_users(app: &mut App) {
724    app.iam_state.user_tab = app.iam_state.user_tab.prev();
725}
726
727pub fn next_detail_tab_groups(app: &mut App) {
728    app.iam_state.group_tab = app.iam_state.group_tab.next();
729}
730
731pub fn prev_detail_tab_groups(app: &mut App) {
732    app.iam_state.group_tab = app.iam_state.group_tab.prev();
733}
734
735// ── Select item ───────────────────────────────────────────────────────────────
736
737pub fn select_item_users(app: &mut App) {
738    if app.iam_state.current_user.is_some() {
739        if app.iam_state.user_tab == UserTab::Permissions {
740            let filtered = filtered_iam_policies(app);
741            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
742                app.iam_state.current_policy = Some(policy.policy_name.clone());
743                app.iam_state.policy_scroll = 0;
744                app.view_mode = crate::app::ViewMode::PolicyView;
745                app.iam_state.policies.loading = true;
746                app.update_current_tab_breadcrumb();
747            }
748        }
749    } else {
750        let filtered = filtered_iam_users(app);
751        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
752            app.iam_state.current_user = Some(user.user_name.clone());
753            app.iam_state.user_tab = UserTab::Permissions;
754            app.iam_state.policies.reset();
755            app.update_current_tab_breadcrumb();
756        }
757    }
758}
759
760pub fn select_item_roles(app: &mut App) {
761    if app.iam_state.current_role.is_some() {
762        if app.iam_state.role_tab == RoleTab::Permissions {
763            let filtered = filtered_iam_policies(app);
764            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
765                app.iam_state.current_policy = Some(policy.policy_name.clone());
766                app.iam_state.policy_scroll = 0;
767                app.view_mode = crate::app::ViewMode::PolicyView;
768                app.iam_state.policies.loading = true;
769                app.update_current_tab_breadcrumb();
770            }
771        }
772    } else {
773        let filtered = filtered_iam_roles(app);
774        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
775            app.iam_state.current_role = Some(role.role_name.clone());
776            app.iam_state.role_tab = RoleTab::Permissions;
777            app.iam_state.policies.reset();
778            app.update_current_tab_breadcrumb();
779        }
780    }
781}
782
783pub fn select_item_groups(app: &mut App) {
784    if app.iam_state.current_group.is_none() {
785        let filtered: Vec<_> = app
786            .iam_state
787            .groups
788            .items
789            .iter()
790            .filter(|g| {
791                app.iam_state.groups.filter.is_empty()
792                    || g.group_name
793                        .to_lowercase()
794                        .contains(&app.iam_state.groups.filter.to_lowercase())
795            })
796            .collect();
797        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
798            app.iam_state.current_group = Some(group.group_name.clone());
799            app.update_current_tab_breadcrumb();
800        }
801    }
802}
803
804// ── Yank ──────────────────────────────────────────────────────────────────────
805
806pub fn yank_users(app: &App) {
807    use crate::app::copy_to_clipboard;
808    // In policy view, copy the policy document JSON
809    if app.view_mode == crate::app::ViewMode::PolicyView {
810        if !app.iam_state.policy_document.is_empty() {
811            copy_to_clipboard(&app.iam_state.policy_document);
812        }
813        return;
814    }
815    if app.iam_state.current_user.is_some() {
816        if let Some(user_name) = &app.iam_state.current_user {
817            if let Some(user) = app
818                .iam_state
819                .users
820                .items
821                .iter()
822                .find(|u| u.user_name == *user_name)
823            {
824                copy_to_clipboard(&user.arn);
825            }
826        }
827    } else {
828        let filtered = filtered_iam_users(app);
829        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
830            copy_to_clipboard(&user.arn);
831        }
832    }
833}
834
835pub fn yank_roles(app: &App) {
836    use crate::app::copy_to_clipboard;
837    // In policy view, copy the policy document JSON
838    if app.view_mode == crate::app::ViewMode::PolicyView {
839        if !app.iam_state.policy_document.is_empty() {
840            copy_to_clipboard(&app.iam_state.policy_document);
841        }
842        return;
843    }
844    if app.iam_state.current_role.is_some() {
845        if let Some(role_name) = &app.iam_state.current_role {
846            if let Some(role) = app
847                .iam_state
848                .roles
849                .items
850                .iter()
851                .find(|r| r.role_name == *role_name)
852            {
853                copy_to_clipboard(&role.arn);
854            }
855        }
856    } else {
857        let filtered = filtered_iam_roles(app);
858        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
859            copy_to_clipboard(&role.arn);
860        }
861    }
862}
863
864pub fn yank_groups(app: &App) {
865    use crate::app::copy_to_clipboard;
866    use crate::iam;
867    if let Some(group_name) = &app.iam_state.current_group {
868        let arn = iam::format_arn(&app.config.account_id, "group", group_name);
869        copy_to_clipboard(&arn);
870    } else {
871        let filtered: Vec<_> = app
872            .iam_state
873            .groups
874            .items
875            .iter()
876            .filter(|g| {
877                app.iam_state.groups.filter.is_empty()
878                    || g.group_name
879                        .to_lowercase()
880                        .contains(&app.iam_state.groups.filter.to_lowercase())
881            })
882            .collect();
883        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
884            let arn = iam::format_arn(&app.config.account_id, "group", &group.group_name);
885            copy_to_clipboard(&arn);
886        }
887    }
888}
889
890// ── Column preferences ────────────────────────────────────────────────────────
891
892pub fn toggle_column_users(app: &mut App) {
893    use crate::app::{toggle_iam_page_size_only, toggle_iam_preference_static};
894    let idx = app.column_selector_index;
895    if app.iam_state.current_user.is_some() {
896        match app.iam_state.user_tab {
897            UserTab::Permissions => {
898                if idx > 0 && idx <= app.iam_policy_column_ids.len() {
899                    if let Some(col) = app.iam_policy_column_ids.get(idx - 1) {
900                        if let Some(pos) = app
901                            .iam_policy_visible_column_ids
902                            .iter()
903                            .position(|c| c == col)
904                        {
905                            app.iam_policy_visible_column_ids.remove(pos);
906                        } else {
907                            app.iam_policy_visible_column_ids.push(col.clone());
908                        }
909                    }
910                } else if idx == app.iam_policy_column_ids.len() + 3 {
911                    app.iam_state.policies.page_size = PageSize::Ten;
912                } else if idx == app.iam_policy_column_ids.len() + 4 {
913                    app.iam_state.policies.page_size = PageSize::TwentyFive;
914                } else if idx == app.iam_policy_column_ids.len() + 5 {
915                    app.iam_state.policies.page_size = PageSize::Fifty;
916                }
917            }
918            UserTab::Groups => toggle_iam_page_size_only(
919                idx,
920                5,
921                &mut app.iam_state.user_group_memberships.page_size,
922            ),
923            UserTab::Tags => {
924                toggle_iam_page_size_only(idx, 5, &mut app.iam_state.user_tags.page_size)
925            }
926            UserTab::LastAccessed => toggle_iam_page_size_only(
927                idx,
928                6,
929                &mut app.iam_state.last_accessed_services.page_size,
930            ),
931            _ => {}
932        }
933    } else {
934        toggle_iam_preference_static(
935            idx,
936            &app.iam_user_column_ids.clone(),
937            &mut app.iam_user_visible_column_ids,
938            &mut app.iam_state.users.page_size,
939        );
940    }
941}
942
943pub fn toggle_column_roles(app: &mut App) {
944    use crate::app::{
945        toggle_iam_page_size_only, toggle_iam_preference, toggle_iam_preference_static,
946    };
947    let idx = app.column_selector_index;
948    if app.iam_state.current_role.is_some() {
949        match app.iam_state.role_tab {
950            RoleTab::Permissions => toggle_iam_preference(
951                idx,
952                &app.iam_policy_column_ids.clone(),
953                &mut app.iam_policy_visible_column_ids,
954                &mut app.iam_state.policies.page_size,
955            ),
956            RoleTab::LastAccessed => toggle_iam_page_size_only(
957                idx,
958                6,
959                &mut app.iam_state.last_accessed_services.page_size,
960            ),
961            _ => {}
962        }
963    } else {
964        toggle_iam_preference_static(
965            idx,
966            &app.iam_role_column_ids.clone(),
967            &mut app.iam_role_visible_column_ids,
968            &mut app.iam_state.roles.page_size,
969        );
970    }
971}
972
973pub fn toggle_column_groups(app: &mut App) {
974    use crate::app::toggle_iam_preference;
975    toggle_iam_preference(
976        app.column_selector_index,
977        &app.iam_group_column_ids.clone(),
978        &mut app.iam_group_visible_column_ids,
979        &mut app.iam_state.groups.page_size,
980    );
981}
982
983// ── Prefs cycling ─────────────────────────────────────────────────────────────
984
985pub fn next_preferences_users(app: &mut App) {
986    if app.iam_state.current_user.is_some() {
987        match app.iam_state.user_tab {
988            UserTab::Permissions => {
989                let page_size_idx = app.iam_policy_column_ids.len() + 2;
990                if app.column_selector_index < page_size_idx {
991                    app.column_selector_index = page_size_idx;
992                } else {
993                    app.column_selector_index = 0;
994                }
995            }
996            UserTab::Groups | UserTab::Tags => {
997                if app.column_selector_index < 4 {
998                    app.column_selector_index = 4;
999                } else {
1000                    app.column_selector_index = 0;
1001                }
1002            }
1003            UserTab::LastAccessed => {
1004                if app.column_selector_index < 5 {
1005                    app.column_selector_index = 5;
1006                } else {
1007                    app.column_selector_index = 0;
1008                }
1009            }
1010            _ => {}
1011        }
1012    } else {
1013        let page_size_idx = app.iam_user_column_ids.len() + 2;
1014        if app.column_selector_index < page_size_idx {
1015            app.column_selector_index = page_size_idx;
1016        } else {
1017            app.column_selector_index = 0;
1018        }
1019    }
1020}
1021
1022pub fn prev_preferences_users(app: &mut App) {
1023    if app.iam_state.current_user.is_some() {
1024        match app.iam_state.user_tab {
1025            UserTab::Permissions => {
1026                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1027                if app.column_selector_index >= page_size_idx {
1028                    app.column_selector_index = 0;
1029                } else {
1030                    app.column_selector_index = page_size_idx;
1031                }
1032            }
1033            UserTab::Groups | UserTab::Tags => {
1034                if app.column_selector_index >= 4 {
1035                    app.column_selector_index = 0;
1036                } else {
1037                    app.column_selector_index = 4;
1038                }
1039            }
1040            UserTab::LastAccessed => {
1041                if app.column_selector_index >= 5 {
1042                    app.column_selector_index = 0;
1043                } else {
1044                    app.column_selector_index = 5;
1045                }
1046            }
1047            _ => {}
1048        }
1049    } else {
1050        let page_size_idx = app.iam_user_column_ids.len() + 2;
1051        if app.column_selector_index >= page_size_idx {
1052            app.column_selector_index = 0;
1053        } else {
1054            app.column_selector_index = page_size_idx;
1055        }
1056    }
1057}
1058
1059pub fn next_preferences_roles(app: &mut App) {
1060    if app.iam_state.current_role.is_some() {
1061        match app.iam_state.role_tab {
1062            RoleTab::Permissions => {
1063                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1064                if app.column_selector_index < page_size_idx {
1065                    app.column_selector_index = page_size_idx;
1066                } else {
1067                    app.column_selector_index = 0;
1068                }
1069            }
1070            RoleTab::Tags => {
1071                if app.column_selector_index < 4 {
1072                    app.column_selector_index = 4;
1073                } else {
1074                    app.column_selector_index = 0;
1075                }
1076            }
1077            RoleTab::LastAccessed => {
1078                if app.column_selector_index < 5 {
1079                    app.column_selector_index = 5;
1080                } else {
1081                    app.column_selector_index = 0;
1082                }
1083            }
1084            _ => {}
1085        }
1086    } else {
1087        let page_size_idx = app.iam_role_column_ids.len() + 2;
1088        if app.column_selector_index < page_size_idx {
1089            app.column_selector_index = page_size_idx;
1090        } else {
1091            app.column_selector_index = 0;
1092        }
1093    }
1094}
1095
1096pub fn prev_preferences_roles(app: &mut App) {
1097    if app.iam_state.current_role.is_some() {
1098        match app.iam_state.role_tab {
1099            RoleTab::Permissions => {
1100                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1101                if app.column_selector_index >= page_size_idx {
1102                    app.column_selector_index = 0;
1103                } else {
1104                    app.column_selector_index = page_size_idx;
1105                }
1106            }
1107            RoleTab::Tags => {
1108                if app.column_selector_index >= 4 {
1109                    app.column_selector_index = 0;
1110                } else {
1111                    app.column_selector_index = 4;
1112                }
1113            }
1114            RoleTab::LastAccessed => {
1115                if app.column_selector_index >= 5 {
1116                    app.column_selector_index = 0;
1117                } else {
1118                    app.column_selector_index = 5;
1119                }
1120            }
1121            _ => {}
1122        }
1123    } else {
1124        let page_size_idx = app.iam_role_column_ids.len() + 2;
1125        if app.column_selector_index >= page_size_idx {
1126            app.column_selector_index = 0;
1127        } else {
1128            app.column_selector_index = page_size_idx;
1129        }
1130    }
1131}
1132
1133pub fn next_preferences_groups(app: &mut App) {
1134    let page_size_idx = app.iam_group_column_ids.len() + 2;
1135    if app.column_selector_index < page_size_idx {
1136        app.column_selector_index = page_size_idx;
1137    } else {
1138        app.column_selector_index = 0;
1139    }
1140}
1141
1142pub fn prev_preferences_groups(app: &mut App) {
1143    let page_size_idx = app.iam_group_column_ids.len() + 2;
1144    if app.column_selector_index >= page_size_idx {
1145        app.column_selector_index = 0;
1146    } else {
1147        app.column_selector_index = page_size_idx;
1148    }
1149}
1150
1151pub fn column_selector_max_users(app: &App) -> usize {
1152    if app.iam_state.current_user.is_some() {
1153        app.iam_policy_column_ids.len() + 5
1154    } else {
1155        app.iam_user_column_ids.len() + 5
1156    }
1157}
1158
1159pub fn column_count_users(app: &App) -> usize {
1160    if app.iam_state.current_user.is_some() {
1161        app.iam_policy_column_ids.len()
1162    } else {
1163        app.iam_user_column_ids.len()
1164    }
1165}
1166
1167pub fn column_selector_max_roles(app: &App) -> usize {
1168    if app.iam_state.current_role.is_some() {
1169        app.iam_policy_column_ids.len() + 5
1170    } else {
1171        app.iam_role_column_ids.len() + 5
1172    }
1173}
1174
1175pub fn column_count_roles(app: &App) -> usize {
1176    if app.iam_state.current_role.is_some() {
1177        app.iam_policy_column_ids.len()
1178    } else {
1179        app.iam_role_column_ids.len()
1180    }
1181}
1182
1183pub fn column_selector_max_groups(app: &App) -> usize {
1184    app.iam_group_column_ids.len() + 5
1185}
1186
1187pub fn column_count_groups(app: &App) -> usize {
1188    app.iam_group_column_ids.len()
1189}
1190
1191// ── Breadcrumb / console URL ──────────────────────────────────────────────────
1192
1193pub fn breadcrumb_users() -> Vec<String> {
1194    vec!["IAM".to_string(), "Users".to_string()]
1195}
1196
1197pub fn breadcrumb_roles(app: &App) -> Vec<String> {
1198    let mut parts = vec!["IAM".to_string(), "Roles".to_string()];
1199    if let Some(role_name) = &app.iam_state.current_role {
1200        parts.push(role_name.clone());
1201        if let Some(policy_name) = &app.iam_state.current_policy {
1202            parts.push(policy_name.clone());
1203        }
1204    }
1205    parts
1206}
1207
1208pub fn breadcrumb_groups(app: &App) -> Vec<String> {
1209    let mut parts = vec!["IAM".to_string(), "User Groups".to_string()];
1210    if let Some(group_name) = &app.iam_state.current_group {
1211        parts.push(group_name.clone());
1212    }
1213    parts
1214}
1215
1216pub fn console_url_users(app: &App) -> String {
1217    use crate::iam;
1218    if let Some(user_name) = &app.iam_state.current_user {
1219        let section = match app.iam_state.user_tab {
1220            UserTab::Permissions => "permissions",
1221            UserTab::Groups => "groups",
1222            UserTab::Tags => "tags",
1223            UserTab::SecurityCredentials => "security_credentials",
1224            UserTab::LastAccessed => "access_advisor",
1225        };
1226        iam::console_url_user_detail(&app.config.region, user_name, section)
1227    } else {
1228        iam::console_url_users(&app.config.region)
1229    }
1230}
1231
1232pub fn console_url_roles(app: &App) -> String {
1233    use crate::iam;
1234    if let Some(policy_name) = &app.iam_state.current_policy {
1235        if let Some(role_name) = &app.iam_state.current_role {
1236            return iam::console_url_role_policy(&app.config.region, role_name, policy_name);
1237        }
1238    }
1239    if let Some(role_name) = &app.iam_state.current_role {
1240        let section = match app.iam_state.role_tab {
1241            RoleTab::Permissions => "permissions",
1242            RoleTab::TrustRelationships => "trust_relationships",
1243            RoleTab::Tags => "tags",
1244            RoleTab::LastAccessed => "access_advisor",
1245            RoleTab::RevokeSessions => "revoke_sessions",
1246        };
1247        iam::console_url_role_detail(&app.config.region, role_name, section)
1248    } else {
1249        iam::console_url_roles(&app.config.region)
1250    }
1251}
1252
1253pub fn console_url_groups(app: &App) -> String {
1254    use crate::iam;
1255    iam::console_url_groups(&app.config.region)
1256}