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.iam_state.current_role.is_some() {
606        if app.iam_state.role_tab == RoleTab::Tags && app.iam_state.tags.has_expanded_item() {
607            app.iam_state.tags.collapse();
608        } else if app.iam_state.role_tab == RoleTab::LastAccessed
609            && app.iam_state.last_accessed_services.expanded_item.is_some()
610        {
611            app.iam_state.last_accessed_services.collapse();
612        } else if app.iam_state.policies.has_expanded_item() {
613            app.iam_state.policies.collapse();
614        }
615    } else if app.iam_state.roles.has_expanded_item() {
616        app.iam_state.roles.collapse();
617    }
618}
619
620pub fn prev_pane_groups(app: &mut App) {
621    if app.iam_state.current_group.is_some() {
622        if app.iam_state.group_tab == GroupTab::Users
623            && app.iam_state.group_users.has_expanded_item()
624        {
625            app.iam_state.group_users.collapse();
626        } else if app.iam_state.group_tab == GroupTab::Permissions
627            && app.iam_state.policies.has_expanded_item()
628        {
629            app.iam_state.policies.collapse();
630        } else if app.iam_state.group_tab == GroupTab::AccessAdvisor
631            && app.iam_state.last_accessed_services.expanded_item.is_some()
632        {
633            app.iam_state.last_accessed_services.collapse();
634        }
635    } else if app.iam_state.groups.has_expanded_item() {
636        app.iam_state.groups.collapse();
637    }
638}
639
640pub fn collapse_row_users(app: &mut App) {
641    if app.iam_state.current_user.is_some() {
642        match app.iam_state.user_tab {
643            UserTab::Permissions => app.iam_state.policies.collapse(),
644            UserTab::Groups => app.iam_state.user_group_memberships.collapse(),
645            UserTab::Tags => app.iam_state.user_tags.collapse(),
646            _ => {}
647        }
648    } else {
649        app.iam_state.users.collapse();
650    }
651}
652
653pub fn collapse_row_roles(app: &mut App) {
654    if app.iam_state.current_role.is_some() {
655        match app.iam_state.role_tab {
656            RoleTab::Permissions => app.iam_state.policies.collapse(),
657            RoleTab::Tags => app.iam_state.tags.collapse(),
658            _ => {}
659        }
660    } else {
661        app.iam_state.roles.collapse();
662    }
663}
664
665pub fn collapse_row_groups(app: &mut App) {
666    app.iam_state.groups.collapse();
667}
668
669// ── Go back ───────────────────────────────────────────────────────────────────
670
671pub fn go_back_users(app: &mut App) {
672    app.iam_state.current_user = None;
673    app.iam_state.policies.items.clear();
674    app.iam_state.policies.reset();
675    app.update_current_tab_breadcrumb();
676}
677
678pub fn go_back_groups(app: &mut App) {
679    app.iam_state.current_group = None;
680    app.update_current_tab_breadcrumb();
681}
682
683pub fn go_back_roles(app: &mut App) {
684    if app.view_mode == crate::app::ViewMode::PolicyView {
685        app.view_mode = crate::app::ViewMode::Detail;
686        app.iam_state.current_policy = None;
687        app.iam_state.policy_document.clear();
688        app.iam_state.policy_scroll = 0;
689        app.update_current_tab_breadcrumb();
690    } else if app.iam_state.current_role.is_some() {
691        app.iam_state.current_role = None;
692        app.iam_state.policies.items.clear();
693        app.iam_state.policies.reset();
694        app.update_current_tab_breadcrumb();
695    }
696}
697
698// ── Detail tabs ───────────────────────────────────────────────────────────────
699
700pub fn next_detail_tab_roles(app: &mut App) {
701    app.iam_state.role_tab = app.iam_state.role_tab.next();
702    if app.iam_state.role_tab == RoleTab::Tags {
703        app.iam_state.tags.loading = true;
704    }
705}
706
707pub fn prev_detail_tab_roles(app: &mut App) {
708    app.iam_state.role_tab = app.iam_state.role_tab.prev();
709}
710
711pub fn next_detail_tab_users(app: &mut App) {
712    app.iam_state.user_tab = app.iam_state.user_tab.next();
713    if app.iam_state.user_tab == UserTab::Tags {
714        app.iam_state.user_tags.loading = true;
715    }
716}
717
718pub fn prev_detail_tab_users(app: &mut App) {
719    app.iam_state.user_tab = app.iam_state.user_tab.prev();
720}
721
722pub fn next_detail_tab_groups(app: &mut App) {
723    app.iam_state.group_tab = app.iam_state.group_tab.next();
724}
725
726pub fn prev_detail_tab_groups(app: &mut App) {
727    app.iam_state.group_tab = app.iam_state.group_tab.prev();
728}
729
730// ── Select item ───────────────────────────────────────────────────────────────
731
732pub fn select_item_users(app: &mut App) {
733    if app.iam_state.current_user.is_some() {
734        if app.iam_state.user_tab == UserTab::Permissions {
735            let filtered = filtered_iam_policies(app);
736            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
737                app.iam_state.current_policy = Some(policy.policy_name.clone());
738                app.iam_state.policy_scroll = 0;
739                app.view_mode = crate::app::ViewMode::PolicyView;
740                app.iam_state.policies.loading = true;
741                app.update_current_tab_breadcrumb();
742            }
743        }
744    } else {
745        let filtered = filtered_iam_users(app);
746        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
747            app.iam_state.current_user = Some(user.user_name.clone());
748            app.iam_state.user_tab = UserTab::Permissions;
749            app.iam_state.policies.reset();
750            app.update_current_tab_breadcrumb();
751        }
752    }
753}
754
755pub fn select_item_roles(app: &mut App) {
756    if app.iam_state.current_role.is_some() {
757        if app.iam_state.role_tab == RoleTab::Permissions {
758            let filtered = filtered_iam_policies(app);
759            if let Some(policy) = app.iam_state.policies.get_selected(&filtered) {
760                app.iam_state.current_policy = Some(policy.policy_name.clone());
761                app.iam_state.policy_scroll = 0;
762                app.view_mode = crate::app::ViewMode::PolicyView;
763                app.iam_state.policies.loading = true;
764                app.update_current_tab_breadcrumb();
765            }
766        }
767    } else {
768        let filtered = filtered_iam_roles(app);
769        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
770            app.iam_state.current_role = Some(role.role_name.clone());
771            app.iam_state.role_tab = RoleTab::Permissions;
772            app.iam_state.policies.reset();
773            app.update_current_tab_breadcrumb();
774        }
775    }
776}
777
778pub fn select_item_groups(app: &mut App) {
779    if app.iam_state.current_group.is_none() {
780        let filtered: Vec<_> = app
781            .iam_state
782            .groups
783            .items
784            .iter()
785            .filter(|g| {
786                app.iam_state.groups.filter.is_empty()
787                    || g.group_name
788                        .to_lowercase()
789                        .contains(&app.iam_state.groups.filter.to_lowercase())
790            })
791            .collect();
792        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
793            app.iam_state.current_group = Some(group.group_name.clone());
794            app.update_current_tab_breadcrumb();
795        }
796    }
797}
798
799// ── Yank ──────────────────────────────────────────────────────────────────────
800
801pub fn yank_users(app: &App) {
802    use crate::app::copy_to_clipboard;
803    if app.iam_state.current_user.is_some() {
804        if let Some(user_name) = &app.iam_state.current_user {
805            if let Some(user) = app
806                .iam_state
807                .users
808                .items
809                .iter()
810                .find(|u| u.user_name == *user_name)
811            {
812                copy_to_clipboard(&user.arn);
813            }
814        }
815    } else {
816        let filtered = filtered_iam_users(app);
817        if let Some(user) = app.iam_state.users.get_selected(&filtered) {
818            copy_to_clipboard(&user.arn);
819        }
820    }
821}
822
823pub fn yank_roles(app: &App) {
824    use crate::app::copy_to_clipboard;
825    if app.iam_state.current_role.is_some() {
826        if let Some(role_name) = &app.iam_state.current_role {
827            if let Some(role) = app
828                .iam_state
829                .roles
830                .items
831                .iter()
832                .find(|r| r.role_name == *role_name)
833            {
834                copy_to_clipboard(&role.arn);
835            }
836        }
837    } else {
838        let filtered = filtered_iam_roles(app);
839        if let Some(role) = app.iam_state.roles.get_selected(&filtered) {
840            copy_to_clipboard(&role.arn);
841        }
842    }
843}
844
845pub fn yank_groups(app: &App) {
846    use crate::app::copy_to_clipboard;
847    use crate::iam;
848    if let Some(group_name) = &app.iam_state.current_group {
849        let arn = iam::format_arn(&app.config.account_id, "group", group_name);
850        copy_to_clipboard(&arn);
851    } else {
852        let filtered: Vec<_> = app
853            .iam_state
854            .groups
855            .items
856            .iter()
857            .filter(|g| {
858                app.iam_state.groups.filter.is_empty()
859                    || g.group_name
860                        .to_lowercase()
861                        .contains(&app.iam_state.groups.filter.to_lowercase())
862            })
863            .collect();
864        if let Some(group) = app.iam_state.groups.get_selected(&filtered) {
865            let arn = iam::format_arn(&app.config.account_id, "group", &group.group_name);
866            copy_to_clipboard(&arn);
867        }
868    }
869}
870
871// ── Column preferences ────────────────────────────────────────────────────────
872
873pub fn toggle_column_users(app: &mut App) {
874    use crate::app::{toggle_iam_page_size_only, toggle_iam_preference_static};
875    let idx = app.column_selector_index;
876    if app.iam_state.current_user.is_some() {
877        match app.iam_state.user_tab {
878            UserTab::Permissions => {
879                if idx > 0 && idx <= app.iam_policy_column_ids.len() {
880                    if let Some(col) = app.iam_policy_column_ids.get(idx - 1) {
881                        if let Some(pos) = app
882                            .iam_policy_visible_column_ids
883                            .iter()
884                            .position(|c| c == col)
885                        {
886                            app.iam_policy_visible_column_ids.remove(pos);
887                        } else {
888                            app.iam_policy_visible_column_ids.push(col.clone());
889                        }
890                    }
891                } else if idx == app.iam_policy_column_ids.len() + 3 {
892                    app.iam_state.policies.page_size = PageSize::Ten;
893                } else if idx == app.iam_policy_column_ids.len() + 4 {
894                    app.iam_state.policies.page_size = PageSize::TwentyFive;
895                } else if idx == app.iam_policy_column_ids.len() + 5 {
896                    app.iam_state.policies.page_size = PageSize::Fifty;
897                }
898            }
899            UserTab::Groups => toggle_iam_page_size_only(
900                idx,
901                5,
902                &mut app.iam_state.user_group_memberships.page_size,
903            ),
904            UserTab::Tags => {
905                toggle_iam_page_size_only(idx, 5, &mut app.iam_state.user_tags.page_size)
906            }
907            UserTab::LastAccessed => toggle_iam_page_size_only(
908                idx,
909                6,
910                &mut app.iam_state.last_accessed_services.page_size,
911            ),
912            _ => {}
913        }
914    } else {
915        toggle_iam_preference_static(
916            idx,
917            &app.iam_user_column_ids.clone(),
918            &mut app.iam_user_visible_column_ids,
919            &mut app.iam_state.users.page_size,
920        );
921    }
922}
923
924pub fn toggle_column_roles(app: &mut App) {
925    use crate::app::{
926        toggle_iam_page_size_only, toggle_iam_preference, toggle_iam_preference_static,
927    };
928    let idx = app.column_selector_index;
929    if app.iam_state.current_role.is_some() {
930        match app.iam_state.role_tab {
931            RoleTab::Permissions => toggle_iam_preference(
932                idx,
933                &app.iam_policy_column_ids.clone(),
934                &mut app.iam_policy_visible_column_ids,
935                &mut app.iam_state.policies.page_size,
936            ),
937            RoleTab::LastAccessed => toggle_iam_page_size_only(
938                idx,
939                6,
940                &mut app.iam_state.last_accessed_services.page_size,
941            ),
942            _ => {}
943        }
944    } else {
945        toggle_iam_preference_static(
946            idx,
947            &app.iam_role_column_ids.clone(),
948            &mut app.iam_role_visible_column_ids,
949            &mut app.iam_state.roles.page_size,
950        );
951    }
952}
953
954pub fn toggle_column_groups(app: &mut App) {
955    use crate::app::toggle_iam_preference;
956    toggle_iam_preference(
957        app.column_selector_index,
958        &app.iam_group_column_ids.clone(),
959        &mut app.iam_group_visible_column_ids,
960        &mut app.iam_state.groups.page_size,
961    );
962}
963
964// ── Prefs cycling ─────────────────────────────────────────────────────────────
965
966pub fn next_preferences_users(app: &mut App) {
967    if app.iam_state.current_user.is_some() {
968        match app.iam_state.user_tab {
969            UserTab::Permissions => {
970                let page_size_idx = app.iam_policy_column_ids.len() + 2;
971                if app.column_selector_index < page_size_idx {
972                    app.column_selector_index = page_size_idx;
973                } else {
974                    app.column_selector_index = 0;
975                }
976            }
977            UserTab::Groups | UserTab::Tags => {
978                if app.column_selector_index < 4 {
979                    app.column_selector_index = 4;
980                } else {
981                    app.column_selector_index = 0;
982                }
983            }
984            UserTab::LastAccessed => {
985                if app.column_selector_index < 5 {
986                    app.column_selector_index = 5;
987                } else {
988                    app.column_selector_index = 0;
989                }
990            }
991            _ => {}
992        }
993    } else {
994        let page_size_idx = app.iam_user_column_ids.len() + 2;
995        if app.column_selector_index < page_size_idx {
996            app.column_selector_index = page_size_idx;
997        } else {
998            app.column_selector_index = 0;
999        }
1000    }
1001}
1002
1003pub fn prev_preferences_users(app: &mut App) {
1004    if app.iam_state.current_user.is_some() {
1005        match app.iam_state.user_tab {
1006            UserTab::Permissions => {
1007                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1008                if app.column_selector_index >= page_size_idx {
1009                    app.column_selector_index = 0;
1010                } else {
1011                    app.column_selector_index = page_size_idx;
1012                }
1013            }
1014            UserTab::Groups | UserTab::Tags => {
1015                if app.column_selector_index >= 4 {
1016                    app.column_selector_index = 0;
1017                } else {
1018                    app.column_selector_index = 4;
1019                }
1020            }
1021            UserTab::LastAccessed => {
1022                if app.column_selector_index >= 5 {
1023                    app.column_selector_index = 0;
1024                } else {
1025                    app.column_selector_index = 5;
1026                }
1027            }
1028            _ => {}
1029        }
1030    } else {
1031        let page_size_idx = app.iam_user_column_ids.len() + 2;
1032        if app.column_selector_index >= page_size_idx {
1033            app.column_selector_index = 0;
1034        } else {
1035            app.column_selector_index = page_size_idx;
1036        }
1037    }
1038}
1039
1040pub fn next_preferences_roles(app: &mut App) {
1041    if app.iam_state.current_role.is_some() {
1042        match app.iam_state.role_tab {
1043            RoleTab::Permissions => {
1044                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1045                if app.column_selector_index < page_size_idx {
1046                    app.column_selector_index = page_size_idx;
1047                } else {
1048                    app.column_selector_index = 0;
1049                }
1050            }
1051            RoleTab::Tags => {
1052                if app.column_selector_index < 4 {
1053                    app.column_selector_index = 4;
1054                } else {
1055                    app.column_selector_index = 0;
1056                }
1057            }
1058            RoleTab::LastAccessed => {
1059                if app.column_selector_index < 5 {
1060                    app.column_selector_index = 5;
1061                } else {
1062                    app.column_selector_index = 0;
1063                }
1064            }
1065            _ => {}
1066        }
1067    } else {
1068        let page_size_idx = app.iam_role_column_ids.len() + 2;
1069        if app.column_selector_index < page_size_idx {
1070            app.column_selector_index = page_size_idx;
1071        } else {
1072            app.column_selector_index = 0;
1073        }
1074    }
1075}
1076
1077pub fn prev_preferences_roles(app: &mut App) {
1078    if app.iam_state.current_role.is_some() {
1079        match app.iam_state.role_tab {
1080            RoleTab::Permissions => {
1081                let page_size_idx = app.iam_policy_column_ids.len() + 2;
1082                if app.column_selector_index >= page_size_idx {
1083                    app.column_selector_index = 0;
1084                } else {
1085                    app.column_selector_index = page_size_idx;
1086                }
1087            }
1088            RoleTab::Tags => {
1089                if app.column_selector_index >= 4 {
1090                    app.column_selector_index = 0;
1091                } else {
1092                    app.column_selector_index = 4;
1093                }
1094            }
1095            RoleTab::LastAccessed => {
1096                if app.column_selector_index >= 5 {
1097                    app.column_selector_index = 0;
1098                } else {
1099                    app.column_selector_index = 5;
1100                }
1101            }
1102            _ => {}
1103        }
1104    } else {
1105        let page_size_idx = app.iam_role_column_ids.len() + 2;
1106        if app.column_selector_index >= page_size_idx {
1107            app.column_selector_index = 0;
1108        } else {
1109            app.column_selector_index = page_size_idx;
1110        }
1111    }
1112}
1113
1114pub fn next_preferences_groups(app: &mut App) {
1115    let page_size_idx = app.iam_group_column_ids.len() + 2;
1116    if app.column_selector_index < page_size_idx {
1117        app.column_selector_index = page_size_idx;
1118    } else {
1119        app.column_selector_index = 0;
1120    }
1121}
1122
1123pub fn prev_preferences_groups(app: &mut App) {
1124    let page_size_idx = app.iam_group_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
1132pub fn column_selector_max_users(app: &App) -> usize {
1133    if app.iam_state.current_user.is_some() {
1134        app.iam_policy_column_ids.len() + 5
1135    } else {
1136        app.iam_user_column_ids.len() + 5
1137    }
1138}
1139
1140pub fn column_count_users(app: &App) -> usize {
1141    if app.iam_state.current_user.is_some() {
1142        app.iam_policy_column_ids.len()
1143    } else {
1144        app.iam_user_column_ids.len()
1145    }
1146}
1147
1148pub fn column_selector_max_roles(app: &App) -> usize {
1149    if app.iam_state.current_role.is_some() {
1150        app.iam_policy_column_ids.len() + 5
1151    } else {
1152        app.iam_role_column_ids.len() + 5
1153    }
1154}
1155
1156pub fn column_count_roles(app: &App) -> usize {
1157    if app.iam_state.current_role.is_some() {
1158        app.iam_policy_column_ids.len()
1159    } else {
1160        app.iam_role_column_ids.len()
1161    }
1162}
1163
1164pub fn column_selector_max_groups(app: &App) -> usize {
1165    app.iam_group_column_ids.len() + 5
1166}
1167
1168pub fn column_count_groups(app: &App) -> usize {
1169    app.iam_group_column_ids.len()
1170}
1171
1172// ── Breadcrumb / console URL ──────────────────────────────────────────────────
1173
1174pub fn breadcrumb_users() -> Vec<String> {
1175    vec!["IAM".to_string(), "Users".to_string()]
1176}
1177
1178pub fn breadcrumb_roles(app: &App) -> Vec<String> {
1179    let mut parts = vec!["IAM".to_string(), "Roles".to_string()];
1180    if let Some(role_name) = &app.iam_state.current_role {
1181        parts.push(role_name.clone());
1182        if let Some(policy_name) = &app.iam_state.current_policy {
1183            parts.push(policy_name.clone());
1184        }
1185    }
1186    parts
1187}
1188
1189pub fn breadcrumb_groups(app: &App) -> Vec<String> {
1190    let mut parts = vec!["IAM".to_string(), "User Groups".to_string()];
1191    if let Some(group_name) = &app.iam_state.current_group {
1192        parts.push(group_name.clone());
1193    }
1194    parts
1195}
1196
1197pub fn console_url_users(app: &App) -> String {
1198    use crate::iam;
1199    if let Some(user_name) = &app.iam_state.current_user {
1200        let section = match app.iam_state.user_tab {
1201            UserTab::Permissions => "permissions",
1202            UserTab::Groups => "groups",
1203            UserTab::Tags => "tags",
1204            UserTab::SecurityCredentials => "security_credentials",
1205            UserTab::LastAccessed => "access_advisor",
1206        };
1207        iam::console_url_user_detail(&app.config.region, user_name, section)
1208    } else {
1209        iam::console_url_users(&app.config.region)
1210    }
1211}
1212
1213pub fn console_url_roles(app: &App) -> String {
1214    use crate::iam;
1215    if let Some(policy_name) = &app.iam_state.current_policy {
1216        if let Some(role_name) = &app.iam_state.current_role {
1217            return iam::console_url_role_policy(&app.config.region, role_name, policy_name);
1218        }
1219    }
1220    if let Some(role_name) = &app.iam_state.current_role {
1221        let section = match app.iam_state.role_tab {
1222            RoleTab::Permissions => "permissions",
1223            RoleTab::TrustRelationships => "trust_relationships",
1224            RoleTab::Tags => "tags",
1225            RoleTab::LastAccessed => "access_advisor",
1226            RoleTab::RevokeSessions => "revoke_sessions",
1227        };
1228        iam::console_url_role_detail(&app.config.region, role_name, section)
1229    } else {
1230        iam::console_url_roles(&app.config.region)
1231    }
1232}
1233
1234pub fn console_url_groups(app: &App) -> String {
1235    use crate::iam;
1236    iam::console_url_groups(&app.config.region)
1237}