rusticity_term/
app.rs

1pub use crate::aws::{filter_profiles, Profile as AwsProfile, Region as AwsRegion};
2use crate::cfn::{Column as CfnColumn, Stack as CfnStack};
3use crate::common::{ColumnId, CyclicEnum, InputFocus, PageSize, SortDirection};
4pub use crate::cw::insights::InsightsFocus;
5use crate::cw::insights::InsightsState;
6pub use crate::cw::{Alarm, AlarmColumn};
7pub use crate::ec2::{Column as Ec2Column, Instance as Ec2Instance};
8use crate::ecr::image::{Column as EcrImageColumn, Image as EcrImage};
9use crate::ecr::repo::{Column as EcrColumn, Repository as EcrRepository};
10use crate::iam::{
11    self, GroupUser as IamGroupUser, Policy as IamPolicy, RoleColumn, RoleTag as IamRoleTag,
12    UserColumn, UserTag as IamUserTag,
13};
14#[cfg(test)]
15use crate::iam::{IamRole, IamUser, LastAccessedService};
16use crate::keymap::{Action, Mode};
17pub use crate::lambda::{
18    Alias as LambdaAlias, Application as LambdaApplication,
19    ApplicationColumn as LambdaApplicationColumn, Deployment, DeploymentColumn,
20    Function as LambdaFunction, FunctionColumn as LambdaColumn, Layer as LambdaLayer, Resource,
21    ResourceColumn, Version as LambdaVersion,
22};
23pub use crate::s3::{Bucket as S3Bucket, BucketColumn as S3BucketColumn, Object as S3Object};
24use crate::session::{Session, SessionTab};
25pub use crate::sqs::queue::Column as SqsColumn;
26pub use crate::sqs::trigger::Column as SqsTriggerColumn;
27use crate::sqs::{console_url_queue_detail, console_url_queues};
28#[cfg(test)]
29use crate::sqs::{
30    EventBridgePipe, LambdaTrigger, Queue as SqsQueue, QueueTag as SqsQueueTag, SnsSubscription,
31};
32use crate::table::TableState;
33use crate::ui::cfn::State as CfnStateConstants;
34pub use crate::ui::cfn::{
35    filtered_cloudformation_stacks, filtered_outputs, filtered_parameters, filtered_resources,
36    output_column_ids, parameter_column_ids, resource_column_ids, DetailTab as CfnDetailTab,
37    State as CfnState, StatusFilter as CfnStatusFilter,
38};
39pub use crate::ui::cw::alarms::{
40    AlarmTab, AlarmViewMode, FILTER_CONTROLS as ALARM_FILTER_CONTROLS,
41};
42pub use crate::ui::cw::logs::{
43    filtered_log_events, filtered_log_groups, filtered_log_streams, selected_log_group,
44    DetailTab as CwLogsDetailTab, EventFilterFocus, FILTER_CONTROLS as LOG_FILTER_CONTROLS,
45};
46use crate::ui::ec2;
47use crate::ui::ec2::filtered_ec2_instances;
48pub use crate::ui::ec2::{
49    DetailTab as Ec2DetailTab, State as Ec2State, StateFilter as Ec2StateFilter,
50    STATE_FILTER as EC2_STATE_FILTER,
51};
52pub use crate::ui::ecr::{
53    filtered_ecr_images, filtered_ecr_repositories, State as EcrState, Tab as EcrTab,
54    FILTER_CONTROLS as ECR_FILTER_CONTROLS,
55};
56use crate::ui::iam::{
57    filtered_iam_policies, filtered_iam_roles, filtered_iam_users, filtered_last_accessed,
58    filtered_tags as filtered_iam_tags, filtered_user_tags, GroupTab, RoleTab, State as IamState,
59    UserTab,
60};
61pub use crate::ui::lambda::{
62    filtered_lambda_applications, filtered_lambda_functions,
63    ApplicationDetailTab as LambdaApplicationDetailTab, ApplicationState as LambdaApplicationState,
64    DetailTab as LambdaDetailTab, State as LambdaState, FILTER_CONTROLS as LAMBDA_FILTER_CONTROLS,
65};
66use crate::ui::monitoring::MonitoringState;
67pub use crate::ui::s3::{
68    calculate_total_bucket_rows, calculate_total_object_rows, BucketType as S3BucketType,
69    ObjectTab as S3ObjectTab, State as S3State,
70};
71pub use crate::ui::sqs::{
72    extract_account_id, extract_region, filtered_eventbridge_pipes, filtered_lambda_triggers,
73    filtered_queues, filtered_subscriptions, filtered_tags, QueueDetailTab as SqsQueueDetailTab,
74    State as SqsState, FILTER_CONTROLS as SQS_FILTER_CONTROLS,
75    SUBSCRIPTION_FILTER_CONTROLS as SQS_SUBSCRIPTION_FILTER_CONTROLS, SUBSCRIPTION_REGION,
76};
77pub use crate::ui::{
78    CloudWatchLogGroupsState, DateRangeType, DetailTab, EventColumn, LogGroupColumn, Preferences,
79    StreamColumn, StreamSort, TimeUnit,
80};
81#[cfg(test)]
82use rusticity_core::LogStream;
83use rusticity_core::{
84    AlarmsClient, AwsConfig, CloudFormationClient, CloudWatchClient, Ec2Client, EcrClient,
85    IamClient, LambdaClient, S3Client, SqsClient,
86};
87
88#[derive(Clone)]
89pub struct Tab {
90    pub service: Service,
91    pub title: String,
92    pub breadcrumb: String,
93}
94
95pub struct App {
96    pub running: bool,
97    pub mode: Mode,
98    pub config: AwsConfig,
99    pub cloudwatch_client: CloudWatchClient,
100    pub s3_client: S3Client,
101    pub sqs_client: SqsClient,
102    pub alarms_client: AlarmsClient,
103    pub ec2_client: Ec2Client,
104    pub ecr_client: EcrClient,
105    pub iam_client: IamClient,
106    pub lambda_client: LambdaClient,
107    pub cloudformation_client: CloudFormationClient,
108    pub current_service: Service,
109    pub tabs: Vec<Tab>,
110    pub current_tab: usize,
111    pub tab_picker_selected: usize,
112    pub tab_filter: String,
113    pub pending_key: Option<char>,
114    pub log_groups_state: CloudWatchLogGroupsState,
115    pub insights_state: CloudWatchInsightsState,
116    pub alarms_state: CloudWatchAlarmsState,
117    pub s3_state: S3State,
118    pub sqs_state: SqsState,
119    pub ec2_state: Ec2State,
120    pub ecr_state: EcrState,
121    pub lambda_state: LambdaState,
122    pub lambda_application_state: LambdaApplicationState,
123    pub cfn_state: CfnState,
124    pub iam_state: IamState,
125    pub service_picker: ServicePickerState,
126    pub service_selected: bool,
127    pub profile: String,
128    pub region: String,
129    pub region_selector_index: usize,
130    pub cw_log_group_visible_column_ids: Vec<ColumnId>,
131    pub cw_log_group_column_ids: Vec<ColumnId>,
132    pub column_selector_index: usize,
133    pub preference_section: Preferences,
134    pub cw_log_stream_visible_column_ids: Vec<ColumnId>,
135    pub cw_log_stream_column_ids: Vec<ColumnId>,
136    pub cw_log_event_visible_column_ids: Vec<ColumnId>,
137    pub cw_log_event_column_ids: Vec<ColumnId>,
138    pub cw_alarm_visible_column_ids: Vec<ColumnId>,
139    pub cw_alarm_column_ids: Vec<ColumnId>,
140    pub s3_bucket_visible_column_ids: Vec<ColumnId>,
141    pub s3_bucket_column_ids: Vec<ColumnId>,
142    pub sqs_visible_column_ids: Vec<ColumnId>,
143    pub sqs_column_ids: Vec<ColumnId>,
144    pub ec2_visible_column_ids: Vec<ColumnId>,
145    pub ec2_column_ids: Vec<ColumnId>,
146    pub ecr_repo_visible_column_ids: Vec<ColumnId>,
147    pub ecr_repo_column_ids: Vec<ColumnId>,
148    pub ecr_image_visible_column_ids: Vec<ColumnId>,
149    pub ecr_image_column_ids: Vec<ColumnId>,
150    pub lambda_application_visible_column_ids: Vec<ColumnId>,
151    pub lambda_application_column_ids: Vec<ColumnId>,
152    pub lambda_deployment_visible_column_ids: Vec<ColumnId>,
153    pub lambda_deployment_column_ids: Vec<ColumnId>,
154    pub lambda_resource_visible_column_ids: Vec<ColumnId>,
155    pub lambda_resource_column_ids: Vec<ColumnId>,
156    pub cfn_visible_column_ids: Vec<ColumnId>,
157    pub cfn_column_ids: Vec<ColumnId>,
158    pub cfn_parameter_visible_column_ids: Vec<ColumnId>,
159    pub cfn_parameter_column_ids: Vec<ColumnId>,
160    pub cfn_output_visible_column_ids: Vec<ColumnId>,
161    pub cfn_output_column_ids: Vec<ColumnId>,
162    pub cfn_resource_visible_column_ids: Vec<ColumnId>,
163    pub cfn_resource_column_ids: Vec<ColumnId>,
164    pub iam_user_visible_column_ids: Vec<ColumnId>,
165    pub iam_user_column_ids: Vec<ColumnId>,
166    pub iam_role_visible_column_ids: Vec<ColumnId>,
167    pub iam_role_column_ids: Vec<ColumnId>,
168    pub iam_group_visible_column_ids: Vec<String>,
169    pub iam_group_column_ids: Vec<String>,
170    pub iam_policy_visible_column_ids: Vec<String>,
171    pub iam_policy_column_ids: Vec<String>,
172    pub view_mode: ViewMode,
173    pub error_message: Option<String>,
174    pub error_scroll: usize,
175    pub page_input: String,
176    pub calendar_date: Option<time::Date>,
177    pub calendar_selecting: CalendarField,
178    pub cursor_pos: usize,
179    pub current_session: Option<Session>,
180    pub sessions: Vec<Session>,
181    pub session_picker_selected: usize,
182    pub session_filter: String,
183    pub region_filter: String,
184    pub region_picker_selected: usize,
185    pub region_latencies: std::collections::HashMap<String, u64>,
186    pub profile_filter: String,
187    pub profile_picker_selected: usize,
188    pub available_profiles: Vec<AwsProfile>,
189    pub snapshot_requested: bool,
190}
191
192#[derive(Debug, Clone, Copy, PartialEq)]
193pub enum CalendarField {
194    StartDate,
195    EndDate,
196}
197
198pub struct CloudWatchInsightsState {
199    pub insights: InsightsState,
200    pub loading: bool,
201}
202
203pub struct CloudWatchAlarmsState {
204    pub table: TableState<Alarm>,
205    pub alarm_tab: AlarmTab,
206    pub view_as: AlarmViewMode,
207    pub wrap_lines: bool,
208    pub sort_column: String,
209    pub sort_direction: SortDirection,
210    pub input_focus: InputFocus,
211}
212
213impl PageSize {
214    pub fn value(&self) -> usize {
215        match self {
216            PageSize::Ten => 10,
217            PageSize::TwentyFive => 25,
218            PageSize::Fifty => 50,
219            PageSize::OneHundred => 100,
220        }
221    }
222
223    pub fn next(&self) -> Self {
224        match self {
225            PageSize::Ten => PageSize::TwentyFive,
226            PageSize::TwentyFive => PageSize::Fifty,
227            PageSize::Fifty => PageSize::OneHundred,
228            PageSize::OneHundred => PageSize::Ten,
229        }
230    }
231}
232
233pub struct ServicePickerState {
234    pub filter: String,
235    pub selected: usize,
236    pub services: Vec<&'static str>,
237}
238
239#[derive(Debug, Clone, Copy, PartialEq)]
240pub enum ViewMode {
241    List,
242    Detail,
243    Events,
244    InsightsResults,
245    PolicyView,
246}
247
248#[derive(Debug, Clone, Copy, PartialEq)]
249pub enum Service {
250    CloudWatchLogGroups,
251    CloudWatchInsights,
252    CloudWatchAlarms,
253    S3Buckets,
254    SqsQueues,
255    Ec2Instances,
256    EcrRepositories,
257    LambdaFunctions,
258    LambdaApplications,
259    CloudFormationStacks,
260    IamUsers,
261    IamRoles,
262    IamUserGroups,
263}
264
265impl Service {
266    pub fn name(&self) -> &str {
267        match self {
268            Service::CloudWatchLogGroups => "CloudWatch > Log Groups",
269            Service::CloudWatchInsights => "CloudWatch > Logs Insights",
270            Service::CloudWatchAlarms => "CloudWatch > Alarms",
271            Service::S3Buckets => "S3 > Buckets",
272            Service::SqsQueues => "SQS > Queues",
273            Service::Ec2Instances => "EC2 > Instances",
274            Service::EcrRepositories => "ECR > Repositories",
275            Service::LambdaFunctions => "Lambda > Functions",
276            Service::LambdaApplications => "Lambda > Applications",
277            Service::CloudFormationStacks => "CloudFormation > Stacks",
278            Service::IamUsers => "IAM > Users",
279            Service::IamRoles => "IAM > Roles",
280            Service::IamUserGroups => "IAM > User Groups",
281        }
282    }
283}
284
285fn copy_to_clipboard(text: &str) {
286    use std::io::Write;
287    use std::process::{Command, Stdio};
288    if let Ok(mut child) = Command::new("pbcopy").stdin(Stdio::piped()).spawn() {
289        if let Some(mut stdin) = child.stdin.take() {
290            let _ = stdin.write_all(text.as_bytes());
291        }
292        let _ = child.wait();
293    }
294}
295
296fn nav_page_down(selected: &mut usize, max: usize, page_size: usize) {
297    if max > 0 {
298        *selected = (*selected + page_size).min(max - 1);
299    }
300}
301
302fn toggle_iam_preference(
303    idx: usize,
304    column_ids: &[String],
305    visible_column_ids: &mut Vec<String>,
306    page_size: &mut PageSize,
307) {
308    if idx > 0 && idx <= column_ids.len() {
309        if let Some(col) = column_ids.get(idx - 1) {
310            if let Some(pos) = visible_column_ids.iter().position(|c| c == col) {
311                visible_column_ids.remove(pos);
312            } else {
313                visible_column_ids.push(col.clone());
314            }
315        }
316    } else if idx == column_ids.len() + 3 {
317        *page_size = PageSize::Ten;
318    } else if idx == column_ids.len() + 4 {
319        *page_size = PageSize::TwentyFive;
320    } else if idx == column_ids.len() + 5 {
321        *page_size = PageSize::Fifty;
322    }
323}
324
325fn toggle_iam_preference_static(
326    idx: usize,
327    column_ids: &[ColumnId],
328    visible_column_ids: &mut Vec<ColumnId>,
329    page_size: &mut PageSize,
330) {
331    if idx > 0 && idx <= column_ids.len() {
332        if let Some(col) = column_ids.get(idx - 1) {
333            if let Some(pos) = visible_column_ids.iter().position(|c| c == col) {
334                visible_column_ids.remove(pos);
335            } else {
336                visible_column_ids.push(*col);
337            }
338        }
339    } else if idx == column_ids.len() + 3 {
340        *page_size = PageSize::Ten;
341    } else if idx == column_ids.len() + 4 {
342        *page_size = PageSize::TwentyFive;
343    } else if idx == column_ids.len() + 5 {
344        *page_size = PageSize::Fifty;
345    }
346}
347
348fn toggle_iam_page_size_only(idx: usize, base_idx: usize, page_size: &mut PageSize) {
349    if idx == base_idx {
350        *page_size = PageSize::Ten;
351    } else if idx == base_idx + 1 {
352        *page_size = PageSize::TwentyFive;
353    } else if idx == base_idx + 2 {
354        *page_size = PageSize::Fifty;
355    }
356}
357
358impl App {
359    pub fn get_input_focus(&self) -> InputFocus {
360        InputFocus::Filter
361    }
362
363    fn get_active_filter_mut(&mut self) -> Option<&mut String> {
364        if self.current_service == Service::CloudWatchAlarms {
365            Some(&mut self.alarms_state.table.filter)
366        } else if self.current_service == Service::Ec2Instances {
367            if self.ec2_state.current_instance.is_some()
368                && self.ec2_state.detail_tab == Ec2DetailTab::Tags
369            {
370                Some(&mut self.ec2_state.tags.filter)
371            } else {
372                Some(&mut self.ec2_state.table.filter)
373            }
374        } else if self.current_service == Service::S3Buckets {
375            if self.s3_state.current_bucket.is_some() {
376                Some(&mut self.s3_state.object_filter)
377            } else {
378                Some(&mut self.s3_state.buckets.filter)
379            }
380        } else if self.current_service == Service::EcrRepositories {
381            if self.ecr_state.current_repository.is_some() {
382                Some(&mut self.ecr_state.images.filter)
383            } else {
384                Some(&mut self.ecr_state.repositories.filter)
385            }
386        } else if self.current_service == Service::SqsQueues {
387            if self.sqs_state.current_queue.is_some()
388                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
389            {
390                Some(&mut self.sqs_state.triggers.filter)
391            } else if self.sqs_state.current_queue.is_some()
392                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
393            {
394                Some(&mut self.sqs_state.pipes.filter)
395            } else if self.sqs_state.current_queue.is_some()
396                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
397            {
398                Some(&mut self.sqs_state.tags.filter)
399            } else if self.sqs_state.current_queue.is_some()
400                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
401            {
402                Some(&mut self.sqs_state.subscriptions.filter)
403            } else {
404                Some(&mut self.sqs_state.queues.filter)
405            }
406        } else if self.current_service == Service::LambdaFunctions {
407            if self.lambda_state.current_version.is_some()
408                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
409            {
410                Some(&mut self.lambda_state.alias_table.filter)
411            } else if self.lambda_state.current_function.is_some()
412                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
413            {
414                Some(&mut self.lambda_state.version_table.filter)
415            } else if self.lambda_state.current_function.is_some()
416                && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
417            {
418                Some(&mut self.lambda_state.alias_table.filter)
419            } else {
420                Some(&mut self.lambda_state.table.filter)
421            }
422        } else if self.current_service == Service::LambdaApplications {
423            if self.lambda_application_state.current_application.is_some() {
424                if self.lambda_application_state.detail_tab
425                    == LambdaApplicationDetailTab::Deployments
426                {
427                    Some(&mut self.lambda_application_state.deployments.filter)
428                } else {
429                    Some(&mut self.lambda_application_state.resources.filter)
430                }
431            } else {
432                Some(&mut self.lambda_application_state.table.filter)
433            }
434        } else if self.current_service == Service::CloudFormationStacks {
435            if self.cfn_state.current_stack.is_some()
436                && self.cfn_state.detail_tab == CfnDetailTab::Resources
437            {
438                Some(&mut self.cfn_state.resources.filter)
439            } else {
440                Some(&mut self.cfn_state.table.filter)
441            }
442        } else if self.current_service == Service::IamUsers {
443            if self.iam_state.current_user.is_some() {
444                if self.iam_state.user_tab == UserTab::Tags {
445                    Some(&mut self.iam_state.user_tags.filter)
446                } else {
447                    Some(&mut self.iam_state.policies.filter)
448                }
449            } else {
450                Some(&mut self.iam_state.users.filter)
451            }
452        } else if self.current_service == Service::IamRoles {
453            if self.iam_state.current_role.is_some() {
454                if self.iam_state.role_tab == RoleTab::Tags {
455                    Some(&mut self.iam_state.tags.filter)
456                } else if self.iam_state.role_tab == RoleTab::LastAccessed {
457                    Some(&mut self.iam_state.last_accessed_filter)
458                } else {
459                    Some(&mut self.iam_state.policies.filter)
460                }
461            } else {
462                Some(&mut self.iam_state.roles.filter)
463            }
464        } else if self.current_service == Service::IamUserGroups {
465            if self.iam_state.current_group.is_some() {
466                if self.iam_state.group_tab == GroupTab::Permissions {
467                    Some(&mut self.iam_state.policies.filter)
468                } else if self.iam_state.group_tab == GroupTab::Users {
469                    Some(&mut self.iam_state.group_users.filter)
470                } else {
471                    None
472                }
473            } else {
474                Some(&mut self.iam_state.groups.filter)
475            }
476        } else if self.view_mode == ViewMode::List {
477            Some(&mut self.log_groups_state.log_groups.filter)
478        } else if self.view_mode == ViewMode::Detail
479            && self.log_groups_state.detail_tab == DetailTab::LogStreams
480        {
481            Some(&mut self.log_groups_state.stream_filter)
482        } else {
483            None
484        }
485    }
486
487    fn apply_filter_operation<F>(&mut self, op: F)
488    where
489        F: FnOnce(&mut String),
490    {
491        if let Some(filter) = self.get_active_filter_mut() {
492            op(filter);
493            // Automatically reset selection for all services
494            if self.current_service == Service::CloudWatchAlarms {
495                self.alarms_state.table.reset();
496            } else if self.current_service == Service::Ec2Instances {
497                self.ec2_state.table.reset();
498            } else if self.current_service == Service::S3Buckets {
499                if self.s3_state.current_bucket.is_some() {
500                    self.s3_state.selected_object = 0;
501                } else {
502                    self.s3_state.buckets.reset();
503                    self.s3_state.selected_row = 0;
504                    self.s3_state.bucket_scroll_offset = 0;
505                }
506            } else if self.current_service == Service::EcrRepositories {
507                if self.ecr_state.current_repository.is_some() {
508                    self.ecr_state.images.reset();
509                } else {
510                    self.ecr_state.repositories.reset();
511                }
512            } else if self.current_service == Service::SqsQueues {
513                self.sqs_state.queues.reset();
514            } else if self.current_service == Service::LambdaFunctions {
515                if self.lambda_state.current_version.is_some()
516                    || self.lambda_state.current_function.is_some()
517                {
518                    self.lambda_state.version_table.reset();
519                    self.lambda_state.alias_table.reset();
520                } else {
521                    self.lambda_state.table.reset();
522                }
523            } else if self.current_service == Service::LambdaApplications {
524                if self.lambda_application_state.current_application.is_some() {
525                    self.lambda_application_state.deployments.reset();
526                    self.lambda_application_state.resources.reset();
527                } else {
528                    self.lambda_application_state.table.reset();
529                }
530            } else if self.current_service == Service::CloudFormationStacks {
531                self.cfn_state.table.reset();
532            } else if self.current_service == Service::IamUsers {
533                if self.iam_state.current_user.is_some() {
534                    self.iam_state.user_tags.reset();
535                    self.iam_state.policies.reset();
536                } else {
537                    self.iam_state.users.reset();
538                }
539            } else if self.current_service == Service::IamRoles {
540                if self.iam_state.current_role.is_some() {
541                    self.iam_state.tags.reset();
542                    self.iam_state.policies.reset();
543                } else {
544                    self.iam_state.roles.reset();
545                }
546            } else if self.current_service == Service::IamUserGroups {
547                if self.iam_state.current_group.is_some() {
548                    self.iam_state.policies.reset();
549                    self.iam_state.group_users.reset();
550                } else {
551                    self.iam_state.groups.reset();
552                }
553            } else if self.current_service == Service::CloudWatchLogGroups {
554                if self.view_mode == ViewMode::List {
555                    self.log_groups_state.log_groups.reset();
556                } else if self.log_groups_state.detail_tab == DetailTab::LogStreams {
557                    self.log_groups_state.selected_stream = 0;
558                }
559            }
560        }
561    }
562
563    pub async fn new(profile: Option<String>, region: Option<String>) -> anyhow::Result<Self> {
564        let profile_name = profile.or_else(|| std::env::var("AWS_PROFILE").ok())
565            .ok_or_else(|| anyhow::anyhow!("No AWS profile specified. Set AWS_PROFILE environment variable or select a profile."))?;
566
567        std::env::set_var("AWS_PROFILE", &profile_name);
568
569        let config = AwsConfig::new(region).await?;
570        let cloudwatch_client = CloudWatchClient::new(config.clone()).await?;
571        let s3_client = S3Client::new(config.clone());
572        let sqs_client = SqsClient::new(config.clone());
573        let alarms_client = AlarmsClient::new(config.clone());
574        let ec2_client = Ec2Client::new(config.clone());
575        let ecr_client = EcrClient::new(config.clone());
576        let iam_client = IamClient::new(config.clone());
577        let lambda_client = LambdaClient::new(config.clone());
578        let cloudformation_client = CloudFormationClient::new(config.clone());
579        let region_name = config.region.clone();
580
581        Ok(Self {
582            running: true,
583            mode: Mode::ServicePicker,
584            config,
585            cloudwatch_client,
586            s3_client,
587            sqs_client,
588            alarms_client,
589            ec2_client,
590            ecr_client,
591            iam_client,
592            lambda_client,
593            cloudformation_client,
594            current_service: Service::CloudWatchLogGroups,
595            tabs: Vec::new(),
596            current_tab: 0,
597            tab_picker_selected: 0,
598            tab_filter: String::new(),
599            pending_key: None,
600            log_groups_state: CloudWatchLogGroupsState::new(),
601            insights_state: CloudWatchInsightsState::new(),
602            alarms_state: CloudWatchAlarmsState::new(),
603            s3_state: S3State::new(),
604            sqs_state: SqsState::new(),
605            ec2_state: Ec2State::default(),
606            ecr_state: EcrState::new(),
607            lambda_state: LambdaState::new(),
608            lambda_application_state: LambdaApplicationState::new(),
609            cfn_state: CfnState::new(),
610            iam_state: IamState::new(),
611            service_picker: ServicePickerState::new(),
612            service_selected: false,
613            profile: profile_name,
614            region: region_name,
615            region_selector_index: 0,
616            cw_log_group_visible_column_ids: LogGroupColumn::default_visible(),
617            cw_log_group_column_ids: LogGroupColumn::ids(),
618            column_selector_index: 0,
619            cw_log_stream_visible_column_ids: StreamColumn::default_visible(),
620            cw_log_stream_column_ids: StreamColumn::ids(),
621            cw_log_event_visible_column_ids: EventColumn::default_visible(),
622            cw_log_event_column_ids: EventColumn::ids(),
623            cw_alarm_visible_column_ids: [
624                AlarmColumn::Name,
625                AlarmColumn::State,
626                AlarmColumn::LastStateUpdate,
627                AlarmColumn::Conditions,
628                AlarmColumn::Actions,
629            ]
630            .iter()
631            .map(|c| c.id())
632            .collect(),
633            cw_alarm_column_ids: AlarmColumn::ids(),
634            s3_bucket_visible_column_ids: S3BucketColumn::ids(),
635            s3_bucket_column_ids: S3BucketColumn::ids(),
636            sqs_visible_column_ids: [
637                SqsColumn::Name,
638                SqsColumn::Type,
639                SqsColumn::Created,
640                SqsColumn::MessagesAvailable,
641                SqsColumn::MessagesInFlight,
642                SqsColumn::Encryption,
643                SqsColumn::ContentBasedDeduplication,
644            ]
645            .iter()
646            .map(|c| c.id())
647            .collect(),
648            sqs_column_ids: SqsColumn::ids(),
649            ec2_visible_column_ids: [
650                Ec2Column::Name,
651                Ec2Column::InstanceId,
652                Ec2Column::InstanceState,
653                Ec2Column::InstanceType,
654                Ec2Column::StatusCheck,
655                Ec2Column::AlarmStatus,
656                Ec2Column::AvailabilityZone,
657                Ec2Column::PublicIpv4Dns,
658                Ec2Column::PublicIpv4Address,
659                Ec2Column::ElasticIp,
660                Ec2Column::Ipv6Ips,
661                Ec2Column::Monitoring,
662                Ec2Column::SecurityGroupName,
663                Ec2Column::KeyName,
664                Ec2Column::LaunchTime,
665                Ec2Column::PlatformDetails,
666            ]
667            .iter()
668            .map(|c| c.id())
669            .collect(),
670            ec2_column_ids: Ec2Column::ids(),
671            ecr_repo_visible_column_ids: EcrColumn::ids(),
672            ecr_repo_column_ids: EcrColumn::ids(),
673            ecr_image_visible_column_ids: EcrImageColumn::ids(),
674            ecr_image_column_ids: EcrImageColumn::ids(),
675            lambda_application_visible_column_ids: LambdaApplicationColumn::visible(),
676            lambda_application_column_ids: LambdaApplicationColumn::ids(),
677            lambda_deployment_visible_column_ids: DeploymentColumn::ids(),
678            lambda_deployment_column_ids: DeploymentColumn::ids(),
679            lambda_resource_visible_column_ids: ResourceColumn::ids(),
680            lambda_resource_column_ids: ResourceColumn::ids(),
681            cfn_visible_column_ids: [
682                CfnColumn::Name,
683                CfnColumn::Status,
684                CfnColumn::CreatedTime,
685                CfnColumn::Description,
686            ]
687            .iter()
688            .map(|c| c.id())
689            .collect(),
690            cfn_column_ids: CfnColumn::ids(),
691            cfn_parameter_visible_column_ids: parameter_column_ids(),
692            cfn_parameter_column_ids: parameter_column_ids(),
693            cfn_output_visible_column_ids: output_column_ids(),
694            cfn_output_column_ids: output_column_ids(),
695            cfn_resource_visible_column_ids: resource_column_ids(),
696            cfn_resource_column_ids: resource_column_ids(),
697            iam_user_visible_column_ids: UserColumn::visible(),
698            iam_user_column_ids: UserColumn::ids(),
699            iam_role_visible_column_ids: RoleColumn::visible(),
700            iam_role_column_ids: RoleColumn::ids(),
701            iam_group_visible_column_ids: vec![
702                "Group name".to_string(),
703                "Users".to_string(),
704                "Permissions".to_string(),
705                "Creation time".to_string(),
706            ],
707            iam_group_column_ids: vec![
708                "Group name".to_string(),
709                "Path".to_string(),
710                "Users".to_string(),
711                "Permissions".to_string(),
712                "Creation time".to_string(),
713            ],
714            iam_policy_visible_column_ids: vec![
715                "Policy name".to_string(),
716                "Type".to_string(),
717                "Attached via".to_string(),
718            ],
719            iam_policy_column_ids: vec![
720                "Policy name".to_string(),
721                "Type".to_string(),
722                "Attached via".to_string(),
723                "Attached entities".to_string(),
724                "Description".to_string(),
725                "Creation time".to_string(),
726                "Edited time".to_string(),
727            ],
728            preference_section: Preferences::Columns,
729            view_mode: ViewMode::List,
730            error_message: None,
731            error_scroll: 0,
732            page_input: String::new(),
733            calendar_date: None,
734            calendar_selecting: CalendarField::StartDate,
735            cursor_pos: 0,
736            current_session: None,
737            sessions: Vec::new(),
738            session_picker_selected: 0,
739            session_filter: String::new(),
740            region_filter: String::new(),
741            region_picker_selected: 0,
742            region_latencies: std::collections::HashMap::new(),
743            profile_filter: String::new(),
744            profile_picker_selected: 0,
745            available_profiles: Vec::new(),
746            snapshot_requested: false,
747        })
748    }
749
750    pub fn new_without_client(profile: String, region: Option<String>) -> Self {
751        let config = AwsConfig::dummy(region.clone());
752        Self {
753            running: true,
754            mode: Mode::ServicePicker,
755            config: config.clone(),
756            cloudwatch_client: CloudWatchClient::dummy(config.clone()),
757            s3_client: S3Client::new(config.clone()),
758            sqs_client: SqsClient::new(config.clone()),
759            alarms_client: AlarmsClient::new(config.clone()),
760            ec2_client: Ec2Client::new(config.clone()),
761            ecr_client: EcrClient::new(config.clone()),
762            iam_client: IamClient::new(config.clone()),
763            lambda_client: LambdaClient::new(config.clone()),
764            cloudformation_client: CloudFormationClient::new(config.clone()),
765            current_service: Service::CloudWatchLogGroups,
766            tabs: Vec::new(),
767            current_tab: 0,
768            tab_picker_selected: 0,
769            tab_filter: String::new(),
770            pending_key: None,
771            log_groups_state: CloudWatchLogGroupsState::new(),
772            insights_state: CloudWatchInsightsState::new(),
773            alarms_state: CloudWatchAlarmsState::new(),
774            s3_state: S3State::new(),
775            sqs_state: SqsState::new(),
776            ec2_state: Ec2State::default(),
777            ecr_state: EcrState::new(),
778            lambda_state: LambdaState::new(),
779            lambda_application_state: LambdaApplicationState::new(),
780            cfn_state: CfnState::new(),
781            iam_state: IamState::new(),
782            service_picker: ServicePickerState::new(),
783            service_selected: false,
784            profile,
785            region: region.unwrap_or_default(),
786            region_selector_index: 0,
787            cw_log_group_visible_column_ids: LogGroupColumn::default_visible(),
788            cw_log_group_column_ids: LogGroupColumn::ids(),
789            column_selector_index: 0,
790            preference_section: Preferences::Columns,
791            cw_log_stream_visible_column_ids: StreamColumn::default_visible(),
792            cw_log_stream_column_ids: StreamColumn::ids(),
793            cw_log_event_visible_column_ids: EventColumn::default_visible(),
794            cw_log_event_column_ids: EventColumn::ids(),
795            cw_alarm_visible_column_ids: [
796                AlarmColumn::Name,
797                AlarmColumn::State,
798                AlarmColumn::LastStateUpdate,
799                AlarmColumn::Conditions,
800                AlarmColumn::Actions,
801            ]
802            .iter()
803            .map(|c| c.id())
804            .collect(),
805            cw_alarm_column_ids: AlarmColumn::ids(),
806            s3_bucket_visible_column_ids: S3BucketColumn::ids(),
807            s3_bucket_column_ids: S3BucketColumn::ids(),
808            sqs_visible_column_ids: [
809                SqsColumn::Name,
810                SqsColumn::Type,
811                SqsColumn::Created,
812                SqsColumn::MessagesAvailable,
813                SqsColumn::MessagesInFlight,
814                SqsColumn::Encryption,
815                SqsColumn::ContentBasedDeduplication,
816            ]
817            .iter()
818            .map(|c| c.id())
819            .collect(),
820            sqs_column_ids: SqsColumn::ids(),
821            ec2_visible_column_ids: [
822                Ec2Column::Name,
823                Ec2Column::InstanceId,
824                Ec2Column::InstanceState,
825                Ec2Column::InstanceType,
826                Ec2Column::StatusCheck,
827                Ec2Column::AlarmStatus,
828                Ec2Column::AvailabilityZone,
829                Ec2Column::PublicIpv4Dns,
830                Ec2Column::PublicIpv4Address,
831                Ec2Column::ElasticIp,
832                Ec2Column::Ipv6Ips,
833                Ec2Column::Monitoring,
834                Ec2Column::SecurityGroupName,
835                Ec2Column::KeyName,
836                Ec2Column::LaunchTime,
837                Ec2Column::PlatformDetails,
838            ]
839            .iter()
840            .map(|c| c.id())
841            .collect(),
842            ec2_column_ids: Ec2Column::ids(),
843            ecr_repo_visible_column_ids: EcrColumn::ids(),
844            ecr_repo_column_ids: EcrColumn::ids(),
845            ecr_image_visible_column_ids: EcrImageColumn::ids(),
846            ecr_image_column_ids: EcrImageColumn::ids(),
847            lambda_application_visible_column_ids: LambdaApplicationColumn::visible(),
848            lambda_application_column_ids: LambdaApplicationColumn::ids(),
849            lambda_deployment_visible_column_ids: DeploymentColumn::ids(),
850            lambda_deployment_column_ids: DeploymentColumn::ids(),
851            lambda_resource_visible_column_ids: ResourceColumn::ids(),
852            lambda_resource_column_ids: ResourceColumn::ids(),
853            cfn_visible_column_ids: [
854                CfnColumn::Name,
855                CfnColumn::Status,
856                CfnColumn::CreatedTime,
857                CfnColumn::Description,
858            ]
859            .iter()
860            .map(|c| c.id())
861            .collect(),
862            cfn_column_ids: CfnColumn::ids(),
863            iam_user_visible_column_ids: UserColumn::visible(),
864            cfn_parameter_visible_column_ids: parameter_column_ids(),
865            cfn_parameter_column_ids: parameter_column_ids(),
866            cfn_output_visible_column_ids: output_column_ids(),
867            cfn_output_column_ids: output_column_ids(),
868            cfn_resource_visible_column_ids: resource_column_ids(),
869            cfn_resource_column_ids: resource_column_ids(),
870            iam_user_column_ids: UserColumn::ids(),
871            iam_role_visible_column_ids: RoleColumn::visible(),
872            iam_role_column_ids: RoleColumn::ids(),
873            iam_group_visible_column_ids: vec![
874                "Group name".to_string(),
875                "Users".to_string(),
876                "Permissions".to_string(),
877                "Creation time".to_string(),
878            ],
879            iam_group_column_ids: vec![
880                "Group name".to_string(),
881                "Path".to_string(),
882                "Users".to_string(),
883                "Permissions".to_string(),
884                "Creation time".to_string(),
885            ],
886            iam_policy_visible_column_ids: vec![
887                "Policy name".to_string(),
888                "Type".to_string(),
889                "Attached via".to_string(),
890            ],
891            iam_policy_column_ids: vec![
892                "Policy name".to_string(),
893                "Type".to_string(),
894                "Attached via".to_string(),
895                "Attached entities".to_string(),
896                "Description".to_string(),
897                "Creation time".to_string(),
898                "Edited time".to_string(),
899            ],
900            view_mode: ViewMode::List,
901            error_message: None,
902            error_scroll: 0,
903            page_input: String::new(),
904            calendar_date: None,
905            calendar_selecting: CalendarField::StartDate,
906            cursor_pos: 0,
907            current_session: None,
908            sessions: Vec::new(),
909            session_picker_selected: 0,
910            session_filter: String::new(),
911            region_filter: String::new(),
912            region_picker_selected: 0,
913            region_latencies: std::collections::HashMap::new(),
914            profile_filter: String::new(),
915            profile_picker_selected: 0,
916            available_profiles: Vec::new(),
917            snapshot_requested: false,
918        }
919    }
920
921    pub fn handle_action(&mut self, action: Action) {
922        match action {
923            Action::Quit => {
924                self.save_current_session();
925                self.running = false;
926            }
927            Action::CloseService => {
928                if !self.tabs.is_empty() {
929                    // Close the current tab
930                    self.tabs.remove(self.current_tab);
931
932                    if self.tabs.is_empty() {
933                        // Last tab closed - show service picker
934                        self.service_selected = false;
935                        self.current_tab = 0;
936                        self.mode = Mode::ServicePicker;
937                    } else {
938                        // Tabs remain - switch to adjacent tab
939                        if self.current_tab >= self.tabs.len() {
940                            self.current_tab = self.tabs.len() - 1;
941                        }
942                        self.current_service = self.tabs[self.current_tab].service;
943                        self.service_selected = true;
944                        self.mode = Mode::Normal;
945                    }
946                } else {
947                    // No tabs - just close service picker if open
948                    self.service_selected = false;
949                    self.mode = Mode::Normal;
950                }
951                self.service_picker.filter.clear();
952                self.service_picker.selected = 0;
953            }
954            Action::NextItem => self.next_item(),
955            Action::PrevItem => self.prev_item(),
956            Action::PageUp => self.page_up(),
957            Action::PageDown => self.page_down(),
958            Action::NextPane => self.next_pane(),
959            Action::PrevPane => self.prev_pane(),
960            Action::CollapseRow => self.collapse_row(),
961            Action::ExpandRow => self.expand_row(),
962            Action::Select => self.select_item(),
963            Action::OpenSpaceMenu => {
964                self.mode = Mode::SpaceMenu;
965                self.service_picker.filter.clear();
966                self.service_picker.selected = 0;
967            }
968            Action::CloseMenu => {
969                self.mode = Mode::Normal;
970                self.service_picker.filter.clear();
971                // Reset selection when closing filter to avoid out-of-bounds
972                match self.current_service {
973                    Service::S3Buckets => {
974                        self.s3_state.selected_row = 0;
975                        self.s3_state.selected_object = 0;
976                    }
977                    Service::CloudFormationStacks => {
978                        if self.cfn_state.current_stack.is_some()
979                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
980                        {
981                            self.cfn_state.parameters.reset();
982                        } else if self.cfn_state.current_stack.is_some()
983                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
984                        {
985                            self.cfn_state.outputs.reset();
986                        } else {
987                            self.cfn_state.table.reset();
988                        }
989                    }
990                    Service::LambdaFunctions => {
991                        self.lambda_state.table.reset();
992                    }
993                    Service::SqsQueues => {
994                        self.sqs_state.queues.reset();
995                    }
996                    Service::IamRoles => {
997                        self.iam_state.roles.reset();
998                    }
999                    Service::IamUsers => {
1000                        self.iam_state.users.reset();
1001                    }
1002                    Service::IamUserGroups => {
1003                        self.iam_state.groups.reset();
1004                    }
1005                    Service::CloudWatchAlarms => {
1006                        self.alarms_state.table.reset();
1007                    }
1008                    Service::Ec2Instances => {
1009                        self.ec2_state.table.reset();
1010                    }
1011                    Service::EcrRepositories => {
1012                        self.ecr_state.repositories.reset();
1013                    }
1014                    Service::LambdaApplications => {
1015                        self.lambda_application_state.table.reset();
1016                    }
1017                    _ => {}
1018                }
1019            }
1020            Action::NextTab => {
1021                if !self.tabs.is_empty() {
1022                    self.current_tab = (self.current_tab + 1) % self.tabs.len();
1023                    self.current_service = self.tabs[self.current_tab].service;
1024                }
1025            }
1026            Action::PrevTab => {
1027                if !self.tabs.is_empty() {
1028                    self.current_tab = if self.current_tab == 0 {
1029                        self.tabs.len() - 1
1030                    } else {
1031                        self.current_tab - 1
1032                    };
1033                    self.current_service = self.tabs[self.current_tab].service;
1034                }
1035            }
1036            Action::CloseTab => {
1037                if !self.tabs.is_empty() {
1038                    self.tabs.remove(self.current_tab);
1039                    if self.tabs.is_empty() {
1040                        // Last tab closed - show service picker
1041                        self.service_selected = false;
1042                        self.current_tab = 0;
1043                        self.service_picker.filter.clear();
1044                        self.service_picker.selected = 0;
1045                        self.mode = Mode::ServicePicker;
1046                    } else {
1047                        // If we closed the last tab, move to the left
1048                        // Otherwise stay at same index (which is now the next tab to the right)
1049                        if self.current_tab >= self.tabs.len() {
1050                            self.current_tab = self.tabs.len() - 1;
1051                        }
1052                        self.current_service = self.tabs[self.current_tab].service;
1053                        self.service_selected = true;
1054                        self.mode = Mode::Normal;
1055                    }
1056                }
1057            }
1058            Action::OpenTabPicker => {
1059                if !self.tabs.is_empty() {
1060                    self.tab_picker_selected = self.current_tab;
1061                    self.mode = Mode::TabPicker;
1062                } else {
1063                    self.mode = Mode::Normal;
1064                }
1065            }
1066            Action::OpenSessionPicker => {
1067                self.save_current_session();
1068                self.sessions = Session::list_all().unwrap_or_default();
1069                self.session_picker_selected = 0;
1070                self.mode = Mode::SessionPicker;
1071            }
1072            Action::LoadSession => {
1073                let filtered_sessions = self.get_filtered_sessions();
1074                if let Some(&session) = filtered_sessions.get(self.session_picker_selected) {
1075                    let session = session.clone();
1076                    // Load the session
1077                    self.profile = session.profile.clone();
1078                    self.region = session.region.clone();
1079                    self.config.account_id = session.account_id.clone();
1080                    self.config.role_arn = session.role_arn.clone();
1081
1082                    // Clear existing tabs and load session tabs
1083                    self.tabs.clear();
1084                    for session_tab in &session.tabs {
1085                        // Parse service from string
1086                        let service = match session_tab.service.as_str() {
1087                            "CloudWatchLogGroups" => Service::CloudWatchLogGroups,
1088                            "CloudWatchInsights" => Service::CloudWatchInsights,
1089                            "CloudWatchAlarms" => Service::CloudWatchAlarms,
1090                            "S3Buckets" => Service::S3Buckets,
1091                            "SqsQueues" => Service::SqsQueues,
1092                            "Ec2Instances" => Service::Ec2Instances,
1093                            "EcrRepositories" => Service::EcrRepositories,
1094                            "LambdaFunctions" => Service::LambdaFunctions,
1095                            "LambdaApplications" => Service::LambdaApplications,
1096                            "CloudFormationStacks" => Service::CloudFormationStacks,
1097                            "IamUsers" => Service::IamUsers,
1098                            "IamRoles" => Service::IamRoles,
1099                            "IamUserGroups" => Service::IamUserGroups,
1100                            _ => continue,
1101                        };
1102
1103                        self.tabs.push(Tab {
1104                            service,
1105                            title: session_tab.title.clone(),
1106                            breadcrumb: session_tab.breadcrumb.clone(),
1107                        });
1108
1109                        // Restore filter if present
1110                        if let Some(filter) = &session_tab.filter {
1111                            if service == Service::CloudWatchLogGroups {
1112                                self.log_groups_state.log_groups.filter = filter.clone();
1113                            }
1114                        }
1115                    }
1116
1117                    if !self.tabs.is_empty() {
1118                        self.current_tab = 0;
1119                        self.current_service = self.tabs[0].service;
1120                        self.service_selected = true;
1121                        self.current_session = Some(session.clone());
1122                    }
1123                }
1124                self.mode = Mode::Normal;
1125            }
1126            Action::SaveSession => {
1127                // TODO: Implement session saving
1128            }
1129            Action::OpenServicePicker => {
1130                if self.mode == Mode::ServicePicker {
1131                    self.tabs.push(Tab {
1132                        service: Service::S3Buckets,
1133                        title: "S3 > Buckets".to_string(),
1134                        breadcrumb: "S3 > Buckets".to_string(),
1135                    });
1136                    self.current_tab = self.tabs.len() - 1;
1137                    self.current_service = Service::S3Buckets;
1138                    self.view_mode = ViewMode::List;
1139                    self.service_selected = true;
1140                    self.mode = Mode::Normal;
1141                } else {
1142                    self.mode = Mode::ServicePicker;
1143                    self.service_picker.filter.clear();
1144                    self.service_picker.selected = 0;
1145                }
1146            }
1147            Action::OpenCloudWatch => {
1148                self.current_service = Service::CloudWatchLogGroups;
1149                self.view_mode = ViewMode::List;
1150                self.service_selected = true;
1151                self.mode = Mode::Normal;
1152            }
1153            Action::OpenCloudWatchSplit => {
1154                self.current_service = Service::CloudWatchInsights;
1155                self.view_mode = ViewMode::InsightsResults;
1156                self.service_selected = true;
1157                self.mode = Mode::Normal;
1158            }
1159            Action::OpenCloudWatchAlarms => {
1160                self.current_service = Service::CloudWatchAlarms;
1161                self.view_mode = ViewMode::List;
1162                self.service_selected = true;
1163                self.mode = Mode::Normal;
1164            }
1165            Action::FilterInput(c) => {
1166                if self.mode == Mode::TabPicker {
1167                    self.tab_filter.push(c);
1168                    self.tab_picker_selected = 0;
1169                } else if self.mode == Mode::RegionPicker {
1170                    self.region_filter.push(c);
1171                    self.region_picker_selected = 0;
1172                } else if self.mode == Mode::ProfilePicker {
1173                    self.profile_filter.push(c);
1174                    self.profile_picker_selected = 0;
1175                } else if self.mode == Mode::SessionPicker {
1176                    self.session_filter.push(c);
1177                    self.session_picker_selected = 0;
1178                } else if self.mode == Mode::ServicePicker {
1179                    self.service_picker.filter.push(c);
1180                    self.service_picker.selected = 0;
1181                } else if self.mode == Mode::InsightsInput {
1182                    match self.insights_state.insights.insights_focus {
1183                        InsightsFocus::Query => {
1184                            self.insights_state.insights.query_text.push(c);
1185                        }
1186                        InsightsFocus::LogGroupSearch => {
1187                            self.insights_state.insights.log_group_search.push(c);
1188                            // Update matches
1189                            if !self.insights_state.insights.log_group_search.is_empty() {
1190                                self.insights_state.insights.log_group_matches = self
1191                                    .log_groups_state
1192                                    .log_groups
1193                                    .items
1194                                    .iter()
1195                                    .filter(|g| {
1196                                        g.name.to_lowercase().contains(
1197                                            &self
1198                                                .insights_state
1199                                                .insights
1200                                                .log_group_search
1201                                                .to_lowercase(),
1202                                        )
1203                                    })
1204                                    .take(50)
1205                                    .map(|g| g.name.clone())
1206                                    .collect();
1207                                self.insights_state.insights.show_dropdown = true;
1208                            } else {
1209                                self.insights_state.insights.log_group_matches.clear();
1210                                self.insights_state.insights.show_dropdown = false;
1211                            }
1212                        }
1213                        _ => {}
1214                    }
1215                } else if self.mode == Mode::FilterInput {
1216                    // Check if we should capture digits for page navigation
1217                    let is_pagination_focused = if self.current_service
1218                        == Service::LambdaApplications
1219                    {
1220                        if self.lambda_application_state.current_application.is_some() {
1221                            if self.lambda_application_state.detail_tab
1222                                == LambdaApplicationDetailTab::Deployments
1223                            {
1224                                self.lambda_application_state.deployment_input_focus
1225                                    == InputFocus::Pagination
1226                            } else {
1227                                self.lambda_application_state.resource_input_focus
1228                                    == InputFocus::Pagination
1229                            }
1230                        } else {
1231                            self.lambda_application_state.input_focus == InputFocus::Pagination
1232                        }
1233                    } else if self.current_service == Service::CloudFormationStacks {
1234                        self.cfn_state.input_focus == InputFocus::Pagination
1235                    } else if self.current_service == Service::IamRoles
1236                        && self.iam_state.current_role.is_none()
1237                    {
1238                        self.iam_state.role_input_focus == InputFocus::Pagination
1239                    } else if self.view_mode == ViewMode::PolicyView {
1240                        self.iam_state.policy_input_focus == InputFocus::Pagination
1241                    } else if self.current_service == Service::CloudWatchAlarms {
1242                        self.alarms_state.input_focus == InputFocus::Pagination
1243                    } else if self.current_service == Service::Ec2Instances {
1244                        self.ec2_state.input_focus == InputFocus::Pagination
1245                    } else if self.current_service == Service::CloudWatchLogGroups {
1246                        self.log_groups_state.input_focus == InputFocus::Pagination
1247                    } else if self.current_service == Service::EcrRepositories
1248                        && self.ecr_state.current_repository.is_none()
1249                    {
1250                        self.ecr_state.input_focus == InputFocus::Pagination
1251                    } else if self.current_service == Service::LambdaFunctions {
1252                        if self.lambda_state.current_function.is_some()
1253                            && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1254                        {
1255                            self.lambda_state.version_input_focus == InputFocus::Pagination
1256                        } else if self.lambda_state.current_function.is_none() {
1257                            self.lambda_state.input_focus == InputFocus::Pagination
1258                        } else {
1259                            false
1260                        }
1261                    } else if self.current_service == Service::SqsQueues {
1262                        if self.sqs_state.current_queue.is_some()
1263                            && (self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1264                                || self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1265                                || self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1266                                || self.sqs_state.detail_tab == SqsQueueDetailTab::Encryption
1267                                || self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions)
1268                        {
1269                            self.sqs_state.input_focus == InputFocus::Pagination
1270                        } else {
1271                            false
1272                        }
1273                    } else {
1274                        false
1275                    };
1276
1277                    if is_pagination_focused && c.is_ascii_digit() {
1278                        self.page_input.push(c);
1279                    } else if self.current_service == Service::LambdaApplications {
1280                        let is_input_focused =
1281                            if self.lambda_application_state.current_application.is_some() {
1282                                if self.lambda_application_state.detail_tab
1283                                    == LambdaApplicationDetailTab::Deployments
1284                                {
1285                                    self.lambda_application_state.deployment_input_focus
1286                                        == InputFocus::Filter
1287                                } else {
1288                                    self.lambda_application_state.resource_input_focus
1289                                        == InputFocus::Filter
1290                                }
1291                            } else {
1292                                self.lambda_application_state.input_focus == InputFocus::Filter
1293                            };
1294                        if is_input_focused {
1295                            self.apply_filter_operation(|f| f.push(c));
1296                        }
1297                    } else if self.current_service == Service::CloudFormationStacks {
1298                        if self.cfn_state.current_stack.is_some()
1299                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
1300                        {
1301                            if self.cfn_state.parameters_input_focus == InputFocus::Filter {
1302                                self.cfn_state.parameters.filter.push(c);
1303                                self.cfn_state.parameters.selected = 0;
1304                            }
1305                        } else if self.cfn_state.current_stack.is_some()
1306                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
1307                        {
1308                            if self.cfn_state.outputs_input_focus == InputFocus::Filter {
1309                                self.cfn_state.outputs.filter.push(c);
1310                                self.cfn_state.outputs.selected = 0;
1311                            }
1312                        } else if self.cfn_state.current_stack.is_some()
1313                            && self.cfn_state.detail_tab == CfnDetailTab::Resources
1314                        {
1315                            if self.cfn_state.resources_input_focus == InputFocus::Filter {
1316                                self.cfn_state.resources.filter.push(c);
1317                                self.cfn_state.resources.selected = 0;
1318                            }
1319                        } else if self.cfn_state.input_focus == InputFocus::Filter {
1320                            self.apply_filter_operation(|f| f.push(c));
1321                        }
1322                    } else if self.current_service == Service::EcrRepositories
1323                        && self.ecr_state.current_repository.is_none()
1324                    {
1325                        if self.ecr_state.input_focus == InputFocus::Filter {
1326                            self.apply_filter_operation(|f| f.push(c));
1327                        }
1328                    } else if self.current_service == Service::IamRoles
1329                        && self.iam_state.current_role.is_none()
1330                    {
1331                        if self.iam_state.role_input_focus == InputFocus::Filter {
1332                            self.apply_filter_operation(|f| f.push(c));
1333                        }
1334                    } else if self.view_mode == ViewMode::PolicyView {
1335                        if self.iam_state.policy_input_focus == InputFocus::Filter {
1336                            self.apply_filter_operation(|f| f.push(c));
1337                        }
1338                    } else if self.current_service == Service::LambdaFunctions
1339                        && self.lambda_state.current_version.is_some()
1340                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
1341                    {
1342                        if self.lambda_state.alias_input_focus == InputFocus::Filter {
1343                            self.apply_filter_operation(|f| f.push(c));
1344                        }
1345                    } else if self.current_service == Service::LambdaFunctions
1346                        && self.lambda_state.current_function.is_some()
1347                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1348                    {
1349                        if self.lambda_state.version_input_focus == InputFocus::Filter {
1350                            self.apply_filter_operation(|f| f.push(c));
1351                        }
1352                    } else if self.current_service == Service::LambdaFunctions
1353                        && self.lambda_state.current_function.is_some()
1354                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
1355                    {
1356                        if self.lambda_state.alias_input_focus == InputFocus::Filter {
1357                            self.apply_filter_operation(|f| f.push(c));
1358                        }
1359                    } else if self.current_service == Service::SqsQueues
1360                        && self.sqs_state.current_queue.is_some()
1361                        && (self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1362                            || self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1363                            || self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1364                            || self.sqs_state.detail_tab == SqsQueueDetailTab::Encryption
1365                            || self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions)
1366                    {
1367                        if self.sqs_state.input_focus == InputFocus::Filter {
1368                            self.apply_filter_operation(|f| f.push(c));
1369                        }
1370                    } else if self.current_service == Service::Ec2Instances
1371                        && self.ec2_state.current_instance.is_some()
1372                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
1373                    {
1374                        if self.ec2_state.input_focus == InputFocus::Filter {
1375                            self.ec2_state.tags.filter.push(c);
1376                            self.ec2_state.tags.selected = 0;
1377                        }
1378                    } else if self.current_service == Service::CloudWatchLogGroups {
1379                        if self.log_groups_state.input_focus == InputFocus::Filter {
1380                            self.apply_filter_operation(|f| f.push(c));
1381                        }
1382                    } else {
1383                        self.apply_filter_operation(|f| f.push(c));
1384                    }
1385                } else if self.mode == Mode::EventFilterInput {
1386                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1387                        self.log_groups_state.event_filter.push(c);
1388                    } else if c.is_ascii_digit() {
1389                        self.log_groups_state.relative_amount.push(c);
1390                    }
1391                } else if self.mode == Mode::Normal && c.is_ascii_digit() {
1392                    self.page_input.push(c);
1393                }
1394            }
1395            Action::FilterBackspace => {
1396                if self.mode == Mode::ServicePicker {
1397                    self.service_picker.filter.pop();
1398                    self.service_picker.selected = 0;
1399                } else if self.mode == Mode::TabPicker {
1400                    self.tab_filter.pop();
1401                    self.tab_picker_selected = 0;
1402                } else if self.mode == Mode::RegionPicker {
1403                    self.region_filter.pop();
1404                    self.region_picker_selected = 0;
1405                } else if self.mode == Mode::ProfilePicker {
1406                    self.profile_filter.pop();
1407                    self.profile_picker_selected = 0;
1408                } else if self.mode == Mode::SessionPicker {
1409                    self.session_filter.pop();
1410                    self.session_picker_selected = 0;
1411                } else if self.mode == Mode::InsightsInput {
1412                    match self.insights_state.insights.insights_focus {
1413                        InsightsFocus::Query => {
1414                            self.insights_state.insights.query_text.pop();
1415                        }
1416                        InsightsFocus::LogGroupSearch => {
1417                            self.insights_state.insights.log_group_search.pop();
1418                            // Update matches
1419                            if !self.insights_state.insights.log_group_search.is_empty() {
1420                                self.insights_state.insights.log_group_matches = self
1421                                    .log_groups_state
1422                                    .log_groups
1423                                    .items
1424                                    .iter()
1425                                    .filter(|g| {
1426                                        g.name.to_lowercase().contains(
1427                                            &self
1428                                                .insights_state
1429                                                .insights
1430                                                .log_group_search
1431                                                .to_lowercase(),
1432                                        )
1433                                    })
1434                                    .take(50)
1435                                    .map(|g| g.name.clone())
1436                                    .collect();
1437                                self.insights_state.insights.show_dropdown = true;
1438                            } else {
1439                                self.insights_state.insights.log_group_matches.clear();
1440                                self.insights_state.insights.show_dropdown = false;
1441                            }
1442                        }
1443                        _ => {}
1444                    }
1445                } else if self.mode == Mode::FilterInput {
1446                    // Only allow backspace when focus is on the input field
1447                    if self.current_service == Service::CloudFormationStacks {
1448                        if self.cfn_state.current_stack.is_some()
1449                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
1450                        {
1451                            if self.cfn_state.parameters_input_focus == InputFocus::Filter {
1452                                self.cfn_state.parameters.filter.pop();
1453                                self.cfn_state.parameters.selected = 0;
1454                            }
1455                        } else if self.cfn_state.current_stack.is_some()
1456                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
1457                        {
1458                            if self.cfn_state.outputs_input_focus == InputFocus::Filter {
1459                                self.cfn_state.outputs.filter.pop();
1460                                self.cfn_state.outputs.selected = 0;
1461                            }
1462                        } else if self.cfn_state.current_stack.is_some()
1463                            && self.cfn_state.detail_tab == CfnDetailTab::Resources
1464                        {
1465                            if self.cfn_state.resources_input_focus == InputFocus::Filter {
1466                                self.cfn_state.resources.filter.pop();
1467                                self.cfn_state.resources.selected = 0;
1468                            }
1469                        } else if self.cfn_state.input_focus == InputFocus::Filter {
1470                            self.apply_filter_operation(|f| {
1471                                f.pop();
1472                            });
1473                        }
1474                    } else if self.current_service == Service::Ec2Instances
1475                        && self.ec2_state.current_instance.is_some()
1476                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
1477                    {
1478                        if self.ec2_state.input_focus == InputFocus::Filter {
1479                            self.ec2_state.tags.filter.pop();
1480                            self.ec2_state.tags.selected = 0;
1481                        }
1482                    } else if self.current_service == Service::CloudWatchLogGroups {
1483                        if self.log_groups_state.input_focus == InputFocus::Filter {
1484                            self.apply_filter_operation(|f| {
1485                                f.pop();
1486                            });
1487                        }
1488                    } else {
1489                        self.apply_filter_operation(|f| {
1490                            f.pop();
1491                        });
1492                    }
1493                } else if self.mode == Mode::EventFilterInput {
1494                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1495                        self.log_groups_state.event_filter.pop();
1496                    } else {
1497                        self.log_groups_state.relative_amount.pop();
1498                    }
1499                }
1500            }
1501            Action::DeleteWord => {
1502                let text = if self.mode == Mode::ServicePicker {
1503                    &mut self.service_picker.filter
1504                } else if self.mode == Mode::InsightsInput {
1505                    use crate::app::InsightsFocus;
1506                    match self.insights_state.insights.insights_focus {
1507                        InsightsFocus::Query => &mut self.insights_state.insights.query_text,
1508                        InsightsFocus::LogGroupSearch => {
1509                            &mut self.insights_state.insights.log_group_search
1510                        }
1511                        _ => return,
1512                    }
1513                } else if self.mode == Mode::FilterInput {
1514                    if let Some(filter) = self.get_active_filter_mut() {
1515                        filter
1516                    } else {
1517                        return;
1518                    }
1519                } else if self.mode == Mode::EventFilterInput {
1520                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1521                        &mut self.log_groups_state.event_filter
1522                    } else {
1523                        &mut self.log_groups_state.relative_amount
1524                    }
1525                } else {
1526                    return;
1527                };
1528
1529                if text.is_empty() {
1530                    return;
1531                }
1532
1533                let mut chars: Vec<char> = text.chars().collect();
1534                while !chars.is_empty() && chars.last().is_some_and(|c| c.is_whitespace()) {
1535                    chars.pop();
1536                }
1537                while !chars.is_empty() && !chars.last().is_some_and(|c| c.is_whitespace()) {
1538                    chars.pop();
1539                }
1540                *text = chars.into_iter().collect();
1541            }
1542            Action::WordLeft => {
1543                // Not implemented - would need cursor position tracking
1544            }
1545            Action::WordRight => {
1546                // Not implemented - would need cursor position tracking
1547            }
1548            Action::OpenColumnSelector => {
1549                // Don't allow opening preferences in Template or GitSync tabs
1550                if self.current_service == Service::CloudFormationStacks
1551                    && self.cfn_state.current_stack.is_some()
1552                    && (self.cfn_state.detail_tab == CfnDetailTab::Template
1553                        || self.cfn_state.detail_tab == CfnDetailTab::GitSync)
1554                {
1555                    return;
1556                }
1557
1558                // Don't allow opening preferences for IAM user tabs without preferences
1559                if self.current_service == Service::IamUsers
1560                    && self.iam_state.current_user.is_some()
1561                    && self.iam_state.user_tab == UserTab::SecurityCredentials
1562                {
1563                    return;
1564                }
1565
1566                // Don't allow opening preferences for IAM role tabs without preferences
1567                if self.current_service == Service::IamRoles
1568                    && self.iam_state.current_role.is_some()
1569                    && (self.iam_state.role_tab == RoleTab::TrustRelationships
1570                        || self.iam_state.role_tab == RoleTab::RevokeSessions)
1571                {
1572                    return;
1573                }
1574
1575                // Don't allow opening preferences for certain SQS tabs
1576                if self.current_service == Service::SqsQueues
1577                    && self.sqs_state.current_queue.is_some()
1578                    && matches!(
1579                        self.sqs_state.detail_tab,
1580                        SqsQueueDetailTab::QueuePolicies
1581                            | SqsQueueDetailTab::Monitoring
1582                            | SqsQueueDetailTab::DeadLetterQueue
1583                            | SqsQueueDetailTab::Encryption
1584                            | SqsQueueDetailTab::DeadLetterQueueRedriveTasks
1585                    )
1586                {
1587                    return;
1588                }
1589
1590                // Don't allow opening preferences for EC2 instance detail tabs except Tags
1591                if self.current_service == Service::Ec2Instances
1592                    && self.ec2_state.table.expanded_item.is_some()
1593                    && self.ec2_state.detail_tab != Ec2DetailTab::Tags
1594                {
1595                    return;
1596                }
1597
1598                // If we have page input, apply it instead of opening column selector
1599                if !self.page_input.is_empty() {
1600                    if let Ok(page) = self.page_input.parse::<usize>() {
1601                        self.go_to_page(page);
1602                    }
1603                    self.page_input.clear();
1604                } else {
1605                    self.mode = Mode::ColumnSelector;
1606                    self.column_selector_index = 0;
1607                }
1608            }
1609            Action::ToggleColumn => {
1610                if self.current_service == Service::S3Buckets
1611                    && self.s3_state.current_bucket.is_none()
1612                {
1613                    let idx = self.column_selector_index;
1614                    if idx > 0 && idx <= self.s3_bucket_column_ids.len() {
1615                        if let Some(col) = self.s3_bucket_column_ids.get(idx - 1) {
1616                            if let Some(pos) = self
1617                                .s3_bucket_visible_column_ids
1618                                .iter()
1619                                .position(|c| c == col)
1620                            {
1621                                self.s3_bucket_visible_column_ids.remove(pos);
1622                            } else {
1623                                self.s3_bucket_visible_column_ids.push(*col);
1624                            }
1625                        }
1626                    } else if idx == self.s3_bucket_column_ids.len() + 3 {
1627                        self.s3_state.buckets.page_size = PageSize::Ten;
1628                    } else if idx == self.s3_bucket_column_ids.len() + 4 {
1629                        self.s3_state.buckets.page_size = PageSize::TwentyFive;
1630                    } else if idx == self.s3_bucket_column_ids.len() + 5 {
1631                        self.s3_state.buckets.page_size = PageSize::Fifty;
1632                    } else if idx == self.s3_bucket_column_ids.len() + 6 {
1633                        self.s3_state.buckets.page_size = PageSize::OneHundred;
1634                    }
1635                } else if self.current_service == Service::CloudWatchAlarms {
1636                    // Map flat list index to actual item
1637                    // 0: Columns header, 1-16: columns, 17: empty, 18: ViewAs header, 19-20: view options
1638                    // 21: empty, 22: PageSize header, 23-25: page sizes, 26: empty, 27: WrapLines header, 28: wrap option
1639                    let idx = self.column_selector_index;
1640                    if (1..=16).contains(&idx) {
1641                        // Column toggle
1642                        if let Some(col) = self.cw_alarm_column_ids.get(idx - 1) {
1643                            if let Some(pos) = self
1644                                .cw_alarm_visible_column_ids
1645                                .iter()
1646                                .position(|c| c == col)
1647                            {
1648                                self.cw_alarm_visible_column_ids.remove(pos);
1649                            } else {
1650                                self.cw_alarm_visible_column_ids.push(*col);
1651                            }
1652                        }
1653                    } else if idx == 19 {
1654                        self.alarms_state.view_as = AlarmViewMode::Table;
1655                    } else if idx == 20 {
1656                        self.alarms_state.view_as = AlarmViewMode::Cards;
1657                    } else if idx == 23 {
1658                        self.alarms_state.table.page_size = PageSize::Ten;
1659                    } else if idx == 24 {
1660                        self.alarms_state.table.page_size = PageSize::TwentyFive;
1661                    } else if idx == 25 {
1662                        self.alarms_state.table.page_size = PageSize::Fifty;
1663                    } else if idx == 26 {
1664                        self.alarms_state.table.page_size = PageSize::OneHundred;
1665                    } else if idx == 29 {
1666                        self.alarms_state.wrap_lines = !self.alarms_state.wrap_lines;
1667                    }
1668                } else if self.current_service == Service::EcrRepositories {
1669                    if self.ecr_state.current_repository.is_some() {
1670                        // Images view - columns + page size
1671                        let idx = self.column_selector_index;
1672                        if let Some(col) = self.ecr_image_column_ids.get(idx) {
1673                            if let Some(pos) = self
1674                                .ecr_image_visible_column_ids
1675                                .iter()
1676                                .position(|c| c == col)
1677                            {
1678                                self.ecr_image_visible_column_ids.remove(pos);
1679                            } else {
1680                                self.ecr_image_visible_column_ids.push(*col);
1681                            }
1682                        }
1683                    } else {
1684                        // Repositories view - columns + page size
1685                        let idx = self.column_selector_index;
1686                        if idx > 0 && idx <= self.ecr_repo_column_ids.len() {
1687                            if let Some(col) = self.ecr_repo_column_ids.get(idx - 1) {
1688                                if let Some(pos) = self
1689                                    .ecr_repo_visible_column_ids
1690                                    .iter()
1691                                    .position(|c| c == col)
1692                                {
1693                                    self.ecr_repo_visible_column_ids.remove(pos);
1694                                } else {
1695                                    self.ecr_repo_visible_column_ids.push(*col);
1696                                }
1697                            }
1698                        } else if idx == self.ecr_repo_column_ids.len() + 3 {
1699                            self.ecr_state.repositories.page_size = PageSize::Ten;
1700                        } else if idx == self.ecr_repo_column_ids.len() + 4 {
1701                            self.ecr_state.repositories.page_size = PageSize::TwentyFive;
1702                        } else if idx == self.ecr_repo_column_ids.len() + 5 {
1703                            self.ecr_state.repositories.page_size = PageSize::Fifty;
1704                        } else if idx == self.ecr_repo_column_ids.len() + 6 {
1705                            self.ecr_state.repositories.page_size = PageSize::OneHundred;
1706                        }
1707                    }
1708                } else if self.current_service == Service::Ec2Instances {
1709                    if self.ec2_state.current_instance.is_some()
1710                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
1711                    {
1712                        let idx = self.column_selector_index;
1713                        if idx > 0 && idx <= self.ec2_state.tag_column_ids.len() {
1714                            if let Some(col) = self.ec2_state.tag_column_ids.get(idx - 1) {
1715                                if let Some(pos) = self
1716                                    .ec2_state
1717                                    .tag_visible_column_ids
1718                                    .iter()
1719                                    .position(|c| c == col)
1720                                {
1721                                    self.ec2_state.tag_visible_column_ids.remove(pos);
1722                                } else {
1723                                    self.ec2_state.tag_visible_column_ids.push(col.clone());
1724                                }
1725                            }
1726                        } else if idx == self.ec2_state.tag_column_ids.len() + 3 {
1727                            self.ec2_state.tags.page_size = PageSize::Ten;
1728                        } else if idx == self.ec2_state.tag_column_ids.len() + 4 {
1729                            self.ec2_state.tags.page_size = PageSize::TwentyFive;
1730                        } else if idx == self.ec2_state.tag_column_ids.len() + 5 {
1731                            self.ec2_state.tags.page_size = PageSize::Fifty;
1732                        } else if idx == self.ec2_state.tag_column_ids.len() + 6 {
1733                            self.ec2_state.tags.page_size = PageSize::OneHundred;
1734                        }
1735                    } else {
1736                        let idx = self.column_selector_index;
1737                        if idx > 0 && idx <= self.ec2_column_ids.len() {
1738                            if let Some(col) = self.ec2_column_ids.get(idx - 1) {
1739                                if let Some(pos) =
1740                                    self.ec2_visible_column_ids.iter().position(|c| c == col)
1741                                {
1742                                    self.ec2_visible_column_ids.remove(pos);
1743                                } else {
1744                                    self.ec2_visible_column_ids.push(*col);
1745                                }
1746                            }
1747                        } else if idx == self.ec2_column_ids.len() + 3 {
1748                            self.ec2_state.table.page_size = PageSize::Ten;
1749                        } else if idx == self.ec2_column_ids.len() + 4 {
1750                            self.ec2_state.table.page_size = PageSize::TwentyFive;
1751                        } else if idx == self.ec2_column_ids.len() + 5 {
1752                            self.ec2_state.table.page_size = PageSize::Fifty;
1753                        } else if idx == self.ec2_column_ids.len() + 6 {
1754                            self.ec2_state.table.page_size = PageSize::OneHundred;
1755                        }
1756                    }
1757                } else if self.current_service == Service::SqsQueues {
1758                    if self.sqs_state.current_queue.is_some()
1759                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1760                    {
1761                        // Triggers tab - columns + page size
1762                        let idx = self.column_selector_index;
1763                        if idx > 0 && idx <= self.sqs_state.trigger_column_ids.len() {
1764                            if let Some(col) = self.sqs_state.trigger_column_ids.get(idx - 1) {
1765                                if let Some(pos) = self
1766                                    .sqs_state
1767                                    .trigger_visible_column_ids
1768                                    .iter()
1769                                    .position(|c| c == col)
1770                                {
1771                                    self.sqs_state.trigger_visible_column_ids.remove(pos);
1772                                } else {
1773                                    self.sqs_state.trigger_visible_column_ids.push(col.clone());
1774                                }
1775                            }
1776                        } else if idx == self.sqs_state.trigger_column_ids.len() + 3 {
1777                            self.sqs_state.triggers.page_size = PageSize::Ten;
1778                        } else if idx == self.sqs_state.trigger_column_ids.len() + 4 {
1779                            self.sqs_state.triggers.page_size = PageSize::TwentyFive;
1780                        } else if idx == self.sqs_state.trigger_column_ids.len() + 5 {
1781                            self.sqs_state.triggers.page_size = PageSize::Fifty;
1782                        } else if idx == self.sqs_state.trigger_column_ids.len() + 6 {
1783                            self.sqs_state.triggers.page_size = PageSize::OneHundred;
1784                        }
1785                    } else if self.sqs_state.current_queue.is_some()
1786                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1787                    {
1788                        // Pipes tab - columns + page size
1789                        let idx = self.column_selector_index;
1790                        if idx > 0 && idx <= self.sqs_state.pipe_column_ids.len() {
1791                            if let Some(col) = self.sqs_state.pipe_column_ids.get(idx - 1) {
1792                                if let Some(pos) = self
1793                                    .sqs_state
1794                                    .pipe_visible_column_ids
1795                                    .iter()
1796                                    .position(|c| c == col)
1797                                {
1798                                    self.sqs_state.pipe_visible_column_ids.remove(pos);
1799                                } else {
1800                                    self.sqs_state.pipe_visible_column_ids.push(col.clone());
1801                                }
1802                            }
1803                        } else if idx == self.sqs_state.pipe_column_ids.len() + 3 {
1804                            self.sqs_state.pipes.page_size = PageSize::Ten;
1805                        } else if idx == self.sqs_state.pipe_column_ids.len() + 4 {
1806                            self.sqs_state.pipes.page_size = PageSize::TwentyFive;
1807                        } else if idx == self.sqs_state.pipe_column_ids.len() + 5 {
1808                            self.sqs_state.pipes.page_size = PageSize::Fifty;
1809                        } else if idx == self.sqs_state.pipe_column_ids.len() + 6 {
1810                            self.sqs_state.pipes.page_size = PageSize::OneHundred;
1811                        }
1812                    } else if self.sqs_state.current_queue.is_some()
1813                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1814                    {
1815                        // Tags tab - columns + page size
1816                        let idx = self.column_selector_index;
1817                        if idx > 0 && idx <= self.sqs_state.tag_column_ids.len() {
1818                            if let Some(col) = self.sqs_state.tag_column_ids.get(idx - 1) {
1819                                if let Some(pos) = self
1820                                    .sqs_state
1821                                    .tag_visible_column_ids
1822                                    .iter()
1823                                    .position(|c| c == col)
1824                                {
1825                                    self.sqs_state.tag_visible_column_ids.remove(pos);
1826                                } else {
1827                                    self.sqs_state.tag_visible_column_ids.push(col.clone());
1828                                }
1829                            }
1830                        } else if idx == self.sqs_state.tag_column_ids.len() + 3 {
1831                            self.sqs_state.tags.page_size = PageSize::Ten;
1832                        } else if idx == self.sqs_state.tag_column_ids.len() + 4 {
1833                            self.sqs_state.tags.page_size = PageSize::TwentyFive;
1834                        } else if idx == self.sqs_state.tag_column_ids.len() + 5 {
1835                            self.sqs_state.tags.page_size = PageSize::Fifty;
1836                        } else if idx == self.sqs_state.tag_column_ids.len() + 6 {
1837                            self.sqs_state.tags.page_size = PageSize::OneHundred;
1838                        }
1839                    } else if self.sqs_state.current_queue.is_some()
1840                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
1841                    {
1842                        // Subscriptions tab - columns + page size
1843                        let idx = self.column_selector_index;
1844                        if idx > 0 && idx <= self.sqs_state.subscription_column_ids.len() {
1845                            if let Some(col) = self.sqs_state.subscription_column_ids.get(idx - 1) {
1846                                if let Some(pos) = self
1847                                    .sqs_state
1848                                    .subscription_visible_column_ids
1849                                    .iter()
1850                                    .position(|c| c == col)
1851                                {
1852                                    self.sqs_state.subscription_visible_column_ids.remove(pos);
1853                                } else {
1854                                    self.sqs_state
1855                                        .subscription_visible_column_ids
1856                                        .push(col.clone());
1857                                }
1858                            }
1859                        } else if idx == self.sqs_state.subscription_column_ids.len() + 3 {
1860                            self.sqs_state.subscriptions.page_size = PageSize::Ten;
1861                        } else if idx == self.sqs_state.subscription_column_ids.len() + 4 {
1862                            self.sqs_state.subscriptions.page_size = PageSize::TwentyFive;
1863                        } else if idx == self.sqs_state.subscription_column_ids.len() + 5 {
1864                            self.sqs_state.subscriptions.page_size = PageSize::Fifty;
1865                        } else if idx == self.sqs_state.subscription_column_ids.len() + 6 {
1866                            self.sqs_state.subscriptions.page_size = PageSize::OneHundred;
1867                        }
1868                    } else {
1869                        // Queues list - columns + page size
1870                        let idx = self.column_selector_index;
1871                        if let Some(col) = self.sqs_column_ids.get(idx) {
1872                            if let Some(pos) =
1873                                self.sqs_visible_column_ids.iter().position(|c| c == col)
1874                            {
1875                                self.sqs_visible_column_ids.remove(pos);
1876                            } else {
1877                                self.sqs_visible_column_ids.push(*col);
1878                            }
1879                        } else if idx == self.sqs_column_ids.len() + 2 {
1880                            self.sqs_state.queues.page_size = PageSize::Ten;
1881                        } else if idx == self.sqs_column_ids.len() + 3 {
1882                            self.sqs_state.queues.page_size = PageSize::TwentyFive;
1883                        } else if idx == self.sqs_column_ids.len() + 4 {
1884                            self.sqs_state.queues.page_size = PageSize::Fifty;
1885                        } else if idx == self.sqs_column_ids.len() + 5 {
1886                            self.sqs_state.queues.page_size = PageSize::OneHundred;
1887                        }
1888                    }
1889                } else if self.current_service == Service::LambdaFunctions {
1890                    let idx = self.column_selector_index;
1891                    // Check if we're in Versions tab
1892                    if self.lambda_state.current_function.is_some()
1893                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1894                    {
1895                        // Version columns
1896                        if idx > 0 && idx <= self.lambda_state.version_column_ids.len() {
1897                            if let Some(col) = self.lambda_state.version_column_ids.get(idx - 1) {
1898                                if let Some(pos) = self
1899                                    .lambda_state
1900                                    .version_visible_column_ids
1901                                    .iter()
1902                                    .position(|c| *c == *col)
1903                                {
1904                                    self.lambda_state.version_visible_column_ids.remove(pos);
1905                                } else {
1906                                    self.lambda_state
1907                                        .version_visible_column_ids
1908                                        .push(col.clone());
1909                                }
1910                            }
1911                        } else if idx == self.lambda_state.version_column_ids.len() + 3 {
1912                            self.lambda_state.version_table.page_size = PageSize::Ten;
1913                        } else if idx == self.lambda_state.version_column_ids.len() + 4 {
1914                            self.lambda_state.version_table.page_size = PageSize::TwentyFive;
1915                        } else if idx == self.lambda_state.version_column_ids.len() + 5 {
1916                            self.lambda_state.version_table.page_size = PageSize::Fifty;
1917                        } else if idx == self.lambda_state.version_column_ids.len() + 6 {
1918                            self.lambda_state.version_table.page_size = PageSize::OneHundred;
1919                        }
1920                    } else if (self.lambda_state.current_function.is_some()
1921                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases)
1922                        || (self.lambda_state.current_version.is_some()
1923                            && self.lambda_state.detail_tab == LambdaDetailTab::Configuration)
1924                    {
1925                        // Alias columns
1926                        if idx > 0 && idx <= self.lambda_state.alias_column_ids.len() {
1927                            if let Some(col) = self.lambda_state.alias_column_ids.get(idx - 1) {
1928                                if let Some(pos) = self
1929                                    .lambda_state
1930                                    .alias_visible_column_ids
1931                                    .iter()
1932                                    .position(|c| *c == *col)
1933                                {
1934                                    self.lambda_state.alias_visible_column_ids.remove(pos);
1935                                } else {
1936                                    self.lambda_state.alias_visible_column_ids.push(col.clone());
1937                                }
1938                            }
1939                        } else if idx == self.lambda_state.alias_column_ids.len() + 3 {
1940                            self.lambda_state.alias_table.page_size = PageSize::Ten;
1941                        } else if idx == self.lambda_state.alias_column_ids.len() + 4 {
1942                            self.lambda_state.alias_table.page_size = PageSize::TwentyFive;
1943                        } else if idx == self.lambda_state.alias_column_ids.len() + 5 {
1944                            self.lambda_state.alias_table.page_size = PageSize::Fifty;
1945                        } else if idx == self.lambda_state.alias_column_ids.len() + 6 {
1946                            self.lambda_state.alias_table.page_size = PageSize::OneHundred;
1947                        }
1948                    } else {
1949                        // Function columns
1950                        if idx > 0 && idx <= self.lambda_state.function_column_ids.len() {
1951                            if let Some(col) = self.lambda_state.function_column_ids.get(idx - 1) {
1952                                if let Some(pos) = self
1953                                    .lambda_state
1954                                    .function_visible_column_ids
1955                                    .iter()
1956                                    .position(|c| *c == *col)
1957                                {
1958                                    self.lambda_state.function_visible_column_ids.remove(pos);
1959                                } else {
1960                                    self.lambda_state.function_visible_column_ids.push(*col);
1961                                }
1962                            }
1963                        } else if idx == self.lambda_state.function_column_ids.len() + 3 {
1964                            self.lambda_state.table.page_size = PageSize::Ten;
1965                        } else if idx == self.lambda_state.function_column_ids.len() + 4 {
1966                            self.lambda_state.table.page_size = PageSize::TwentyFive;
1967                        } else if idx == self.lambda_state.function_column_ids.len() + 5 {
1968                            self.lambda_state.table.page_size = PageSize::Fifty;
1969                        } else if idx == self.lambda_state.function_column_ids.len() + 6 {
1970                            self.lambda_state.table.page_size = PageSize::OneHundred;
1971                        }
1972                    }
1973                } else if self.current_service == Service::LambdaApplications {
1974                    if self.lambda_application_state.current_application.is_some() {
1975                        // In detail view - handle resource or deployment columns
1976                        if self.lambda_application_state.detail_tab
1977                            == LambdaApplicationDetailTab::Overview
1978                        {
1979                            // Resources columns
1980                            let idx = self.column_selector_index;
1981                            if idx > 0 && idx <= self.lambda_resource_column_ids.len() {
1982                                if let Some(col) = self.lambda_resource_column_ids.get(idx - 1) {
1983                                    if let Some(pos) = self
1984                                        .lambda_resource_visible_column_ids
1985                                        .iter()
1986                                        .position(|c| c == col)
1987                                    {
1988                                        self.lambda_resource_visible_column_ids.remove(pos);
1989                                    } else {
1990                                        self.lambda_resource_visible_column_ids.push(*col);
1991                                    }
1992                                }
1993                            } else if idx == self.lambda_resource_column_ids.len() + 3 {
1994                                self.lambda_application_state.resources.page_size = PageSize::Ten;
1995                            } else if idx == self.lambda_resource_column_ids.len() + 4 {
1996                                self.lambda_application_state.resources.page_size =
1997                                    PageSize::TwentyFive;
1998                            } else if idx == self.lambda_resource_column_ids.len() + 5 {
1999                                self.lambda_application_state.resources.page_size = PageSize::Fifty;
2000                            }
2001                        } else {
2002                            // Deployments columns
2003                            let idx = self.column_selector_index;
2004                            if idx > 0 && idx <= self.lambda_deployment_column_ids.len() {
2005                                if let Some(col) = self.lambda_deployment_column_ids.get(idx - 1) {
2006                                    if let Some(pos) = self
2007                                        .lambda_deployment_visible_column_ids
2008                                        .iter()
2009                                        .position(|c| c == col)
2010                                    {
2011                                        self.lambda_deployment_visible_column_ids.remove(pos);
2012                                    } else {
2013                                        self.lambda_deployment_visible_column_ids.push(*col);
2014                                    }
2015                                }
2016                            } else if idx == self.lambda_deployment_column_ids.len() + 3 {
2017                                self.lambda_application_state.deployments.page_size = PageSize::Ten;
2018                            } else if idx == self.lambda_deployment_column_ids.len() + 4 {
2019                                self.lambda_application_state.deployments.page_size =
2020                                    PageSize::TwentyFive;
2021                            } else if idx == self.lambda_deployment_column_ids.len() + 5 {
2022                                self.lambda_application_state.deployments.page_size =
2023                                    PageSize::Fifty;
2024                            }
2025                        }
2026                    } else {
2027                        // In list view - handle application columns
2028                        let idx = self.column_selector_index;
2029                        if idx > 0 && idx <= self.lambda_application_column_ids.len() {
2030                            if let Some(col) = self.lambda_application_column_ids.get(idx - 1) {
2031                                if let Some(pos) = self
2032                                    .lambda_application_visible_column_ids
2033                                    .iter()
2034                                    .position(|c| *c == *col)
2035                                {
2036                                    self.lambda_application_visible_column_ids.remove(pos);
2037                                } else {
2038                                    self.lambda_application_visible_column_ids.push(*col);
2039                                }
2040                            }
2041                        } else if idx == self.lambda_application_column_ids.len() + 3 {
2042                            self.lambda_application_state.table.page_size = PageSize::Ten;
2043                        } else if idx == self.lambda_application_column_ids.len() + 4 {
2044                            self.lambda_application_state.table.page_size = PageSize::TwentyFive;
2045                        } else if idx == self.lambda_application_column_ids.len() + 5 {
2046                            self.lambda_application_state.table.page_size = PageSize::Fifty;
2047                        }
2048                    }
2049                } else if self.view_mode == ViewMode::Events {
2050                    if let Some(col) = self.cw_log_event_column_ids.get(self.column_selector_index)
2051                    {
2052                        if let Some(pos) = self
2053                            .cw_log_event_visible_column_ids
2054                            .iter()
2055                            .position(|c| c == col)
2056                        {
2057                            self.cw_log_event_visible_column_ids.remove(pos);
2058                        } else {
2059                            self.cw_log_event_visible_column_ids.push(*col);
2060                        }
2061                    }
2062                } else if self.view_mode == ViewMode::Detail {
2063                    let idx = self.column_selector_index;
2064                    if idx > 0 && idx <= self.cw_log_stream_column_ids.len() {
2065                        if let Some(col) = self.cw_log_stream_column_ids.get(idx - 1) {
2066                            if let Some(pos) = self
2067                                .cw_log_stream_visible_column_ids
2068                                .iter()
2069                                .position(|c| c == col)
2070                            {
2071                                self.cw_log_stream_visible_column_ids.remove(pos);
2072                            } else {
2073                                self.cw_log_stream_visible_column_ids.push(*col);
2074                            }
2075                        }
2076                    } else if idx == self.cw_log_stream_column_ids.len() + 3 {
2077                        self.log_groups_state.stream_page_size = 10;
2078                        self.log_groups_state.stream_current_page = 0;
2079                    } else if idx == self.cw_log_stream_column_ids.len() + 4 {
2080                        self.log_groups_state.stream_page_size = 25;
2081                        self.log_groups_state.stream_current_page = 0;
2082                    } else if idx == self.cw_log_stream_column_ids.len() + 5 {
2083                        self.log_groups_state.stream_page_size = 50;
2084                        self.log_groups_state.stream_current_page = 0;
2085                    } else if idx == self.cw_log_stream_column_ids.len() + 6 {
2086                        self.log_groups_state.stream_page_size = 100;
2087                        self.log_groups_state.stream_current_page = 0;
2088                    }
2089                } else if self.current_service == Service::CloudFormationStacks {
2090                    let idx = self.column_selector_index;
2091                    // Check if we're in StackInfo tab (tags)
2092                    if self.cfn_state.current_stack.is_some()
2093                        && self.cfn_state.detail_tab == CfnDetailTab::StackInfo
2094                    {
2095                        // Tags have 2 columns (Key, Value) - always visible, so only handle page size
2096                        if idx == 4 {
2097                            self.cfn_state.tags.page_size = PageSize::Ten;
2098                        } else if idx == 5 {
2099                            self.cfn_state.tags.page_size = PageSize::TwentyFive;
2100                        } else if idx == 6 {
2101                            self.cfn_state.tags.page_size = PageSize::Fifty;
2102                        } else if idx == 7 {
2103                            self.cfn_state.tags.page_size = PageSize::OneHundred;
2104                        }
2105                    } else if self.cfn_state.current_stack.is_some()
2106                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
2107                    {
2108                        if idx > 0 && idx <= self.cfn_parameter_column_ids.len() {
2109                            if let Some(col) = self.cfn_parameter_column_ids.get(idx - 1) {
2110                                if let Some(pos) = self
2111                                    .cfn_parameter_visible_column_ids
2112                                    .iter()
2113                                    .position(|c| c == col)
2114                                {
2115                                    self.cfn_parameter_visible_column_ids.remove(pos);
2116                                } else {
2117                                    self.cfn_parameter_visible_column_ids.push(col);
2118                                }
2119                            }
2120                        } else if idx == self.cfn_parameter_column_ids.len() + 3 {
2121                            self.cfn_state.parameters.page_size = PageSize::Ten;
2122                        } else if idx == self.cfn_parameter_column_ids.len() + 4 {
2123                            self.cfn_state.parameters.page_size = PageSize::TwentyFive;
2124                        } else if idx == self.cfn_parameter_column_ids.len() + 5 {
2125                            self.cfn_state.parameters.page_size = PageSize::Fifty;
2126                        } else if idx == self.cfn_parameter_column_ids.len() + 6 {
2127                            self.cfn_state.parameters.page_size = PageSize::OneHundred;
2128                        }
2129                    } else if self.cfn_state.current_stack.is_some()
2130                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
2131                    {
2132                        if idx > 0 && idx <= self.cfn_output_column_ids.len() {
2133                            if let Some(col) = self.cfn_output_column_ids.get(idx - 1) {
2134                                if let Some(pos) = self
2135                                    .cfn_output_visible_column_ids
2136                                    .iter()
2137                                    .position(|c| c == col)
2138                                {
2139                                    self.cfn_output_visible_column_ids.remove(pos);
2140                                } else {
2141                                    self.cfn_output_visible_column_ids.push(col);
2142                                }
2143                            }
2144                        } else if idx == self.cfn_output_column_ids.len() + 3 {
2145                            self.cfn_state.outputs.page_size = PageSize::Ten;
2146                        } else if idx == self.cfn_output_column_ids.len() + 4 {
2147                            self.cfn_state.outputs.page_size = PageSize::TwentyFive;
2148                        } else if idx == self.cfn_output_column_ids.len() + 5 {
2149                            self.cfn_state.outputs.page_size = PageSize::Fifty;
2150                        } else if idx == self.cfn_output_column_ids.len() + 6 {
2151                            self.cfn_state.outputs.page_size = PageSize::OneHundred;
2152                        }
2153                    } else if self.cfn_state.current_stack.is_some()
2154                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
2155                    {
2156                        if idx > 0 && idx <= self.cfn_resource_column_ids.len() {
2157                            if let Some(col) = self.cfn_resource_column_ids.get(idx - 1) {
2158                                if let Some(pos) = self
2159                                    .cfn_resource_visible_column_ids
2160                                    .iter()
2161                                    .position(|c| c == col)
2162                                {
2163                                    self.cfn_resource_visible_column_ids.remove(pos);
2164                                } else {
2165                                    self.cfn_resource_visible_column_ids.push(col);
2166                                }
2167                            }
2168                        } else if idx == self.cfn_resource_column_ids.len() + 3 {
2169                            self.cfn_state.resources.page_size = PageSize::Ten;
2170                        } else if idx == self.cfn_resource_column_ids.len() + 4 {
2171                            self.cfn_state.resources.page_size = PageSize::TwentyFive;
2172                        } else if idx == self.cfn_resource_column_ids.len() + 5 {
2173                            self.cfn_state.resources.page_size = PageSize::Fifty;
2174                        } else if idx == self.cfn_resource_column_ids.len() + 6 {
2175                            self.cfn_state.resources.page_size = PageSize::OneHundred;
2176                        }
2177                    } else if self.cfn_state.current_stack.is_none() {
2178                        // Stack list view
2179                        if idx > 0 && idx <= self.cfn_column_ids.len() {
2180                            if let Some(col) = self.cfn_column_ids.get(idx - 1) {
2181                                if let Some(pos) =
2182                                    self.cfn_visible_column_ids.iter().position(|c| c == col)
2183                                {
2184                                    self.cfn_visible_column_ids.remove(pos);
2185                                } else {
2186                                    self.cfn_visible_column_ids.push(*col);
2187                                }
2188                            }
2189                        } else if idx == self.cfn_column_ids.len() + 3 {
2190                            self.cfn_state.table.page_size = PageSize::Ten;
2191                        } else if idx == self.cfn_column_ids.len() + 4 {
2192                            self.cfn_state.table.page_size = PageSize::TwentyFive;
2193                        } else if idx == self.cfn_column_ids.len() + 5 {
2194                            self.cfn_state.table.page_size = PageSize::Fifty;
2195                        } else if idx == self.cfn_column_ids.len() + 6 {
2196                            self.cfn_state.table.page_size = PageSize::OneHundred;
2197                        }
2198                    }
2199                    // Template tab: no column toggle
2200                } else if self.current_service == Service::IamUsers {
2201                    let idx = self.column_selector_index;
2202                    if self.iam_state.current_user.is_some() {
2203                        match self.iam_state.user_tab {
2204                            UserTab::Permissions => {
2205                                // Policy columns
2206                                if idx > 0 && idx <= self.iam_policy_column_ids.len() {
2207                                    if let Some(col) = self.iam_policy_column_ids.get(idx - 1) {
2208                                        if let Some(pos) = self
2209                                            .iam_policy_visible_column_ids
2210                                            .iter()
2211                                            .position(|c| c == col)
2212                                        {
2213                                            self.iam_policy_visible_column_ids.remove(pos);
2214                                        } else {
2215                                            self.iam_policy_visible_column_ids.push(col.clone());
2216                                        }
2217                                    }
2218                                } else if idx == self.iam_policy_column_ids.len() + 3 {
2219                                    self.iam_state.policies.page_size = PageSize::Ten;
2220                                } else if idx == self.iam_policy_column_ids.len() + 4 {
2221                                    self.iam_state.policies.page_size = PageSize::TwentyFive;
2222                                } else if idx == self.iam_policy_column_ids.len() + 5 {
2223                                    self.iam_state.policies.page_size = PageSize::Fifty;
2224                                }
2225                            }
2226                            UserTab::Groups => {
2227                                toggle_iam_page_size_only(
2228                                    idx,
2229                                    5,
2230                                    &mut self.iam_state.user_group_memberships.page_size,
2231                                );
2232                            }
2233                            UserTab::Tags => {
2234                                toggle_iam_page_size_only(
2235                                    idx,
2236                                    5,
2237                                    &mut self.iam_state.user_tags.page_size,
2238                                );
2239                            }
2240                            UserTab::LastAccessed => {
2241                                toggle_iam_page_size_only(
2242                                    idx,
2243                                    6,
2244                                    &mut self.iam_state.last_accessed_services.page_size,
2245                                );
2246                            }
2247                            _ => {}
2248                        }
2249                    } else {
2250                        // User list columns
2251                        toggle_iam_preference_static(
2252                            idx,
2253                            &self.iam_user_column_ids,
2254                            &mut self.iam_user_visible_column_ids,
2255                            &mut self.iam_state.users.page_size,
2256                        );
2257                    }
2258                } else if self.current_service == Service::IamRoles {
2259                    let idx = self.column_selector_index;
2260                    if self.iam_state.current_role.is_some() {
2261                        match self.iam_state.role_tab {
2262                            RoleTab::Permissions => {
2263                                // Policy columns for role detail
2264                                toggle_iam_preference(
2265                                    idx,
2266                                    &self.iam_policy_column_ids,
2267                                    &mut self.iam_policy_visible_column_ids,
2268                                    &mut self.iam_state.policies.page_size,
2269                                );
2270                            }
2271                            RoleTab::LastAccessed => {
2272                                // 0: header, 1-3: columns, 4: empty, 5: PageSize header, 6-8: page sizes
2273                                toggle_iam_page_size_only(
2274                                    idx,
2275                                    6,
2276                                    &mut self.iam_state.last_accessed_services.page_size,
2277                                );
2278                            }
2279                            _ => {}
2280                        }
2281                    } else {
2282                        // Role list columns
2283                        toggle_iam_preference_static(
2284                            idx,
2285                            &self.iam_role_column_ids,
2286                            &mut self.iam_role_visible_column_ids,
2287                            &mut self.iam_state.roles.page_size,
2288                        );
2289                    }
2290                } else if self.current_service == Service::IamUserGroups {
2291                    toggle_iam_preference(
2292                        self.column_selector_index,
2293                        &self.iam_group_column_ids,
2294                        &mut self.iam_group_visible_column_ids,
2295                        &mut self.iam_state.groups.page_size,
2296                    );
2297                } else {
2298                    let idx = self.column_selector_index;
2299                    if idx > 0 && idx <= self.cw_log_group_column_ids.len() {
2300                        if let Some(col) = self.cw_log_group_column_ids.get(idx - 1) {
2301                            if let Some(pos) = self
2302                                .cw_log_group_visible_column_ids
2303                                .iter()
2304                                .position(|c| c == col)
2305                            {
2306                                self.cw_log_group_visible_column_ids.remove(pos);
2307                            } else {
2308                                self.cw_log_group_visible_column_ids.push(*col);
2309                            }
2310                        }
2311                    } else if idx == self.cw_log_group_column_ids.len() + 3 {
2312                        self.log_groups_state.log_groups.page_size = PageSize::Ten;
2313                    } else if idx == self.cw_log_group_column_ids.len() + 4 {
2314                        self.log_groups_state.log_groups.page_size = PageSize::TwentyFive;
2315                    } else if idx == self.cw_log_group_column_ids.len() + 5 {
2316                        self.log_groups_state.log_groups.page_size = PageSize::Fifty;
2317                    } else if idx == self.cw_log_group_column_ids.len() + 6 {
2318                        self.log_groups_state.log_groups.page_size = PageSize::OneHundred;
2319                    }
2320                }
2321            }
2322            Action::NextPreferences => {
2323                if self.current_service == Service::CloudWatchAlarms {
2324                    // Jump to next section: Columns(0), ViewAs(18), PageSize(22), WrapLines(28)
2325                    if self.column_selector_index < 18 {
2326                        self.column_selector_index = 18; // ViewAs header
2327                    } else if self.column_selector_index < 22 {
2328                        self.column_selector_index = 22; // PageSize header
2329                    } else if self.column_selector_index < 28 {
2330                        self.column_selector_index = 28; // WrapLines header
2331                    } else {
2332                        self.column_selector_index = 0; // Back to Columns header
2333                    }
2334                } else if self.current_service == Service::EcrRepositories
2335                    && self.ecr_state.current_repository.is_some()
2336                {
2337                    // Images view: Columns(0), PageSize(columns.len() + 2)
2338                    let page_size_idx = self.ecr_image_column_ids.len() + 2;
2339                    if self.column_selector_index < page_size_idx {
2340                        self.column_selector_index = page_size_idx;
2341                    } else {
2342                        self.column_selector_index = 0;
2343                    }
2344                } else if self.current_service == Service::LambdaFunctions {
2345                    // Lambda: Columns(0), PageSize(columns.len() + 2)
2346                    let page_size_idx = self.lambda_state.function_column_ids.len() + 2;
2347                    if self.column_selector_index < page_size_idx {
2348                        self.column_selector_index = page_size_idx;
2349                    } else {
2350                        self.column_selector_index = 0;
2351                    }
2352                } else if self.current_service == Service::LambdaApplications {
2353                    // Lambda Applications: Columns(0), PageSize(columns.len() + 2)
2354                    let page_size_idx = self.lambda_application_column_ids.len() + 2;
2355                    if self.column_selector_index < page_size_idx {
2356                        self.column_selector_index = page_size_idx;
2357                    } else {
2358                        self.column_selector_index = 0;
2359                    }
2360                } else if self.current_service == Service::CloudFormationStacks {
2361                    // CloudFormation: Columns(0), PageSize(columns.len() + 2)
2362                    let page_size_idx = self.cfn_column_ids.len() + 2;
2363                    if self.column_selector_index < page_size_idx {
2364                        self.column_selector_index = page_size_idx;
2365                    } else {
2366                        self.column_selector_index = 0;
2367                    }
2368                } else if self.current_service == Service::Ec2Instances {
2369                    let page_size_idx = self.ec2_column_ids.len() + 2;
2370                    if self.column_selector_index < page_size_idx {
2371                        self.column_selector_index = page_size_idx;
2372                    } else {
2373                        self.column_selector_index = 0;
2374                    }
2375                } else if self.current_service == Service::IamUsers {
2376                    if self.iam_state.current_user.is_some() {
2377                        match self.iam_state.user_tab {
2378                            UserTab::Permissions => {
2379                                // Columns(0), PageSize(columns.len() + 2)
2380                                let page_size_idx = self.iam_policy_column_ids.len() + 2;
2381                                if self.column_selector_index < page_size_idx {
2382                                    self.column_selector_index = page_size_idx;
2383                                } else {
2384                                    self.column_selector_index = 0;
2385                                }
2386                            }
2387                            UserTab::Groups | UserTab::Tags => {
2388                                // Columns(0), PageSize(4)
2389                                if self.column_selector_index < 4 {
2390                                    self.column_selector_index = 4;
2391                                } else {
2392                                    self.column_selector_index = 0;
2393                                }
2394                            }
2395                            UserTab::LastAccessed => {
2396                                // Columns(0), PageSize(5)
2397                                if self.column_selector_index < 5 {
2398                                    self.column_selector_index = 5;
2399                                } else {
2400                                    self.column_selector_index = 0;
2401                                }
2402                            }
2403                            _ => {}
2404                        }
2405                    } else {
2406                        // User columns: Columns(0), PageSize(columns.len() + 2)
2407                        let page_size_idx = self.iam_user_column_ids.len() + 2;
2408                        if self.column_selector_index < page_size_idx {
2409                            self.column_selector_index = page_size_idx;
2410                        } else {
2411                            self.column_selector_index = 0;
2412                        }
2413                    }
2414                } else if self.current_service == Service::IamRoles {
2415                    if self.iam_state.current_role.is_some() {
2416                        match self.iam_state.role_tab {
2417                            RoleTab::Permissions => {
2418                                // Columns(0), PageSize(columns.len() + 2)
2419                                let page_size_idx = self.iam_policy_column_ids.len() + 2;
2420                                if self.column_selector_index < page_size_idx {
2421                                    self.column_selector_index = page_size_idx;
2422                                } else {
2423                                    self.column_selector_index = 0;
2424                                }
2425                            }
2426                            RoleTab::Tags => {
2427                                // Columns(0), PageSize(4)
2428                                if self.column_selector_index < 4 {
2429                                    self.column_selector_index = 4;
2430                                } else {
2431                                    self.column_selector_index = 0;
2432                                }
2433                            }
2434                            RoleTab::LastAccessed => {
2435                                // Columns(0), PageSize(5)
2436                                if self.column_selector_index < 5 {
2437                                    self.column_selector_index = 5;
2438                                } else {
2439                                    self.column_selector_index = 0;
2440                                }
2441                            }
2442                            _ => {}
2443                        }
2444                    } else {
2445                        // Role columns: Columns(0), PageSize(columns.len() + 2)
2446                        let page_size_idx = self.iam_role_column_ids.len() + 2;
2447                        if self.column_selector_index < page_size_idx {
2448                            self.column_selector_index = page_size_idx;
2449                        } else {
2450                            self.column_selector_index = 0;
2451                        }
2452                    }
2453                } else if self.current_service == Service::IamUserGroups {
2454                    // Group columns: Columns(0), PageSize(columns.len() + 2)
2455                    let page_size_idx = self.iam_group_column_ids.len() + 2;
2456                    if self.column_selector_index < page_size_idx {
2457                        self.column_selector_index = page_size_idx;
2458                    } else {
2459                        self.column_selector_index = 0;
2460                    }
2461                } else if self.current_service == Service::SqsQueues
2462                    && self.sqs_state.current_queue.is_some()
2463                    && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
2464                {
2465                    // Triggers tab: Columns(0), PageSize(columns.len() + 2)
2466                    let page_size_idx = self.sqs_state.trigger_column_ids.len() + 2;
2467                    if self.column_selector_index < page_size_idx {
2468                        self.column_selector_index = page_size_idx;
2469                    } else {
2470                        self.column_selector_index = 0;
2471                    }
2472                } else if self.current_service == Service::SqsQueues
2473                    && self.sqs_state.current_queue.is_some()
2474                    && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
2475                {
2476                    // Pipes tab: Columns(0), PageSize(columns.len() + 2)
2477                    let page_size_idx = self.sqs_state.pipe_column_ids.len() + 2;
2478                    if self.column_selector_index < page_size_idx {
2479                        self.column_selector_index = page_size_idx;
2480                    } else {
2481                        self.column_selector_index = 0;
2482                    }
2483                } else if self.current_service == Service::SqsQueues
2484                    && self.sqs_state.current_queue.is_some()
2485                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
2486                {
2487                    // Tags tab: Columns(0), PageSize(columns.len() + 2)
2488                    let page_size_idx = self.sqs_state.tag_column_ids.len() + 2;
2489                    if self.column_selector_index < page_size_idx {
2490                        self.column_selector_index = page_size_idx;
2491                    } else {
2492                        self.column_selector_index = 0;
2493                    }
2494                } else if self.current_service == Service::SqsQueues
2495                    && self.sqs_state.current_queue.is_some()
2496                    && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
2497                {
2498                    // Subscriptions tab: Columns(0), PageSize(columns.len() + 2)
2499                    let page_size_idx = self.sqs_state.subscription_column_ids.len() + 2;
2500                    if self.column_selector_index < page_size_idx {
2501                        self.column_selector_index = page_size_idx;
2502                    } else {
2503                        self.column_selector_index = 0;
2504                    }
2505                } else if self.current_service == Service::S3Buckets
2506                    && self.s3_state.current_bucket.is_none()
2507                {
2508                    let page_size_idx = self.s3_bucket_column_ids.len() + 2;
2509                    if self.column_selector_index < page_size_idx {
2510                        self.column_selector_index = page_size_idx;
2511                    } else {
2512                        self.column_selector_index = 0;
2513                    }
2514                }
2515            }
2516            Action::PrevPreferences => {
2517                if self.current_service == Service::CloudWatchAlarms {
2518                    // Jump to prev section: Columns(0), ViewAs(18), PageSize(22), WrapLines(28)
2519                    if self.column_selector_index >= 28 {
2520                        self.column_selector_index = 22;
2521                    } else if self.column_selector_index >= 22 {
2522                        self.column_selector_index = 18;
2523                    } else if self.column_selector_index >= 18 {
2524                        self.column_selector_index = 0;
2525                    } else {
2526                        self.column_selector_index = 28;
2527                    }
2528                } else if self.current_service == Service::EcrRepositories
2529                    && self.ecr_state.current_repository.is_some()
2530                {
2531                    let page_size_idx = self.ecr_image_column_ids.len() + 2;
2532                    if self.column_selector_index >= page_size_idx {
2533                        self.column_selector_index = 0;
2534                    } else {
2535                        self.column_selector_index = page_size_idx;
2536                    }
2537                } else if self.current_service == Service::LambdaFunctions {
2538                    let page_size_idx = self.lambda_state.function_column_ids.len() + 2;
2539                    if self.column_selector_index >= page_size_idx {
2540                        self.column_selector_index = 0;
2541                    } else {
2542                        self.column_selector_index = page_size_idx;
2543                    }
2544                } else if self.current_service == Service::LambdaApplications {
2545                    let page_size_idx = self.lambda_application_column_ids.len() + 2;
2546                    if self.column_selector_index >= page_size_idx {
2547                        self.column_selector_index = 0;
2548                    } else {
2549                        self.column_selector_index = page_size_idx;
2550                    }
2551                } else if self.current_service == Service::CloudFormationStacks {
2552                    let page_size_idx = self.cfn_column_ids.len() + 2;
2553                    if self.column_selector_index >= page_size_idx {
2554                        self.column_selector_index = 0;
2555                    } else {
2556                        self.column_selector_index = page_size_idx;
2557                    }
2558                } else if self.current_service == Service::Ec2Instances {
2559                    let page_size_idx = self.ec2_column_ids.len() + 2;
2560                    if self.column_selector_index >= page_size_idx {
2561                        self.column_selector_index = 0;
2562                    } else {
2563                        self.column_selector_index = page_size_idx;
2564                    }
2565                } else if self.current_service == Service::IamUsers {
2566                    if self.iam_state.current_user.is_some() {
2567                        match self.iam_state.user_tab {
2568                            UserTab::Permissions => {
2569                                let page_size_idx = self.iam_policy_column_ids.len() + 2;
2570                                if self.column_selector_index >= page_size_idx {
2571                                    self.column_selector_index = 0;
2572                                } else {
2573                                    self.column_selector_index = page_size_idx;
2574                                }
2575                            }
2576                            UserTab::Groups | UserTab::Tags => {
2577                                if self.column_selector_index >= 4 {
2578                                    self.column_selector_index = 0;
2579                                } else {
2580                                    self.column_selector_index = 4;
2581                                }
2582                            }
2583                            UserTab::LastAccessed => {
2584                                if self.column_selector_index >= 5 {
2585                                    self.column_selector_index = 0;
2586                                } else {
2587                                    self.column_selector_index = 5;
2588                                }
2589                            }
2590                            _ => {}
2591                        }
2592                    } else {
2593                        let page_size_idx = self.iam_user_column_ids.len() + 2;
2594                        if self.column_selector_index >= page_size_idx {
2595                            self.column_selector_index = 0;
2596                        } else {
2597                            self.column_selector_index = page_size_idx;
2598                        }
2599                    }
2600                } else if self.current_service == Service::IamRoles {
2601                    if self.iam_state.current_role.is_some() {
2602                        match self.iam_state.role_tab {
2603                            RoleTab::Permissions => {
2604                                let page_size_idx = self.iam_policy_column_ids.len() + 2;
2605                                if self.column_selector_index >= page_size_idx {
2606                                    self.column_selector_index = 0;
2607                                } else {
2608                                    self.column_selector_index = page_size_idx;
2609                                }
2610                            }
2611                            RoleTab::Tags => {
2612                                if self.column_selector_index >= 4 {
2613                                    self.column_selector_index = 0;
2614                                } else {
2615                                    self.column_selector_index = 4;
2616                                }
2617                            }
2618                            RoleTab::LastAccessed => {
2619                                if self.column_selector_index >= 5 {
2620                                    self.column_selector_index = 0;
2621                                } else {
2622                                    self.column_selector_index = 5;
2623                                }
2624                            }
2625                            _ => {}
2626                        }
2627                    } else {
2628                        let page_size_idx = self.iam_role_column_ids.len() + 2;
2629                        if self.column_selector_index >= page_size_idx {
2630                            self.column_selector_index = 0;
2631                        } else {
2632                            self.column_selector_index = page_size_idx;
2633                        }
2634                    }
2635                } else if self.current_service == Service::IamUserGroups {
2636                    let page_size_idx = self.iam_group_column_ids.len() + 2;
2637                    if self.column_selector_index >= page_size_idx {
2638                        self.column_selector_index = 0;
2639                    } else {
2640                        self.column_selector_index = page_size_idx;
2641                    }
2642                } else if self.current_service == Service::SqsQueues
2643                    && self.sqs_state.current_queue.is_some()
2644                {
2645                    let page_size_idx = match self.sqs_state.detail_tab {
2646                        SqsQueueDetailTab::LambdaTriggers => {
2647                            self.sqs_state.trigger_column_ids.len() + 2
2648                        }
2649                        SqsQueueDetailTab::EventBridgePipes => {
2650                            self.sqs_state.pipe_column_ids.len() + 2
2651                        }
2652                        SqsQueueDetailTab::Tagging => self.sqs_state.tag_column_ids.len() + 2,
2653                        SqsQueueDetailTab::SnsSubscriptions => {
2654                            self.sqs_state.subscription_column_ids.len() + 2
2655                        }
2656                        _ => 0,
2657                    };
2658                    if page_size_idx > 0 {
2659                        if self.column_selector_index >= page_size_idx {
2660                            self.column_selector_index = 0;
2661                        } else {
2662                            self.column_selector_index = page_size_idx;
2663                        }
2664                    }
2665                } else if self.current_service == Service::S3Buckets
2666                    && self.s3_state.current_bucket.is_none()
2667                {
2668                    let page_size_idx = self.s3_bucket_column_ids.len() + 2;
2669                    if self.column_selector_index >= page_size_idx {
2670                        self.column_selector_index = 0;
2671                    } else {
2672                        self.column_selector_index = page_size_idx;
2673                    }
2674                }
2675            }
2676            Action::CloseColumnSelector => {
2677                self.mode = Mode::Normal;
2678                self.preference_section = Preferences::Columns;
2679            }
2680            Action::NextDetailTab => {
2681                if self.current_service == Service::SqsQueues
2682                    && self.sqs_state.current_queue.is_some()
2683                {
2684                    self.sqs_state.detail_tab = self.sqs_state.detail_tab.next();
2685                    if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
2686                        self.sqs_state.set_metrics_loading(true);
2687                        self.sqs_state.set_monitoring_scroll(0);
2688                        self.sqs_state.clear_metrics();
2689                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers {
2690                        self.sqs_state.triggers.loading = true;
2691                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes {
2692                        self.sqs_state.pipes.loading = true;
2693                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging {
2694                        self.sqs_state.tags.loading = true;
2695                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions {
2696                        self.sqs_state.subscriptions.loading = true;
2697                    }
2698                } else if self.current_service == Service::Ec2Instances
2699                    && self.ec2_state.current_instance.is_some()
2700                {
2701                    self.ec2_state.detail_tab = self.ec2_state.detail_tab.next();
2702                    if self.ec2_state.detail_tab == Ec2DetailTab::Tags {
2703                        self.ec2_state.tags.loading = true;
2704                    } else if self.ec2_state.detail_tab == Ec2DetailTab::Monitoring {
2705                        self.ec2_state.set_metrics_loading(true);
2706                        self.ec2_state.set_monitoring_scroll(0);
2707                        self.ec2_state.clear_metrics();
2708                    }
2709                } else if self.current_service == Service::LambdaApplications
2710                    && self.lambda_application_state.current_application.is_some()
2711                {
2712                    self.lambda_application_state.detail_tab =
2713                        self.lambda_application_state.detail_tab.next();
2714                } else if self.current_service == Service::IamRoles
2715                    && self.iam_state.current_role.is_some()
2716                {
2717                    self.iam_state.role_tab = self.iam_state.role_tab.next();
2718                    if self.iam_state.role_tab == RoleTab::Tags {
2719                        self.iam_state.tags.loading = true;
2720                    }
2721                } else if self.current_service == Service::IamUsers
2722                    && self.iam_state.current_user.is_some()
2723                {
2724                    self.iam_state.user_tab = self.iam_state.user_tab.next();
2725                    if self.iam_state.user_tab == UserTab::Tags {
2726                        self.iam_state.user_tags.loading = true;
2727                    }
2728                } else if self.current_service == Service::IamUserGroups
2729                    && self.iam_state.current_group.is_some()
2730                {
2731                    self.iam_state.group_tab = self.iam_state.group_tab.next();
2732                } else if self.view_mode == ViewMode::Detail {
2733                    self.log_groups_state.detail_tab = self.log_groups_state.detail_tab.next();
2734                } else if self.current_service == Service::S3Buckets {
2735                    if self.s3_state.current_bucket.is_some() {
2736                        self.s3_state.object_tab = self.s3_state.object_tab.next();
2737                    } else {
2738                        self.s3_state.bucket_type = match self.s3_state.bucket_type {
2739                            S3BucketType::GeneralPurpose => S3BucketType::Directory,
2740                            S3BucketType::Directory => S3BucketType::GeneralPurpose,
2741                        };
2742                        self.s3_state.buckets.reset();
2743                    }
2744                } else if self.current_service == Service::CloudWatchAlarms {
2745                    self.alarms_state.alarm_tab = match self.alarms_state.alarm_tab {
2746                        AlarmTab::AllAlarms => AlarmTab::InAlarm,
2747                        AlarmTab::InAlarm => AlarmTab::AllAlarms,
2748                    };
2749                    self.alarms_state.table.reset();
2750                } else if self.current_service == Service::EcrRepositories
2751                    && self.ecr_state.current_repository.is_none()
2752                {
2753                    self.ecr_state.tab = self.ecr_state.tab.next();
2754                    self.ecr_state.repositories.reset();
2755                    self.ecr_state.repositories.loading = true;
2756                } else if self.current_service == Service::LambdaFunctions
2757                    && self.lambda_state.current_function.is_some()
2758                {
2759                    if self.lambda_state.current_version.is_some() {
2760                        // Version view: use VersionDetailTab enum
2761                        self.lambda_state.version_detail_tab =
2762                            self.lambda_state.version_detail_tab.next();
2763                        self.lambda_state.detail_tab =
2764                            self.lambda_state.version_detail_tab.to_detail_tab();
2765                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2766                            self.lambda_state.set_metrics_loading(true);
2767                            self.lambda_state.set_monitoring_scroll(0);
2768                            self.lambda_state.clear_metrics();
2769                        }
2770                    } else {
2771                        self.lambda_state.detail_tab = self.lambda_state.detail_tab.next();
2772                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2773                            self.lambda_state.set_metrics_loading(true);
2774                            self.lambda_state.set_monitoring_scroll(0);
2775                            self.lambda_state.clear_metrics();
2776                        }
2777                    }
2778                } else if self.current_service == Service::CloudFormationStacks
2779                    && self.cfn_state.current_stack.is_some()
2780                {
2781                    self.cfn_state.detail_tab = self.cfn_state.detail_tab.next();
2782                }
2783            }
2784            Action::PrevDetailTab => {
2785                if self.current_service == Service::SqsQueues
2786                    && self.sqs_state.current_queue.is_some()
2787                {
2788                    self.sqs_state.detail_tab = self.sqs_state.detail_tab.prev();
2789                    if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
2790                        self.sqs_state.set_metrics_loading(true);
2791                        self.sqs_state.set_monitoring_scroll(0);
2792                        self.sqs_state.clear_metrics();
2793                    }
2794                } else if self.current_service == Service::Ec2Instances
2795                    && self.ec2_state.current_instance.is_some()
2796                {
2797                    self.ec2_state.detail_tab = self.ec2_state.detail_tab.prev();
2798                    if self.ec2_state.detail_tab == Ec2DetailTab::Tags {
2799                        self.ec2_state.tags.loading = true;
2800                    } else if self.ec2_state.detail_tab == Ec2DetailTab::Monitoring {
2801                        self.ec2_state.set_metrics_loading(true);
2802                        self.ec2_state.set_monitoring_scroll(0);
2803                        self.ec2_state.clear_metrics();
2804                    }
2805                } else if self.current_service == Service::LambdaApplications
2806                    && self.lambda_application_state.current_application.is_some()
2807                {
2808                    self.lambda_application_state.detail_tab =
2809                        self.lambda_application_state.detail_tab.prev();
2810                } else if self.current_service == Service::IamRoles
2811                    && self.iam_state.current_role.is_some()
2812                {
2813                    self.iam_state.role_tab = self.iam_state.role_tab.prev();
2814                } else if self.current_service == Service::IamUsers
2815                    && self.iam_state.current_user.is_some()
2816                {
2817                    self.iam_state.user_tab = self.iam_state.user_tab.prev();
2818                } else if self.current_service == Service::IamUserGroups
2819                    && self.iam_state.current_group.is_some()
2820                {
2821                    self.iam_state.group_tab = self.iam_state.group_tab.prev();
2822                } else if self.view_mode == ViewMode::Detail {
2823                    self.log_groups_state.detail_tab = self.log_groups_state.detail_tab.prev();
2824                } else if self.current_service == Service::S3Buckets {
2825                    if self.s3_state.current_bucket.is_some() {
2826                        self.s3_state.object_tab = self.s3_state.object_tab.prev();
2827                    }
2828                } else if self.current_service == Service::CloudWatchAlarms {
2829                    self.alarms_state.alarm_tab = match self.alarms_state.alarm_tab {
2830                        AlarmTab::AllAlarms => AlarmTab::InAlarm,
2831                        AlarmTab::InAlarm => AlarmTab::AllAlarms,
2832                    };
2833                } else if self.current_service == Service::EcrRepositories
2834                    && self.ecr_state.current_repository.is_none()
2835                {
2836                    self.ecr_state.tab = self.ecr_state.tab.prev();
2837                    self.ecr_state.repositories.reset();
2838                    self.ecr_state.repositories.loading = true;
2839                } else if self.current_service == Service::LambdaFunctions
2840                    && self.lambda_state.current_function.is_some()
2841                {
2842                    if self.lambda_state.current_version.is_some() {
2843                        // Version view: use VersionDetailTab enum
2844                        self.lambda_state.version_detail_tab =
2845                            self.lambda_state.version_detail_tab.prev();
2846                        self.lambda_state.detail_tab =
2847                            self.lambda_state.version_detail_tab.to_detail_tab();
2848                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2849                            self.lambda_state.set_metrics_loading(true);
2850                            self.lambda_state.set_monitoring_scroll(0);
2851                            self.lambda_state.clear_metrics();
2852                        }
2853                    } else {
2854                        self.lambda_state.detail_tab = self.lambda_state.detail_tab.prev();
2855                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2856                            self.lambda_state.set_metrics_loading(true);
2857                            self.lambda_state.set_monitoring_scroll(0);
2858                            self.lambda_state.clear_metrics();
2859                        }
2860                    }
2861                } else if self.current_service == Service::CloudFormationStacks
2862                    && self.cfn_state.current_stack.is_some()
2863                {
2864                    self.cfn_state.detail_tab = self.cfn_state.detail_tab.prev();
2865                }
2866            }
2867            Action::StartFilter => {
2868                // Don't allow filter mode when no service is selected and no tabs open
2869                if !self.service_selected && self.tabs.is_empty() {
2870                    return;
2871                }
2872
2873                if self.current_service == Service::CloudWatchInsights {
2874                    self.mode = Mode::InsightsInput;
2875                } else if self.current_service == Service::CloudWatchAlarms {
2876                    self.mode = Mode::FilterInput;
2877                } else if self.current_service == Service::S3Buckets {
2878                    self.mode = Mode::FilterInput;
2879                    self.log_groups_state.filter_mode = true;
2880                } else if self.current_service == Service::EcrRepositories
2881                    || self.current_service == Service::IamUsers
2882                    || self.current_service == Service::IamUserGroups
2883                {
2884                    self.mode = Mode::FilterInput;
2885                    if self.current_service == Service::EcrRepositories
2886                        && self.ecr_state.current_repository.is_none()
2887                    {
2888                        self.ecr_state.input_focus = InputFocus::Filter;
2889                    }
2890                } else if self.current_service == Service::LambdaFunctions {
2891                    self.mode = Mode::FilterInput;
2892                    if self.lambda_state.current_version.is_some()
2893                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
2894                    {
2895                        self.lambda_state.alias_input_focus = InputFocus::Filter;
2896                    } else if self.lambda_state.current_function.is_some()
2897                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
2898                    {
2899                        self.lambda_state.version_input_focus = InputFocus::Filter;
2900                    } else if self.lambda_state.current_function.is_none() {
2901                        self.lambda_state.input_focus = InputFocus::Filter;
2902                    }
2903                } else if self.current_service == Service::LambdaApplications {
2904                    self.mode = Mode::FilterInput;
2905                    if self.lambda_application_state.current_application.is_some() {
2906                        // In detail view - check which tab
2907                        if self.lambda_application_state.detail_tab
2908                            == LambdaApplicationDetailTab::Overview
2909                        {
2910                            self.lambda_application_state.resource_input_focus = InputFocus::Filter;
2911                        } else {
2912                            self.lambda_application_state.deployment_input_focus =
2913                                InputFocus::Filter;
2914                        }
2915                    } else {
2916                        self.lambda_application_state.input_focus = InputFocus::Filter;
2917                    }
2918                } else if self.current_service == Service::IamRoles {
2919                    self.mode = Mode::FilterInput;
2920                } else if self.current_service == Service::CloudFormationStacks {
2921                    self.mode = Mode::FilterInput;
2922                    if self.cfn_state.current_stack.is_some()
2923                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
2924                    {
2925                        self.cfn_state.parameters_input_focus = InputFocus::Filter;
2926                    } else if self.cfn_state.current_stack.is_some()
2927                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
2928                    {
2929                        self.cfn_state.outputs_input_focus = InputFocus::Filter;
2930                    } else {
2931                        self.cfn_state.input_focus = InputFocus::Filter;
2932                    }
2933                } else if self.current_service == Service::SqsQueues {
2934                    self.mode = Mode::FilterInput;
2935                    self.sqs_state.input_focus = InputFocus::Filter;
2936                } else if self.view_mode == ViewMode::List
2937                    || (self.view_mode == ViewMode::Detail
2938                        && self.log_groups_state.detail_tab == DetailTab::LogStreams)
2939                {
2940                    self.mode = Mode::FilterInput;
2941                    self.log_groups_state.filter_mode = true;
2942                    self.log_groups_state.input_focus = InputFocus::Filter;
2943                } else if self.view_mode == ViewMode::Events {
2944                    self.mode = Mode::EventFilterInput;
2945                }
2946            }
2947            Action::StartEventFilter => {
2948                if self.current_service == Service::CloudWatchInsights {
2949                    self.mode = Mode::InsightsInput;
2950                } else if self.view_mode == ViewMode::List {
2951                    self.mode = Mode::FilterInput;
2952                    self.log_groups_state.filter_mode = true;
2953                    self.log_groups_state.input_focus = InputFocus::Filter;
2954                } else if self.view_mode == ViewMode::Events {
2955                    self.mode = Mode::EventFilterInput;
2956                } else if self.view_mode == ViewMode::Detail
2957                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
2958                {
2959                    self.mode = Mode::FilterInput;
2960                    self.log_groups_state.filter_mode = true;
2961                    self.log_groups_state.input_focus = InputFocus::Filter;
2962                }
2963            }
2964            Action::NextFilterFocus => {
2965                if self.mode == Mode::FilterInput && self.current_service == Service::S3Buckets {
2966                    const S3_FILTER_CONTROLS: [InputFocus; 2] =
2967                        [InputFocus::Filter, InputFocus::Pagination];
2968                    self.s3_state.input_focus = self.s3_state.input_focus.next(&S3_FILTER_CONTROLS);
2969                } else if self.mode == Mode::FilterInput
2970                    && self.current_service == Service::Ec2Instances
2971                {
2972                    self.ec2_state.input_focus =
2973                        self.ec2_state.input_focus.next(&ec2::FILTER_CONTROLS);
2974                } else if self.mode == Mode::FilterInput
2975                    && self.current_service == Service::LambdaApplications
2976                {
2977                    use crate::ui::lambda::FILTER_CONTROLS;
2978                    if self.lambda_application_state.current_application.is_some() {
2979                        if self.lambda_application_state.detail_tab
2980                            == LambdaApplicationDetailTab::Deployments
2981                        {
2982                            self.lambda_application_state.deployment_input_focus = self
2983                                .lambda_application_state
2984                                .deployment_input_focus
2985                                .next(&FILTER_CONTROLS);
2986                        } else {
2987                            self.lambda_application_state.resource_input_focus = self
2988                                .lambda_application_state
2989                                .resource_input_focus
2990                                .next(&FILTER_CONTROLS);
2991                        }
2992                    } else {
2993                        self.lambda_application_state.input_focus = self
2994                            .lambda_application_state
2995                            .input_focus
2996                            .next(&FILTER_CONTROLS);
2997                    }
2998                } else if self.mode == Mode::FilterInput
2999                    && self.current_service == Service::IamRoles
3000                    && self.iam_state.current_role.is_some()
3001                {
3002                    use crate::ui::iam::POLICY_FILTER_CONTROLS;
3003                    self.iam_state.policy_input_focus = self
3004                        .iam_state
3005                        .policy_input_focus
3006                        .next(&POLICY_FILTER_CONTROLS);
3007                } else if self.mode == Mode::FilterInput
3008                    && self.current_service == Service::IamRoles
3009                    && self.iam_state.current_role.is_none()
3010                {
3011                    use crate::ui::iam::ROLE_FILTER_CONTROLS;
3012                    self.iam_state.role_input_focus =
3013                        self.iam_state.role_input_focus.next(&ROLE_FILTER_CONTROLS);
3014                } else if self.mode == Mode::FilterInput
3015                    && self.current_service == Service::IamUsers
3016                    && self.iam_state.current_user.is_some()
3017                {
3018                    use crate::ui::iam::{
3019                        POLICY_FILTER_CONTROLS, USER_LAST_ACCESSED_FILTER_CONTROLS,
3020                        USER_SIMPLE_FILTER_CONTROLS,
3021                    };
3022                    if self.iam_state.user_tab == UserTab::Permissions {
3023                        self.iam_state.policy_input_focus = self
3024                            .iam_state
3025                            .policy_input_focus
3026                            .next(&POLICY_FILTER_CONTROLS);
3027                    } else if self.iam_state.user_tab == UserTab::LastAccessed {
3028                        self.iam_state.last_accessed_input_focus = self
3029                            .iam_state
3030                            .last_accessed_input_focus
3031                            .next(&USER_LAST_ACCESSED_FILTER_CONTROLS);
3032                    } else {
3033                        self.iam_state.user_input_focus = self
3034                            .iam_state
3035                            .user_input_focus
3036                            .next(&USER_SIMPLE_FILTER_CONTROLS);
3037                    }
3038                } else if self.mode == Mode::FilterInput
3039                    && self.current_service == Service::IamUserGroups
3040                {
3041                    use crate::ui::iam::GROUP_FILTER_CONTROLS;
3042                    self.iam_state.group_input_focus = self
3043                        .iam_state
3044                        .group_input_focus
3045                        .next(&GROUP_FILTER_CONTROLS);
3046                } else if self.mode == Mode::InsightsInput {
3047                    use crate::app::InsightsFocus;
3048                    self.insights_state.insights.insights_focus =
3049                        match self.insights_state.insights.insights_focus {
3050                            InsightsFocus::QueryLanguage => InsightsFocus::DatePicker,
3051                            InsightsFocus::DatePicker => InsightsFocus::LogGroupSearch,
3052                            InsightsFocus::LogGroupSearch => InsightsFocus::Query,
3053                            InsightsFocus::Query => InsightsFocus::QueryLanguage,
3054                        };
3055                } else if self.mode == Mode::FilterInput
3056                    && self.current_service == Service::CloudFormationStacks
3057                {
3058                    if self.cfn_state.current_stack.is_some()
3059                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
3060                    {
3061                        self.cfn_state.parameters_input_focus = self
3062                            .cfn_state
3063                            .parameters_input_focus
3064                            .next(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
3065                    } else if self.cfn_state.current_stack.is_some()
3066                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
3067                    {
3068                        self.cfn_state.outputs_input_focus = self
3069                            .cfn_state
3070                            .outputs_input_focus
3071                            .next(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
3072                    } else if self.cfn_state.current_stack.is_some()
3073                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
3074                    {
3075                        self.cfn_state.resources_input_focus = self
3076                            .cfn_state
3077                            .resources_input_focus
3078                            .next(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
3079                    } else {
3080                        self.cfn_state.input_focus = self
3081                            .cfn_state
3082                            .input_focus
3083                            .next(&CfnStateConstants::FILTER_CONTROLS);
3084                    }
3085                } else if self.mode == Mode::FilterInput
3086                    && self.current_service == Service::SqsQueues
3087                {
3088                    if self.sqs_state.current_queue.is_some()
3089                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
3090                    {
3091                        use crate::ui::sqs::SUBSCRIPTION_FILTER_CONTROLS;
3092                        self.sqs_state.input_focus = self
3093                            .sqs_state
3094                            .input_focus
3095                            .next(SUBSCRIPTION_FILTER_CONTROLS);
3096                    } else {
3097                        use crate::ui::sqs::FILTER_CONTROLS;
3098                        self.sqs_state.input_focus =
3099                            self.sqs_state.input_focus.next(FILTER_CONTROLS);
3100                    }
3101                } else if self.mode == Mode::FilterInput
3102                    && self.current_service == Service::CloudWatchLogGroups
3103                {
3104                    use crate::ui::cw::logs::FILTER_CONTROLS;
3105                    self.log_groups_state.input_focus =
3106                        self.log_groups_state.input_focus.next(&FILTER_CONTROLS);
3107                } else if self.mode == Mode::EventFilterInput {
3108                    self.log_groups_state.event_input_focus =
3109                        self.log_groups_state.event_input_focus.next();
3110                } else if self.mode == Mode::FilterInput
3111                    && self.current_service == Service::CloudWatchAlarms
3112                {
3113                    use crate::ui::cw::alarms::FILTER_CONTROLS;
3114                    self.alarms_state.input_focus =
3115                        self.alarms_state.input_focus.next(&FILTER_CONTROLS);
3116                } else if self.mode == Mode::FilterInput
3117                    && self.current_service == Service::EcrRepositories
3118                    && self.ecr_state.current_repository.is_none()
3119                {
3120                    use crate::ui::ecr::FILTER_CONTROLS;
3121                    self.ecr_state.input_focus = self.ecr_state.input_focus.next(&FILTER_CONTROLS);
3122                } else if self.mode == Mode::FilterInput
3123                    && self.current_service == Service::LambdaFunctions
3124                {
3125                    use crate::ui::lambda::FILTER_CONTROLS;
3126                    if self.lambda_state.current_version.is_some()
3127                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
3128                    {
3129                        self.lambda_state.alias_input_focus =
3130                            self.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
3131                    } else if self.lambda_state.current_function.is_some()
3132                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
3133                    {
3134                        self.lambda_state.version_input_focus =
3135                            self.lambda_state.version_input_focus.next(&FILTER_CONTROLS);
3136                    } else if self.lambda_state.current_function.is_some()
3137                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
3138                    {
3139                        self.lambda_state.alias_input_focus =
3140                            self.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
3141                    } else if self.lambda_state.current_function.is_none() {
3142                        self.lambda_state.input_focus =
3143                            self.lambda_state.input_focus.next(&FILTER_CONTROLS);
3144                    }
3145                }
3146            }
3147            Action::PrevFilterFocus => {
3148                if self.mode == Mode::FilterInput && self.current_service == Service::Ec2Instances {
3149                    self.ec2_state.input_focus =
3150                        self.ec2_state.input_focus.prev(&ec2::FILTER_CONTROLS);
3151                } else if self.mode == Mode::FilterInput
3152                    && self.current_service == Service::LambdaApplications
3153                {
3154                    use crate::ui::lambda::FILTER_CONTROLS;
3155                    if self.lambda_application_state.current_application.is_some() {
3156                        if self.lambda_application_state.detail_tab
3157                            == LambdaApplicationDetailTab::Deployments
3158                        {
3159                            self.lambda_application_state.deployment_input_focus = self
3160                                .lambda_application_state
3161                                .deployment_input_focus
3162                                .prev(&FILTER_CONTROLS);
3163                        } else {
3164                            self.lambda_application_state.resource_input_focus = self
3165                                .lambda_application_state
3166                                .resource_input_focus
3167                                .prev(&FILTER_CONTROLS);
3168                        }
3169                    } else {
3170                        self.lambda_application_state.input_focus = self
3171                            .lambda_application_state
3172                            .input_focus
3173                            .prev(&FILTER_CONTROLS);
3174                    }
3175                } else if self.mode == Mode::FilterInput
3176                    && self.current_service == Service::CloudFormationStacks
3177                {
3178                    if self.cfn_state.current_stack.is_some()
3179                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
3180                    {
3181                        self.cfn_state.parameters_input_focus = self
3182                            .cfn_state
3183                            .parameters_input_focus
3184                            .prev(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
3185                    } else if self.cfn_state.current_stack.is_some()
3186                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
3187                    {
3188                        self.cfn_state.outputs_input_focus = self
3189                            .cfn_state
3190                            .outputs_input_focus
3191                            .prev(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
3192                    } else if self.cfn_state.current_stack.is_some()
3193                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
3194                    {
3195                        self.cfn_state.resources_input_focus = self
3196                            .cfn_state
3197                            .resources_input_focus
3198                            .prev(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
3199                    } else {
3200                        self.cfn_state.input_focus = self
3201                            .cfn_state
3202                            .input_focus
3203                            .prev(&CfnStateConstants::FILTER_CONTROLS);
3204                    }
3205                } else if self.mode == Mode::FilterInput
3206                    && self.current_service == Service::SqsQueues
3207                {
3208                    if self.sqs_state.current_queue.is_some()
3209                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
3210                    {
3211                        use crate::ui::sqs::SUBSCRIPTION_FILTER_CONTROLS;
3212                        self.sqs_state.input_focus = self
3213                            .sqs_state
3214                            .input_focus
3215                            .prev(SUBSCRIPTION_FILTER_CONTROLS);
3216                    } else {
3217                        use crate::ui::sqs::FILTER_CONTROLS;
3218                        self.sqs_state.input_focus =
3219                            self.sqs_state.input_focus.prev(FILTER_CONTROLS);
3220                    }
3221                } else if self.mode == Mode::FilterInput
3222                    && self.current_service == Service::IamRoles
3223                    && self.iam_state.current_role.is_none()
3224                {
3225                    use crate::ui::iam::ROLE_FILTER_CONTROLS;
3226                    self.iam_state.role_input_focus =
3227                        self.iam_state.role_input_focus.prev(&ROLE_FILTER_CONTROLS);
3228                } else if self.mode == Mode::FilterInput
3229                    && self.current_service == Service::IamUsers
3230                    && self.iam_state.current_user.is_some()
3231                {
3232                    use crate::ui::iam::{
3233                        POLICY_FILTER_CONTROLS, USER_LAST_ACCESSED_FILTER_CONTROLS,
3234                        USER_SIMPLE_FILTER_CONTROLS,
3235                    };
3236                    if self.iam_state.user_tab == UserTab::Permissions {
3237                        self.iam_state.policy_input_focus = self
3238                            .iam_state
3239                            .policy_input_focus
3240                            .prev(&POLICY_FILTER_CONTROLS);
3241                    } else if self.iam_state.user_tab == UserTab::LastAccessed {
3242                        self.iam_state.last_accessed_input_focus = self
3243                            .iam_state
3244                            .last_accessed_input_focus
3245                            .prev(&USER_LAST_ACCESSED_FILTER_CONTROLS);
3246                    } else {
3247                        self.iam_state.user_input_focus = self
3248                            .iam_state
3249                            .user_input_focus
3250                            .prev(&USER_SIMPLE_FILTER_CONTROLS);
3251                    }
3252                } else if self.mode == Mode::FilterInput
3253                    && self.current_service == Service::IamUserGroups
3254                {
3255                    use crate::ui::iam::GROUP_FILTER_CONTROLS;
3256                    self.iam_state.group_input_focus = self
3257                        .iam_state
3258                        .group_input_focus
3259                        .prev(&GROUP_FILTER_CONTROLS);
3260                } else if self.mode == Mode::FilterInput
3261                    && self.current_service == Service::CloudWatchLogGroups
3262                {
3263                    use crate::ui::cw::logs::FILTER_CONTROLS;
3264                    self.log_groups_state.input_focus =
3265                        self.log_groups_state.input_focus.prev(&FILTER_CONTROLS);
3266                } else if self.mode == Mode::EventFilterInput {
3267                    self.log_groups_state.event_input_focus =
3268                        self.log_groups_state.event_input_focus.prev();
3269                } else if self.mode == Mode::FilterInput
3270                    && self.current_service == Service::IamRoles
3271                    && self.iam_state.current_role.is_some()
3272                {
3273                    use crate::ui::iam::POLICY_FILTER_CONTROLS;
3274                    self.iam_state.policy_input_focus = self
3275                        .iam_state
3276                        .policy_input_focus
3277                        .prev(&POLICY_FILTER_CONTROLS);
3278                } else if self.mode == Mode::FilterInput
3279                    && self.current_service == Service::CloudWatchAlarms
3280                {
3281                    use crate::ui::cw::alarms::FILTER_CONTROLS;
3282                    self.alarms_state.input_focus =
3283                        self.alarms_state.input_focus.prev(&FILTER_CONTROLS);
3284                } else if self.mode == Mode::FilterInput
3285                    && self.current_service == Service::EcrRepositories
3286                    && self.ecr_state.current_repository.is_none()
3287                {
3288                    use crate::ui::ecr::FILTER_CONTROLS;
3289                    self.ecr_state.input_focus = self.ecr_state.input_focus.prev(&FILTER_CONTROLS);
3290                } else if self.mode == Mode::FilterInput
3291                    && self.current_service == Service::LambdaFunctions
3292                {
3293                    use crate::ui::lambda::FILTER_CONTROLS;
3294                    if self.lambda_state.current_version.is_some()
3295                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
3296                    {
3297                        self.lambda_state.alias_input_focus =
3298                            self.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
3299                    } else if self.lambda_state.current_function.is_some()
3300                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
3301                    {
3302                        self.lambda_state.version_input_focus =
3303                            self.lambda_state.version_input_focus.prev(&FILTER_CONTROLS);
3304                    } else if self.lambda_state.current_function.is_some()
3305                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
3306                    {
3307                        self.lambda_state.alias_input_focus =
3308                            self.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
3309                    } else if self.lambda_state.current_function.is_none() {
3310                        self.lambda_state.input_focus =
3311                            self.lambda_state.input_focus.prev(&FILTER_CONTROLS);
3312                    }
3313                }
3314            }
3315            Action::ToggleFilterCheckbox => {
3316                if self.mode == Mode::FilterInput && self.current_service == Service::Ec2Instances {
3317                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
3318                        self.ec2_state.state_filter = self.ec2_state.state_filter.next();
3319                        self.ec2_state.table.reset();
3320                    }
3321                } else if self.mode == Mode::InsightsInput {
3322                    use crate::app::InsightsFocus;
3323                    if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
3324                        && self.insights_state.insights.show_dropdown
3325                        && !self.insights_state.insights.log_group_matches.is_empty()
3326                    {
3327                        let selected_idx = self.insights_state.insights.dropdown_selected;
3328                        if let Some(group_name) = self
3329                            .insights_state
3330                            .insights
3331                            .log_group_matches
3332                            .get(selected_idx)
3333                        {
3334                            let group_name = group_name.clone();
3335                            if let Some(pos) = self
3336                                .insights_state
3337                                .insights
3338                                .selected_log_groups
3339                                .iter()
3340                                .position(|g| g == &group_name)
3341                            {
3342                                self.insights_state.insights.selected_log_groups.remove(pos);
3343                            } else if self.insights_state.insights.selected_log_groups.len() < 50 {
3344                                self.insights_state
3345                                    .insights
3346                                    .selected_log_groups
3347                                    .push(group_name);
3348                            }
3349                        }
3350                    }
3351                } else if self.mode == Mode::FilterInput
3352                    && self.current_service == Service::CloudFormationStacks
3353                {
3354                    use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
3355                    match self.cfn_state.input_focus {
3356                        STATUS_FILTER => {
3357                            self.cfn_state.status_filter = self.cfn_state.status_filter.next();
3358                            self.cfn_state.table.reset();
3359                        }
3360                        VIEW_NESTED => {
3361                            self.cfn_state.view_nested = !self.cfn_state.view_nested;
3362                            self.cfn_state.table.reset();
3363                        }
3364                        _ => {}
3365                    }
3366                } else if self.mode == Mode::FilterInput
3367                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3368                {
3369                    match self.log_groups_state.input_focus {
3370                        InputFocus::Checkbox("ExactMatch") => {
3371                            self.log_groups_state.exact_match = !self.log_groups_state.exact_match
3372                        }
3373                        InputFocus::Checkbox("ShowExpired") => {
3374                            self.log_groups_state.show_expired = !self.log_groups_state.show_expired
3375                        }
3376                        _ => {}
3377                    }
3378                } else if self.mode == Mode::EventFilterInput
3379                    && self.log_groups_state.event_input_focus == EventFilterFocus::DateRange
3380                {
3381                    self.log_groups_state.relative_unit =
3382                        self.log_groups_state.relative_unit.next();
3383                }
3384            }
3385            Action::CycleSortColumn => {
3386                if self.view_mode == ViewMode::Detail
3387                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3388                {
3389                    self.log_groups_state.stream_sort = match self.log_groups_state.stream_sort {
3390                        StreamSort::Name => StreamSort::CreationTime,
3391                        StreamSort::CreationTime => StreamSort::LastEventTime,
3392                        StreamSort::LastEventTime => StreamSort::Name,
3393                    };
3394                }
3395            }
3396            Action::ToggleSortDirection => {
3397                if self.view_mode == ViewMode::Detail
3398                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3399                {
3400                    self.log_groups_state.stream_sort_desc =
3401                        !self.log_groups_state.stream_sort_desc;
3402                }
3403            }
3404            Action::ScrollUp => {
3405                if self.mode == Mode::ErrorModal {
3406                    self.error_scroll = self.error_scroll.saturating_sub(1);
3407                } else if self.current_service == Service::LambdaFunctions
3408                    && self.lambda_state.current_function.is_some()
3409                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
3410                    && !self.lambda_state.is_metrics_loading()
3411                {
3412                    self.lambda_state.set_monitoring_scroll(
3413                        self.lambda_state.monitoring_scroll().saturating_sub(1),
3414                    );
3415                } else if self.current_service == Service::Ec2Instances
3416                    && self.ec2_state.current_instance.is_some()
3417                    && self.ec2_state.detail_tab == Ec2DetailTab::Monitoring
3418                    && !self.ec2_state.is_metrics_loading()
3419                {
3420                    self.ec2_state.set_monitoring_scroll(
3421                        self.ec2_state.monitoring_scroll().saturating_sub(1),
3422                    );
3423                } else if self.current_service == Service::SqsQueues
3424                    && self.sqs_state.current_queue.is_some()
3425                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
3426                    && !self.sqs_state.is_metrics_loading()
3427                {
3428                    self.sqs_state.set_monitoring_scroll(
3429                        self.sqs_state.monitoring_scroll().saturating_sub(1),
3430                    );
3431                } else if self.view_mode == ViewMode::PolicyView {
3432                    self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(10);
3433                } else if self.current_service == Service::IamRoles
3434                    && self.iam_state.current_role.is_some()
3435                    && self.iam_state.role_tab == RoleTab::TrustRelationships
3436                {
3437                    self.iam_state.trust_policy_scroll =
3438                        self.iam_state.trust_policy_scroll.saturating_sub(10);
3439                } else if self.view_mode == ViewMode::Events {
3440                    if self.log_groups_state.event_scroll_offset == 0
3441                        && self.log_groups_state.has_older_events
3442                    {
3443                        self.log_groups_state.loading = true;
3444                    } else {
3445                        self.log_groups_state.event_scroll_offset =
3446                            self.log_groups_state.event_scroll_offset.saturating_sub(1);
3447                    }
3448                } else if self.view_mode == ViewMode::InsightsResults {
3449                    self.insights_state.insights.results_selected = self
3450                        .insights_state
3451                        .insights
3452                        .results_selected
3453                        .saturating_sub(1);
3454                } else if self.view_mode == ViewMode::Detail {
3455                    self.log_groups_state.selected_stream =
3456                        self.log_groups_state.selected_stream.saturating_sub(1);
3457                    self.log_groups_state.expanded_stream = None;
3458                } else if self.view_mode == ViewMode::List
3459                    && self.current_service == Service::CloudWatchLogGroups
3460                {
3461                    self.log_groups_state.log_groups.selected =
3462                        self.log_groups_state.log_groups.selected.saturating_sub(1);
3463                    self.log_groups_state.log_groups.snap_to_page();
3464                } else if self.current_service == Service::EcrRepositories {
3465                    if self.ecr_state.current_repository.is_some() {
3466                        self.ecr_state.images.page_up();
3467                    } else {
3468                        self.ecr_state.repositories.page_up();
3469                    }
3470                }
3471            }
3472            Action::ScrollDown => {
3473                if self.mode == Mode::ErrorModal {
3474                    if let Some(error_msg) = &self.error_message {
3475                        let lines = error_msg.lines().count();
3476                        let max_scroll = lines.saturating_sub(1);
3477                        self.error_scroll = (self.error_scroll + 1).min(max_scroll);
3478                    }
3479                } else if self.current_service == Service::SqsQueues
3480                    && self.sqs_state.current_queue.is_some()
3481                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
3482                {
3483                    self.sqs_state
3484                        .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(1));
3485                } else if self.view_mode == ViewMode::PolicyView {
3486                    let lines = self.iam_state.policy_document.lines().count();
3487                    let max_scroll = lines.saturating_sub(1);
3488                    self.iam_state.policy_scroll =
3489                        (self.iam_state.policy_scroll + 10).min(max_scroll);
3490                } else if self.current_service == Service::IamRoles
3491                    && self.iam_state.current_role.is_some()
3492                    && self.iam_state.role_tab == RoleTab::TrustRelationships
3493                {
3494                    let lines = self.iam_state.trust_policy_document.lines().count();
3495                    let max_scroll = lines.saturating_sub(1);
3496                    self.iam_state.trust_policy_scroll =
3497                        (self.iam_state.trust_policy_scroll + 10).min(max_scroll);
3498                } else if self.view_mode == ViewMode::Events {
3499                    let max_scroll = self.log_groups_state.log_events.len().saturating_sub(1);
3500                    if self.log_groups_state.event_scroll_offset >= max_scroll {
3501                        // At the end, do nothing
3502                    } else {
3503                        self.log_groups_state.event_scroll_offset =
3504                            (self.log_groups_state.event_scroll_offset + 1).min(max_scroll);
3505                    }
3506                } else if self.view_mode == ViewMode::InsightsResults {
3507                    let max = self
3508                        .insights_state
3509                        .insights
3510                        .query_results
3511                        .len()
3512                        .saturating_sub(1);
3513                    self.insights_state.insights.results_selected =
3514                        (self.insights_state.insights.results_selected + 1).min(max);
3515                } else if self.view_mode == ViewMode::Detail {
3516                    let filtered_streams = filtered_log_streams(self);
3517                    let max = filtered_streams.len().saturating_sub(1);
3518                    self.log_groups_state.selected_stream =
3519                        (self.log_groups_state.selected_stream + 1).min(max);
3520                } else if self.view_mode == ViewMode::List
3521                    && self.current_service == Service::CloudWatchLogGroups
3522                {
3523                    let filtered_groups = filtered_log_groups(self);
3524                    self.log_groups_state
3525                        .log_groups
3526                        .next_item(filtered_groups.len());
3527                } else if self.current_service == Service::EcrRepositories {
3528                    if self.ecr_state.current_repository.is_some() {
3529                        let filtered_images = filtered_ecr_images(self);
3530                        self.ecr_state.images.page_down(filtered_images.len());
3531                    } else {
3532                        let filtered_repos = filtered_ecr_repositories(self);
3533                        self.ecr_state.repositories.page_down(filtered_repos.len());
3534                    }
3535                }
3536            }
3537
3538            Action::Refresh => {
3539                if self.mode == Mode::ProfilePicker {
3540                    self.log_groups_state.loading = true;
3541                    self.log_groups_state.loading_message = "Refreshing...".to_string();
3542                } else if self.mode == Mode::RegionPicker {
3543                    self.measure_region_latencies();
3544                } else if self.mode == Mode::SessionPicker {
3545                    self.sessions = Session::list_all().unwrap_or_default();
3546                } else if self.current_service == Service::CloudWatchInsights
3547                    && !self.insights_state.insights.selected_log_groups.is_empty()
3548                {
3549                    self.log_groups_state.loading = true;
3550                    self.insights_state.insights.query_completed = true;
3551                } else if self.current_service == Service::LambdaFunctions {
3552                    self.lambda_state.table.loading = true;
3553                } else if self.current_service == Service::LambdaApplications {
3554                    self.lambda_application_state.table.loading = true;
3555                } else if matches!(
3556                    self.view_mode,
3557                    ViewMode::Events | ViewMode::Detail | ViewMode::List
3558                ) {
3559                    self.log_groups_state.loading = true;
3560                }
3561            }
3562            Action::Yank => {
3563                if self.mode == Mode::ErrorModal {
3564                    // Copy error message
3565                    if let Some(error) = &self.error_message {
3566                        copy_to_clipboard(error);
3567                    }
3568                } else if self.view_mode == ViewMode::Events {
3569                    if let Some(event) = self
3570                        .log_groups_state
3571                        .log_events
3572                        .get(self.log_groups_state.event_scroll_offset)
3573                    {
3574                        copy_to_clipboard(&event.message);
3575                    }
3576                } else if self.current_service == Service::EcrRepositories {
3577                    if self.ecr_state.current_repository.is_some() {
3578                        let filtered_images = filtered_ecr_images(self);
3579                        if let Some(image) = self.ecr_state.images.get_selected(&filtered_images) {
3580                            copy_to_clipboard(&image.uri);
3581                        }
3582                    } else {
3583                        let filtered_repos = filtered_ecr_repositories(self);
3584                        if let Some(repo) =
3585                            self.ecr_state.repositories.get_selected(&filtered_repos)
3586                        {
3587                            copy_to_clipboard(&repo.uri);
3588                        }
3589                    }
3590                } else if self.current_service == Service::LambdaFunctions {
3591                    let filtered_functions = filtered_lambda_functions(self);
3592                    if let Some(func) = self.lambda_state.table.get_selected(&filtered_functions) {
3593                        copy_to_clipboard(&func.arn);
3594                    }
3595                } else if self.current_service == Service::CloudFormationStacks {
3596                    if let Some(stack_name) = &self.cfn_state.current_stack {
3597                        // In detail view - copy current stack ARN
3598                        if let Some(stack) = self
3599                            .cfn_state
3600                            .table
3601                            .items
3602                            .iter()
3603                            .find(|s| &s.name == stack_name)
3604                        {
3605                            copy_to_clipboard(&stack.stack_id);
3606                        }
3607                    } else {
3608                        // In list view - copy selected stack ARN
3609                        let filtered_stacks = filtered_cloudformation_stacks(self);
3610                        if let Some(stack) = self.cfn_state.table.get_selected(&filtered_stacks) {
3611                            copy_to_clipboard(&stack.stack_id);
3612                        }
3613                    }
3614                } else if self.current_service == Service::IamUsers {
3615                    if self.iam_state.current_user.is_some() {
3616                        if let Some(user_name) = &self.iam_state.current_user {
3617                            if let Some(user) = self
3618                                .iam_state
3619                                .users
3620                                .items
3621                                .iter()
3622                                .find(|u| u.user_name == *user_name)
3623                            {
3624                                copy_to_clipboard(&user.arn);
3625                            }
3626                        }
3627                    } else {
3628                        let filtered_users = filtered_iam_users(self);
3629                        if let Some(user) = self.iam_state.users.get_selected(&filtered_users) {
3630                            copy_to_clipboard(&user.arn);
3631                        }
3632                    }
3633                } else if self.current_service == Service::IamRoles {
3634                    if self.iam_state.current_role.is_some() {
3635                        if let Some(role_name) = &self.iam_state.current_role {
3636                            if let Some(role) = self
3637                                .iam_state
3638                                .roles
3639                                .items
3640                                .iter()
3641                                .find(|r| r.role_name == *role_name)
3642                            {
3643                                copy_to_clipboard(&role.arn);
3644                            }
3645                        }
3646                    } else {
3647                        let filtered_roles = filtered_iam_roles(self);
3648                        if let Some(role) = self.iam_state.roles.get_selected(&filtered_roles) {
3649                            copy_to_clipboard(&role.arn);
3650                        }
3651                    }
3652                } else if self.current_service == Service::IamUserGroups {
3653                    if self.iam_state.current_group.is_some() {
3654                        if let Some(group_name) = &self.iam_state.current_group {
3655                            let arn = iam::format_arn(&self.config.account_id, "group", group_name);
3656                            copy_to_clipboard(&arn);
3657                        }
3658                    } else {
3659                        let filtered_groups: Vec<_> = self
3660                            .iam_state
3661                            .groups
3662                            .items
3663                            .iter()
3664                            .filter(|g| {
3665                                if self.iam_state.groups.filter.is_empty() {
3666                                    true
3667                                } else {
3668                                    g.group_name
3669                                        .to_lowercase()
3670                                        .contains(&self.iam_state.groups.filter.to_lowercase())
3671                                }
3672                            })
3673                            .collect();
3674                        if let Some(group) = self.iam_state.groups.get_selected(&filtered_groups) {
3675                            let arn = iam::format_arn(
3676                                &self.config.account_id,
3677                                "group",
3678                                &group.group_name,
3679                            );
3680                            copy_to_clipboard(&arn);
3681                        }
3682                    }
3683                } else if self.current_service == Service::SqsQueues {
3684                    if self.sqs_state.current_queue.is_some() {
3685                        // In queue detail view - copy queue ARN
3686                        if let Some(queue) = self
3687                            .sqs_state
3688                            .queues
3689                            .items
3690                            .iter()
3691                            .find(|q| Some(&q.url) == self.sqs_state.current_queue.as_ref())
3692                        {
3693                            let arn = format!(
3694                                "arn:aws:sqs:{}:{}:{}",
3695                                extract_region(&queue.url),
3696                                extract_account_id(&queue.url),
3697                                queue.name
3698                            );
3699                            copy_to_clipboard(&arn);
3700                        }
3701                    } else {
3702                        // In list view - copy selected queue ARN
3703                        let filtered_queues = filtered_queues(
3704                            &self.sqs_state.queues.items,
3705                            &self.sqs_state.queues.filter,
3706                        );
3707                        if let Some(queue) = self.sqs_state.queues.get_selected(&filtered_queues) {
3708                            let arn = format!(
3709                                "arn:aws:sqs:{}:{}:{}",
3710                                extract_region(&queue.url),
3711                                extract_account_id(&queue.url),
3712                                queue.name
3713                            );
3714                            copy_to_clipboard(&arn);
3715                        }
3716                    }
3717                }
3718            }
3719            Action::CopyToClipboard => {
3720                // Request snapshot - will be captured after next render
3721                self.snapshot_requested = true;
3722            }
3723            Action::RetryLoad => {
3724                self.error_message = None;
3725                self.mode = Mode::Normal;
3726                self.log_groups_state.loading = true;
3727            }
3728            Action::ApplyFilter => {
3729                if self.mode == Mode::FilterInput
3730                    && self.current_service == Service::SqsQueues
3731                    && self.sqs_state.input_focus == InputFocus::Dropdown("SubscriptionRegion")
3732                {
3733                    let regions = AwsRegion::all();
3734                    if let Some(region) = regions.get(self.sqs_state.subscription_region_selected) {
3735                        self.sqs_state.subscription_region_filter = region.code.to_string();
3736                    }
3737                    self.mode = Mode::Normal;
3738                } else if self.mode == Mode::InsightsInput {
3739                    use crate::app::InsightsFocus;
3740                    if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
3741                        && self.insights_state.insights.show_dropdown
3742                    {
3743                        // Close dropdown, exit input mode, and execute query
3744                        self.insights_state.insights.show_dropdown = false;
3745                        self.mode = Mode::Normal;
3746                        if !self.insights_state.insights.selected_log_groups.is_empty() {
3747                            self.log_groups_state.loading = true;
3748                            self.insights_state.insights.query_completed = true;
3749                        }
3750                    }
3751                } else if self.mode == Mode::Normal && !self.page_input.is_empty() {
3752                    if let Ok(page) = self.page_input.parse::<usize>() {
3753                        self.go_to_page(page);
3754                    }
3755                    self.page_input.clear();
3756                } else {
3757                    self.mode = Mode::Normal;
3758                    self.log_groups_state.filter_mode = false;
3759                }
3760            }
3761            Action::ToggleExactMatch => {
3762                if self.view_mode == ViewMode::Detail
3763                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3764                {
3765                    self.log_groups_state.exact_match = !self.log_groups_state.exact_match;
3766                }
3767            }
3768            Action::ToggleShowExpired => {
3769                if self.view_mode == ViewMode::Detail
3770                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3771                {
3772                    self.log_groups_state.show_expired = !self.log_groups_state.show_expired;
3773                }
3774            }
3775            Action::GoBack => {
3776                // ServicePicker: close if we have tabs
3777                if self.mode == Mode::ServicePicker && !self.tabs.is_empty() {
3778                    self.mode = Mode::Normal;
3779                    self.service_picker.filter.clear();
3780                }
3781                // S3: pop navigation stack first, then exit bucket
3782                else if self.current_service == Service::S3Buckets
3783                    && self.s3_state.current_bucket.is_some()
3784                {
3785                    if !self.s3_state.prefix_stack.is_empty() {
3786                        self.s3_state.prefix_stack.pop();
3787                        self.s3_state.buckets.loading = true;
3788                    } else {
3789                        self.s3_state.current_bucket = None;
3790                        self.s3_state.objects.clear();
3791                    }
3792                }
3793                // ECR: go back from images to repositories
3794                else if self.current_service == Service::EcrRepositories
3795                    && self.ecr_state.current_repository.is_some()
3796                {
3797                    if self.ecr_state.images.has_expanded_item() {
3798                        self.ecr_state.images.collapse();
3799                    } else {
3800                        self.ecr_state.current_repository = None;
3801                        self.ecr_state.current_repository_uri = None;
3802                        self.ecr_state.images.items.clear();
3803                        self.ecr_state.images.reset();
3804                    }
3805                }
3806                // EC2: go back from instance detail to list
3807                else if self.current_service == Service::Ec2Instances
3808                    && self.ec2_state.current_instance.is_some()
3809                {
3810                    self.ec2_state.current_instance = None;
3811                    self.view_mode = ViewMode::List;
3812                    self.update_current_tab_breadcrumb();
3813                }
3814                // SQS: go back from queue detail to list
3815                else if self.current_service == Service::SqsQueues
3816                    && self.sqs_state.current_queue.is_some()
3817                {
3818                    self.sqs_state.current_queue = None;
3819                }
3820                // IAM: go back from user detail to list
3821                else if self.current_service == Service::IamUsers
3822                    && self.iam_state.current_user.is_some()
3823                {
3824                    self.iam_state.current_user = None;
3825                    self.iam_state.policies.items.clear();
3826                    self.iam_state.policies.reset();
3827                    self.update_current_tab_breadcrumb();
3828                }
3829                // IAM: go back from group detail to list
3830                else if self.current_service == Service::IamUserGroups
3831                    && self.iam_state.current_group.is_some()
3832                {
3833                    self.iam_state.current_group = None;
3834                    self.update_current_tab_breadcrumb();
3835                }
3836                // IAM: go back from role detail to list
3837                else if self.current_service == Service::IamRoles {
3838                    if self.view_mode == ViewMode::PolicyView {
3839                        // Go back from policy view to role detail
3840                        self.view_mode = ViewMode::Detail;
3841                        self.iam_state.current_policy = None;
3842                        self.iam_state.policy_document.clear();
3843                        self.iam_state.policy_scroll = 0;
3844                        self.update_current_tab_breadcrumb();
3845                    } else if self.iam_state.current_role.is_some() {
3846                        self.iam_state.current_role = None;
3847                        self.iam_state.policies.items.clear();
3848                        self.iam_state.policies.reset();
3849                        self.update_current_tab_breadcrumb();
3850                    }
3851                }
3852                // Lambda: go back from version detail to function detail
3853                else if self.current_service == Service::LambdaFunctions
3854                    && self.lambda_state.current_version.is_some()
3855                {
3856                    self.lambda_state.current_version = None;
3857                    self.lambda_state.detail_tab = LambdaDetailTab::Versions;
3858                }
3859                // Lambda: go back from alias detail to function detail
3860                else if self.current_service == Service::LambdaFunctions
3861                    && self.lambda_state.current_alias.is_some()
3862                {
3863                    self.lambda_state.current_alias = None;
3864                    self.lambda_state.detail_tab = LambdaDetailTab::Aliases;
3865                }
3866                // Lambda: go back from function detail to list
3867                else if self.current_service == Service::LambdaFunctions
3868                    && self.lambda_state.current_function.is_some()
3869                {
3870                    self.lambda_state.current_function = None;
3871                    self.update_current_tab_breadcrumb();
3872                }
3873                // Lambda Applications: go back from application detail to list
3874                else if self.current_service == Service::LambdaApplications
3875                    && self.lambda_application_state.current_application.is_some()
3876                {
3877                    self.lambda_application_state.current_application = None;
3878                    self.update_current_tab_breadcrumb();
3879                }
3880                // CloudFormation: go back from stack detail to list
3881                else if self.current_service == Service::CloudFormationStacks
3882                    && self.cfn_state.current_stack.is_some()
3883                {
3884                    self.cfn_state.current_stack = None;
3885                    self.update_current_tab_breadcrumb();
3886                }
3887                // From insights results -> collapse if expanded, otherwise back to sidebar
3888                else if self.view_mode == ViewMode::InsightsResults {
3889                    if self.insights_state.insights.expanded_result.is_some() {
3890                        self.insights_state.insights.expanded_result = None;
3891                    }
3892                }
3893                // From alarms view -> collapse if expanded
3894                else if self.current_service == Service::CloudWatchAlarms {
3895                    if self.alarms_state.table.has_expanded_item() {
3896                        self.alarms_state.table.collapse();
3897                    }
3898                }
3899                // From EC2 instances view -> always collapse
3900                else if self.current_service == Service::Ec2Instances {
3901                    if self.ec2_state.current_instance.is_some()
3902                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
3903                    {
3904                        self.ec2_state.tags.collapse();
3905                    } else {
3906                        self.ec2_state.table.collapse();
3907                    }
3908                }
3909                // From events view -> collapse if expanded, otherwise back to detail view
3910                else if self.view_mode == ViewMode::Events {
3911                    if self.log_groups_state.expanded_event.is_some() {
3912                        self.log_groups_state.expanded_event = None;
3913                    } else {
3914                        self.view_mode = ViewMode::Detail;
3915                        self.log_groups_state.event_filter.clear();
3916                    }
3917                }
3918                // From detail view -> back to list view
3919                else if self.view_mode == ViewMode::Detail {
3920                    self.view_mode = ViewMode::List;
3921                    self.log_groups_state.stream_filter.clear();
3922                    self.log_groups_state.exact_match = false;
3923                    self.log_groups_state.show_expired = false;
3924                }
3925            }
3926            Action::OpenInConsole | Action::OpenInBrowser => {
3927                let url = self.get_console_url();
3928                let _ = webbrowser::open(&url);
3929            }
3930            Action::ShowHelp => {
3931                self.mode = Mode::HelpModal;
3932            }
3933            Action::OpenRegionPicker => {
3934                self.region_filter.clear();
3935                self.region_picker_selected = 0;
3936                self.measure_region_latencies();
3937                self.mode = Mode::RegionPicker;
3938            }
3939            Action::OpenProfilePicker => {
3940                self.profile_filter.clear();
3941                self.profile_picker_selected = 0;
3942                self.available_profiles = Self::load_aws_profiles();
3943                self.mode = Mode::ProfilePicker;
3944            }
3945            Action::OpenCalendar => {
3946                self.calendar_date = Some(time::OffsetDateTime::now_utc().date());
3947                self.calendar_selecting = CalendarField::StartDate;
3948                self.mode = Mode::CalendarPicker;
3949            }
3950            Action::CloseCalendar => {
3951                self.mode = Mode::Normal;
3952                self.calendar_date = None;
3953            }
3954            Action::CalendarPrevDay => {
3955                if let Some(date) = self.calendar_date {
3956                    self.calendar_date = date.checked_sub(time::Duration::days(1));
3957                }
3958            }
3959            Action::CalendarNextDay => {
3960                if let Some(date) = self.calendar_date {
3961                    self.calendar_date = date.checked_add(time::Duration::days(1));
3962                }
3963            }
3964            Action::CalendarPrevWeek => {
3965                if let Some(date) = self.calendar_date {
3966                    self.calendar_date = date.checked_sub(time::Duration::weeks(1));
3967                }
3968            }
3969            Action::CalendarNextWeek => {
3970                if let Some(date) = self.calendar_date {
3971                    self.calendar_date = date.checked_add(time::Duration::weeks(1));
3972                }
3973            }
3974            Action::CalendarPrevMonth => {
3975                if let Some(date) = self.calendar_date {
3976                    self.calendar_date = Some(if date.month() == time::Month::January {
3977                        date.replace_month(time::Month::December)
3978                            .unwrap()
3979                            .replace_year(date.year() - 1)
3980                            .unwrap()
3981                    } else {
3982                        date.replace_month(date.month().previous()).unwrap()
3983                    });
3984                }
3985            }
3986            Action::CalendarNextMonth => {
3987                if let Some(date) = self.calendar_date {
3988                    self.calendar_date = Some(if date.month() == time::Month::December {
3989                        date.replace_month(time::Month::January)
3990                            .unwrap()
3991                            .replace_year(date.year() + 1)
3992                            .unwrap()
3993                    } else {
3994                        date.replace_month(date.month().next()).unwrap()
3995                    });
3996                }
3997            }
3998            Action::CalendarSelect => {
3999                if let Some(date) = self.calendar_date {
4000                    let timestamp = time::OffsetDateTime::new_utc(date, time::Time::MIDNIGHT)
4001                        .unix_timestamp()
4002                        * 1000;
4003                    match self.calendar_selecting {
4004                        CalendarField::StartDate => {
4005                            self.log_groups_state.start_time = Some(timestamp);
4006                            self.calendar_selecting = CalendarField::EndDate;
4007                        }
4008                        CalendarField::EndDate => {
4009                            self.log_groups_state.end_time = Some(timestamp);
4010                            self.mode = Mode::Normal;
4011                            self.calendar_date = None;
4012                        }
4013                    }
4014                }
4015            }
4016        }
4017    }
4018
4019    pub fn filtered_services(&self) -> Vec<&'static str> {
4020        let mut services = if self.service_picker.filter.is_empty() {
4021            self.service_picker.services.clone()
4022        } else {
4023            self.service_picker
4024                .services
4025                .iter()
4026                .filter(|s| {
4027                    s.to_lowercase()
4028                        .contains(&self.service_picker.filter.to_lowercase())
4029                })
4030                .copied()
4031                .collect()
4032        };
4033        services.sort();
4034        services
4035    }
4036
4037    pub fn breadcrumbs(&self) -> String {
4038        if !self.service_selected {
4039            return String::new();
4040        }
4041
4042        let mut parts = vec![];
4043
4044        match self.current_service {
4045            Service::CloudWatchLogGroups => {
4046                parts.push("CloudWatch".to_string());
4047                parts.push("Log groups".to_string());
4048
4049                if self.view_mode != ViewMode::List {
4050                    if let Some(group) = selected_log_group(self) {
4051                        parts.push(group.name.clone());
4052                    }
4053                }
4054
4055                if self.view_mode == ViewMode::Events {
4056                    if let Some(stream) = self
4057                        .log_groups_state
4058                        .log_streams
4059                        .get(self.log_groups_state.selected_stream)
4060                    {
4061                        parts.push(stream.name.clone());
4062                    }
4063                }
4064            }
4065            Service::CloudWatchInsights => {
4066                parts.push("CloudWatch".to_string());
4067                parts.push("Insights".to_string());
4068            }
4069            Service::CloudWatchAlarms => {
4070                parts.push("CloudWatch".to_string());
4071                parts.push("Alarms".to_string());
4072            }
4073            Service::S3Buckets => {
4074                parts.push("S3".to_string());
4075                if let Some(bucket) = &self.s3_state.current_bucket {
4076                    parts.push(bucket.clone());
4077                    if let Some(prefix) = self.s3_state.prefix_stack.last() {
4078                        parts.push(prefix.trim_end_matches('/').to_string());
4079                    }
4080                } else {
4081                    parts.push("Buckets".to_string());
4082                }
4083            }
4084            Service::SqsQueues => {
4085                parts.push("SQS".to_string());
4086                parts.push("Queues".to_string());
4087            }
4088            Service::EcrRepositories => {
4089                parts.push("ECR".to_string());
4090                if let Some(repo) = &self.ecr_state.current_repository {
4091                    parts.push(repo.clone());
4092                } else {
4093                    parts.push("Repositories".to_string());
4094                }
4095            }
4096            Service::LambdaFunctions => {
4097                parts.push("Lambda".to_string());
4098                if let Some(func) = &self.lambda_state.current_function {
4099                    parts.push(func.clone());
4100                } else {
4101                    parts.push("Functions".to_string());
4102                }
4103            }
4104            Service::LambdaApplications => {
4105                parts.push("Lambda".to_string());
4106                parts.push("Applications".to_string());
4107            }
4108            Service::CloudFormationStacks => {
4109                parts.push("CloudFormation".to_string());
4110                if let Some(stack_name) = &self.cfn_state.current_stack {
4111                    parts.push(stack_name.clone());
4112                } else {
4113                    parts.push("Stacks".to_string());
4114                }
4115            }
4116            Service::IamUsers => {
4117                parts.push("IAM".to_string());
4118                parts.push("Users".to_string());
4119            }
4120            Service::IamRoles => {
4121                parts.push("IAM".to_string());
4122                parts.push("Roles".to_string());
4123                if let Some(role_name) = &self.iam_state.current_role {
4124                    parts.push(role_name.clone());
4125                    if let Some(policy_name) = &self.iam_state.current_policy {
4126                        parts.push(policy_name.clone());
4127                    }
4128                }
4129            }
4130            Service::IamUserGroups => {
4131                parts.push("IAM".to_string());
4132                parts.push("User Groups".to_string());
4133                if let Some(group_name) = &self.iam_state.current_group {
4134                    parts.push(group_name.clone());
4135                }
4136            }
4137            Service::Ec2Instances => {
4138                parts.push("EC2".to_string());
4139                parts.push("Instances".to_string());
4140            }
4141        }
4142
4143        parts.join(" > ")
4144    }
4145
4146    pub fn update_current_tab_breadcrumb(&mut self) {
4147        if !self.tabs.is_empty() {
4148            self.tabs[self.current_tab].breadcrumb = self.breadcrumbs();
4149        }
4150    }
4151
4152    pub fn get_console_url(&self) -> String {
4153        use crate::{cfn, cw, ecr, iam, lambda, s3};
4154
4155        match self.current_service {
4156            Service::CloudWatchLogGroups => {
4157                if self.view_mode == ViewMode::Events {
4158                    if let Some(group) = selected_log_group(self) {
4159                        if let Some(stream) = self
4160                            .log_groups_state
4161                            .log_streams
4162                            .get(self.log_groups_state.selected_stream)
4163                        {
4164                            return cw::logs::console_url_stream(
4165                                &self.config.region,
4166                                &group.name,
4167                                &stream.name,
4168                            );
4169                        }
4170                    }
4171                } else if self.view_mode == ViewMode::Detail {
4172                    if let Some(group) = selected_log_group(self) {
4173                        return cw::logs::console_url_detail(&self.config.region, &group.name);
4174                    }
4175                }
4176                cw::logs::console_url_list(&self.config.region)
4177            }
4178            Service::CloudWatchInsights => cw::insights::console_url(
4179                &self.config.region,
4180                &self.config.account_id,
4181                &self.insights_state.insights.query_text,
4182                &self.insights_state.insights.selected_log_groups,
4183            ),
4184            Service::CloudWatchAlarms => {
4185                let view_type = match self.alarms_state.view_as {
4186                    AlarmViewMode::Table | AlarmViewMode::Detail => "table",
4187                    AlarmViewMode::Cards => "card",
4188                };
4189                cw::alarms::console_url(
4190                    &self.config.region,
4191                    view_type,
4192                    self.alarms_state.table.page_size.value(),
4193                    &self.alarms_state.sort_column,
4194                    self.alarms_state.sort_direction.as_str(),
4195                )
4196            }
4197            Service::S3Buckets => {
4198                if let Some(bucket_name) = &self.s3_state.current_bucket {
4199                    let prefix = self.s3_state.prefix_stack.join("");
4200                    s3::console_url_bucket(&self.config.region, bucket_name, &prefix)
4201                } else {
4202                    s3::console_url_buckets(&self.config.region)
4203                }
4204            }
4205            Service::SqsQueues => {
4206                if let Some(queue_url) = &self.sqs_state.current_queue {
4207                    console_url_queue_detail(&self.config.region, queue_url)
4208                } else {
4209                    console_url_queues(&self.config.region)
4210                }
4211            }
4212            Service::EcrRepositories => {
4213                if let Some(repo_name) = &self.ecr_state.current_repository {
4214                    ecr::console_url_private_repository(
4215                        &self.config.region,
4216                        &self.config.account_id,
4217                        repo_name,
4218                    )
4219                } else {
4220                    ecr::console_url_repositories(&self.config.region)
4221                }
4222            }
4223            Service::LambdaFunctions => {
4224                if let Some(func_name) = &self.lambda_state.current_function {
4225                    if let Some(version) = &self.lambda_state.current_version {
4226                        lambda::console_url_function_version(
4227                            &self.config.region,
4228                            func_name,
4229                            version,
4230                            &self.lambda_state.detail_tab,
4231                        )
4232                    } else {
4233                        lambda::console_url_function_detail(&self.config.region, func_name)
4234                    }
4235                } else {
4236                    lambda::console_url_functions(&self.config.region)
4237                }
4238            }
4239            Service::LambdaApplications => {
4240                if let Some(app_name) = &self.lambda_application_state.current_application {
4241                    lambda::console_url_application_detail(
4242                        &self.config.region,
4243                        app_name,
4244                        &self.lambda_application_state.detail_tab,
4245                    )
4246                } else {
4247                    lambda::console_url_applications(&self.config.region)
4248                }
4249            }
4250            Service::CloudFormationStacks => {
4251                if let Some(stack_name) = &self.cfn_state.current_stack {
4252                    if let Some(stack) = self
4253                        .cfn_state
4254                        .table
4255                        .items
4256                        .iter()
4257                        .find(|s| &s.name == stack_name)
4258                    {
4259                        return cfn::console_url_stack_detail_with_tab(
4260                            &self.config.region,
4261                            &stack.stack_id,
4262                            &self.cfn_state.detail_tab,
4263                        );
4264                    }
4265                }
4266                cfn::console_url_stacks(&self.config.region)
4267            }
4268            Service::IamUsers => {
4269                if let Some(user_name) = &self.iam_state.current_user {
4270                    let section = match self.iam_state.user_tab {
4271                        UserTab::Permissions => "permissions",
4272                        UserTab::Groups => "groups",
4273                        UserTab::Tags => "tags",
4274                        UserTab::SecurityCredentials => "security_credentials",
4275                        UserTab::LastAccessed => "access_advisor",
4276                    };
4277                    iam::console_url_user_detail(&self.config.region, user_name, section)
4278                } else {
4279                    iam::console_url_users(&self.config.region)
4280                }
4281            }
4282            Service::IamRoles => {
4283                if let Some(policy_name) = &self.iam_state.current_policy {
4284                    if let Some(role_name) = &self.iam_state.current_role {
4285                        return iam::console_url_role_policy(
4286                            &self.config.region,
4287                            role_name,
4288                            policy_name,
4289                        );
4290                    }
4291                }
4292                if let Some(role_name) = &self.iam_state.current_role {
4293                    let section = match self.iam_state.role_tab {
4294                        RoleTab::Permissions => "permissions",
4295                        RoleTab::TrustRelationships => "trust_relationships",
4296                        RoleTab::Tags => "tags",
4297                        RoleTab::LastAccessed => "access_advisor",
4298                        RoleTab::RevokeSessions => "revoke_sessions",
4299                    };
4300                    iam::console_url_role_detail(&self.config.region, role_name, section)
4301                } else {
4302                    iam::console_url_roles(&self.config.region)
4303                }
4304            }
4305            Service::IamUserGroups => iam::console_url_groups(&self.config.region),
4306            Service::Ec2Instances => {
4307                if let Some(instance_id) = &self.ec2_state.current_instance {
4308                    format!(
4309                        "https://{}.console.aws.amazon.com/ec2/home?region={}#InstanceDetails:instanceId={}",
4310                        self.config.region, self.config.region, instance_id
4311                    )
4312                } else {
4313                    format!(
4314                        "https://{}.console.aws.amazon.com/ec2/home?region={}#Instances:",
4315                        self.config.region, self.config.region
4316                    )
4317                }
4318            }
4319        }
4320    }
4321
4322    pub fn calculate_total_bucket_rows(&self) -> usize {
4323        calculate_total_bucket_rows(self)
4324    }
4325
4326    fn calculate_total_object_rows(&self) -> usize {
4327        calculate_total_object_rows(self)
4328    }
4329
4330    fn get_column_selector_max(&self) -> usize {
4331        if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none() {
4332            self.s3_bucket_column_ids.len() + 6
4333        } else if self.view_mode == ViewMode::Events {
4334            self.cw_log_event_column_ids.len() - 1
4335        } else if self.view_mode == ViewMode::Detail {
4336            self.cw_log_stream_column_ids.len() + 6
4337        } else if self.current_service == Service::CloudWatchAlarms {
4338            29
4339        } else if self.current_service == Service::Ec2Instances {
4340            if self.ec2_state.current_instance.is_some()
4341                && self.ec2_state.detail_tab == Ec2DetailTab::Tags
4342            {
4343                self.ec2_state.tag_column_ids.len() + 6
4344            } else {
4345                self.ec2_column_ids.len() + 6
4346            }
4347        } else if self.current_service == Service::EcrRepositories {
4348            if self.ecr_state.current_repository.is_some() {
4349                self.ecr_image_column_ids.len() + 6
4350            } else {
4351                self.ecr_repo_column_ids.len() + 6
4352            }
4353        } else if self.current_service == Service::SqsQueues {
4354            self.sqs_column_ids.len() - 1
4355        } else if self.current_service == Service::LambdaFunctions {
4356            self.lambda_state.function_column_ids.len() + 6
4357        } else if self.current_service == Service::LambdaApplications {
4358            self.lambda_application_column_ids.len() + 5
4359        } else if self.current_service == Service::CloudFormationStacks {
4360            self.cfn_column_ids.len() + 6
4361        } else if self.current_service == Service::IamUsers {
4362            if self.iam_state.current_user.is_some() {
4363                self.iam_policy_column_ids.len() + 5
4364            } else {
4365                self.iam_user_column_ids.len() + 5
4366            }
4367        } else if self.current_service == Service::IamRoles {
4368            if self.iam_state.current_role.is_some() {
4369                self.iam_policy_column_ids.len() + 5
4370            } else {
4371                self.iam_role_column_ids.len() + 5
4372            }
4373        } else {
4374            self.cw_log_group_column_ids.len() + 6
4375        }
4376    }
4377
4378    fn next_item(&mut self) {
4379        match self.mode {
4380            Mode::FilterInput => {
4381                if self.current_service == Service::S3Buckets
4382                    && self.s3_state.input_focus == InputFocus::Pagination
4383                {
4384                    // Navigate to next page
4385                    let page_size = self.s3_state.buckets.page_size.value();
4386                    let total_rows = crate::ui::s3::calculate_filtered_bucket_rows(self);
4387                    let max_offset = total_rows.saturating_sub(page_size);
4388                    self.s3_state.selected_row =
4389                        (self.s3_state.selected_row + page_size).min(max_offset);
4390                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
4391                } else if self.current_service == Service::CloudFormationStacks {
4392                    use crate::ui::cfn::STATUS_FILTER;
4393                    if self.cfn_state.input_focus == STATUS_FILTER {
4394                        self.cfn_state.status_filter = self.cfn_state.status_filter.next();
4395                        self.cfn_state.table.reset();
4396                    }
4397                } else if self.current_service == Service::IamUsers
4398                    && self.iam_state.current_user.is_some()
4399                {
4400                    use crate::ui::iam::{HISTORY_FILTER, POLICY_TYPE_DROPDOWN};
4401                    if self.iam_state.user_tab == UserTab::Permissions
4402                        && self.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
4403                    {
4404                        self.cycle_policy_type_next();
4405                    } else if self.iam_state.user_tab == UserTab::LastAccessed
4406                        && self.iam_state.last_accessed_input_focus == HISTORY_FILTER
4407                    {
4408                        self.iam_state.last_accessed_history_filter =
4409                            self.iam_state.last_accessed_history_filter.next();
4410                        self.iam_state.last_accessed_services.reset();
4411                    }
4412                } else if self.current_service == Service::IamRoles
4413                    && self.iam_state.current_role.is_some()
4414                    && self.iam_state.role_tab == RoleTab::Permissions
4415                {
4416                    use crate::ui::iam::POLICY_TYPE_DROPDOWN;
4417                    if self.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN {
4418                        self.cycle_policy_type_next();
4419                    }
4420                } else if self.current_service == Service::Ec2Instances {
4421                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
4422                        self.ec2_state.state_filter = self.ec2_state.state_filter.next();
4423                        self.ec2_state.table.reset();
4424                    }
4425                } else if self.current_service == Service::SqsQueues {
4426                    use crate::ui::sqs::SUBSCRIPTION_REGION;
4427                    if self.sqs_state.input_focus == SUBSCRIPTION_REGION {
4428                        let regions = AwsRegion::all();
4429                        self.sqs_state.subscription_region_selected =
4430                            (self.sqs_state.subscription_region_selected + 1)
4431                                .min(regions.len() - 1);
4432                        self.sqs_state.subscriptions.reset();
4433                    }
4434                }
4435            }
4436            Mode::RegionPicker => {
4437                let filtered = self.get_filtered_regions();
4438                if !filtered.is_empty() {
4439                    self.region_picker_selected =
4440                        (self.region_picker_selected + 1).min(filtered.len() - 1);
4441                }
4442            }
4443            Mode::ProfilePicker => {
4444                let filtered = self.get_filtered_profiles();
4445                if !filtered.is_empty() {
4446                    self.profile_picker_selected =
4447                        (self.profile_picker_selected + 1).min(filtered.len() - 1);
4448                }
4449            }
4450            Mode::SessionPicker => {
4451                let filtered = self.get_filtered_sessions();
4452                if !filtered.is_empty() {
4453                    self.session_picker_selected =
4454                        (self.session_picker_selected + 1).min(filtered.len() - 1);
4455                }
4456            }
4457            Mode::InsightsInput => {
4458                use crate::app::InsightsFocus;
4459                if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
4460                    && self.insights_state.insights.show_dropdown
4461                    && !self.insights_state.insights.log_group_matches.is_empty()
4462                {
4463                    let max = self.insights_state.insights.log_group_matches.len() - 1;
4464                    self.insights_state.insights.dropdown_selected =
4465                        (self.insights_state.insights.dropdown_selected + 1).min(max);
4466                }
4467            }
4468            Mode::ColumnSelector => {
4469                let max = self.get_column_selector_max();
4470                self.column_selector_index = (self.column_selector_index + 1).min(max);
4471            }
4472            Mode::ServicePicker => {
4473                let filtered = self.filtered_services();
4474                if !filtered.is_empty() {
4475                    self.service_picker.selected =
4476                        (self.service_picker.selected + 1).min(filtered.len() - 1);
4477                }
4478            }
4479            Mode::TabPicker => {
4480                let filtered = self.get_filtered_tabs();
4481                if !filtered.is_empty() {
4482                    self.tab_picker_selected =
4483                        (self.tab_picker_selected + 1).min(filtered.len() - 1);
4484                }
4485            }
4486            Mode::Normal => {
4487                if !self.service_selected {
4488                    let filtered = self.filtered_services();
4489                    if !filtered.is_empty() {
4490                        self.service_picker.selected =
4491                            (self.service_picker.selected + 1).min(filtered.len() - 1);
4492                    }
4493                } else if self.current_service == Service::S3Buckets {
4494                    if self.s3_state.current_bucket.is_some() {
4495                        if self.s3_state.object_tab == S3ObjectTab::Properties {
4496                            // Scroll properties view
4497                            self.s3_state.properties_scroll =
4498                                self.s3_state.properties_scroll.saturating_add(1);
4499                        } else {
4500                            // Calculate total rows including all nested preview items
4501                            let total_rows = self.calculate_total_object_rows();
4502                            let max = total_rows.saturating_sub(1);
4503                            self.s3_state.selected_object =
4504                                (self.s3_state.selected_object + 1).min(max);
4505
4506                            // Adjust scroll offset if selection goes below viewport
4507                            let visible_rows = self.s3_state.object_visible_rows.get();
4508                            if self.s3_state.selected_object
4509                                >= self.s3_state.object_scroll_offset + visible_rows
4510                            {
4511                                self.s3_state.object_scroll_offset =
4512                                    self.s3_state.selected_object - visible_rows + 1;
4513                            }
4514                        }
4515                    } else {
4516                        // Navigate rows in bucket list
4517                        let total_rows = crate::ui::s3::calculate_filtered_bucket_rows(self);
4518                        if total_rows > 0 {
4519                            self.s3_state.selected_row =
4520                                (self.s3_state.selected_row + 1).min(total_rows - 1);
4521
4522                            // Adjust scroll offset if selection goes below viewport
4523                            let visible_rows = self.s3_state.bucket_visible_rows.get();
4524                            if self.s3_state.selected_row
4525                                >= self.s3_state.bucket_scroll_offset + visible_rows
4526                            {
4527                                self.s3_state.bucket_scroll_offset =
4528                                    self.s3_state.selected_row - visible_rows + 1;
4529                            }
4530                        }
4531                    }
4532                } else if self.view_mode == ViewMode::InsightsResults {
4533                    let max = self
4534                        .insights_state
4535                        .insights
4536                        .query_results
4537                        .len()
4538                        .saturating_sub(1);
4539                    if self.insights_state.insights.results_selected < max {
4540                        self.insights_state.insights.results_selected += 1;
4541                    }
4542                } else if self.view_mode == ViewMode::PolicyView {
4543                    let lines = self.iam_state.policy_document.lines().count();
4544                    let max_scroll = lines.saturating_sub(1);
4545                    self.iam_state.policy_scroll =
4546                        (self.iam_state.policy_scroll + 1).min(max_scroll);
4547                } else if self.current_service == Service::CloudFormationStacks
4548                    && self.cfn_state.current_stack.is_some()
4549                    && self.cfn_state.detail_tab == CfnDetailTab::Template
4550                {
4551                    let lines = self.cfn_state.template_body.lines().count();
4552                    let max_scroll = lines.saturating_sub(1);
4553                    self.cfn_state.template_scroll =
4554                        (self.cfn_state.template_scroll + 1).min(max_scroll);
4555                } else if self.current_service == Service::SqsQueues
4556                    && self.sqs_state.current_queue.is_some()
4557                    && self.sqs_state.detail_tab == SqsQueueDetailTab::QueuePolicies
4558                {
4559                    let lines = self.sqs_state.policy_document.lines().count();
4560                    let max_scroll = lines.saturating_sub(1);
4561                    self.sqs_state.policy_scroll =
4562                        (self.sqs_state.policy_scroll + 1).min(max_scroll);
4563                } else if self.current_service == Service::LambdaFunctions
4564                    && self.lambda_state.current_function.is_some()
4565                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
4566                    && !self.lambda_state.is_metrics_loading()
4567                {
4568                    self.lambda_state
4569                        .set_monitoring_scroll((self.lambda_state.monitoring_scroll() + 1).min(9));
4570                } else if self.current_service == Service::Ec2Instances
4571                    && self.ec2_state.current_instance.is_some()
4572                    && self.ec2_state.detail_tab == Ec2DetailTab::Monitoring
4573                    && !self.ec2_state.is_metrics_loading()
4574                {
4575                    self.ec2_state
4576                        .set_monitoring_scroll((self.ec2_state.monitoring_scroll() + 1).min(5));
4577                } else if self.current_service == Service::SqsQueues
4578                    && self.sqs_state.current_queue.is_some()
4579                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
4580                    && !self.sqs_state.is_metrics_loading()
4581                {
4582                    self.sqs_state
4583                        .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(8));
4584                } else if self.view_mode == ViewMode::Events {
4585                    let max_scroll = self.log_groups_state.log_events.len().saturating_sub(1);
4586                    if self.log_groups_state.event_scroll_offset >= max_scroll {
4587                        // At the end, do nothing
4588                    } else {
4589                        self.log_groups_state.event_scroll_offset =
4590                            (self.log_groups_state.event_scroll_offset + 1).min(max_scroll);
4591                    }
4592                } else if self.current_service == Service::CloudWatchLogGroups {
4593                    if self.view_mode == ViewMode::List {
4594                        let filtered_groups = filtered_log_groups(self);
4595                        self.log_groups_state
4596                            .log_groups
4597                            .next_item(filtered_groups.len());
4598                    } else if self.view_mode == ViewMode::Detail {
4599                        let filtered_streams = filtered_log_streams(self);
4600                        if !filtered_streams.is_empty() {
4601                            let max = filtered_streams.len() - 1;
4602                            if self.log_groups_state.selected_stream >= max {
4603                                // At the end, do nothing
4604                            } else {
4605                                self.log_groups_state.selected_stream =
4606                                    (self.log_groups_state.selected_stream + 1).min(max);
4607                                self.log_groups_state.expanded_stream = None;
4608                            }
4609                        }
4610                    }
4611                } else if self.current_service == Service::CloudWatchAlarms {
4612                    let filtered_alarms = match self.alarms_state.alarm_tab {
4613                        AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
4614                        AlarmTab::InAlarm => self
4615                            .alarms_state
4616                            .table
4617                            .items
4618                            .iter()
4619                            .filter(|a| a.state.to_uppercase() == "ALARM")
4620                            .count(),
4621                    };
4622                    if filtered_alarms > 0 {
4623                        self.alarms_state.table.next_item(filtered_alarms);
4624                    }
4625                } else if self.current_service == Service::Ec2Instances {
4626                    if self.ec2_state.current_instance.is_some()
4627                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
4628                    {
4629                        let filtered = crate::ui::ec2::filtered_tags(self);
4630                        if !filtered.is_empty() {
4631                            self.ec2_state.tags.next_item(filtered.len());
4632                        }
4633                    } else {
4634                        let filtered: Vec<_> = self
4635                            .ec2_state
4636                            .table
4637                            .items
4638                            .iter()
4639                            .filter(|i| self.ec2_state.state_filter.matches(&i.state))
4640                            .filter(|i| {
4641                                if self.ec2_state.table.filter.is_empty() {
4642                                    return true;
4643                                }
4644                                i.name.contains(&self.ec2_state.table.filter)
4645                                    || i.instance_id.contains(&self.ec2_state.table.filter)
4646                                    || i.state.contains(&self.ec2_state.table.filter)
4647                                    || i.instance_type.contains(&self.ec2_state.table.filter)
4648                                    || i.availability_zone.contains(&self.ec2_state.table.filter)
4649                                    || i.security_groups.contains(&self.ec2_state.table.filter)
4650                                    || i.key_name.contains(&self.ec2_state.table.filter)
4651                            })
4652                            .collect();
4653                        if !filtered.is_empty() {
4654                            self.ec2_state.table.next_item(filtered.len());
4655                        }
4656                    }
4657                } else if self.current_service == Service::EcrRepositories {
4658                    if self.ecr_state.current_repository.is_some() {
4659                        let filtered_images = filtered_ecr_images(self);
4660                        if !filtered_images.is_empty() {
4661                            self.ecr_state.images.next_item(filtered_images.len());
4662                        }
4663                    } else {
4664                        let filtered_repos = filtered_ecr_repositories(self);
4665                        if !filtered_repos.is_empty() {
4666                            self.ecr_state.repositories.selected =
4667                                (self.ecr_state.repositories.selected + 1)
4668                                    .min(filtered_repos.len() - 1);
4669                            self.ecr_state.repositories.snap_to_page();
4670                        }
4671                    }
4672                } else if self.current_service == Service::SqsQueues {
4673                    if self.sqs_state.current_queue.is_some()
4674                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
4675                    {
4676                        let filtered = filtered_lambda_triggers(self);
4677                        if !filtered.is_empty() {
4678                            self.sqs_state.triggers.next_item(filtered.len());
4679                        }
4680                    } else if self.sqs_state.current_queue.is_some()
4681                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
4682                    {
4683                        let filtered = filtered_eventbridge_pipes(self);
4684                        if !filtered.is_empty() {
4685                            self.sqs_state.pipes.next_item(filtered.len());
4686                        }
4687                    } else if self.sqs_state.current_queue.is_some()
4688                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
4689                    {
4690                        let filtered = filtered_tags(self);
4691                        if !filtered.is_empty() {
4692                            self.sqs_state.tags.next_item(filtered.len());
4693                        }
4694                    } else if self.sqs_state.current_queue.is_some()
4695                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
4696                    {
4697                        let filtered = filtered_subscriptions(self);
4698                        if !filtered.is_empty() {
4699                            self.sqs_state.subscriptions.next_item(filtered.len());
4700                        }
4701                    } else {
4702                        let filtered_queues = filtered_queues(
4703                            &self.sqs_state.queues.items,
4704                            &self.sqs_state.queues.filter,
4705                        );
4706                        if !filtered_queues.is_empty() {
4707                            self.sqs_state.queues.next_item(filtered_queues.len());
4708                        }
4709                    }
4710                } else if self.current_service == Service::LambdaFunctions {
4711                    if self.lambda_state.current_function.is_some()
4712                        && self.lambda_state.detail_tab == LambdaDetailTab::Code
4713                    {
4714                        // Layer table navigation in Code tab
4715                        if let Some(func_name) = &self.lambda_state.current_function {
4716                            if let Some(func) = self
4717                                .lambda_state
4718                                .table
4719                                .items
4720                                .iter()
4721                                .find(|f| f.name == *func_name)
4722                            {
4723                                let max = func.layers.len().saturating_sub(1);
4724                                if !func.layers.is_empty() {
4725                                    self.lambda_state.layer_selected =
4726                                        (self.lambda_state.layer_selected + 1).min(max);
4727                                }
4728                            }
4729                        }
4730                    } else if self.lambda_state.current_function.is_some()
4731                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
4732                    {
4733                        // Version table navigation
4734                        let filtered: Vec<_> = self
4735                            .lambda_state
4736                            .version_table
4737                            .items
4738                            .iter()
4739                            .filter(|v| {
4740                                self.lambda_state.version_table.filter.is_empty()
4741                                    || v.version.to_lowercase().contains(
4742                                        &self.lambda_state.version_table.filter.to_lowercase(),
4743                                    )
4744                                    || v.aliases.to_lowercase().contains(
4745                                        &self.lambda_state.version_table.filter.to_lowercase(),
4746                                    )
4747                                    || v.description.to_lowercase().contains(
4748                                        &self.lambda_state.version_table.filter.to_lowercase(),
4749                                    )
4750                            })
4751                            .collect();
4752                        if !filtered.is_empty() {
4753                            self.lambda_state.version_table.selected =
4754                                (self.lambda_state.version_table.selected + 1)
4755                                    .min(filtered.len() - 1);
4756                            self.lambda_state.version_table.snap_to_page();
4757                        }
4758                    } else if self.lambda_state.current_function.is_some()
4759                        && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
4760                            || (self.lambda_state.current_version.is_some()
4761                                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
4762                    {
4763                        // Alias table navigation (both in Aliases tab and Version Configuration)
4764                        let version_filter = self.lambda_state.current_version.clone();
4765                        let filtered: Vec<_> = self
4766                            .lambda_state
4767                            .alias_table
4768                            .items
4769                            .iter()
4770                            .filter(|a| {
4771                                (version_filter.is_none()
4772                                    || a.versions.contains(version_filter.as_ref().unwrap()))
4773                                    && (self.lambda_state.alias_table.filter.is_empty()
4774                                        || a.name.to_lowercase().contains(
4775                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4776                                        )
4777                                        || a.versions.to_lowercase().contains(
4778                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4779                                        )
4780                                        || a.description.to_lowercase().contains(
4781                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4782                                        ))
4783                            })
4784                            .collect();
4785                        if !filtered.is_empty() {
4786                            self.lambda_state.alias_table.selected =
4787                                (self.lambda_state.alias_table.selected + 1)
4788                                    .min(filtered.len() - 1);
4789                            self.lambda_state.alias_table.snap_to_page();
4790                        }
4791                    } else if self.lambda_state.current_function.is_none() {
4792                        let filtered = filtered_lambda_functions(self);
4793                        if !filtered.is_empty() {
4794                            self.lambda_state.table.next_item(filtered.len());
4795                            self.lambda_state.table.snap_to_page();
4796                        }
4797                    }
4798                } else if self.current_service == Service::LambdaApplications {
4799                    if self.lambda_application_state.current_application.is_some() {
4800                        if self.lambda_application_state.detail_tab
4801                            == LambdaApplicationDetailTab::Overview
4802                        {
4803                            let len = self.lambda_application_state.resources.items.len();
4804                            if len > 0 {
4805                                self.lambda_application_state.resources.next_item(len);
4806                            }
4807                        } else {
4808                            let len = self.lambda_application_state.deployments.items.len();
4809                            if len > 0 {
4810                                self.lambda_application_state.deployments.next_item(len);
4811                            }
4812                        }
4813                    } else {
4814                        let filtered = filtered_lambda_applications(self);
4815                        if !filtered.is_empty() {
4816                            self.lambda_application_state.table.selected =
4817                                (self.lambda_application_state.table.selected + 1)
4818                                    .min(filtered.len() - 1);
4819                            self.lambda_application_state.table.snap_to_page();
4820                        }
4821                    }
4822                } else if self.current_service == Service::CloudFormationStacks {
4823                    if self.cfn_state.current_stack.is_some()
4824                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
4825                    {
4826                        let filtered = filtered_parameters(self);
4827                        self.cfn_state.parameters.next_item(filtered.len());
4828                    } else if self.cfn_state.current_stack.is_some()
4829                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
4830                    {
4831                        let filtered = filtered_outputs(self);
4832                        self.cfn_state.outputs.next_item(filtered.len());
4833                    } else if self.cfn_state.current_stack.is_some()
4834                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
4835                    {
4836                        let filtered = filtered_resources(self);
4837                        self.cfn_state.resources.next_item(filtered.len());
4838                    } else {
4839                        let filtered = filtered_cloudformation_stacks(self);
4840                        self.cfn_state.table.next_item(filtered.len());
4841                    }
4842                } else if self.current_service == Service::IamUsers {
4843                    if self.iam_state.current_user.is_some() {
4844                        if self.iam_state.user_tab == UserTab::Tags {
4845                            let filtered = filtered_user_tags(self);
4846                            if !filtered.is_empty() {
4847                                self.iam_state.user_tags.next_item(filtered.len());
4848                            }
4849                        } else {
4850                            let filtered = filtered_iam_policies(self);
4851                            if !filtered.is_empty() {
4852                                self.iam_state.policies.next_item(filtered.len());
4853                            }
4854                        }
4855                    } else {
4856                        let filtered = filtered_iam_users(self);
4857                        if !filtered.is_empty() {
4858                            self.iam_state.users.next_item(filtered.len());
4859                        }
4860                    }
4861                } else if self.current_service == Service::IamRoles {
4862                    if self.iam_state.current_role.is_some() {
4863                        if self.iam_state.role_tab == RoleTab::TrustRelationships {
4864                            let lines = self.iam_state.trust_policy_document.lines().count();
4865                            let max_scroll = lines.saturating_sub(1);
4866                            self.iam_state.trust_policy_scroll =
4867                                (self.iam_state.trust_policy_scroll + 1).min(max_scroll);
4868                        } else if self.iam_state.role_tab == RoleTab::RevokeSessions {
4869                            self.iam_state.revoke_sessions_scroll =
4870                                (self.iam_state.revoke_sessions_scroll + 1).min(19);
4871                        } else if self.iam_state.role_tab == RoleTab::Tags {
4872                            let filtered = filtered_iam_tags(self);
4873                            if !filtered.is_empty() {
4874                                self.iam_state.tags.next_item(filtered.len());
4875                            }
4876                        } else if self.iam_state.role_tab == RoleTab::LastAccessed {
4877                            let filtered = filtered_last_accessed(self);
4878                            if !filtered.is_empty() {
4879                                self.iam_state
4880                                    .last_accessed_services
4881                                    .next_item(filtered.len());
4882                            }
4883                        } else {
4884                            let filtered = filtered_iam_policies(self);
4885                            if !filtered.is_empty() {
4886                                self.iam_state.policies.next_item(filtered.len());
4887                            }
4888                        }
4889                    } else {
4890                        let filtered = filtered_iam_roles(self);
4891                        if !filtered.is_empty() {
4892                            self.iam_state.roles.next_item(filtered.len());
4893                        }
4894                    }
4895                } else if self.current_service == Service::IamUserGroups {
4896                    if self.iam_state.current_group.is_some() {
4897                        if self.iam_state.group_tab == GroupTab::Users {
4898                            let filtered: Vec<_> = self
4899                                .iam_state
4900                                .group_users
4901                                .items
4902                                .iter()
4903                                .filter(|u| {
4904                                    if self.iam_state.group_users.filter.is_empty() {
4905                                        true
4906                                    } else {
4907                                        u.user_name.to_lowercase().contains(
4908                                            &self.iam_state.group_users.filter.to_lowercase(),
4909                                        )
4910                                    }
4911                                })
4912                                .collect();
4913                            if !filtered.is_empty() {
4914                                self.iam_state.group_users.next_item(filtered.len());
4915                            }
4916                        } else if self.iam_state.group_tab == GroupTab::Permissions {
4917                            let filtered = filtered_iam_policies(self);
4918                            if !filtered.is_empty() {
4919                                self.iam_state.policies.next_item(filtered.len());
4920                            }
4921                        } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
4922                            let filtered = filtered_last_accessed(self);
4923                            if !filtered.is_empty() {
4924                                self.iam_state
4925                                    .last_accessed_services
4926                                    .next_item(filtered.len());
4927                            }
4928                        }
4929                    } else {
4930                        let filtered: Vec<_> = self
4931                            .iam_state
4932                            .groups
4933                            .items
4934                            .iter()
4935                            .filter(|g| {
4936                                if self.iam_state.groups.filter.is_empty() {
4937                                    true
4938                                } else {
4939                                    g.group_name
4940                                        .to_lowercase()
4941                                        .contains(&self.iam_state.groups.filter.to_lowercase())
4942                                }
4943                            })
4944                            .collect();
4945                        if !filtered.is_empty() {
4946                            self.iam_state.groups.next_item(filtered.len());
4947                        }
4948                    }
4949                }
4950            }
4951            _ => {}
4952        }
4953    }
4954
4955    fn prev_item(&mut self) {
4956        match self.mode {
4957            Mode::FilterInput => {
4958                if self.current_service == Service::S3Buckets
4959                    && self.s3_state.input_focus == InputFocus::Pagination
4960                {
4961                    // Navigate to previous page
4962                    let page_size = self.s3_state.buckets.page_size.value();
4963                    self.s3_state.selected_row =
4964                        self.s3_state.selected_row.saturating_sub(page_size);
4965                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
4966                } else if self.current_service == Service::CloudFormationStacks {
4967                    use crate::ui::cfn::STATUS_FILTER;
4968                    if self.cfn_state.input_focus == STATUS_FILTER {
4969                        self.cfn_state.status_filter = self.cfn_state.status_filter.prev();
4970                        self.cfn_state.table.reset();
4971                    }
4972                } else if self.current_service == Service::IamUsers
4973                    && self.iam_state.current_user.is_some()
4974                {
4975                    use crate::ui::iam::{HISTORY_FILTER, POLICY_TYPE_DROPDOWN};
4976                    if self.iam_state.user_tab == UserTab::Permissions
4977                        && self.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN
4978                    {
4979                        self.cycle_policy_type_prev();
4980                    } else if self.iam_state.user_tab == UserTab::LastAccessed
4981                        && self.iam_state.last_accessed_input_focus == HISTORY_FILTER
4982                    {
4983                        self.iam_state.last_accessed_history_filter =
4984                            self.iam_state.last_accessed_history_filter.prev();
4985                        self.iam_state.last_accessed_services.reset();
4986                    }
4987                } else if self.current_service == Service::IamRoles
4988                    && self.iam_state.current_role.is_some()
4989                    && self.iam_state.role_tab == RoleTab::Permissions
4990                {
4991                    use crate::ui::iam::POLICY_TYPE_DROPDOWN;
4992                    if self.iam_state.policy_input_focus == POLICY_TYPE_DROPDOWN {
4993                        self.cycle_policy_type_prev();
4994                    }
4995                } else if self.current_service == Service::Ec2Instances {
4996                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
4997                        self.ec2_state.state_filter = self.ec2_state.state_filter.prev();
4998                        self.ec2_state.table.reset();
4999                    }
5000                } else if self.current_service == Service::SqsQueues {
5001                    use crate::ui::sqs::SUBSCRIPTION_REGION;
5002                    if self.sqs_state.input_focus == SUBSCRIPTION_REGION {
5003                        self.sqs_state.subscription_region_selected = self
5004                            .sqs_state
5005                            .subscription_region_selected
5006                            .saturating_sub(1);
5007                        self.sqs_state.subscriptions.reset();
5008                    }
5009                }
5010            }
5011            Mode::RegionPicker => {
5012                self.region_picker_selected = self.region_picker_selected.saturating_sub(1);
5013            }
5014            Mode::ProfilePicker => {
5015                self.profile_picker_selected = self.profile_picker_selected.saturating_sub(1);
5016            }
5017            Mode::SessionPicker => {
5018                self.session_picker_selected = self.session_picker_selected.saturating_sub(1);
5019            }
5020            Mode::InsightsInput => {
5021                use crate::app::InsightsFocus;
5022                if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
5023                    && self.insights_state.insights.show_dropdown
5024                    && !self.insights_state.insights.log_group_matches.is_empty()
5025                {
5026                    self.insights_state.insights.dropdown_selected = self
5027                        .insights_state
5028                        .insights
5029                        .dropdown_selected
5030                        .saturating_sub(1);
5031                }
5032            }
5033            Mode::ColumnSelector => {
5034                self.column_selector_index = self.column_selector_index.saturating_sub(1);
5035            }
5036            Mode::ServicePicker => {
5037                self.service_picker.selected = self.service_picker.selected.saturating_sub(1);
5038            }
5039            Mode::TabPicker => {
5040                self.tab_picker_selected = self.tab_picker_selected.saturating_sub(1);
5041            }
5042            Mode::Normal => {
5043                if !self.service_selected {
5044                    self.service_picker.selected = self.service_picker.selected.saturating_sub(1);
5045                } else if self.current_service == Service::S3Buckets {
5046                    if self.s3_state.current_bucket.is_some() {
5047                        if self.s3_state.object_tab == S3ObjectTab::Properties {
5048                            self.s3_state.properties_scroll =
5049                                self.s3_state.properties_scroll.saturating_sub(1);
5050                        } else {
5051                            self.s3_state.selected_object =
5052                                self.s3_state.selected_object.saturating_sub(1);
5053
5054                            // Adjust scroll offset if selection goes above viewport
5055                            if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
5056                                self.s3_state.object_scroll_offset = self.s3_state.selected_object;
5057                            }
5058                        }
5059                    } else {
5060                        self.s3_state.selected_row = self.s3_state.selected_row.saturating_sub(1);
5061
5062                        // Adjust scroll offset if selection goes above viewport
5063                        if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
5064                            self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
5065                        }
5066                    }
5067                } else if self.view_mode == ViewMode::InsightsResults {
5068                    if self.insights_state.insights.results_selected > 0 {
5069                        self.insights_state.insights.results_selected -= 1;
5070                    }
5071                } else if self.view_mode == ViewMode::PolicyView {
5072                    self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(1);
5073                } else if self.current_service == Service::CloudFormationStacks
5074                    && self.cfn_state.current_stack.is_some()
5075                    && self.cfn_state.detail_tab == CfnDetailTab::Template
5076                {
5077                    self.cfn_state.template_scroll =
5078                        self.cfn_state.template_scroll.saturating_sub(1);
5079                } else if self.current_service == Service::SqsQueues
5080                    && self.sqs_state.current_queue.is_some()
5081                    && self.sqs_state.detail_tab == SqsQueueDetailTab::QueuePolicies
5082                {
5083                    self.sqs_state.policy_scroll = self.sqs_state.policy_scroll.saturating_sub(1);
5084                } else if self.current_service == Service::LambdaFunctions
5085                    && self.lambda_state.current_function.is_some()
5086                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
5087                    && !self.lambda_state.is_metrics_loading()
5088                {
5089                    self.lambda_state.set_monitoring_scroll(
5090                        self.lambda_state.monitoring_scroll().saturating_sub(1),
5091                    );
5092                } else if self.current_service == Service::Ec2Instances
5093                    && self.ec2_state.current_instance.is_some()
5094                    && self.ec2_state.detail_tab == Ec2DetailTab::Monitoring
5095                    && !self.ec2_state.is_metrics_loading()
5096                {
5097                    self.ec2_state.set_monitoring_scroll(
5098                        self.ec2_state.monitoring_scroll().saturating_sub(1),
5099                    );
5100                } else if self.current_service == Service::SqsQueues
5101                    && self.sqs_state.current_queue.is_some()
5102                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
5103                    && !self.sqs_state.is_metrics_loading()
5104                {
5105                    self.sqs_state.set_monitoring_scroll(
5106                        self.sqs_state.monitoring_scroll().saturating_sub(1),
5107                    );
5108                } else if self.view_mode == ViewMode::Events {
5109                    if self.log_groups_state.event_scroll_offset == 0 {
5110                        if self.log_groups_state.has_older_events {
5111                            self.log_groups_state.loading = true;
5112                        }
5113                        // Don't move if at position 0
5114                    } else {
5115                        self.log_groups_state.event_scroll_offset =
5116                            self.log_groups_state.event_scroll_offset.saturating_sub(1);
5117                    }
5118                } else if self.current_service == Service::CloudWatchLogGroups {
5119                    if self.view_mode == ViewMode::List {
5120                        self.log_groups_state.log_groups.prev_item();
5121                    } else if self.view_mode == ViewMode::Detail
5122                        && self.log_groups_state.selected_stream > 0
5123                    {
5124                        self.log_groups_state.selected_stream =
5125                            self.log_groups_state.selected_stream.saturating_sub(1);
5126                        self.log_groups_state.expanded_stream = None;
5127                    }
5128                } else if self.current_service == Service::CloudWatchAlarms {
5129                    self.alarms_state.table.prev_item();
5130                } else if self.current_service == Service::Ec2Instances {
5131                    if self.ec2_state.current_instance.is_some()
5132                        && self.ec2_state.detail_tab == Ec2DetailTab::Tags
5133                    {
5134                        self.ec2_state.tags.prev_item();
5135                    } else {
5136                        self.ec2_state.table.prev_item();
5137                    }
5138                } else if self.current_service == Service::EcrRepositories {
5139                    if self.ecr_state.current_repository.is_some() {
5140                        self.ecr_state.images.prev_item();
5141                    } else {
5142                        self.ecr_state.repositories.prev_item();
5143                    }
5144                } else if self.current_service == Service::SqsQueues {
5145                    if self.sqs_state.current_queue.is_some()
5146                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
5147                    {
5148                        self.sqs_state.triggers.prev_item();
5149                    } else if self.sqs_state.current_queue.is_some()
5150                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
5151                    {
5152                        self.sqs_state.pipes.prev_item();
5153                    } else if self.sqs_state.current_queue.is_some()
5154                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
5155                    {
5156                        self.sqs_state.tags.prev_item();
5157                    } else if self.sqs_state.current_queue.is_some()
5158                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
5159                    {
5160                        self.sqs_state.subscriptions.prev_item();
5161                    } else {
5162                        self.sqs_state.queues.prev_item();
5163                    }
5164                } else if self.current_service == Service::LambdaFunctions {
5165                    if self.lambda_state.current_function.is_some()
5166                        && self.lambda_state.detail_tab == LambdaDetailTab::Code
5167                    {
5168                        // Layer table navigation in Code tab
5169                        self.lambda_state.layer_selected =
5170                            self.lambda_state.layer_selected.saturating_sub(1);
5171                    } else if self.lambda_state.current_function.is_some()
5172                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5173                    {
5174                        self.lambda_state.version_table.prev_item();
5175                    } else if self.lambda_state.current_function.is_some()
5176                        && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
5177                            || (self.lambda_state.current_version.is_some()
5178                                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
5179                    {
5180                        self.lambda_state.alias_table.prev_item();
5181                    } else if self.lambda_state.current_function.is_none() {
5182                        self.lambda_state.table.prev_item();
5183                    }
5184                } else if self.current_service == Service::LambdaApplications {
5185                    if self.lambda_application_state.current_application.is_some()
5186                        && self.lambda_application_state.detail_tab
5187                            == LambdaApplicationDetailTab::Overview
5188                    {
5189                        self.lambda_application_state.resources.selected = self
5190                            .lambda_application_state
5191                            .resources
5192                            .selected
5193                            .saturating_sub(1);
5194                    } else if self.lambda_application_state.current_application.is_some()
5195                        && self.lambda_application_state.detail_tab
5196                            == LambdaApplicationDetailTab::Deployments
5197                    {
5198                        self.lambda_application_state.deployments.selected = self
5199                            .lambda_application_state
5200                            .deployments
5201                            .selected
5202                            .saturating_sub(1);
5203                    } else {
5204                        self.lambda_application_state.table.selected = self
5205                            .lambda_application_state
5206                            .table
5207                            .selected
5208                            .saturating_sub(1);
5209                        self.lambda_application_state.table.snap_to_page();
5210                    }
5211                } else if self.current_service == Service::CloudFormationStacks {
5212                    if self.cfn_state.current_stack.is_some()
5213                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5214                    {
5215                        self.cfn_state.parameters.prev_item();
5216                    } else if self.cfn_state.current_stack.is_some()
5217                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5218                    {
5219                        self.cfn_state.outputs.prev_item();
5220                    } else if self.cfn_state.current_stack.is_some()
5221                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
5222                    {
5223                        self.cfn_state.resources.prev_item();
5224                    } else {
5225                        self.cfn_state.table.prev_item();
5226                    }
5227                } else if self.current_service == Service::IamUsers {
5228                    self.iam_state.users.prev_item();
5229                } else if self.current_service == Service::IamRoles {
5230                    if self.iam_state.current_role.is_some() {
5231                        if self.iam_state.role_tab == RoleTab::TrustRelationships {
5232                            self.iam_state.trust_policy_scroll =
5233                                self.iam_state.trust_policy_scroll.saturating_sub(1);
5234                        } else if self.iam_state.role_tab == RoleTab::RevokeSessions {
5235                            self.iam_state.revoke_sessions_scroll =
5236                                self.iam_state.revoke_sessions_scroll.saturating_sub(1);
5237                        } else if self.iam_state.role_tab == RoleTab::Tags {
5238                            self.iam_state.tags.prev_item();
5239                        } else if self.iam_state.role_tab == RoleTab::LastAccessed {
5240                            self.iam_state.last_accessed_services.prev_item();
5241                        } else {
5242                            self.iam_state.policies.prev_item();
5243                        }
5244                    } else {
5245                        self.iam_state.roles.prev_item();
5246                    }
5247                } else if self.current_service == Service::IamUserGroups {
5248                    if self.iam_state.current_group.is_some() {
5249                        if self.iam_state.group_tab == GroupTab::Users {
5250                            self.iam_state.group_users.prev_item();
5251                        } else if self.iam_state.group_tab == GroupTab::Permissions {
5252                            self.iam_state.policies.prev_item();
5253                        } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
5254                            self.iam_state.last_accessed_services.prev_item();
5255                        }
5256                    } else {
5257                        self.iam_state.groups.prev_item();
5258                    }
5259                }
5260            }
5261            _ => {}
5262        }
5263    }
5264
5265    fn page_down(&mut self) {
5266        if self.mode == Mode::ColumnSelector {
5267            let max = self.get_column_selector_max();
5268            self.column_selector_index = (self.column_selector_index + 10).min(max);
5269        } else if self.mode == Mode::FilterInput && self.current_service == Service::S3Buckets {
5270            if self.s3_state.input_focus == InputFocus::Pagination {
5271                // Navigate to next page
5272                let page_size = self.s3_state.buckets.page_size.value();
5273                let total_rows = self.calculate_total_bucket_rows();
5274                let max_offset = total_rows.saturating_sub(page_size);
5275                self.s3_state.selected_row =
5276                    (self.s3_state.selected_row + page_size).min(max_offset);
5277                self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
5278            }
5279        } else if self.mode == Mode::FilterInput
5280            && self.current_service == Service::CloudFormationStacks
5281        {
5282            if self.cfn_state.current_stack.is_some()
5283                && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5284            {
5285                let page_size = self.cfn_state.parameters.page_size.value();
5286                let filtered_count = filtered_parameters(self).len();
5287                self.cfn_state.parameters_input_focus.handle_page_down(
5288                    &mut self.cfn_state.parameters.selected,
5289                    &mut self.cfn_state.parameters.scroll_offset,
5290                    page_size,
5291                    filtered_count,
5292                );
5293            } else if self.cfn_state.current_stack.is_some()
5294                && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5295            {
5296                let page_size = self.cfn_state.outputs.page_size.value();
5297                let filtered_count = filtered_outputs(self).len();
5298                self.cfn_state.outputs_input_focus.handle_page_down(
5299                    &mut self.cfn_state.outputs.selected,
5300                    &mut self.cfn_state.outputs.scroll_offset,
5301                    page_size,
5302                    filtered_count,
5303                );
5304            } else {
5305                use crate::ui::cfn::filtered_cloudformation_stacks;
5306                let page_size = self.cfn_state.table.page_size.value();
5307                let filtered_count = filtered_cloudformation_stacks(self).len();
5308                self.cfn_state.input_focus.handle_page_down(
5309                    &mut self.cfn_state.table.selected,
5310                    &mut self.cfn_state.table.scroll_offset,
5311                    page_size,
5312                    filtered_count,
5313                );
5314            }
5315        } else if self.mode == Mode::FilterInput
5316            && self.current_service == Service::IamRoles
5317            && self.iam_state.current_role.is_none()
5318        {
5319            let page_size = self.iam_state.roles.page_size.value();
5320            let filtered_count = filtered_iam_roles(self).len();
5321            self.iam_state.role_input_focus.handle_page_down(
5322                &mut self.iam_state.roles.selected,
5323                &mut self.iam_state.roles.scroll_offset,
5324                page_size,
5325                filtered_count,
5326            );
5327        } else if self.mode == Mode::FilterInput
5328            && self.current_service == Service::CloudWatchAlarms
5329        {
5330            let page_size = self.alarms_state.table.page_size.value();
5331            let filtered_count = self.alarms_state.table.items.len();
5332            self.alarms_state.input_focus.handle_page_down(
5333                &mut self.alarms_state.table.selected,
5334                &mut self.alarms_state.table.scroll_offset,
5335                page_size,
5336                filtered_count,
5337            );
5338        } else if self.mode == Mode::FilterInput
5339            && self.current_service == Service::CloudWatchLogGroups
5340        {
5341            if self.view_mode == ViewMode::List {
5342                // Log groups list pagination
5343                let filtered = filtered_log_groups(self);
5344                let page_size = self.log_groups_state.log_groups.page_size.value();
5345                let filtered_count = filtered.len();
5346                self.log_groups_state.input_focus.handle_page_down(
5347                    &mut self.log_groups_state.log_groups.selected,
5348                    &mut self.log_groups_state.log_groups.scroll_offset,
5349                    page_size,
5350                    filtered_count,
5351                );
5352            } else {
5353                // Log streams pagination
5354                let filtered = filtered_log_streams(self);
5355                let page_size = self.log_groups_state.stream_page_size;
5356                let filtered_count = filtered.len();
5357                self.log_groups_state.input_focus.handle_page_down(
5358                    &mut self.log_groups_state.selected_stream,
5359                    &mut self.log_groups_state.stream_current_page,
5360                    page_size,
5361                    filtered_count,
5362                );
5363                self.log_groups_state.expanded_stream = None;
5364            }
5365        } else if self.mode == Mode::FilterInput && self.current_service == Service::LambdaFunctions
5366        {
5367            if self.lambda_state.current_function.is_some()
5368                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5369                && self.lambda_state.version_input_focus == InputFocus::Pagination
5370            {
5371                let page_size = self.lambda_state.version_table.page_size.value();
5372                let filtered_count: usize = self
5373                    .lambda_state
5374                    .version_table
5375                    .items
5376                    .iter()
5377                    .filter(|v| {
5378                        self.lambda_state.version_table.filter.is_empty()
5379                            || v.version
5380                                .to_lowercase()
5381                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
5382                            || v.aliases
5383                                .to_lowercase()
5384                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
5385                            || v.description
5386                                .to_lowercase()
5387                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
5388                    })
5389                    .count();
5390                let target = self.lambda_state.version_table.selected + page_size;
5391                self.lambda_state.version_table.selected =
5392                    target.min(filtered_count.saturating_sub(1));
5393            } else if self.lambda_state.current_function.is_some()
5394                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
5395                    || (self.lambda_state.current_version.is_some()
5396                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
5397                && self.lambda_state.alias_input_focus == InputFocus::Pagination
5398            {
5399                let page_size = self.lambda_state.alias_table.page_size.value();
5400                let version_filter = self.lambda_state.current_version.clone();
5401                let filtered_count = self
5402                    .lambda_state
5403                    .alias_table
5404                    .items
5405                    .iter()
5406                    .filter(|a| {
5407                        (version_filter.is_none()
5408                            || a.versions.contains(version_filter.as_ref().unwrap()))
5409                            && (self.lambda_state.alias_table.filter.is_empty()
5410                                || a.name
5411                                    .to_lowercase()
5412                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
5413                                || a.versions
5414                                    .to_lowercase()
5415                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
5416                                || a.description
5417                                    .to_lowercase()
5418                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase()))
5419                    })
5420                    .count();
5421                let target = self.lambda_state.alias_table.selected + page_size;
5422                self.lambda_state.alias_table.selected =
5423                    target.min(filtered_count.saturating_sub(1));
5424            } else if self.lambda_state.current_function.is_none() {
5425                let page_size = self.lambda_state.table.page_size.value();
5426                let filtered_count = filtered_lambda_functions(self).len();
5427                self.lambda_state.input_focus.handle_page_down(
5428                    &mut self.lambda_state.table.selected,
5429                    &mut self.lambda_state.table.scroll_offset,
5430                    page_size,
5431                    filtered_count,
5432                );
5433            }
5434        } else if self.mode == Mode::FilterInput
5435            && self.current_service == Service::LambdaApplications
5436        {
5437            if self.lambda_application_state.current_application.is_some() {
5438                if self.lambda_application_state.detail_tab
5439                    == LambdaApplicationDetailTab::Deployments
5440                {
5441                    let page_size = self.lambda_application_state.deployments.page_size.value();
5442                    let filtered_count = self.lambda_application_state.deployments.items.len();
5443                    self.lambda_application_state
5444                        .deployment_input_focus
5445                        .handle_page_down(
5446                            &mut self.lambda_application_state.deployments.selected,
5447                            &mut self.lambda_application_state.deployments.scroll_offset,
5448                            page_size,
5449                            filtered_count,
5450                        );
5451                } else {
5452                    let page_size = self.lambda_application_state.resources.page_size.value();
5453                    let filtered_count = self.lambda_application_state.resources.items.len();
5454                    self.lambda_application_state
5455                        .resource_input_focus
5456                        .handle_page_down(
5457                            &mut self.lambda_application_state.resources.selected,
5458                            &mut self.lambda_application_state.resources.scroll_offset,
5459                            page_size,
5460                            filtered_count,
5461                        );
5462                }
5463            } else {
5464                let page_size = self.lambda_application_state.table.page_size.value();
5465                let filtered_count = filtered_lambda_applications(self).len();
5466                self.lambda_application_state.input_focus.handle_page_down(
5467                    &mut self.lambda_application_state.table.selected,
5468                    &mut self.lambda_application_state.table.scroll_offset,
5469                    page_size,
5470                    filtered_count,
5471                );
5472            }
5473        } else if self.mode == Mode::FilterInput
5474            && self.current_service == Service::EcrRepositories
5475            && self.ecr_state.current_repository.is_none()
5476            && self.ecr_state.input_focus == InputFocus::Filter
5477        {
5478            // When input is focused, allow table scrolling
5479            let filtered = filtered_ecr_repositories(self);
5480            self.ecr_state.repositories.page_down(filtered.len());
5481        } else if self.mode == Mode::FilterInput
5482            && self.current_service == Service::EcrRepositories
5483            && self.ecr_state.current_repository.is_none()
5484        {
5485            let page_size = self.ecr_state.repositories.page_size.value();
5486            let filtered_count = filtered_ecr_repositories(self).len();
5487            self.ecr_state.input_focus.handle_page_down(
5488                &mut self.ecr_state.repositories.selected,
5489                &mut self.ecr_state.repositories.scroll_offset,
5490                page_size,
5491                filtered_count,
5492            );
5493        } else if self.mode == Mode::FilterInput && self.view_mode == ViewMode::PolicyView {
5494            let page_size = self.iam_state.policies.page_size.value();
5495            let filtered_count = filtered_iam_policies(self).len();
5496            self.iam_state.policy_input_focus.handle_page_down(
5497                &mut self.iam_state.policies.selected,
5498                &mut self.iam_state.policies.scroll_offset,
5499                page_size,
5500                filtered_count,
5501            );
5502        } else if self.view_mode == ViewMode::PolicyView {
5503            let lines = self.iam_state.policy_document.lines().count();
5504            let max_scroll = lines.saturating_sub(1);
5505            self.iam_state.policy_scroll = (self.iam_state.policy_scroll + 10).min(max_scroll);
5506        } else if self.current_service == Service::CloudFormationStacks
5507            && self.cfn_state.current_stack.is_some()
5508            && self.cfn_state.detail_tab == CfnDetailTab::Template
5509        {
5510            let lines = self.cfn_state.template_body.lines().count();
5511            let max_scroll = lines.saturating_sub(1);
5512            self.cfn_state.template_scroll = (self.cfn_state.template_scroll + 10).min(max_scroll);
5513        } else if self.current_service == Service::LambdaFunctions
5514            && self.lambda_state.current_function.is_some()
5515            && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
5516            && !self.lambda_state.is_metrics_loading()
5517        {
5518            self.lambda_state
5519                .set_monitoring_scroll((self.lambda_state.monitoring_scroll() + 1).min(9));
5520        } else if self.current_service == Service::Ec2Instances
5521            && self.ec2_state.current_instance.is_some()
5522            && self.ec2_state.detail_tab == Ec2DetailTab::Monitoring
5523            && !self.ec2_state.is_metrics_loading()
5524        {
5525            self.ec2_state
5526                .set_monitoring_scroll((self.ec2_state.monitoring_scroll() + 1).min(5));
5527        } else if self.current_service == Service::SqsQueues
5528            && self.sqs_state.current_queue.is_some()
5529        {
5530            if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
5531                self.sqs_state
5532                    .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(8));
5533            } else {
5534                let lines = self.sqs_state.policy_document.lines().count();
5535                let max_scroll = lines.saturating_sub(1);
5536                self.sqs_state.policy_scroll = (self.sqs_state.policy_scroll + 10).min(max_scroll);
5537            }
5538        } else if self.current_service == Service::IamRoles
5539            && self.iam_state.current_role.is_some()
5540            && self.iam_state.role_tab == RoleTab::TrustRelationships
5541        {
5542            let lines = self.iam_state.trust_policy_document.lines().count();
5543            let max_scroll = lines.saturating_sub(1);
5544            self.iam_state.trust_policy_scroll =
5545                (self.iam_state.trust_policy_scroll + 10).min(max_scroll);
5546        } else if self.current_service == Service::IamRoles
5547            && self.iam_state.current_role.is_some()
5548            && self.iam_state.role_tab == RoleTab::RevokeSessions
5549        {
5550            self.iam_state.revoke_sessions_scroll =
5551                (self.iam_state.revoke_sessions_scroll + 10).min(19);
5552        } else if self.mode == Mode::Normal {
5553            if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none()
5554            {
5555                let total_rows = self.calculate_total_bucket_rows();
5556                self.s3_state.selected_row = self
5557                    .s3_state
5558                    .selected_row
5559                    .saturating_add(10)
5560                    .min(total_rows.saturating_sub(1));
5561
5562                // Adjust scroll offset if selection goes below viewport
5563                let visible_rows = self.s3_state.bucket_visible_rows.get();
5564                if self.s3_state.selected_row >= self.s3_state.bucket_scroll_offset + visible_rows {
5565                    self.s3_state.bucket_scroll_offset =
5566                        self.s3_state.selected_row - visible_rows + 1;
5567                }
5568            } else if self.current_service == Service::S3Buckets
5569                && self.s3_state.current_bucket.is_some()
5570            {
5571                let total_rows = self.calculate_total_object_rows();
5572                self.s3_state.selected_object = self
5573                    .s3_state
5574                    .selected_object
5575                    .saturating_add(10)
5576                    .min(total_rows.saturating_sub(1));
5577
5578                // Adjust scroll offset if selection goes below viewport
5579                let visible_rows = self.s3_state.object_visible_rows.get();
5580                if self.s3_state.selected_object
5581                    >= self.s3_state.object_scroll_offset + visible_rows
5582                {
5583                    self.s3_state.object_scroll_offset =
5584                        self.s3_state.selected_object - visible_rows + 1;
5585                }
5586            } else if self.current_service == Service::CloudWatchLogGroups
5587                && self.view_mode == ViewMode::List
5588            {
5589                let filtered = filtered_log_groups(self);
5590                self.log_groups_state.log_groups.page_down(filtered.len());
5591            } else if self.current_service == Service::CloudWatchLogGroups
5592                && self.view_mode == ViewMode::Detail
5593            {
5594                let len = filtered_log_streams(self).len();
5595                nav_page_down(&mut self.log_groups_state.selected_stream, len, 10);
5596            } else if self.view_mode == ViewMode::Events {
5597                let max = self.log_groups_state.log_events.len();
5598                nav_page_down(&mut self.log_groups_state.event_scroll_offset, max, 10);
5599            } else if self.view_mode == ViewMode::InsightsResults {
5600                let max = self.insights_state.insights.query_results.len();
5601                nav_page_down(&mut self.insights_state.insights.results_selected, max, 10);
5602            } else if self.current_service == Service::CloudWatchAlarms {
5603                let filtered = match self.alarms_state.alarm_tab {
5604                    AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
5605                    AlarmTab::InAlarm => self
5606                        .alarms_state
5607                        .table
5608                        .items
5609                        .iter()
5610                        .filter(|a| a.state.to_uppercase() == "ALARM")
5611                        .count(),
5612                };
5613                if filtered > 0 {
5614                    self.alarms_state.table.page_down(filtered);
5615                }
5616            } else if self.current_service == Service::Ec2Instances {
5617                let filtered: Vec<_> = self
5618                    .ec2_state
5619                    .table
5620                    .items
5621                    .iter()
5622                    .filter(|i| self.ec2_state.state_filter.matches(&i.state))
5623                    .filter(|i| {
5624                        if self.ec2_state.table.filter.is_empty() {
5625                            return true;
5626                        }
5627                        i.name.contains(&self.ec2_state.table.filter)
5628                            || i.instance_id.contains(&self.ec2_state.table.filter)
5629                            || i.state.contains(&self.ec2_state.table.filter)
5630                            || i.instance_type.contains(&self.ec2_state.table.filter)
5631                            || i.availability_zone.contains(&self.ec2_state.table.filter)
5632                            || i.security_groups.contains(&self.ec2_state.table.filter)
5633                            || i.key_name.contains(&self.ec2_state.table.filter)
5634                    })
5635                    .collect();
5636                if !filtered.is_empty() {
5637                    self.ec2_state.table.page_down(filtered.len());
5638                }
5639            } else if self.current_service == Service::EcrRepositories {
5640                if self.ecr_state.current_repository.is_some() {
5641                    let filtered = filtered_ecr_images(self);
5642                    self.ecr_state.images.page_down(filtered.len());
5643                } else {
5644                    let filtered = filtered_ecr_repositories(self);
5645                    self.ecr_state.repositories.page_down(filtered.len());
5646                }
5647            } else if self.current_service == Service::SqsQueues {
5648                let filtered =
5649                    filtered_queues(&self.sqs_state.queues.items, &self.sqs_state.queues.filter);
5650                self.sqs_state.queues.page_down(filtered.len());
5651            } else if self.current_service == Service::LambdaFunctions {
5652                let len = filtered_lambda_functions(self).len();
5653                self.lambda_state.table.page_down(len);
5654            } else if self.current_service == Service::LambdaApplications {
5655                let len = filtered_lambda_applications(self).len();
5656                self.lambda_application_state.table.page_down(len);
5657            } else if self.current_service == Service::CloudFormationStacks {
5658                if self.cfn_state.current_stack.is_some()
5659                    && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5660                {
5661                    let filtered = filtered_parameters(self);
5662                    self.cfn_state.parameters.page_down(filtered.len());
5663                } else if self.cfn_state.current_stack.is_some()
5664                    && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5665                {
5666                    let filtered = filtered_outputs(self);
5667                    self.cfn_state.outputs.page_down(filtered.len());
5668                } else {
5669                    let filtered = filtered_cloudformation_stacks(self);
5670                    self.cfn_state.table.page_down(filtered.len());
5671                }
5672            } else if self.current_service == Service::IamUsers {
5673                let len = filtered_iam_users(self).len();
5674                nav_page_down(&mut self.iam_state.users.selected, len, 10);
5675            } else if self.current_service == Service::IamRoles {
5676                if self.iam_state.current_role.is_some() {
5677                    let filtered = filtered_iam_policies(self);
5678                    if !filtered.is_empty() {
5679                        self.iam_state.policies.page_down(filtered.len());
5680                    }
5681                } else {
5682                    let filtered = filtered_iam_roles(self);
5683                    self.iam_state.roles.page_down(filtered.len());
5684                }
5685            } else if self.current_service == Service::IamUserGroups {
5686                if self.iam_state.current_group.is_some() {
5687                    if self.iam_state.group_tab == GroupTab::Users {
5688                        let filtered: Vec<_> = self
5689                            .iam_state
5690                            .group_users
5691                            .items
5692                            .iter()
5693                            .filter(|u| {
5694                                if self.iam_state.group_users.filter.is_empty() {
5695                                    true
5696                                } else {
5697                                    u.user_name
5698                                        .to_lowercase()
5699                                        .contains(&self.iam_state.group_users.filter.to_lowercase())
5700                                }
5701                            })
5702                            .collect();
5703                        if !filtered.is_empty() {
5704                            self.iam_state.group_users.page_down(filtered.len());
5705                        }
5706                    } else if self.iam_state.group_tab == GroupTab::Permissions {
5707                        let filtered = filtered_iam_policies(self);
5708                        if !filtered.is_empty() {
5709                            self.iam_state.policies.page_down(filtered.len());
5710                        }
5711                    } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
5712                        let filtered = filtered_last_accessed(self);
5713                        if !filtered.is_empty() {
5714                            self.iam_state
5715                                .last_accessed_services
5716                                .page_down(filtered.len());
5717                        }
5718                    }
5719                } else {
5720                    let filtered: Vec<_> = self
5721                        .iam_state
5722                        .groups
5723                        .items
5724                        .iter()
5725                        .filter(|g| {
5726                            if self.iam_state.groups.filter.is_empty() {
5727                                true
5728                            } else {
5729                                g.group_name
5730                                    .to_lowercase()
5731                                    .contains(&self.iam_state.groups.filter.to_lowercase())
5732                            }
5733                        })
5734                        .collect();
5735                    if !filtered.is_empty() {
5736                        self.iam_state.groups.page_down(filtered.len());
5737                    }
5738                }
5739            }
5740        }
5741    }
5742
5743    fn cycle_policy_type_next(&mut self) {
5744        let types = ["All types", "AWS managed", "Customer managed"];
5745        let current_idx = types
5746            .iter()
5747            .position(|&t| t == self.iam_state.policy_type_filter)
5748            .unwrap_or(0);
5749        let next_idx = (current_idx + 1) % types.len();
5750        self.iam_state.policy_type_filter = types[next_idx].to_string();
5751        self.iam_state.policies.reset();
5752    }
5753
5754    fn cycle_policy_type_prev(&mut self) {
5755        let types = ["All types", "AWS managed", "Customer managed"];
5756        let current_idx = types
5757            .iter()
5758            .position(|&t| t == self.iam_state.policy_type_filter)
5759            .unwrap_or(0);
5760        let prev_idx = if current_idx == 0 {
5761            types.len() - 1
5762        } else {
5763            current_idx - 1
5764        };
5765        self.iam_state.policy_type_filter = types[prev_idx].to_string();
5766        self.iam_state.policies.reset();
5767    }
5768
5769    fn page_up(&mut self) {
5770        if self.mode == Mode::ColumnSelector {
5771            self.column_selector_index = self.column_selector_index.saturating_sub(10);
5772        } else if self.mode == Mode::FilterInput && self.current_service == Service::S3Buckets {
5773            if self.s3_state.input_focus == InputFocus::Pagination {
5774                // Navigate to previous page
5775                let page_size = self.s3_state.buckets.page_size.value();
5776                self.s3_state.selected_row = self.s3_state.selected_row.saturating_sub(page_size);
5777                self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
5778            }
5779        } else if self.mode == Mode::FilterInput
5780            && self.current_service == Service::CloudFormationStacks
5781        {
5782            if self.cfn_state.current_stack.is_some()
5783                && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5784            {
5785                let page_size = self.cfn_state.parameters.page_size.value();
5786                self.cfn_state.parameters_input_focus.handle_page_up(
5787                    &mut self.cfn_state.parameters.selected,
5788                    &mut self.cfn_state.parameters.scroll_offset,
5789                    page_size,
5790                );
5791            } else if self.cfn_state.current_stack.is_some()
5792                && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5793            {
5794                let page_size = self.cfn_state.outputs.page_size.value();
5795                self.cfn_state.outputs_input_focus.handle_page_up(
5796                    &mut self.cfn_state.outputs.selected,
5797                    &mut self.cfn_state.outputs.scroll_offset,
5798                    page_size,
5799                );
5800            } else {
5801                let page_size = self.cfn_state.table.page_size.value();
5802                self.cfn_state.input_focus.handle_page_up(
5803                    &mut self.cfn_state.table.selected,
5804                    &mut self.cfn_state.table.scroll_offset,
5805                    page_size,
5806                );
5807            }
5808        } else if self.mode == Mode::FilterInput
5809            && self.current_service == Service::IamRoles
5810            && self.iam_state.current_role.is_none()
5811        {
5812            let page_size = self.iam_state.roles.page_size.value();
5813            self.iam_state.role_input_focus.handle_page_up(
5814                &mut self.iam_state.roles.selected,
5815                &mut self.iam_state.roles.scroll_offset,
5816                page_size,
5817            );
5818        } else if self.mode == Mode::FilterInput
5819            && self.current_service == Service::CloudWatchAlarms
5820        {
5821            let page_size = self.alarms_state.table.page_size.value();
5822            self.alarms_state.input_focus.handle_page_up(
5823                &mut self.alarms_state.table.selected,
5824                &mut self.alarms_state.table.scroll_offset,
5825                page_size,
5826            );
5827        } else if self.mode == Mode::FilterInput
5828            && self.current_service == Service::CloudWatchLogGroups
5829        {
5830            if self.view_mode == ViewMode::List {
5831                // Log groups list pagination
5832                let page_size = self.log_groups_state.log_groups.page_size.value();
5833                self.log_groups_state.input_focus.handle_page_up(
5834                    &mut self.log_groups_state.log_groups.selected,
5835                    &mut self.log_groups_state.log_groups.scroll_offset,
5836                    page_size,
5837                );
5838            } else {
5839                // Log streams pagination
5840                let page_size = self.log_groups_state.stream_page_size;
5841                self.log_groups_state.input_focus.handle_page_up(
5842                    &mut self.log_groups_state.selected_stream,
5843                    &mut self.log_groups_state.stream_current_page,
5844                    page_size,
5845                );
5846                self.log_groups_state.expanded_stream = None;
5847            }
5848        } else if self.mode == Mode::FilterInput && self.current_service == Service::LambdaFunctions
5849        {
5850            if self.lambda_state.current_function.is_some()
5851                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5852                && self.lambda_state.version_input_focus == InputFocus::Pagination
5853            {
5854                let page_size = self.lambda_state.version_table.page_size.value();
5855                self.lambda_state.version_table.selected = self
5856                    .lambda_state
5857                    .version_table
5858                    .selected
5859                    .saturating_sub(page_size);
5860            } else if self.lambda_state.current_function.is_some()
5861                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
5862                    || (self.lambda_state.current_version.is_some()
5863                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
5864                && self.lambda_state.alias_input_focus == InputFocus::Pagination
5865            {
5866                let page_size = self.lambda_state.alias_table.page_size.value();
5867                self.lambda_state.alias_table.selected = self
5868                    .lambda_state
5869                    .alias_table
5870                    .selected
5871                    .saturating_sub(page_size);
5872            } else if self.lambda_state.current_function.is_none() {
5873                let page_size = self.lambda_state.table.page_size.value();
5874                self.lambda_state.input_focus.handle_page_up(
5875                    &mut self.lambda_state.table.selected,
5876                    &mut self.lambda_state.table.scroll_offset,
5877                    page_size,
5878                );
5879            }
5880        } else if self.mode == Mode::FilterInput
5881            && self.current_service == Service::LambdaApplications
5882        {
5883            if self.lambda_application_state.current_application.is_some() {
5884                if self.lambda_application_state.detail_tab
5885                    == LambdaApplicationDetailTab::Deployments
5886                {
5887                    let page_size = self.lambda_application_state.deployments.page_size.value();
5888                    self.lambda_application_state
5889                        .deployment_input_focus
5890                        .handle_page_up(
5891                            &mut self.lambda_application_state.deployments.selected,
5892                            &mut self.lambda_application_state.deployments.scroll_offset,
5893                            page_size,
5894                        );
5895                } else {
5896                    let page_size = self.lambda_application_state.resources.page_size.value();
5897                    self.lambda_application_state
5898                        .resource_input_focus
5899                        .handle_page_up(
5900                            &mut self.lambda_application_state.resources.selected,
5901                            &mut self.lambda_application_state.resources.scroll_offset,
5902                            page_size,
5903                        );
5904                }
5905            } else {
5906                let page_size = self.lambda_application_state.table.page_size.value();
5907                self.lambda_application_state.input_focus.handle_page_up(
5908                    &mut self.lambda_application_state.table.selected,
5909                    &mut self.lambda_application_state.table.scroll_offset,
5910                    page_size,
5911                );
5912            }
5913        } else if self.mode == Mode::FilterInput
5914            && self.current_service == Service::EcrRepositories
5915            && self.ecr_state.current_repository.is_none()
5916            && self.ecr_state.input_focus == InputFocus::Filter
5917        {
5918            // When input is focused, allow table scrolling
5919            self.ecr_state.repositories.page_up();
5920        } else if self.mode == Mode::FilterInput
5921            && self.current_service == Service::EcrRepositories
5922            && self.ecr_state.current_repository.is_none()
5923        {
5924            let page_size = self.ecr_state.repositories.page_size.value();
5925            self.ecr_state.input_focus.handle_page_up(
5926                &mut self.ecr_state.repositories.selected,
5927                &mut self.ecr_state.repositories.scroll_offset,
5928                page_size,
5929            );
5930        } else if self.mode == Mode::FilterInput && self.view_mode == ViewMode::PolicyView {
5931            let page_size = self.iam_state.policies.page_size.value();
5932            self.iam_state.policy_input_focus.handle_page_up(
5933                &mut self.iam_state.policies.selected,
5934                &mut self.iam_state.policies.scroll_offset,
5935                page_size,
5936            );
5937        } else if self.view_mode == ViewMode::PolicyView {
5938            self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(10);
5939        } else if self.current_service == Service::CloudFormationStacks
5940            && self.cfn_state.current_stack.is_some()
5941            && self.cfn_state.detail_tab == CfnDetailTab::Template
5942        {
5943            self.cfn_state.template_scroll = self.cfn_state.template_scroll.saturating_sub(10);
5944        } else if self.current_service == Service::SqsQueues
5945            && self.sqs_state.current_queue.is_some()
5946        {
5947            if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
5948                self.sqs_state
5949                    .set_monitoring_scroll(self.sqs_state.monitoring_scroll().saturating_sub(1));
5950            } else {
5951                self.sqs_state.policy_scroll = self.sqs_state.policy_scroll.saturating_sub(10);
5952            }
5953        } else if self.current_service == Service::IamRoles
5954            && self.iam_state.current_role.is_some()
5955            && self.iam_state.role_tab == RoleTab::TrustRelationships
5956        {
5957            self.iam_state.trust_policy_scroll =
5958                self.iam_state.trust_policy_scroll.saturating_sub(10);
5959        } else if self.current_service == Service::IamRoles
5960            && self.iam_state.current_role.is_some()
5961            && self.iam_state.role_tab == RoleTab::RevokeSessions
5962        {
5963            self.iam_state.revoke_sessions_scroll =
5964                self.iam_state.revoke_sessions_scroll.saturating_sub(10);
5965        } else if self.mode == Mode::Normal {
5966            if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none()
5967            {
5968                self.s3_state.selected_row = self.s3_state.selected_row.saturating_sub(10);
5969
5970                // Adjust scroll offset if selection goes above viewport
5971                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
5972                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
5973                }
5974            } else if self.current_service == Service::S3Buckets
5975                && self.s3_state.current_bucket.is_some()
5976            {
5977                self.s3_state.selected_object = self.s3_state.selected_object.saturating_sub(10);
5978
5979                // Adjust scroll offset if selection goes above viewport
5980                if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
5981                    self.s3_state.object_scroll_offset = self.s3_state.selected_object;
5982                }
5983            } else if self.current_service == Service::CloudWatchLogGroups
5984                && self.view_mode == ViewMode::List
5985            {
5986                self.log_groups_state.log_groups.page_up();
5987            } else if self.current_service == Service::CloudWatchLogGroups
5988                && self.view_mode == ViewMode::Detail
5989            {
5990                self.log_groups_state.selected_stream =
5991                    self.log_groups_state.selected_stream.saturating_sub(10);
5992            } else if self.view_mode == ViewMode::Events {
5993                if self.log_groups_state.event_scroll_offset < 10
5994                    && self.log_groups_state.has_older_events
5995                {
5996                    self.log_groups_state.loading = true;
5997                }
5998                self.log_groups_state.event_scroll_offset =
5999                    self.log_groups_state.event_scroll_offset.saturating_sub(10);
6000            } else if self.view_mode == ViewMode::InsightsResults {
6001                self.insights_state.insights.results_selected = self
6002                    .insights_state
6003                    .insights
6004                    .results_selected
6005                    .saturating_sub(10);
6006            } else if self.current_service == Service::CloudWatchAlarms {
6007                self.alarms_state.table.page_up();
6008            } else if self.current_service == Service::Ec2Instances {
6009                self.ec2_state.table.page_up();
6010            } else if self.current_service == Service::EcrRepositories {
6011                if self.ecr_state.current_repository.is_some() {
6012                    self.ecr_state.images.page_up();
6013                } else {
6014                    self.ecr_state.repositories.page_up();
6015                }
6016            } else if self.current_service == Service::SqsQueues {
6017                self.sqs_state.queues.page_up();
6018            } else if self.current_service == Service::LambdaFunctions {
6019                self.lambda_state.table.page_up();
6020            } else if self.current_service == Service::LambdaApplications {
6021                self.lambda_application_state.table.page_up();
6022            } else if self.current_service == Service::CloudFormationStacks {
6023                if self.cfn_state.current_stack.is_some()
6024                    && self.cfn_state.detail_tab == CfnDetailTab::Parameters
6025                {
6026                    self.cfn_state.parameters.page_up();
6027                } else if self.cfn_state.current_stack.is_some()
6028                    && self.cfn_state.detail_tab == CfnDetailTab::Outputs
6029                {
6030                    self.cfn_state.outputs.page_up();
6031                } else {
6032                    self.cfn_state.table.page_up();
6033                }
6034            } else if self.current_service == Service::IamUsers {
6035                self.iam_state.users.page_up();
6036            } else if self.current_service == Service::IamRoles {
6037                if self.iam_state.current_role.is_some() {
6038                    self.iam_state.policies.page_up();
6039                } else {
6040                    self.iam_state.roles.page_up();
6041                }
6042            }
6043        }
6044    }
6045
6046    fn next_pane(&mut self) {
6047        if self.current_service == Service::S3Buckets {
6048            if self.s3_state.current_bucket.is_some() {
6049                // In objects view - expand prefix and trigger preview load
6050                // Map visual index to actual object (including nested items)
6051                let mut visual_idx = 0;
6052                let mut found_obj: Option<S3Object> = None;
6053
6054                // Helper to recursively check nested items
6055                fn check_nested(
6056                    obj: &S3Object,
6057                    visual_idx: &mut usize,
6058                    target_idx: usize,
6059                    expanded_prefixes: &std::collections::HashSet<String>,
6060                    prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
6061                    found_obj: &mut Option<S3Object>,
6062                ) {
6063                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6064                        if let Some(preview) = prefix_preview.get(&obj.key) {
6065                            for nested_obj in preview {
6066                                if *visual_idx == target_idx {
6067                                    *found_obj = Some(nested_obj.clone());
6068                                    return;
6069                                }
6070                                *visual_idx += 1;
6071
6072                                // Recursively check deeper levels
6073                                check_nested(
6074                                    nested_obj,
6075                                    visual_idx,
6076                                    target_idx,
6077                                    expanded_prefixes,
6078                                    prefix_preview,
6079                                    found_obj,
6080                                );
6081                                if found_obj.is_some() {
6082                                    return;
6083                                }
6084                            }
6085                        } else {
6086                            // Loading row
6087                            *visual_idx += 1;
6088                        }
6089                    }
6090                }
6091
6092                for obj in &self.s3_state.objects {
6093                    if visual_idx == self.s3_state.selected_object {
6094                        found_obj = Some(obj.clone());
6095                        break;
6096                    }
6097                    visual_idx += 1;
6098
6099                    // Check nested items recursively
6100                    check_nested(
6101                        obj,
6102                        &mut visual_idx,
6103                        self.s3_state.selected_object,
6104                        &self.s3_state.expanded_prefixes,
6105                        &self.s3_state.prefix_preview,
6106                        &mut found_obj,
6107                    );
6108                    if found_obj.is_some() {
6109                        break;
6110                    }
6111                }
6112
6113                if let Some(obj) = found_obj {
6114                    if obj.is_prefix {
6115                        if !self.s3_state.expanded_prefixes.contains(&obj.key) {
6116                            self.s3_state.expanded_prefixes.insert(obj.key.clone());
6117                            // Trigger preview load if not already cached
6118                            if !self.s3_state.prefix_preview.contains_key(&obj.key) {
6119                                self.s3_state.buckets.loading = true;
6120                            }
6121                        }
6122                        // Move to first child if expanded and has children
6123                        if self.s3_state.expanded_prefixes.contains(&obj.key) {
6124                            if let Some(preview) = self.s3_state.prefix_preview.get(&obj.key) {
6125                                if !preview.is_empty() {
6126                                    self.s3_state.selected_object += 1;
6127                                }
6128                            }
6129                        }
6130                    }
6131                }
6132            } else {
6133                // In bucket list - find which bucket/prefix the selected row corresponds to
6134                let mut row_idx = 0;
6135                let mut found = false;
6136                for bucket in &self.s3_state.buckets.items {
6137                    if row_idx == self.s3_state.selected_row {
6138                        // Selected row is a bucket - expand and move to first child
6139                        if !self.s3_state.expanded_prefixes.contains(&bucket.name) {
6140                            self.s3_state.expanded_prefixes.insert(bucket.name.clone());
6141                            if !self.s3_state.bucket_preview.contains_key(&bucket.name)
6142                                && !self.s3_state.bucket_errors.contains_key(&bucket.name)
6143                            {
6144                                self.s3_state.buckets.loading = true;
6145                            }
6146                        }
6147                        // Move to first child if expanded and has children
6148                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
6149                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
6150                                if !preview.is_empty() {
6151                                    self.s3_state.selected_row = row_idx + 1;
6152                                }
6153                            }
6154                        }
6155                        break;
6156                    }
6157                    row_idx += 1;
6158
6159                    // Skip error rows - they're not selectable
6160                    if self.s3_state.bucket_errors.contains_key(&bucket.name)
6161                        && self.s3_state.expanded_prefixes.contains(&bucket.name)
6162                    {
6163                        continue;
6164                    }
6165
6166                    if self.s3_state.expanded_prefixes.contains(&bucket.name) {
6167                        if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
6168                            // Recursive function to check nested items at any depth
6169                            #[allow(clippy::too_many_arguments)]
6170                            fn check_nested_expansion(
6171                                objects: &[S3Object],
6172                                row_idx: &mut usize,
6173                                target_row: usize,
6174                                expanded_prefixes: &mut std::collections::HashSet<String>,
6175                                prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
6176                                found: &mut bool,
6177                                loading: &mut bool,
6178                                selected_row: &mut usize,
6179                            ) {
6180                                for obj in objects {
6181                                    if *row_idx == target_row {
6182                                        // Selected this item - expand and move to first child
6183                                        if obj.is_prefix {
6184                                            if !expanded_prefixes.contains(&obj.key) {
6185                                                expanded_prefixes.insert(obj.key.clone());
6186                                                if !prefix_preview.contains_key(&obj.key) {
6187                                                    *loading = true;
6188                                                }
6189                                            }
6190                                            // Move to first child if expanded and has children
6191                                            if expanded_prefixes.contains(&obj.key) {
6192                                                if let Some(preview) = prefix_preview.get(&obj.key)
6193                                                {
6194                                                    if !preview.is_empty() {
6195                                                        *selected_row = *row_idx + 1;
6196                                                    }
6197                                                }
6198                                            }
6199                                        }
6200                                        *found = true;
6201                                        return;
6202                                    }
6203                                    *row_idx += 1;
6204
6205                                    // Recursively check nested items if expanded
6206                                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6207                                        if let Some(nested) = prefix_preview.get(&obj.key) {
6208                                            check_nested_expansion(
6209                                                nested,
6210                                                row_idx,
6211                                                target_row,
6212                                                expanded_prefixes,
6213                                                prefix_preview,
6214                                                found,
6215                                                loading,
6216                                                selected_row,
6217                                            );
6218                                            if *found {
6219                                                return;
6220                                            }
6221                                        } else {
6222                                            *row_idx += 1; // Loading row
6223                                        }
6224                                    }
6225                                }
6226                            }
6227
6228                            check_nested_expansion(
6229                                preview,
6230                                &mut row_idx,
6231                                self.s3_state.selected_row,
6232                                &mut self.s3_state.expanded_prefixes,
6233                                &self.s3_state.prefix_preview,
6234                                &mut found,
6235                                &mut self.s3_state.buckets.loading,
6236                                &mut self.s3_state.selected_row,
6237                            );
6238                            if found || row_idx > self.s3_state.selected_row {
6239                                break;
6240                            }
6241                        } else {
6242                            row_idx += 1;
6243                            if row_idx > self.s3_state.selected_row {
6244                                break;
6245                            }
6246                        }
6247                    }
6248                    if found {
6249                        break;
6250                    }
6251                }
6252            }
6253        } else if self.view_mode == ViewMode::InsightsResults {
6254            // Right arrow scrolls horizontally by 1 column
6255            let max_cols = self
6256                .insights_state
6257                .insights
6258                .query_results
6259                .first()
6260                .map(|r| r.len())
6261                .unwrap_or(0);
6262            if self.insights_state.insights.results_horizontal_scroll < max_cols.saturating_sub(1) {
6263                self.insights_state.insights.results_horizontal_scroll += 1;
6264            }
6265        } else if self.current_service == Service::CloudWatchLogGroups
6266            && self.view_mode == ViewMode::List
6267        {
6268            // Expand selected log group
6269            if self.log_groups_state.log_groups.expanded_item
6270                != Some(self.log_groups_state.log_groups.selected)
6271            {
6272                self.log_groups_state.log_groups.expanded_item =
6273                    Some(self.log_groups_state.log_groups.selected);
6274            }
6275        } else if self.current_service == Service::CloudWatchLogGroups
6276            && self.view_mode == ViewMode::Detail
6277        {
6278            // Expand selected log stream
6279            if self.log_groups_state.expanded_stream != Some(self.log_groups_state.selected_stream)
6280            {
6281                self.log_groups_state.expanded_stream = Some(self.log_groups_state.selected_stream);
6282            }
6283        } else if self.view_mode == ViewMode::Events {
6284            // Only scroll if there are hidden columns
6285            // Expand selected event
6286            if self.log_groups_state.expanded_event
6287                != Some(self.log_groups_state.event_scroll_offset)
6288            {
6289                self.log_groups_state.expanded_event =
6290                    Some(self.log_groups_state.event_scroll_offset);
6291            }
6292        } else if self.current_service == Service::CloudWatchAlarms {
6293            // Expand selected alarm
6294            if !self.alarms_state.table.is_expanded() {
6295                self.alarms_state.table.toggle_expand();
6296            }
6297        } else if self.current_service == Service::Ec2Instances {
6298            if self.ec2_state.current_instance.is_some()
6299                && self.ec2_state.detail_tab == Ec2DetailTab::Tags
6300            {
6301                self.ec2_state.tags.toggle_expand();
6302            } else if !self.ec2_state.table.is_expanded() {
6303                self.ec2_state.table.toggle_expand();
6304            }
6305        } else if self.current_service == Service::EcrRepositories {
6306            if self.ecr_state.current_repository.is_some() {
6307                // In images view - expand selected image
6308                self.ecr_state.images.toggle_expand();
6309            } else {
6310                // In repositories view - expand selected repository
6311                self.ecr_state.repositories.toggle_expand();
6312            }
6313        } else if self.current_service == Service::SqsQueues {
6314            if self.sqs_state.current_queue.is_some()
6315                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
6316            {
6317                self.sqs_state.triggers.toggle_expand();
6318            } else if self.sqs_state.current_queue.is_some()
6319                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
6320            {
6321                self.sqs_state.pipes.toggle_expand();
6322            } else if self.sqs_state.current_queue.is_some()
6323                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
6324            {
6325                self.sqs_state.tags.toggle_expand();
6326            } else if self.sqs_state.current_queue.is_some()
6327                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
6328            {
6329                self.sqs_state.subscriptions.toggle_expand();
6330            } else {
6331                self.sqs_state.queues.expand();
6332            }
6333        } else if self.current_service == Service::LambdaFunctions {
6334            if self.lambda_state.current_function.is_some()
6335                && self.lambda_state.detail_tab == LambdaDetailTab::Code
6336            {
6337                // Expand selected layer
6338                if self.lambda_state.layer_expanded != Some(self.lambda_state.layer_selected) {
6339                    self.lambda_state.layer_expanded = Some(self.lambda_state.layer_selected);
6340                } else {
6341                    self.lambda_state.layer_expanded = None;
6342                }
6343            } else if self.lambda_state.current_function.is_some()
6344                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
6345            {
6346                // Expand selected version
6347                self.lambda_state.version_table.toggle_expand();
6348            } else if self.lambda_state.current_function.is_some()
6349                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
6350                    || (self.lambda_state.current_version.is_some()
6351                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
6352            {
6353                // Expand selected alias
6354                self.lambda_state.alias_table.toggle_expand();
6355            } else if self.lambda_state.current_function.is_none() {
6356                // Expand selected function
6357                self.lambda_state.table.toggle_expand();
6358            }
6359        } else if self.current_service == Service::LambdaApplications {
6360            if self.lambda_application_state.current_application.is_some() {
6361                // In detail view - expand resource or deployment
6362                if self.lambda_application_state.detail_tab == LambdaApplicationDetailTab::Overview
6363                {
6364                    self.lambda_application_state.resources.toggle_expand();
6365                } else {
6366                    self.lambda_application_state.deployments.toggle_expand();
6367                }
6368            } else {
6369                // Expand selected application in list
6370                if self.lambda_application_state.table.expanded_item
6371                    != Some(self.lambda_application_state.table.selected)
6372                {
6373                    self.lambda_application_state.table.expanded_item =
6374                        Some(self.lambda_application_state.table.selected);
6375                }
6376            }
6377        } else if self.current_service == Service::CloudFormationStacks
6378            && self.cfn_state.current_stack.is_none()
6379        {
6380            self.cfn_state.table.toggle_expand();
6381        } else if self.current_service == Service::CloudFormationStacks
6382            && self.cfn_state.current_stack.is_some()
6383            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
6384        {
6385            self.cfn_state.parameters.toggle_expand();
6386        } else if self.current_service == Service::CloudFormationStacks
6387            && self.cfn_state.current_stack.is_some()
6388            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
6389        {
6390            self.cfn_state.outputs.toggle_expand();
6391        } else if self.current_service == Service::CloudFormationStacks
6392            && self.cfn_state.current_stack.is_some()
6393            && self.cfn_state.detail_tab == CfnDetailTab::Resources
6394        {
6395            self.cfn_state.resources.toggle_expand();
6396        } else if self.current_service == Service::IamUsers {
6397            if self.iam_state.current_user.is_some() {
6398                if self.iam_state.user_tab == UserTab::Tags {
6399                    if self.iam_state.user_tags.expanded_item
6400                        != Some(self.iam_state.user_tags.selected)
6401                    {
6402                        self.iam_state.user_tags.expanded_item =
6403                            Some(self.iam_state.user_tags.selected);
6404                    }
6405                } else if self.iam_state.policies.expanded_item
6406                    != Some(self.iam_state.policies.selected)
6407                {
6408                    self.iam_state.policies.toggle_expand();
6409                }
6410            } else if !self.iam_state.users.is_expanded() {
6411                self.iam_state.users.toggle_expand();
6412            }
6413        } else if self.current_service == Service::IamRoles {
6414            if self.iam_state.current_role.is_some() {
6415                // Handle expansion based on current tab
6416                if self.iam_state.role_tab == RoleTab::Tags {
6417                    if !self.iam_state.tags.is_expanded() {
6418                        self.iam_state.tags.expand();
6419                    }
6420                } else if self.iam_state.role_tab == RoleTab::LastAccessed {
6421                    if !self.iam_state.last_accessed_services.is_expanded() {
6422                        self.iam_state.last_accessed_services.expand();
6423                    }
6424                } else if !self.iam_state.policies.is_expanded() {
6425                    self.iam_state.policies.expand();
6426                }
6427            } else if !self.iam_state.roles.is_expanded() {
6428                self.iam_state.roles.expand();
6429            }
6430        } else if self.current_service == Service::IamUserGroups {
6431            if self.iam_state.current_group.is_some() {
6432                if self.iam_state.group_tab == GroupTab::Users {
6433                    if !self.iam_state.group_users.is_expanded() {
6434                        self.iam_state.group_users.expand();
6435                    }
6436                } else if self.iam_state.group_tab == GroupTab::Permissions {
6437                    if !self.iam_state.policies.is_expanded() {
6438                        self.iam_state.policies.expand();
6439                    }
6440                } else if self.iam_state.group_tab == GroupTab::AccessAdvisor
6441                    && !self.iam_state.last_accessed_services.is_expanded()
6442                {
6443                    self.iam_state.last_accessed_services.expand();
6444                }
6445            } else if !self.iam_state.groups.is_expanded() {
6446                self.iam_state.groups.expand();
6447            }
6448        }
6449    }
6450
6451    fn go_to_page(&mut self, page: usize) {
6452        if page == 0 {
6453            return;
6454        }
6455
6456        match self.current_service {
6457            Service::CloudWatchAlarms => {
6458                let alarm_page_size = self.alarms_state.table.page_size.value();
6459                let target = (page - 1) * alarm_page_size;
6460                let filtered_count = match self.alarms_state.alarm_tab {
6461                    AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
6462                    AlarmTab::InAlarm => self
6463                        .alarms_state
6464                        .table
6465                        .items
6466                        .iter()
6467                        .filter(|a| a.state.to_uppercase() == "ALARM")
6468                        .count(),
6469                };
6470                let max_offset = filtered_count.saturating_sub(alarm_page_size);
6471                self.alarms_state.table.scroll_offset = target.min(max_offset);
6472                self.alarms_state.table.selected = self
6473                    .alarms_state
6474                    .table
6475                    .scroll_offset
6476                    .min(filtered_count.saturating_sub(1));
6477            }
6478            Service::CloudWatchLogGroups => match self.view_mode {
6479                ViewMode::Events => {
6480                    let page_size = 20;
6481                    let target = (page - 1) * page_size;
6482                    let max = self.log_groups_state.log_events.len().saturating_sub(1);
6483                    self.log_groups_state.event_scroll_offset = target.min(max);
6484                }
6485                ViewMode::Detail => {
6486                    let page_size = self.log_groups_state.stream_page_size;
6487                    self.log_groups_state.stream_current_page = (page - 1).min(
6488                        self.log_groups_state
6489                            .log_streams
6490                            .len()
6491                            .div_ceil(page_size)
6492                            .saturating_sub(1),
6493                    );
6494                    self.log_groups_state.selected_stream = 0;
6495                }
6496                ViewMode::List => {
6497                    let total = self.log_groups_state.log_groups.items.len();
6498                    self.log_groups_state.log_groups.goto_page(page, total);
6499                }
6500                _ => {}
6501            },
6502            Service::EcrRepositories => {
6503                if self.ecr_state.current_repository.is_some() {
6504                    let filtered_count = self
6505                        .ecr_state
6506                        .images
6507                        .filtered(|img| {
6508                            self.ecr_state.images.filter.is_empty()
6509                                || img
6510                                    .tag
6511                                    .to_lowercase()
6512                                    .contains(&self.ecr_state.images.filter.to_lowercase())
6513                                || img
6514                                    .digest
6515                                    .to_lowercase()
6516                                    .contains(&self.ecr_state.images.filter.to_lowercase())
6517                        })
6518                        .len();
6519                    self.ecr_state.images.goto_page(page, filtered_count);
6520                } else {
6521                    let filtered_count = self
6522                        .ecr_state
6523                        .repositories
6524                        .filtered(|r| {
6525                            self.ecr_state.repositories.filter.is_empty()
6526                                || r.name
6527                                    .to_lowercase()
6528                                    .contains(&self.ecr_state.repositories.filter.to_lowercase())
6529                        })
6530                        .len();
6531                    self.ecr_state.repositories.goto_page(page, filtered_count);
6532                }
6533            }
6534            Service::SqsQueues => {
6535                let filtered_count =
6536                    filtered_queues(&self.sqs_state.queues.items, &self.sqs_state.queues.filter)
6537                        .len();
6538                self.sqs_state.queues.goto_page(page, filtered_count);
6539            }
6540            Service::S3Buckets => {
6541                if self.s3_state.current_bucket.is_some() {
6542                    let page_size = 50; // S3 objects use fixed page size
6543                    let target = (page - 1) * page_size;
6544                    let total_rows = self.calculate_total_object_rows();
6545                    let max = total_rows.saturating_sub(1);
6546                    self.s3_state.selected_object = target.min(max);
6547                } else {
6548                    let page_size = self.s3_state.buckets.page_size.value();
6549                    let target = (page - 1) * page_size;
6550                    let total_rows = self.calculate_total_bucket_rows();
6551                    let max = total_rows.saturating_sub(1);
6552                    self.s3_state.selected_row = target.min(max);
6553                    // Adjust scroll offset to show the target page
6554                    self.s3_state.bucket_scroll_offset =
6555                        target.min(total_rows.saturating_sub(page_size));
6556                }
6557            }
6558            Service::LambdaFunctions => {
6559                if self.lambda_state.current_function.is_some()
6560                    && self.lambda_state.detail_tab == LambdaDetailTab::Versions
6561                {
6562                    let filtered_count = self
6563                        .lambda_state
6564                        .version_table
6565                        .filtered(|v| {
6566                            self.lambda_state.version_table.filter.is_empty()
6567                                || v.version.to_lowercase().contains(
6568                                    &self.lambda_state.version_table.filter.to_lowercase(),
6569                                )
6570                                || v.aliases.to_lowercase().contains(
6571                                    &self.lambda_state.version_table.filter.to_lowercase(),
6572                                )
6573                                || v.description.to_lowercase().contains(
6574                                    &self.lambda_state.version_table.filter.to_lowercase(),
6575                                )
6576                        })
6577                        .len();
6578                    self.lambda_state
6579                        .version_table
6580                        .goto_page(page, filtered_count);
6581                } else {
6582                    let filtered_count = filtered_lambda_functions(self).len();
6583                    self.lambda_state.table.goto_page(page, filtered_count);
6584                }
6585            }
6586            Service::LambdaApplications => {
6587                let filtered_count = filtered_lambda_applications(self).len();
6588                self.lambda_application_state
6589                    .table
6590                    .goto_page(page, filtered_count);
6591            }
6592            Service::CloudFormationStacks => {
6593                let filtered_count = filtered_cloudformation_stacks(self).len();
6594                self.cfn_state.table.goto_page(page, filtered_count);
6595            }
6596            Service::IamUsers => {
6597                let filtered_count = filtered_iam_users(self).len();
6598                self.iam_state.users.goto_page(page, filtered_count);
6599            }
6600            Service::IamRoles => {
6601                let filtered_count = filtered_iam_roles(self).len();
6602                self.iam_state.roles.goto_page(page, filtered_count);
6603            }
6604            _ => {}
6605        }
6606    }
6607
6608    fn prev_pane(&mut self) {
6609        if self.current_service == Service::S3Buckets {
6610            if self.s3_state.current_bucket.is_some() {
6611                // In objects view - collapse prefix or jump to parent
6612                // Map visual index to actual object (including nested items)
6613                let mut visual_idx = 0;
6614                let mut found_obj: Option<S3Object> = None;
6615                let mut parent_idx: Option<usize> = None;
6616
6617                // Helper to recursively find object and its parent
6618                #[allow(clippy::too_many_arguments)]
6619                fn find_with_parent(
6620                    objects: &[S3Object],
6621                    visual_idx: &mut usize,
6622                    target_idx: usize,
6623                    expanded_prefixes: &std::collections::HashSet<String>,
6624                    prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
6625                    found_obj: &mut Option<S3Object>,
6626                    parent_idx: &mut Option<usize>,
6627                    current_parent: Option<usize>,
6628                ) {
6629                    for obj in objects {
6630                        if *visual_idx == target_idx {
6631                            *found_obj = Some(obj.clone());
6632                            *parent_idx = current_parent;
6633                            return;
6634                        }
6635                        let obj_idx = *visual_idx;
6636                        *visual_idx += 1;
6637
6638                        // Check nested items if expanded
6639                        if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6640                            if let Some(preview) = prefix_preview.get(&obj.key) {
6641                                find_with_parent(
6642                                    preview,
6643                                    visual_idx,
6644                                    target_idx,
6645                                    expanded_prefixes,
6646                                    prefix_preview,
6647                                    found_obj,
6648                                    parent_idx,
6649                                    Some(obj_idx),
6650                                );
6651                                if found_obj.is_some() {
6652                                    return;
6653                                }
6654                            }
6655                        }
6656                    }
6657                }
6658
6659                find_with_parent(
6660                    &self.s3_state.objects,
6661                    &mut visual_idx,
6662                    self.s3_state.selected_object,
6663                    &self.s3_state.expanded_prefixes,
6664                    &self.s3_state.prefix_preview,
6665                    &mut found_obj,
6666                    &mut parent_idx,
6667                    None,
6668                );
6669
6670                if let Some(obj) = found_obj {
6671                    if obj.is_prefix && self.s3_state.expanded_prefixes.contains(&obj.key) {
6672                        // Expanded: collapse it and jump to parent
6673                        self.s3_state.expanded_prefixes.remove(&obj.key);
6674                        if let Some(parent) = parent_idx {
6675                            self.s3_state.selected_object = parent;
6676                        }
6677                    } else if let Some(parent) = parent_idx {
6678                        // Already collapsed or not a prefix: jump to parent
6679                        self.s3_state.selected_object = parent;
6680                    }
6681                }
6682
6683                // Adjust scroll offset to keep selection visible
6684                let visible_rows = self.s3_state.object_visible_rows.get();
6685                if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
6686                    self.s3_state.object_scroll_offset = self.s3_state.selected_object;
6687                } else if self.s3_state.selected_object
6688                    >= self.s3_state.object_scroll_offset + visible_rows
6689                {
6690                    self.s3_state.object_scroll_offset = self
6691                        .s3_state
6692                        .selected_object
6693                        .saturating_sub(visible_rows - 1);
6694                }
6695            } else {
6696                // In bucket list - find which bucket/prefix the selected row corresponds to
6697                let mut row_idx = 0;
6698                for bucket in &self.s3_state.buckets.items {
6699                    if row_idx == self.s3_state.selected_row {
6700                        // Selected row is a bucket - collapse it
6701                        self.s3_state.expanded_prefixes.remove(&bucket.name);
6702                        break;
6703                    }
6704                    row_idx += 1;
6705                    if self.s3_state.expanded_prefixes.contains(&bucket.name) {
6706                        if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
6707                            // Recursive function to check nested items at any depth
6708                            #[allow(clippy::too_many_arguments)]
6709                            fn check_nested_collapse(
6710                                objects: &[S3Object],
6711                                row_idx: &mut usize,
6712                                target_row: usize,
6713                                expanded_prefixes: &mut std::collections::HashSet<String>,
6714                                prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
6715                                found: &mut bool,
6716                                selected_row: &mut usize,
6717                                parent_row: usize,
6718                            ) {
6719                                for obj in objects {
6720                                    let current_row = *row_idx;
6721                                    if *row_idx == target_row {
6722                                        // Selected this item - collapse or jump to parent
6723                                        if obj.is_prefix {
6724                                            if expanded_prefixes.contains(&obj.key) {
6725                                                // Expanded: collapse it and move to parent
6726                                                expanded_prefixes.remove(&obj.key);
6727                                                *selected_row = parent_row;
6728                                            } else {
6729                                                // Already collapsed: jump to parent
6730                                                *selected_row = parent_row;
6731                                            }
6732                                        } else {
6733                                            // Not a prefix: jump to parent
6734                                            *selected_row = parent_row;
6735                                        }
6736                                        *found = true;
6737                                        return;
6738                                    }
6739                                    *row_idx += 1;
6740
6741                                    // Recursively check nested items if expanded
6742                                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6743                                        if let Some(nested) = prefix_preview.get(&obj.key) {
6744                                            check_nested_collapse(
6745                                                nested,
6746                                                row_idx,
6747                                                target_row,
6748                                                expanded_prefixes,
6749                                                prefix_preview,
6750                                                found,
6751                                                selected_row,
6752                                                current_row,
6753                                            );
6754                                            if *found {
6755                                                return;
6756                                            }
6757                                        } else {
6758                                            *row_idx += 1; // Loading row
6759                                        }
6760                                    }
6761                                }
6762                            }
6763
6764                            let mut found = false;
6765                            let parent_row = row_idx - 1; // Parent is the bucket
6766                            check_nested_collapse(
6767                                preview,
6768                                &mut row_idx,
6769                                self.s3_state.selected_row,
6770                                &mut self.s3_state.expanded_prefixes,
6771                                &self.s3_state.prefix_preview,
6772                                &mut found,
6773                                &mut self.s3_state.selected_row,
6774                                parent_row,
6775                            );
6776                            if found {
6777                                // Adjust scroll offset to keep selection visible
6778                                let visible_rows = self.s3_state.bucket_visible_rows.get();
6779                                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
6780                                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
6781                                } else if self.s3_state.selected_row
6782                                    >= self.s3_state.bucket_scroll_offset + visible_rows
6783                                {
6784                                    self.s3_state.bucket_scroll_offset =
6785                                        self.s3_state.selected_row.saturating_sub(visible_rows - 1);
6786                                }
6787                                return;
6788                            }
6789                        } else {
6790                            row_idx += 1;
6791                        }
6792                    }
6793                }
6794
6795                // Adjust scroll offset to keep selection visible after collapse
6796                let visible_rows = self.s3_state.bucket_visible_rows.get();
6797                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
6798                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
6799                } else if self.s3_state.selected_row
6800                    >= self.s3_state.bucket_scroll_offset + visible_rows
6801                {
6802                    self.s3_state.bucket_scroll_offset =
6803                        self.s3_state.selected_row.saturating_sub(visible_rows - 1);
6804                }
6805            }
6806        } else if self.view_mode == ViewMode::InsightsResults {
6807            // Left arrow scrolls horizontally by 1 column
6808            self.insights_state.insights.results_horizontal_scroll = self
6809                .insights_state
6810                .insights
6811                .results_horizontal_scroll
6812                .saturating_sub(1);
6813        } else if self.current_service == Service::CloudWatchLogGroups
6814            && self.view_mode == ViewMode::List
6815        {
6816            // Collapse expanded log group
6817            if self.log_groups_state.log_groups.has_expanded_item() {
6818                self.log_groups_state.log_groups.collapse();
6819            }
6820        } else if self.current_service == Service::CloudWatchLogGroups
6821            && self.view_mode == ViewMode::Detail
6822        {
6823            // Collapse expanded log stream
6824            if self.log_groups_state.expanded_stream.is_some() {
6825                self.log_groups_state.expanded_stream = None;
6826            }
6827        } else if self.view_mode == ViewMode::Events {
6828            // Collapse expanded event
6829            if self.log_groups_state.expanded_event.is_some() {
6830                self.log_groups_state.expanded_event = None;
6831            }
6832        } else if self.current_service == Service::CloudWatchAlarms {
6833            // Collapse expanded alarm
6834            self.alarms_state.table.collapse();
6835        } else if self.current_service == Service::Ec2Instances {
6836            self.ec2_state.table.collapse();
6837        } else if self.current_service == Service::EcrRepositories {
6838            if self.ecr_state.current_repository.is_some() {
6839                // In images view - collapse expanded image
6840                self.ecr_state.images.collapse();
6841            } else {
6842                // In repositories view - collapse expanded repository
6843                self.ecr_state.repositories.collapse();
6844            }
6845        } else if self.current_service == Service::SqsQueues {
6846            if self.sqs_state.current_queue.is_some()
6847                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
6848            {
6849                self.sqs_state.triggers.collapse();
6850            } else if self.sqs_state.current_queue.is_some()
6851                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
6852            {
6853                self.sqs_state.pipes.collapse();
6854            } else if self.sqs_state.current_queue.is_some()
6855                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
6856            {
6857                self.sqs_state.tags.collapse();
6858            } else if self.sqs_state.current_queue.is_some()
6859                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
6860            {
6861                self.sqs_state.subscriptions.collapse();
6862            } else {
6863                self.sqs_state.queues.collapse();
6864            }
6865        } else if self.current_service == Service::LambdaFunctions {
6866            if self.lambda_state.current_function.is_some()
6867                && self.lambda_state.detail_tab == LambdaDetailTab::Code
6868            {
6869                // Collapse selected layer
6870                self.lambda_state.layer_expanded = None;
6871            } else if self.lambda_state.current_function.is_some()
6872                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
6873            {
6874                // Collapse selected version
6875                self.lambda_state.version_table.collapse();
6876            } else if self.lambda_state.current_function.is_some()
6877                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
6878                    || (self.lambda_state.current_version.is_some()
6879                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
6880            {
6881                // Collapse selected alias
6882                self.lambda_state.alias_table.collapse();
6883            } else if self.lambda_state.current_function.is_none() {
6884                // Collapse expanded function
6885                self.lambda_state.table.collapse();
6886            }
6887        } else if self.current_service == Service::LambdaApplications {
6888            if self.lambda_application_state.current_application.is_some() {
6889                // In detail view - collapse resource or deployment
6890                if self.lambda_application_state.detail_tab == LambdaApplicationDetailTab::Overview
6891                {
6892                    self.lambda_application_state.resources.collapse();
6893                } else {
6894                    self.lambda_application_state.deployments.collapse();
6895                }
6896            } else {
6897                // Collapse expanded application in list
6898                if self.lambda_application_state.table.has_expanded_item() {
6899                    self.lambda_application_state.table.collapse();
6900                }
6901            }
6902        } else if self.current_service == Service::CloudFormationStacks
6903            && self.cfn_state.current_stack.is_none()
6904        {
6905            self.cfn_state.table.collapse();
6906        } else if self.current_service == Service::CloudFormationStacks
6907            && self.cfn_state.current_stack.is_some()
6908            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
6909        {
6910            self.cfn_state.parameters.collapse();
6911        } else if self.current_service == Service::CloudFormationStacks
6912            && self.cfn_state.current_stack.is_some()
6913            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
6914        {
6915            self.cfn_state.outputs.collapse();
6916        } else if self.current_service == Service::CloudFormationStacks
6917            && self.cfn_state.current_stack.is_some()
6918            && self.cfn_state.detail_tab == CfnDetailTab::Resources
6919        {
6920            self.cfn_state.resources.collapse();
6921        } else if self.current_service == Service::IamUsers {
6922            if self.iam_state.users.has_expanded_item() {
6923                self.iam_state.users.collapse();
6924            }
6925        } else if self.current_service == Service::IamRoles {
6926            if self.view_mode == ViewMode::PolicyView {
6927                // Go back from policy view to role detail
6928                self.view_mode = ViewMode::Detail;
6929                self.iam_state.current_policy = None;
6930                self.iam_state.policy_document.clear();
6931                self.iam_state.policy_scroll = 0;
6932            } else if self.iam_state.current_role.is_some() {
6933                if self.iam_state.role_tab == RoleTab::Tags
6934                    && self.iam_state.tags.has_expanded_item()
6935                {
6936                    self.iam_state.tags.collapse();
6937                } else if self.iam_state.role_tab == RoleTab::LastAccessed
6938                    && self
6939                        .iam_state
6940                        .last_accessed_services
6941                        .expanded_item
6942                        .is_some()
6943                {
6944                    self.iam_state.last_accessed_services.collapse();
6945                } else if self.iam_state.policies.has_expanded_item() {
6946                    self.iam_state.policies.collapse();
6947                }
6948            } else if self.iam_state.roles.has_expanded_item() {
6949                self.iam_state.roles.collapse();
6950            }
6951        } else if self.current_service == Service::IamUserGroups {
6952            if self.iam_state.current_group.is_some() {
6953                if self.iam_state.group_tab == GroupTab::Users
6954                    && self.iam_state.group_users.has_expanded_item()
6955                {
6956                    self.iam_state.group_users.collapse();
6957                } else if self.iam_state.group_tab == GroupTab::Permissions
6958                    && self.iam_state.policies.has_expanded_item()
6959                {
6960                    self.iam_state.policies.collapse();
6961                } else if self.iam_state.group_tab == GroupTab::AccessAdvisor
6962                    && self
6963                        .iam_state
6964                        .last_accessed_services
6965                        .expanded_item
6966                        .is_some()
6967                {
6968                    self.iam_state.last_accessed_services.collapse();
6969                }
6970            } else if self.iam_state.groups.has_expanded_item() {
6971                self.iam_state.groups.collapse();
6972            }
6973        }
6974    }
6975
6976    fn collapse_row(&mut self) {
6977        match self.current_service {
6978            Service::S3Buckets => {
6979                if self.s3_state.current_bucket.is_none() {
6980                    // Filter buckets first
6981                    let filtered_buckets: Vec<_> = self
6982                        .s3_state
6983                        .buckets
6984                        .items
6985                        .iter()
6986                        .filter(|b| {
6987                            if self.s3_state.buckets.filter.is_empty() {
6988                                true
6989                            } else {
6990                                b.name
6991                                    .to_lowercase()
6992                                    .contains(&self.s3_state.buckets.filter.to_lowercase())
6993                            }
6994                        })
6995                        .collect();
6996
6997                    // In bucket list - collapse bucket or nested prefix
6998                    let mut row_idx = 0;
6999
7000                    for bucket in filtered_buckets {
7001                        if row_idx == self.s3_state.selected_row {
7002                            // Selected row is a bucket - collapse it
7003                            self.s3_state.expanded_prefixes.remove(&bucket.name);
7004                            // Don't return - let scroll adjustment happen below
7005                            break;
7006                        }
7007                        row_idx += 1;
7008                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
7009                            // Check if bucket has error - don't count error rows as they're not selectable
7010                            if self.s3_state.bucket_errors.contains_key(&bucket.name) {
7011                                // Bucket has error, no child rows to navigate
7012                                continue;
7013                            }
7014                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
7015                                // Recursive function to check nested items at any depth
7016                                #[allow(clippy::too_many_arguments)]
7017                                fn check_nested_collapse(
7018                                    objects: &[S3Object],
7019                                    row_idx: &mut usize,
7020                                    target_row: usize,
7021                                    expanded_prefixes: &mut std::collections::HashSet<String>,
7022                                    prefix_preview: &std::collections::HashMap<
7023                                        String,
7024                                        Vec<S3Object>,
7025                                    >,
7026                                    found: &mut bool,
7027                                    selected_row: &mut usize,
7028                                    parent_row: usize,
7029                                ) {
7030                                    for obj in objects {
7031                                        let current_row = *row_idx;
7032                                        if *row_idx == target_row {
7033                                            // Selected this item - collapse or jump to parent
7034                                            if obj.is_prefix {
7035                                                if expanded_prefixes.contains(&obj.key) {
7036                                                    // Expanded: collapse it and move to parent
7037                                                    expanded_prefixes.remove(&obj.key);
7038                                                    *selected_row = parent_row;
7039                                                } else {
7040                                                    // Already collapsed: jump to parent
7041                                                    *selected_row = parent_row;
7042                                                }
7043                                            } else {
7044                                                // Not a prefix: jump to parent
7045                                                *selected_row = parent_row;
7046                                            }
7047                                            *found = true;
7048                                            return;
7049                                        }
7050                                        *row_idx += 1;
7051
7052                                        // Recursively check nested items if expanded
7053                                        if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
7054                                            if let Some(nested) = prefix_preview.get(&obj.key) {
7055                                                check_nested_collapse(
7056                                                    nested,
7057                                                    row_idx,
7058                                                    target_row,
7059                                                    expanded_prefixes,
7060                                                    prefix_preview,
7061                                                    found,
7062                                                    selected_row,
7063                                                    current_row,
7064                                                );
7065                                                if *found {
7066                                                    return;
7067                                                }
7068                                            } else {
7069                                                *row_idx += 1; // Loading row
7070                                            }
7071                                        }
7072                                    }
7073                                }
7074
7075                                let mut found = false;
7076                                let parent_row = row_idx - 1; // Parent is the bucket
7077                                check_nested_collapse(
7078                                    preview,
7079                                    &mut row_idx,
7080                                    self.s3_state.selected_row,
7081                                    &mut self.s3_state.expanded_prefixes,
7082                                    &self.s3_state.prefix_preview,
7083                                    &mut found,
7084                                    &mut self.s3_state.selected_row,
7085                                    parent_row,
7086                                );
7087                                if found {
7088                                    // Adjust scroll offset to keep selection visible
7089                                    let visible_rows = self.s3_state.bucket_visible_rows.get();
7090                                    if self.s3_state.selected_row
7091                                        < self.s3_state.bucket_scroll_offset
7092                                    {
7093                                        self.s3_state.bucket_scroll_offset =
7094                                            self.s3_state.selected_row;
7095                                    } else if self.s3_state.selected_row
7096                                        >= self.s3_state.bucket_scroll_offset + visible_rows
7097                                    {
7098                                        self.s3_state.bucket_scroll_offset = self
7099                                            .s3_state
7100                                            .selected_row
7101                                            .saturating_sub(visible_rows - 1);
7102                                    }
7103                                    return;
7104                                }
7105                            } else {
7106                                row_idx += 1;
7107                            }
7108                        }
7109                    }
7110
7111                    // Adjust scroll offset to keep selection visible after collapse
7112                    let visible_rows = self.s3_state.bucket_visible_rows.get();
7113                    if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
7114                        self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
7115                    } else if self.s3_state.selected_row
7116                        >= self.s3_state.bucket_scroll_offset + visible_rows
7117                    {
7118                        self.s3_state.bucket_scroll_offset =
7119                            self.s3_state.selected_row.saturating_sub(visible_rows - 1);
7120                    }
7121                }
7122            }
7123            Service::CloudWatchLogGroups => {
7124                if self.view_mode == ViewMode::Events {
7125                    if let Some(idx) = self.log_groups_state.expanded_event {
7126                        self.log_groups_state.expanded_event = None;
7127                        self.log_groups_state.selected_event = idx;
7128                    }
7129                } else if self.view_mode == ViewMode::Detail {
7130                    if let Some(idx) = self.log_groups_state.expanded_stream {
7131                        self.log_groups_state.expanded_stream = None;
7132                        self.log_groups_state.selected_stream = idx;
7133                    }
7134                } else {
7135                    self.log_groups_state.log_groups.collapse();
7136                }
7137            }
7138            Service::CloudWatchAlarms => self.alarms_state.table.collapse(),
7139            Service::Ec2Instances => {
7140                if self.ec2_state.current_instance.is_some()
7141                    && self.ec2_state.detail_tab == Ec2DetailTab::Tags
7142                {
7143                    self.ec2_state.tags.collapse();
7144                } else {
7145                    self.ec2_state.table.collapse();
7146                }
7147            }
7148            Service::EcrRepositories => {
7149                if self.ecr_state.current_repository.is_some() {
7150                    self.ecr_state.images.collapse();
7151                } else {
7152                    self.ecr_state.repositories.collapse();
7153                }
7154            }
7155            Service::LambdaFunctions => self.lambda_state.table.collapse(),
7156            Service::SqsQueues => self.sqs_state.queues.collapse(),
7157            Service::CloudFormationStacks => {
7158                if self.cfn_state.current_stack.is_some() {
7159                    match self.cfn_state.detail_tab {
7160                        crate::ui::cfn::DetailTab::Resources => {
7161                            self.cfn_state.resources.collapse();
7162                        }
7163                        crate::ui::cfn::DetailTab::Parameters => {
7164                            self.cfn_state.parameters.collapse();
7165                        }
7166                        crate::ui::cfn::DetailTab::Outputs => {
7167                            self.cfn_state.outputs.collapse();
7168                        }
7169                        _ => {}
7170                    }
7171                } else {
7172                    self.cfn_state.table.collapse();
7173                }
7174            }
7175            Service::IamUsers => {
7176                if self.iam_state.current_user.is_some() {
7177                    match self.iam_state.user_tab {
7178                        crate::ui::iam::UserTab::Permissions => {
7179                            self.iam_state.policies.collapse();
7180                        }
7181                        crate::ui::iam::UserTab::Groups => {
7182                            self.iam_state.user_group_memberships.collapse();
7183                        }
7184                        crate::ui::iam::UserTab::Tags => {
7185                            self.iam_state.user_tags.collapse();
7186                        }
7187                        _ => {}
7188                    }
7189                } else {
7190                    self.iam_state.users.collapse();
7191                }
7192            }
7193            Service::IamRoles => {
7194                if self.iam_state.current_role.is_some() {
7195                    match self.iam_state.role_tab {
7196                        crate::ui::iam::RoleTab::Permissions => {
7197                            self.iam_state.policies.collapse();
7198                        }
7199                        crate::ui::iam::RoleTab::Tags => {
7200                            self.iam_state.tags.collapse();
7201                        }
7202                        _ => {}
7203                    }
7204                } else {
7205                    self.iam_state.roles.collapse();
7206                }
7207            }
7208            Service::IamUserGroups => self.iam_state.groups.collapse(),
7209            _ => {}
7210        }
7211    }
7212
7213    fn expand_row(&mut self) {
7214        match self.current_service {
7215            Service::S3Buckets => {
7216                if self.s3_state.current_bucket.is_none() {
7217                    // Filter buckets first
7218                    let filtered_buckets: Vec<_> = self
7219                        .s3_state
7220                        .buckets
7221                        .items
7222                        .iter()
7223                        .filter(|b| {
7224                            if self.s3_state.buckets.filter.is_empty() {
7225                                true
7226                            } else {
7227                                b.name
7228                                    .to_lowercase()
7229                                    .contains(&self.s3_state.buckets.filter.to_lowercase())
7230                            }
7231                        })
7232                        .collect();
7233
7234                    // Recursive helper to check nested items
7235                    fn check_nested_expand(
7236                        objects: &[S3Object],
7237                        row_idx: &mut usize,
7238                        target_row: usize,
7239                        expanded_prefixes: &mut std::collections::HashSet<String>,
7240                        prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
7241                    ) -> Option<(bool, usize)> {
7242                        for obj in objects {
7243                            if *row_idx == target_row {
7244                                if obj.is_prefix {
7245                                    // Toggle expansion
7246                                    if expanded_prefixes.contains(&obj.key) {
7247                                        expanded_prefixes.remove(&obj.key);
7248                                        return Some((true, *row_idx));
7249                                    } else {
7250                                        expanded_prefixes.insert(obj.key.clone());
7251                                        return Some((true, *row_idx + 1)); // Move to first child
7252                                    }
7253                                }
7254                                return Some((false, *row_idx));
7255                            }
7256                            *row_idx += 1;
7257
7258                            // Check nested items if expanded
7259                            if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
7260                                if let Some(nested) = prefix_preview.get(&obj.key) {
7261                                    if let Some(result) = check_nested_expand(
7262                                        nested,
7263                                        row_idx,
7264                                        target_row,
7265                                        expanded_prefixes,
7266                                        prefix_preview,
7267                                    ) {
7268                                        return Some(result);
7269                                    }
7270                                }
7271                            }
7272                        }
7273                        None
7274                    }
7275
7276                    let mut row_idx = 0;
7277                    for bucket in filtered_buckets {
7278                        if row_idx == self.s3_state.selected_row {
7279                            // Toggle bucket expansion
7280                            if self.s3_state.expanded_prefixes.contains(&bucket.name) {
7281                                self.s3_state.expanded_prefixes.remove(&bucket.name);
7282                            } else {
7283                                self.s3_state.expanded_prefixes.insert(bucket.name.clone());
7284                                self.s3_state.selected_row = row_idx + 1; // Move to first child
7285                                self.s3_state.buckets.loading = true;
7286                            }
7287                            return;
7288                        }
7289                        row_idx += 1;
7290
7291                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
7292                            if self.s3_state.bucket_errors.contains_key(&bucket.name) {
7293                                continue;
7294                            }
7295                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
7296                                if let Some((loading, new_row)) = check_nested_expand(
7297                                    preview,
7298                                    &mut row_idx,
7299                                    self.s3_state.selected_row,
7300                                    &mut self.s3_state.expanded_prefixes,
7301                                    &self.s3_state.prefix_preview,
7302                                ) {
7303                                    self.s3_state.selected_row = new_row;
7304                                    if loading {
7305                                        self.s3_state.buckets.loading = true;
7306                                    }
7307                                    return;
7308                                }
7309                            }
7310                        }
7311                    }
7312                } else {
7313                    // Inside a bucket - expand objects
7314                    fn check_object_expand(
7315                        objects: &[S3Object],
7316                        row_idx: &mut usize,
7317                        target_row: usize,
7318                        expanded_prefixes: &mut std::collections::HashSet<String>,
7319                        prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
7320                    ) -> Option<(bool, usize)> {
7321                        for obj in objects {
7322                            if *row_idx == target_row {
7323                                if obj.is_prefix {
7324                                    if expanded_prefixes.contains(&obj.key) {
7325                                        expanded_prefixes.remove(&obj.key);
7326                                        return Some((true, *row_idx));
7327                                    } else {
7328                                        expanded_prefixes.insert(obj.key.clone());
7329                                        return Some((true, *row_idx + 1));
7330                                    }
7331                                }
7332                                return Some((false, *row_idx));
7333                            }
7334                            *row_idx += 1;
7335
7336                            if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
7337                                if let Some(nested) = prefix_preview.get(&obj.key) {
7338                                    if let Some(result) = check_object_expand(
7339                                        nested,
7340                                        row_idx,
7341                                        target_row,
7342                                        expanded_prefixes,
7343                                        prefix_preview,
7344                                    ) {
7345                                        return Some(result);
7346                                    }
7347                                }
7348                            }
7349                        }
7350                        None
7351                    }
7352
7353                    let mut row_idx = 0;
7354                    if let Some((loading, new_row)) = check_object_expand(
7355                        &self.s3_state.objects,
7356                        &mut row_idx,
7357                        self.s3_state.selected_object,
7358                        &mut self.s3_state.expanded_prefixes,
7359                        &self.s3_state.prefix_preview,
7360                    ) {
7361                        self.s3_state.selected_object = new_row;
7362                        if loading {
7363                            self.s3_state.buckets.loading = true;
7364                        }
7365                    }
7366                }
7367            }
7368            _ => {
7369                // For other services, Right arrow switches panes
7370                self.next_pane();
7371            }
7372        }
7373    }
7374
7375    fn select_item(&mut self) {
7376        if self.mode == Mode::RegionPicker {
7377            let filtered = self.get_filtered_regions();
7378            if let Some(region) = filtered.get(self.region_picker_selected) {
7379                // Save current session before changing region
7380                if !self.tabs.is_empty() {
7381                    let mut session = Session::new(
7382                        self.profile.clone(),
7383                        self.region.clone(),
7384                        self.config.account_id.clone(),
7385                        self.config.role_arn.clone(),
7386                    );
7387
7388                    for tab in &self.tabs {
7389                        session.tabs.push(SessionTab {
7390                            service: format!("{:?}", tab.service),
7391                            title: tab.title.clone(),
7392                            breadcrumb: tab.breadcrumb.clone(),
7393                            filter: None,
7394                            selected_item: None,
7395                        });
7396                    }
7397
7398                    let _ = session.save();
7399                }
7400
7401                self.region = region.code.to_string();
7402                self.config.region = region.code.to_string();
7403
7404                // Close all tabs - region change invalidates all data
7405                self.tabs.clear();
7406                self.current_tab = 0;
7407                self.service_selected = false;
7408
7409                self.mode = Mode::Normal;
7410            }
7411        } else if self.mode == Mode::ProfilePicker {
7412            let filtered = self.get_filtered_profiles();
7413            if let Some(profile) = filtered.get(self.profile_picker_selected) {
7414                let profile_name = profile.name.clone();
7415                let profile_region = profile.region.clone();
7416
7417                self.profile = profile_name.clone();
7418                std::env::set_var("AWS_PROFILE", &profile_name);
7419
7420                // Use profile's region if available
7421                if let Some(region) = profile_region {
7422                    self.region = region;
7423                }
7424
7425                self.mode = Mode::Normal;
7426                // Note: Changing profile requires reconnecting to AWS
7427            }
7428        } else if self.mode == Mode::ServicePicker {
7429            let filtered = self.filtered_services();
7430            if let Some(&service) = filtered.get(self.service_picker.selected) {
7431                let new_service = match service {
7432                    "CloudWatch > Log Groups" => Service::CloudWatchLogGroups,
7433                    "CloudWatch > Logs Insights" => Service::CloudWatchInsights,
7434                    "CloudWatch > Alarms" => Service::CloudWatchAlarms,
7435                    "CloudFormation > Stacks" => Service::CloudFormationStacks,
7436                    "EC2 > Instances" => Service::Ec2Instances,
7437                    "ECR > Repositories" => Service::EcrRepositories,
7438                    "IAM > Users" => Service::IamUsers,
7439                    "IAM > Roles" => Service::IamRoles,
7440                    "IAM > User Groups" => Service::IamUserGroups,
7441                    "Lambda > Functions" => Service::LambdaFunctions,
7442                    "Lambda > Applications" => Service::LambdaApplications,
7443                    "S3 > Buckets" => Service::S3Buckets,
7444                    "SQS > Queues" => Service::SqsQueues,
7445                    _ => return,
7446                };
7447
7448                // Create new tab
7449                self.tabs.push(Tab {
7450                    service: new_service,
7451                    title: service.to_string(),
7452                    breadcrumb: service.to_string(),
7453                });
7454                self.current_tab = self.tabs.len() - 1;
7455                self.current_service = new_service;
7456                self.view_mode = ViewMode::List;
7457                self.service_selected = true;
7458                self.mode = Mode::Normal;
7459            }
7460        } else if self.mode == Mode::TabPicker {
7461            let filtered = self.get_filtered_tabs();
7462            if let Some(&(idx, _)) = filtered.get(self.tab_picker_selected) {
7463                self.current_tab = idx;
7464                self.current_service = self.tabs[idx].service;
7465                self.mode = Mode::Normal;
7466                self.tab_filter.clear();
7467            }
7468        } else if self.mode == Mode::SessionPicker {
7469            let filtered = self.get_filtered_sessions();
7470            if let Some(&session) = filtered.get(self.session_picker_selected) {
7471                let session = session.clone();
7472
7473                // Load the selected session
7474                self.current_session = Some(session.clone());
7475                self.profile = session.profile.clone();
7476                self.region = session.region.clone();
7477                self.config.region = session.region.clone();
7478                self.config.account_id = session.account_id.clone();
7479                self.config.role_arn = session.role_arn.clone();
7480
7481                // Restore tabs
7482                self.tabs = session
7483                    .tabs
7484                    .iter()
7485                    .map(|st| Tab {
7486                        service: match st.service.as_str() {
7487                            "CloudWatchLogGroups" => Service::CloudWatchLogGroups,
7488                            "CloudWatchInsights" => Service::CloudWatchInsights,
7489                            "CloudWatchAlarms" => Service::CloudWatchAlarms,
7490                            "S3Buckets" => Service::S3Buckets,
7491                            "SqsQueues" => Service::SqsQueues,
7492                            _ => Service::CloudWatchLogGroups,
7493                        },
7494                        title: st.title.clone(),
7495                        breadcrumb: st.breadcrumb.clone(),
7496                    })
7497                    .collect();
7498
7499                if !self.tabs.is_empty() {
7500                    self.current_tab = 0;
7501                    self.current_service = self.tabs[0].service;
7502                    self.service_selected = true;
7503                }
7504
7505                self.mode = Mode::Normal;
7506            }
7507        } else if self.mode == Mode::InsightsInput {
7508            // In InsightsInput mode, behavior depends on focus
7509            use crate::app::InsightsFocus;
7510            match self.insights_state.insights.insights_focus {
7511                InsightsFocus::Query => {
7512                    // Add newline to query
7513                    self.insights_state.insights.query_text.push('\n');
7514                    self.insights_state.insights.query_cursor_line += 1;
7515                    self.insights_state.insights.query_cursor_col = 0;
7516                }
7517                InsightsFocus::LogGroupSearch => {
7518                    // Toggle dropdown
7519                    self.insights_state.insights.show_dropdown =
7520                        !self.insights_state.insights.show_dropdown;
7521                }
7522                _ => {}
7523            }
7524        } else if self.mode == Mode::Normal {
7525            // If no service selected, select from service picker
7526            if !self.service_selected {
7527                let filtered = self.filtered_services();
7528                if let Some(&service) = filtered.get(self.service_picker.selected) {
7529                    match service {
7530                        "CloudWatch > Log Groups" => {
7531                            self.current_service = Service::CloudWatchLogGroups;
7532                            self.view_mode = ViewMode::List;
7533                            self.service_selected = true;
7534                        }
7535                        "CloudWatch > Logs Insights" => {
7536                            self.current_service = Service::CloudWatchInsights;
7537                            self.view_mode = ViewMode::InsightsResults;
7538                            self.service_selected = true;
7539                        }
7540                        "CloudWatch > Alarms" => {
7541                            self.current_service = Service::CloudWatchAlarms;
7542                            self.view_mode = ViewMode::List;
7543                            self.service_selected = true;
7544                        }
7545                        "S3 > Buckets" => {
7546                            self.current_service = Service::S3Buckets;
7547                            self.view_mode = ViewMode::List;
7548                            self.service_selected = true;
7549                        }
7550                        "EC2 > Instances" => {
7551                            self.current_service = Service::Ec2Instances;
7552                            self.view_mode = ViewMode::List;
7553                            self.service_selected = true;
7554                        }
7555                        "ECR > Repositories" => {
7556                            self.current_service = Service::EcrRepositories;
7557                            self.view_mode = ViewMode::List;
7558                            self.service_selected = true;
7559                        }
7560                        "Lambda > Functions" => {
7561                            self.current_service = Service::LambdaFunctions;
7562                            self.view_mode = ViewMode::List;
7563                            self.service_selected = true;
7564                        }
7565                        "Lambda > Applications" => {
7566                            self.current_service = Service::LambdaApplications;
7567                            self.view_mode = ViewMode::List;
7568                            self.service_selected = true;
7569                        }
7570                        _ => {}
7571                    }
7572                }
7573                return;
7574            }
7575
7576            // Select in content area
7577            if self.view_mode == ViewMode::InsightsResults {
7578                // Toggle expand for selected result
7579                if self.insights_state.insights.expanded_result
7580                    == Some(self.insights_state.insights.results_selected)
7581                {
7582                    self.insights_state.insights.expanded_result = None;
7583                } else {
7584                    self.insights_state.insights.expanded_result =
7585                        Some(self.insights_state.insights.results_selected);
7586                }
7587            } else if self.current_service == Service::S3Buckets {
7588                if self.s3_state.current_bucket.is_none() {
7589                    // Filter buckets first
7590                    let filtered_buckets: Vec<_> = self
7591                        .s3_state
7592                        .buckets
7593                        .items
7594                        .iter()
7595                        .filter(|b| {
7596                            if self.s3_state.buckets.filter.is_empty() {
7597                                true
7598                            } else {
7599                                b.name
7600                                    .to_lowercase()
7601                                    .contains(&self.s3_state.buckets.filter.to_lowercase())
7602                            }
7603                        })
7604                        .collect();
7605
7606                    // Find which bucket/prefix the selected row corresponds to
7607                    let mut row_idx = 0;
7608                    for bucket in filtered_buckets {
7609                        if row_idx == self.s3_state.selected_row {
7610                            // Selected a bucket - drill into it
7611                            self.s3_state.current_bucket = Some(bucket.name.clone());
7612                            self.s3_state.prefix_stack.clear();
7613                            self.s3_state.buckets.loading = true;
7614                            return;
7615                        }
7616                        row_idx += 1;
7617
7618                        // Skip error rows - they're not selectable
7619                        if self.s3_state.bucket_errors.contains_key(&bucket.name)
7620                            && self.s3_state.expanded_prefixes.contains(&bucket.name)
7621                        {
7622                            continue;
7623                        }
7624
7625                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
7626                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
7627                                for obj in preview {
7628                                    if row_idx == self.s3_state.selected_row {
7629                                        // Selected a prefix - drill into bucket with this prefix
7630                                        if obj.is_prefix {
7631                                            self.s3_state.current_bucket =
7632                                                Some(bucket.name.clone());
7633                                            self.s3_state.prefix_stack = vec![obj.key.clone()];
7634                                            self.s3_state.buckets.loading = true;
7635                                        }
7636                                        return;
7637                                    }
7638                                    row_idx += 1;
7639
7640                                    // Check nested preview rows
7641                                    if obj.is_prefix
7642                                        && self.s3_state.expanded_prefixes.contains(&obj.key)
7643                                    {
7644                                        if let Some(nested) =
7645                                            self.s3_state.prefix_preview.get(&obj.key)
7646                                        {
7647                                            for nested_obj in nested {
7648                                                if row_idx == self.s3_state.selected_row {
7649                                                    // Selected a nested prefix - drill into bucket with this prefix
7650                                                    if nested_obj.is_prefix {
7651                                                        self.s3_state.current_bucket =
7652                                                            Some(bucket.name.clone());
7653                                                        // Build proper prefix stack: parent, then child
7654                                                        self.s3_state.prefix_stack = vec![
7655                                                            obj.key.clone(),
7656                                                            nested_obj.key.clone(),
7657                                                        ];
7658                                                        self.s3_state.buckets.loading = true;
7659                                                    }
7660                                                    return;
7661                                                }
7662                                                row_idx += 1;
7663                                            }
7664                                        } else {
7665                                            row_idx += 1;
7666                                        }
7667                                    }
7668                                }
7669                            } else {
7670                                row_idx += 1;
7671                            }
7672                        }
7673                    }
7674                } else {
7675                    // In objects view - map visual index to actual object (including nested items)
7676                    let mut visual_idx = 0;
7677                    let mut found_obj: Option<S3Object> = None;
7678
7679                    // Helper to recursively check nested items
7680                    fn check_nested_select(
7681                        obj: &S3Object,
7682                        visual_idx: &mut usize,
7683                        target_idx: usize,
7684                        expanded_prefixes: &std::collections::HashSet<String>,
7685                        prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
7686                        found_obj: &mut Option<S3Object>,
7687                    ) {
7688                        if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
7689                            if let Some(preview) = prefix_preview.get(&obj.key) {
7690                                for nested_obj in preview {
7691                                    if *visual_idx == target_idx {
7692                                        *found_obj = Some(nested_obj.clone());
7693                                        return;
7694                                    }
7695                                    *visual_idx += 1;
7696
7697                                    // Recursively check deeper levels
7698                                    check_nested_select(
7699                                        nested_obj,
7700                                        visual_idx,
7701                                        target_idx,
7702                                        expanded_prefixes,
7703                                        prefix_preview,
7704                                        found_obj,
7705                                    );
7706                                    if found_obj.is_some() {
7707                                        return;
7708                                    }
7709                                }
7710                            } else {
7711                                // Loading row
7712                                *visual_idx += 1;
7713                            }
7714                        }
7715                    }
7716
7717                    for obj in &self.s3_state.objects {
7718                        if visual_idx == self.s3_state.selected_object {
7719                            found_obj = Some(obj.clone());
7720                            break;
7721                        }
7722                        visual_idx += 1;
7723
7724                        // Check nested items recursively
7725                        check_nested_select(
7726                            obj,
7727                            &mut visual_idx,
7728                            self.s3_state.selected_object,
7729                            &self.s3_state.expanded_prefixes,
7730                            &self.s3_state.prefix_preview,
7731                            &mut found_obj,
7732                        );
7733                        if found_obj.is_some() {
7734                            break;
7735                        }
7736                    }
7737
7738                    if let Some(obj) = found_obj {
7739                        if obj.is_prefix {
7740                            // Drill into prefix
7741                            self.s3_state.prefix_stack.push(obj.key.clone());
7742                            self.s3_state.buckets.loading = true;
7743                        }
7744                    }
7745                }
7746            } else if self.current_service == Service::CloudFormationStacks {
7747                if self.cfn_state.current_stack.is_none() {
7748                    // Drill into stack detail view
7749                    let filtered_stacks = filtered_cloudformation_stacks(self);
7750                    if let Some(stack) = self.cfn_state.table.get_selected(&filtered_stacks) {
7751                        let stack_name = stack.name.clone();
7752                        let mut tags = stack.tags.clone();
7753                        tags.sort_by(|a, b| a.0.cmp(&b.0));
7754
7755                        self.cfn_state.current_stack = Some(stack_name);
7756                        self.cfn_state.tags.items = tags;
7757                        self.cfn_state.tags.reset();
7758                        self.cfn_state.table.loading = true;
7759                        self.update_current_tab_breadcrumb();
7760                    }
7761                }
7762            } else if self.current_service == Service::EcrRepositories {
7763                if self.ecr_state.current_repository.is_none() {
7764                    // In repositories view - drill into selected repository
7765                    let filtered_repos = filtered_ecr_repositories(self);
7766                    if let Some(repo) = self.ecr_state.repositories.get_selected(&filtered_repos) {
7767                        let repo_name = repo.name.clone();
7768                        let repo_uri = repo.uri.clone();
7769                        self.ecr_state.current_repository = Some(repo_name);
7770                        self.ecr_state.current_repository_uri = Some(repo_uri);
7771                        self.ecr_state.images.reset();
7772                        self.ecr_state.repositories.loading = true;
7773                    }
7774                }
7775            } else if self.current_service == Service::Ec2Instances {
7776                if self.ec2_state.current_instance.is_none() {
7777                    let filtered_instances = filtered_ec2_instances(self);
7778                    if let Some(instance) = self.ec2_state.table.get_selected(&filtered_instances) {
7779                        self.ec2_state.current_instance = Some(instance.instance_id.clone());
7780                        self.view_mode = ViewMode::Detail;
7781                        self.update_current_tab_breadcrumb();
7782                    }
7783                }
7784            } else if self.current_service == Service::SqsQueues {
7785                if self.sqs_state.current_queue.is_none() {
7786                    let filtered_queues = filtered_queues(
7787                        &self.sqs_state.queues.items,
7788                        &self.sqs_state.queues.filter,
7789                    );
7790                    if let Some(queue) = self.sqs_state.queues.get_selected(&filtered_queues) {
7791                        self.sqs_state.current_queue = Some(queue.url.clone());
7792
7793                        if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
7794                            self.sqs_state.metrics_loading = true;
7795                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers {
7796                            self.sqs_state.triggers.loading = true;
7797                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes {
7798                            self.sqs_state.pipes.loading = true;
7799                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging {
7800                            self.sqs_state.tags.loading = true;
7801                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions {
7802                            self.sqs_state.subscriptions.loading = true;
7803                        }
7804                    }
7805                }
7806            } else if self.current_service == Service::IamUsers {
7807                if self.iam_state.current_user.is_some() {
7808                    // Open policy view only when in Permissions tab
7809                    if self.iam_state.user_tab == UserTab::Permissions {
7810                        let filtered = filtered_iam_policies(self);
7811                        if let Some(policy) = self.iam_state.policies.get_selected(&filtered) {
7812                            self.iam_state.current_policy = Some(policy.policy_name.clone());
7813                            self.iam_state.policy_scroll = 0;
7814                            self.view_mode = ViewMode::PolicyView;
7815                            self.iam_state.policies.loading = true;
7816                            self.update_current_tab_breadcrumb();
7817                        }
7818                    }
7819                } else if self.iam_state.current_user.is_none() {
7820                    let filtered_users = filtered_iam_users(self);
7821                    if let Some(user) = self.iam_state.users.get_selected(&filtered_users) {
7822                        self.iam_state.current_user = Some(user.user_name.clone());
7823                        self.iam_state.user_tab = UserTab::Permissions;
7824                        self.iam_state.policies.reset();
7825                        self.update_current_tab_breadcrumb();
7826                    }
7827                }
7828            } else if self.current_service == Service::IamRoles {
7829                if self.iam_state.current_role.is_some() {
7830                    // Open policy view only when in Permissions tab
7831                    if self.iam_state.role_tab == RoleTab::Permissions {
7832                        let filtered = filtered_iam_policies(self);
7833                        if let Some(policy) = self.iam_state.policies.get_selected(&filtered) {
7834                            self.iam_state.current_policy = Some(policy.policy_name.clone());
7835                            self.iam_state.policy_scroll = 0;
7836                            self.view_mode = ViewMode::PolicyView;
7837                            self.iam_state.policies.loading = true;
7838                            self.update_current_tab_breadcrumb();
7839                        }
7840                    }
7841                } else if self.iam_state.current_role.is_none() {
7842                    let filtered_roles = filtered_iam_roles(self);
7843                    if let Some(role) = self.iam_state.roles.get_selected(&filtered_roles) {
7844                        self.iam_state.current_role = Some(role.role_name.clone());
7845                        self.iam_state.role_tab = RoleTab::Permissions;
7846                        self.iam_state.policies.reset();
7847                        self.update_current_tab_breadcrumb();
7848                    }
7849                }
7850            } else if self.current_service == Service::IamUserGroups {
7851                if self.iam_state.current_group.is_none() {
7852                    let filtered_groups: Vec<_> = self
7853                        .iam_state
7854                        .groups
7855                        .items
7856                        .iter()
7857                        .filter(|g| {
7858                            if self.iam_state.groups.filter.is_empty() {
7859                                true
7860                            } else {
7861                                g.group_name
7862                                    .to_lowercase()
7863                                    .contains(&self.iam_state.groups.filter.to_lowercase())
7864                            }
7865                        })
7866                        .collect();
7867                    if let Some(group) = self.iam_state.groups.get_selected(&filtered_groups) {
7868                        self.iam_state.current_group = Some(group.group_name.clone());
7869                        self.update_current_tab_breadcrumb();
7870                    }
7871                }
7872            } else if self.current_service == Service::LambdaFunctions {
7873                if self.lambda_state.current_function.is_some()
7874                    && self.lambda_state.detail_tab == LambdaDetailTab::Versions
7875                {
7876                    // In Normal mode, select version to open detail view
7877                    // In other modes (FilterInput), toggle expansion
7878                    if self.mode == Mode::Normal {
7879                        let page_size = self.lambda_state.version_table.page_size.value();
7880                        let filtered: Vec<_> = self
7881                            .lambda_state
7882                            .version_table
7883                            .items
7884                            .iter()
7885                            .filter(|v| {
7886                                self.lambda_state.version_table.filter.is_empty()
7887                                    || v.version.to_lowercase().contains(
7888                                        &self.lambda_state.version_table.filter.to_lowercase(),
7889                                    )
7890                                    || v.aliases.to_lowercase().contains(
7891                                        &self.lambda_state.version_table.filter.to_lowercase(),
7892                                    )
7893                            })
7894                            .collect();
7895                        let current_page = self.lambda_state.version_table.selected / page_size;
7896                        let start_idx = current_page * page_size;
7897                        let end_idx = (start_idx + page_size).min(filtered.len());
7898                        let paginated: Vec<_> = filtered[start_idx..end_idx].to_vec();
7899                        let page_index = self.lambda_state.version_table.selected % page_size;
7900                        if let Some(version) = paginated.get(page_index) {
7901                            self.lambda_state.current_version = Some(version.version.clone());
7902                            self.lambda_state.detail_tab = LambdaDetailTab::Code;
7903                        }
7904                    } else {
7905                        // Toggle expansion
7906                        if self.lambda_state.version_table.expanded_item
7907                            == Some(self.lambda_state.version_table.selected)
7908                        {
7909                            self.lambda_state.version_table.collapse();
7910                        } else {
7911                            self.lambda_state.version_table.expanded_item =
7912                                Some(self.lambda_state.version_table.selected);
7913                        }
7914                    }
7915                } else if self.lambda_state.current_function.is_some()
7916                    && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
7917                {
7918                    // Select alias to view detail (no tab change - alias view has no tabs)
7919                    let filtered: Vec<_> = self
7920                        .lambda_state
7921                        .alias_table
7922                        .items
7923                        .iter()
7924                        .filter(|a| {
7925                            self.lambda_state.alias_table.filter.is_empty()
7926                                || a.name
7927                                    .to_lowercase()
7928                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
7929                                || a.versions
7930                                    .to_lowercase()
7931                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
7932                        })
7933                        .collect();
7934                    if let Some(alias) = self.lambda_state.alias_table.get_selected(&filtered) {
7935                        self.lambda_state.current_alias = Some(alias.name.clone());
7936                    }
7937                } else if self.lambda_state.current_function.is_none() {
7938                    let filtered_functions = filtered_lambda_functions(self);
7939                    if let Some(func) = self.lambda_state.table.get_selected(&filtered_functions) {
7940                        self.lambda_state.current_function = Some(func.name.clone());
7941                        self.lambda_state.detail_tab = LambdaDetailTab::Code;
7942                        self.update_current_tab_breadcrumb();
7943                    }
7944                }
7945            } else if self.current_service == Service::LambdaApplications {
7946                let filtered = filtered_lambda_applications(self);
7947                if let Some(app) = self.lambda_application_state.table.get_selected(&filtered) {
7948                    let app_name = app.name.clone();
7949                    self.lambda_application_state.current_application = Some(app_name.clone());
7950                    self.lambda_application_state.detail_tab = LambdaApplicationDetailTab::Overview;
7951
7952                    // Load mock resources
7953                    use crate::lambda::Resource;
7954                    self.lambda_application_state.resources.items = vec![
7955                        Resource {
7956                            logical_id: "ApiGatewayRestApi".to_string(),
7957                            physical_id: "abc123xyz".to_string(),
7958                            resource_type: "AWS::ApiGateway::RestApi".to_string(),
7959                            last_modified: "2025-01-10 14:30:00 (UTC)".to_string(),
7960                        },
7961                        Resource {
7962                            logical_id: "LambdaFunction".to_string(),
7963                            physical_id: format!("{}-function", app_name),
7964                            resource_type: "AWS::Lambda::Function".to_string(),
7965                            last_modified: "2025-01-10 14:25:00 (UTC)".to_string(),
7966                        },
7967                        Resource {
7968                            logical_id: "DynamoDBTable".to_string(),
7969                            physical_id: format!("{}-table", app_name),
7970                            resource_type: "AWS::DynamoDB::Table".to_string(),
7971                            last_modified: "2025-01-09 10:15:00 (UTC)".to_string(),
7972                        },
7973                    ];
7974
7975                    // Load mock deployments
7976                    use crate::lambda::Deployment;
7977                    self.lambda_application_state.deployments.items = vec![
7978                        Deployment {
7979                            deployment_id: "d-ABC123XYZ".to_string(),
7980                            resource_type: "AWS::Serverless::Application".to_string(),
7981                            last_updated: "2025-01-10 14:30:00 (UTC)".to_string(),
7982                            status: "Succeeded".to_string(),
7983                        },
7984                        Deployment {
7985                            deployment_id: "d-DEF456UVW".to_string(),
7986                            resource_type: "AWS::Serverless::Application".to_string(),
7987                            last_updated: "2025-01-09 10:15:00 (UTC)".to_string(),
7988                            status: "Succeeded".to_string(),
7989                        },
7990                    ];
7991
7992                    self.update_current_tab_breadcrumb();
7993                }
7994            } else if self.current_service == Service::CloudWatchLogGroups {
7995                if self.view_mode == ViewMode::List {
7996                    // Map filtered selection to actual group index
7997                    let filtered_groups = filtered_log_groups(self);
7998                    if let Some(selected_group) =
7999                        filtered_groups.get(self.log_groups_state.log_groups.selected)
8000                    {
8001                        if let Some(actual_idx) = self
8002                            .log_groups_state
8003                            .log_groups
8004                            .items
8005                            .iter()
8006                            .position(|g| g.name == selected_group.name)
8007                        {
8008                            self.log_groups_state.log_groups.selected = actual_idx;
8009                        }
8010                    }
8011                    self.view_mode = ViewMode::Detail;
8012                    self.log_groups_state.log_streams.clear();
8013                    self.log_groups_state.selected_stream = 0;
8014                    self.log_groups_state.loading = true;
8015                    self.update_current_tab_breadcrumb();
8016                } else if self.view_mode == ViewMode::Detail {
8017                    // Map filtered stream selection to actual stream index
8018                    let filtered_streams = filtered_log_streams(self);
8019                    if let Some(selected_stream) =
8020                        filtered_streams.get(self.log_groups_state.selected_stream)
8021                    {
8022                        if let Some(actual_idx) = self
8023                            .log_groups_state
8024                            .log_streams
8025                            .iter()
8026                            .position(|s| s.name == selected_stream.name)
8027                        {
8028                            self.log_groups_state.selected_stream = actual_idx;
8029                        }
8030                    }
8031                    self.view_mode = ViewMode::Events;
8032                    self.update_current_tab_breadcrumb();
8033                    self.log_groups_state.log_events.clear();
8034                    self.log_groups_state.event_scroll_offset = 0;
8035                    self.log_groups_state.next_backward_token = None;
8036                    self.log_groups_state.loading = true;
8037                } else if self.view_mode == ViewMode::Events {
8038                    // Toggle expand for selected event
8039                    if self.log_groups_state.expanded_event
8040                        == Some(self.log_groups_state.event_scroll_offset)
8041                    {
8042                        self.log_groups_state.expanded_event = None;
8043                    } else {
8044                        self.log_groups_state.expanded_event =
8045                            Some(self.log_groups_state.event_scroll_offset);
8046                    }
8047                }
8048            } else if self.current_service == Service::CloudWatchAlarms {
8049                // Toggle expand for selected alarm
8050                self.alarms_state.table.toggle_expand();
8051            } else if self.current_service == Service::CloudWatchInsights {
8052                // In Normal mode, Enter always executes query
8053                if !self.insights_state.insights.selected_log_groups.is_empty() {
8054                    self.log_groups_state.loading = true;
8055                    self.insights_state.insights.query_completed = true;
8056                }
8057            }
8058        }
8059    }
8060
8061    pub async fn load_log_groups(&mut self) -> anyhow::Result<()> {
8062        self.log_groups_state.log_groups.items = self.cloudwatch_client.list_log_groups().await?;
8063        Ok(())
8064    }
8065
8066    pub async fn load_alarms(&mut self) -> anyhow::Result<()> {
8067        let alarms = self.alarms_client.list_alarms().await?;
8068        self.alarms_state.table.items = alarms
8069            .into_iter()
8070            .map(
8071                |(
8072                    name,
8073                    state,
8074                    state_updated,
8075                    description,
8076                    metric_name,
8077                    namespace,
8078                    statistic,
8079                    period,
8080                    comparison,
8081                    threshold,
8082                    actions_enabled,
8083                    state_reason,
8084                    resource,
8085                    dimensions,
8086                    expression,
8087                    alarm_type,
8088                    cross_account,
8089                )| Alarm {
8090                    name,
8091                    state,
8092                    state_updated_timestamp: state_updated,
8093                    description,
8094                    metric_name,
8095                    namespace,
8096                    statistic,
8097                    period,
8098                    comparison_operator: comparison,
8099                    threshold,
8100                    actions_enabled,
8101                    state_reason,
8102                    resource,
8103                    dimensions,
8104                    expression,
8105                    alarm_type,
8106                    cross_account,
8107                },
8108            )
8109            .collect();
8110        Ok(())
8111    }
8112
8113    pub async fn load_s3_objects(&mut self) -> anyhow::Result<()> {
8114        if let Some(bucket_name) = &self.s3_state.current_bucket {
8115            // Get or fetch bucket region
8116            let bucket_region = if let Some(bucket) = self
8117                .s3_state
8118                .buckets
8119                .items
8120                .iter_mut()
8121                .find(|b| &b.name == bucket_name)
8122            {
8123                if bucket.region.is_empty() {
8124                    // Fetch the actual region
8125                    let region = self.s3_client.get_bucket_location(bucket_name).await?;
8126                    bucket.region = region.clone();
8127                    region
8128                } else {
8129                    bucket.region.clone()
8130                }
8131            } else {
8132                self.config.region.clone()
8133            };
8134
8135            let prefix = self
8136                .s3_state
8137                .prefix_stack
8138                .last()
8139                .cloned()
8140                .unwrap_or_default();
8141            let objects = self
8142                .s3_client
8143                .list_objects(bucket_name, &bucket_region, &prefix)
8144                .await?;
8145            self.s3_state.objects = objects
8146                .into_iter()
8147                .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
8148                    key,
8149                    size,
8150                    last_modified: modified,
8151                    is_prefix,
8152                    storage_class,
8153                })
8154                .collect();
8155            self.s3_state.selected_object = 0;
8156        }
8157        Ok(())
8158    }
8159
8160    pub async fn load_bucket_preview(&mut self, bucket_name: String) -> anyhow::Result<()> {
8161        let bucket_region = self
8162            .s3_state
8163            .buckets
8164            .items
8165            .iter()
8166            .find(|b| b.name == bucket_name)
8167            .and_then(|b| {
8168                if b.region.is_empty() {
8169                    None
8170                } else {
8171                    Some(b.region.as_str())
8172                }
8173            })
8174            .unwrap_or(self.config.region.as_str());
8175        let objects = self
8176            .s3_client
8177            .list_objects(&bucket_name, bucket_region, "")
8178            .await?;
8179        let preview: Vec<S3Object> = objects
8180            .into_iter()
8181            .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
8182                key,
8183                size,
8184                last_modified: modified,
8185                is_prefix,
8186                storage_class,
8187            })
8188            .collect();
8189        self.s3_state.bucket_preview.insert(bucket_name, preview);
8190        Ok(())
8191    }
8192
8193    pub async fn load_prefix_preview(
8194        &mut self,
8195        bucket_name: String,
8196        prefix: String,
8197    ) -> anyhow::Result<()> {
8198        let bucket_region = self
8199            .s3_state
8200            .buckets
8201            .items
8202            .iter()
8203            .find(|b| b.name == bucket_name)
8204            .and_then(|b| {
8205                if b.region.is_empty() {
8206                    None
8207                } else {
8208                    Some(b.region.as_str())
8209                }
8210            })
8211            .unwrap_or(self.config.region.as_str());
8212        let objects = self
8213            .s3_client
8214            .list_objects(&bucket_name, bucket_region, &prefix)
8215            .await?;
8216        let preview: Vec<S3Object> = objects
8217            .into_iter()
8218            .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
8219                key,
8220                size,
8221                last_modified: modified,
8222                is_prefix,
8223                storage_class,
8224            })
8225            .collect();
8226        self.s3_state.prefix_preview.insert(prefix, preview);
8227        Ok(())
8228    }
8229
8230    pub async fn load_ecr_repositories(&mut self) -> anyhow::Result<()> {
8231        let repos = match self.ecr_state.tab {
8232            EcrTab::Private => self.ecr_client.list_private_repositories().await?,
8233            EcrTab::Public => self.ecr_client.list_public_repositories().await?,
8234        };
8235
8236        self.ecr_state.repositories.items = repos
8237            .into_iter()
8238            .map(|r| EcrRepository {
8239                name: r.name,
8240                uri: r.uri,
8241                created_at: r.created_at,
8242                tag_immutability: r.tag_immutability,
8243                encryption_type: r.encryption_type,
8244            })
8245            .collect();
8246
8247        self.ecr_state
8248            .repositories
8249            .items
8250            .sort_by(|a, b| a.name.cmp(&b.name));
8251        Ok(())
8252    }
8253
8254    pub async fn load_ec2_instances(&mut self) -> anyhow::Result<()> {
8255        let instances = self.ec2_client.list_instances().await?;
8256
8257        self.ec2_state.table.items = instances
8258            .into_iter()
8259            .map(|i| Ec2Instance {
8260                instance_id: i.instance_id,
8261                name: i.name,
8262                state: i.state,
8263                instance_type: i.instance_type,
8264                availability_zone: i.availability_zone,
8265                public_ipv4_dns: i.public_ipv4_dns,
8266                public_ipv4_address: i.public_ipv4_address,
8267                elastic_ip: i.elastic_ip,
8268                ipv6_ips: i.ipv6_ips,
8269                monitoring: i.monitoring,
8270                security_groups: i.security_groups,
8271                key_name: i.key_name,
8272                launch_time: i.launch_time,
8273                platform_details: i.platform_details,
8274                status_checks: i.status_checks,
8275                alarm_status: i.alarm_status,
8276                private_dns_name: String::new(),
8277                private_ip_address: String::new(),
8278                security_group_ids: String::new(),
8279                owner_id: String::new(),
8280                volume_id: String::new(),
8281                root_device_name: String::new(),
8282                root_device_type: String::new(),
8283                ebs_optimized: String::new(),
8284                image_id: String::new(),
8285                kernel_id: String::new(),
8286                ramdisk_id: String::new(),
8287                ami_launch_index: String::new(),
8288                reservation_id: String::new(),
8289                vpc_id: String::new(),
8290                subnet_ids: String::new(),
8291                instance_lifecycle: String::new(),
8292                architecture: String::new(),
8293                virtualization_type: String::new(),
8294                platform: String::new(),
8295                iam_instance_profile_arn: String::new(),
8296                tenancy: String::new(),
8297                affinity: String::new(),
8298                host_id: String::new(),
8299                placement_group: String::new(),
8300                partition_number: String::new(),
8301                capacity_reservation_id: String::new(),
8302                state_transition_reason_code: String::new(),
8303                state_transition_reason_message: String::new(),
8304                stop_hibernation_behavior: String::new(),
8305                outpost_arn: String::new(),
8306                product_codes: String::new(),
8307                availability_zone_id: String::new(),
8308                imdsv2: String::new(),
8309                usage_operation: String::new(),
8310                managed: String::new(),
8311                operator: String::new(),
8312            })
8313            .collect();
8314
8315        // Sort by launch time descending by default
8316        self.ec2_state
8317            .table
8318            .items
8319            .sort_by(|a, b| b.launch_time.cmp(&a.launch_time));
8320        Ok(())
8321    }
8322
8323    pub async fn load_ecr_images(&mut self) -> anyhow::Result<()> {
8324        if let Some(repo_name) = &self.ecr_state.current_repository {
8325            if let Some(repo_uri) = &self.ecr_state.current_repository_uri {
8326                let images = self.ecr_client.list_images(repo_name, repo_uri).await?;
8327
8328                self.ecr_state.images.items = images
8329                    .into_iter()
8330                    .map(|i| EcrImage {
8331                        tag: i.tag,
8332                        artifact_type: i.artifact_type,
8333                        pushed_at: i.pushed_at,
8334                        size_bytes: i.size_bytes,
8335                        uri: i.uri,
8336                        digest: i.digest,
8337                        last_pull_time: i.last_pull_time,
8338                    })
8339                    .collect();
8340
8341                self.ecr_state
8342                    .images
8343                    .items
8344                    .sort_by(|a, b| b.pushed_at.cmp(&a.pushed_at));
8345            }
8346        }
8347        Ok(())
8348    }
8349
8350    pub async fn load_cloudformation_stacks(&mut self) -> anyhow::Result<()> {
8351        let stacks = self
8352            .cloudformation_client
8353            .list_stacks(self.cfn_state.view_nested)
8354            .await?;
8355
8356        let mut stacks: Vec<CfnStack> = stacks
8357            .into_iter()
8358            .map(|s| CfnStack {
8359                name: s.name,
8360                stack_id: s.stack_id,
8361                status: s.status,
8362                created_time: s.created_time,
8363                updated_time: s.updated_time,
8364                deleted_time: s.deleted_time,
8365                drift_status: s.drift_status,
8366                last_drift_check_time: s.last_drift_check_time,
8367                status_reason: s.status_reason,
8368                description: s.description,
8369                detailed_status: String::new(),
8370                root_stack: String::new(),
8371                parent_stack: String::new(),
8372                termination_protection: false,
8373                iam_role: String::new(),
8374                tags: Vec::new(),
8375                stack_policy: String::new(),
8376                rollback_monitoring_time: String::new(),
8377                rollback_alarms: Vec::new(),
8378                notification_arns: Vec::new(),
8379            })
8380            .collect();
8381
8382        // Sort by created_time DESC
8383        stacks.sort_by(|a, b| b.created_time.cmp(&a.created_time));
8384
8385        self.cfn_state.table.items = stacks;
8386
8387        Ok(())
8388    }
8389
8390    pub async fn load_cfn_template(&mut self, stack_name: &str) -> anyhow::Result<()> {
8391        let template = self.cloudformation_client.get_template(stack_name).await?;
8392        self.cfn_state.template_body = template;
8393        self.cfn_state.template_scroll = 0;
8394        Ok(())
8395    }
8396
8397    pub async fn load_cfn_parameters(&mut self, stack_name: &str) -> anyhow::Result<()> {
8398        let mut parameters = self
8399            .cloudformation_client
8400            .get_stack_parameters(stack_name)
8401            .await?;
8402        parameters.sort_by(|a, b| a.key.cmp(&b.key));
8403        self.cfn_state.parameters.items = parameters;
8404        self.cfn_state.parameters.reset();
8405        Ok(())
8406    }
8407
8408    pub async fn load_cfn_outputs(&mut self, stack_name: &str) -> anyhow::Result<()> {
8409        let outputs = self
8410            .cloudformation_client
8411            .get_stack_outputs(stack_name)
8412            .await?;
8413        self.cfn_state.outputs.items = outputs;
8414        self.cfn_state.outputs.reset();
8415        Ok(())
8416    }
8417
8418    pub async fn load_cfn_resources(&mut self, stack_name: &str) -> anyhow::Result<()> {
8419        let resources = self
8420            .cloudformation_client
8421            .get_stack_resources(stack_name)
8422            .await?;
8423        self.cfn_state.resources.items = resources;
8424        self.cfn_state.resources.reset();
8425        Ok(())
8426    }
8427
8428    pub async fn load_role_policies(&mut self, role_name: &str) -> anyhow::Result<()> {
8429        // Load attached (managed) policies
8430        let attached_policies = self
8431            .iam_client
8432            .list_attached_role_policies(role_name)
8433            .await
8434            .map_err(|e| anyhow::anyhow!(e))?;
8435
8436        let mut policies: Vec<IamPolicy> = attached_policies
8437            .into_iter()
8438            .map(|p| IamPolicy {
8439                policy_name: p.policy_name().unwrap_or("").to_string(),
8440                policy_type: "Managed".to_string(),
8441                attached_via: "Direct".to_string(),
8442                attached_entities: "-".to_string(),
8443                description: "-".to_string(),
8444                creation_time: "-".to_string(),
8445                edited_time: "-".to_string(),
8446                policy_arn: p.policy_arn().map(|s| s.to_string()),
8447            })
8448            .collect();
8449
8450        // Load inline policies
8451        let inline_policy_names = self
8452            .iam_client
8453            .list_role_policies(role_name)
8454            .await
8455            .map_err(|e| anyhow::anyhow!(e))?;
8456
8457        for policy_name in inline_policy_names {
8458            policies.push(IamPolicy {
8459                policy_name,
8460                policy_type: "Inline".to_string(),
8461                attached_via: "Direct".to_string(),
8462                attached_entities: "-".to_string(),
8463                description: "-".to_string(),
8464                creation_time: "-".to_string(),
8465                edited_time: "-".to_string(),
8466                policy_arn: None,
8467            });
8468        }
8469
8470        self.iam_state.policies.items = policies;
8471
8472        Ok(())
8473    }
8474
8475    pub async fn load_group_policies(&mut self, group_name: &str) -> anyhow::Result<()> {
8476        let attached_policies = self
8477            .iam_client
8478            .list_attached_group_policies(group_name)
8479            .await
8480            .map_err(|e| anyhow::anyhow!(e))?;
8481
8482        let mut policies: Vec<IamPolicy> = attached_policies
8483            .into_iter()
8484            .map(|p| IamPolicy {
8485                policy_name: p.policy_name().unwrap_or("").to_string(),
8486                policy_type: "AWS managed".to_string(),
8487                attached_via: "Direct".to_string(),
8488                attached_entities: "-".to_string(),
8489                description: "-".to_string(),
8490                creation_time: "-".to_string(),
8491                edited_time: "-".to_string(),
8492                policy_arn: p.policy_arn().map(|s| s.to_string()),
8493            })
8494            .collect();
8495
8496        let inline_policy_names = self
8497            .iam_client
8498            .list_group_policies(group_name)
8499            .await
8500            .map_err(|e| anyhow::anyhow!(e))?;
8501
8502        for policy_name in inline_policy_names {
8503            policies.push(IamPolicy {
8504                policy_name,
8505                policy_type: "Inline".to_string(),
8506                attached_via: "Direct".to_string(),
8507                attached_entities: "-".to_string(),
8508                description: "-".to_string(),
8509                creation_time: "-".to_string(),
8510                edited_time: "-".to_string(),
8511                policy_arn: None,
8512            });
8513        }
8514
8515        self.iam_state.policies.items = policies;
8516
8517        Ok(())
8518    }
8519
8520    pub async fn load_group_users(&mut self, group_name: &str) -> anyhow::Result<()> {
8521        let users = self
8522            .iam_client
8523            .get_group_users(group_name)
8524            .await
8525            .map_err(|e| anyhow::anyhow!(e))?;
8526
8527        let group_users: Vec<IamGroupUser> = users
8528            .into_iter()
8529            .map(|u| {
8530                let creation_time = {
8531                    let dt = u.create_date();
8532                    let timestamp = dt.secs();
8533                    let datetime =
8534                        chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_default();
8535                    datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
8536                };
8537
8538                IamGroupUser {
8539                    user_name: u.user_name().to_string(),
8540                    groups: String::new(),
8541                    last_activity: String::new(),
8542                    creation_time,
8543                }
8544            })
8545            .collect();
8546
8547        self.iam_state.group_users.items = group_users;
8548
8549        Ok(())
8550    }
8551
8552    pub async fn load_policy_document(
8553        &mut self,
8554        role_name: &str,
8555        policy_name: &str,
8556    ) -> anyhow::Result<()> {
8557        // Find the policy to get its ARN and type
8558        let policy = self
8559            .iam_state
8560            .policies
8561            .items
8562            .iter()
8563            .find(|p| p.policy_name == policy_name)
8564            .ok_or_else(|| anyhow::anyhow!("Policy not found"))?;
8565
8566        let document = if let Some(policy_arn) = &policy.policy_arn {
8567            // Managed policy - use get_policy_version
8568            self.iam_client
8569                .get_policy_version(policy_arn)
8570                .await
8571                .map_err(|e| anyhow::anyhow!(e))?
8572        } else {
8573            // Inline policy - use get_role_policy
8574            self.iam_client
8575                .get_role_policy(role_name, policy_name)
8576                .await
8577                .map_err(|e| anyhow::anyhow!(e))?
8578        };
8579
8580        self.iam_state.policy_document = document;
8581
8582        Ok(())
8583    }
8584
8585    pub async fn load_trust_policy(&mut self, role_name: &str) -> anyhow::Result<()> {
8586        let document = self
8587            .iam_client
8588            .get_role(role_name)
8589            .await
8590            .map_err(|e| anyhow::anyhow!(e))?;
8591
8592        self.iam_state.trust_policy_document = document;
8593
8594        Ok(())
8595    }
8596
8597    pub async fn load_last_accessed_services(&mut self, _role_name: &str) -> anyhow::Result<()> {
8598        // TODO: Implement real AWS API call to get service last accessed details
8599        self.iam_state.last_accessed_services.items = vec![];
8600        self.iam_state.last_accessed_services.selected = 0;
8601
8602        Ok(())
8603    }
8604
8605    pub async fn load_role_tags(&mut self, role_name: &str) -> anyhow::Result<()> {
8606        let tags = self
8607            .iam_client
8608            .list_role_tags(role_name)
8609            .await
8610            .map_err(|e| anyhow::anyhow!(e))?;
8611        self.iam_state.tags.items = tags
8612            .into_iter()
8613            .map(|(k, v)| IamRoleTag { key: k, value: v })
8614            .collect();
8615        self.iam_state.tags.reset();
8616        Ok(())
8617    }
8618
8619    pub async fn load_user_tags(&mut self, user_name: &str) -> anyhow::Result<()> {
8620        let tags = self
8621            .iam_client
8622            .list_user_tags(user_name)
8623            .await
8624            .map_err(|e| anyhow::anyhow!(e))?;
8625        self.iam_state.user_tags.items = tags
8626            .into_iter()
8627            .map(|(k, v)| IamUserTag { key: k, value: v })
8628            .collect();
8629        self.iam_state.user_tags.reset();
8630        Ok(())
8631    }
8632
8633    pub async fn load_log_streams(&mut self) -> anyhow::Result<()> {
8634        if let Some(group) = self
8635            .log_groups_state
8636            .log_groups
8637            .items
8638            .get(self.log_groups_state.log_groups.selected)
8639        {
8640            self.log_groups_state.log_streams =
8641                self.cloudwatch_client.list_log_streams(&group.name).await?;
8642            self.log_groups_state.selected_stream = 0;
8643        }
8644        Ok(())
8645    }
8646
8647    pub async fn load_log_events(&mut self) -> anyhow::Result<()> {
8648        if let Some(group) = self
8649            .log_groups_state
8650            .log_groups
8651            .items
8652            .get(self.log_groups_state.log_groups.selected)
8653        {
8654            if let Some(stream) = self
8655                .log_groups_state
8656                .log_streams
8657                .get(self.log_groups_state.selected_stream)
8658            {
8659                // Calculate time range from relative date picker
8660                let (start_time, end_time) =
8661                    if let Ok(amount) = self.log_groups_state.relative_amount.parse::<i64>() {
8662                        let now = chrono::Utc::now().timestamp_millis();
8663                        let duration_ms = match self.log_groups_state.relative_unit {
8664                            TimeUnit::Minutes => amount * 60 * 1000,
8665                            TimeUnit::Hours => amount * 60 * 60 * 1000,
8666                            TimeUnit::Days => amount * 24 * 60 * 60 * 1000,
8667                            TimeUnit::Weeks => amount * 7 * 24 * 60 * 60 * 1000,
8668                        };
8669                        (Some(now - duration_ms), Some(now))
8670                    } else {
8671                        (None, None)
8672                    };
8673
8674                let (mut events, has_more, token) = self
8675                    .cloudwatch_client
8676                    .get_log_events(
8677                        &group.name,
8678                        &stream.name,
8679                        self.log_groups_state.next_backward_token.clone(),
8680                        start_time,
8681                        end_time,
8682                    )
8683                    .await?;
8684
8685                if self.log_groups_state.next_backward_token.is_some() {
8686                    // Prepend older events - keep selection at top
8687                    events.append(&mut self.log_groups_state.log_events);
8688                    self.log_groups_state.event_scroll_offset = 0;
8689                } else {
8690                    // Initial load - start at first event
8691                    self.log_groups_state.event_scroll_offset = 0;
8692                }
8693
8694                self.log_groups_state.log_events = events;
8695                self.log_groups_state.has_older_events =
8696                    has_more && self.log_groups_state.log_events.len() >= 25;
8697                self.log_groups_state.next_backward_token = token;
8698                self.log_groups_state.selected_event = 0;
8699            }
8700        }
8701        Ok(())
8702    }
8703
8704    pub async fn execute_insights_query(&mut self) -> anyhow::Result<()> {
8705        if self.insights_state.insights.selected_log_groups.is_empty() {
8706            return Err(anyhow::anyhow!(
8707                "No log groups selected. Please select at least one log group."
8708            ));
8709        }
8710
8711        let now = chrono::Utc::now().timestamp_millis();
8712        let amount = self
8713            .insights_state
8714            .insights
8715            .insights_relative_amount
8716            .parse::<i64>()
8717            .unwrap_or(1);
8718        let duration_ms = match self.insights_state.insights.insights_relative_unit {
8719            TimeUnit::Minutes => amount * 60 * 1000,
8720            TimeUnit::Hours => amount * 60 * 60 * 1000,
8721            TimeUnit::Days => amount * 24 * 60 * 60 * 1000,
8722            TimeUnit::Weeks => amount * 7 * 24 * 60 * 60 * 1000,
8723        };
8724        let start_time = now - duration_ms;
8725
8726        let query_id = self
8727            .cloudwatch_client
8728            .start_query(
8729                self.insights_state.insights.selected_log_groups.clone(),
8730                self.insights_state.insights.query_text.trim().to_string(),
8731                start_time,
8732                now,
8733            )
8734            .await?;
8735
8736        // Poll for results
8737        for _ in 0..60 {
8738            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
8739            let (status, results) = self.cloudwatch_client.get_query_results(&query_id).await?;
8740
8741            if status == "Complete" {
8742                self.insights_state.insights.query_results = results;
8743                self.insights_state.insights.query_completed = true;
8744                self.insights_state.insights.results_selected = 0;
8745                self.insights_state.insights.expanded_result = None;
8746                self.view_mode = ViewMode::InsightsResults;
8747                return Ok(());
8748            } else if status == "Failed" || status == "Cancelled" {
8749                return Err(anyhow::anyhow!("Query {}", status.to_lowercase()));
8750            }
8751        }
8752
8753        Err(anyhow::anyhow!("Query timeout"))
8754    }
8755}
8756
8757impl CloudWatchInsightsState {
8758    fn new() -> Self {
8759        Self {
8760            insights: InsightsState::default(),
8761            loading: false,
8762        }
8763    }
8764}
8765
8766impl CloudWatchAlarmsState {
8767    fn new() -> Self {
8768        Self {
8769            table: TableState::new(),
8770            alarm_tab: AlarmTab::AllAlarms,
8771            view_as: AlarmViewMode::Table,
8772            wrap_lines: false,
8773            sort_column: "Last state update".to_string(),
8774            sort_direction: SortDirection::Asc,
8775            input_focus: InputFocus::Filter,
8776        }
8777    }
8778}
8779
8780impl ServicePickerState {
8781    fn new() -> Self {
8782        Self {
8783            filter: String::new(),
8784            selected: 0,
8785            services: vec![
8786                "CloudWatch > Log Groups",
8787                "CloudWatch > Logs Insights",
8788                "CloudWatch > Alarms",
8789                "CloudFormation > Stacks",
8790                "EC2 > Instances",
8791                "ECR > Repositories",
8792                "IAM > Users",
8793                "IAM > Roles",
8794                "IAM > User Groups",
8795                "Lambda > Functions",
8796                "Lambda > Applications",
8797                "S3 > Buckets",
8798                "SQS > Queues",
8799            ],
8800        }
8801    }
8802}
8803
8804#[cfg(test)]
8805mod test_helpers {
8806    use super::*;
8807
8808    // Test helper functions to reduce boilerplate
8809    pub fn test_app() -> App {
8810        App::new_without_client("test".to_string(), Some("us-east-1".to_string()))
8811    }
8812
8813    pub fn test_app_no_region() -> App {
8814        App::new_without_client("test".to_string(), None)
8815    }
8816}
8817
8818#[cfg(test)]
8819mod tests {
8820    use super::*;
8821    use crate::keymap::Action;
8822    use test_helpers::*;
8823
8824    #[test]
8825    fn test_next_tab_cycles_forward() {
8826        let mut app = test_app();
8827        app.tabs = vec![
8828            Tab {
8829                service: Service::CloudWatchLogGroups,
8830                title: "CloudWatch > Log Groups".to_string(),
8831                breadcrumb: "CloudWatch > Log Groups".to_string(),
8832            },
8833            Tab {
8834                service: Service::CloudWatchInsights,
8835                title: "CloudWatch > Logs Insights".to_string(),
8836                breadcrumb: "CloudWatch > Logs Insights".to_string(),
8837            },
8838            Tab {
8839                service: Service::CloudWatchAlarms,
8840                title: "CloudWatch > Alarms".to_string(),
8841                breadcrumb: "CloudWatch > Alarms".to_string(),
8842            },
8843        ];
8844        app.current_tab = 0;
8845
8846        app.handle_action(Action::NextTab);
8847        assert_eq!(app.current_tab, 1);
8848        assert_eq!(app.current_service, Service::CloudWatchInsights);
8849
8850        app.handle_action(Action::NextTab);
8851        assert_eq!(app.current_tab, 2);
8852        assert_eq!(app.current_service, Service::CloudWatchAlarms);
8853
8854        // Should wrap around
8855        app.handle_action(Action::NextTab);
8856        assert_eq!(app.current_tab, 0);
8857        assert_eq!(app.current_service, Service::CloudWatchLogGroups);
8858    }
8859
8860    #[test]
8861    fn test_prev_tab_cycles_backward() {
8862        let mut app = test_app();
8863        app.tabs = vec![
8864            Tab {
8865                service: Service::CloudWatchLogGroups,
8866                title: "CloudWatch > Log Groups".to_string(),
8867                breadcrumb: "CloudWatch > Log Groups".to_string(),
8868            },
8869            Tab {
8870                service: Service::CloudWatchInsights,
8871                title: "CloudWatch > Logs Insights".to_string(),
8872                breadcrumb: "CloudWatch > Logs Insights".to_string(),
8873            },
8874            Tab {
8875                service: Service::CloudWatchAlarms,
8876                title: "CloudWatch > Alarms".to_string(),
8877                breadcrumb: "CloudWatch > Alarms".to_string(),
8878            },
8879        ];
8880        app.current_tab = 2;
8881
8882        app.handle_action(Action::PrevTab);
8883        assert_eq!(app.current_tab, 1);
8884        assert_eq!(app.current_service, Service::CloudWatchInsights);
8885
8886        app.handle_action(Action::PrevTab);
8887        assert_eq!(app.current_tab, 0);
8888        assert_eq!(app.current_service, Service::CloudWatchLogGroups);
8889
8890        // Should wrap around
8891        app.handle_action(Action::PrevTab);
8892        assert_eq!(app.current_tab, 2);
8893        assert_eq!(app.current_service, Service::CloudWatchAlarms);
8894    }
8895
8896    #[test]
8897    fn test_close_tab_removes_current() {
8898        let mut app = test_app();
8899        app.tabs = vec![
8900            Tab {
8901                service: Service::CloudWatchLogGroups,
8902                title: "CloudWatch > Log Groups".to_string(),
8903                breadcrumb: "CloudWatch > Log Groups".to_string(),
8904            },
8905            Tab {
8906                service: Service::CloudWatchInsights,
8907                title: "CloudWatch > Logs Insights".to_string(),
8908                breadcrumb: "CloudWatch > Logs Insights".to_string(),
8909            },
8910            Tab {
8911                service: Service::CloudWatchAlarms,
8912                title: "CloudWatch > Alarms".to_string(),
8913                breadcrumb: "CloudWatch > Alarms".to_string(),
8914            },
8915        ];
8916        app.current_tab = 1;
8917        app.service_selected = true;
8918
8919        app.handle_action(Action::CloseTab);
8920        assert_eq!(app.tabs.len(), 2);
8921        assert_eq!(app.current_tab, 1);
8922        assert_eq!(app.current_service, Service::CloudWatchAlarms);
8923    }
8924
8925    #[test]
8926    fn test_close_last_tab_exits_service() {
8927        let mut app = test_app();
8928        app.tabs = vec![Tab {
8929            service: Service::CloudWatchLogGroups,
8930            title: "CloudWatch > Log Groups".to_string(),
8931            breadcrumb: "CloudWatch > Log Groups".to_string(),
8932        }];
8933        app.current_tab = 0;
8934        app.service_selected = true;
8935
8936        app.handle_action(Action::CloseTab);
8937        assert_eq!(app.tabs.len(), 0);
8938        assert!(!app.service_selected);
8939        assert_eq!(app.current_tab, 0);
8940    }
8941
8942    #[test]
8943    fn test_close_service_removes_current_tab() {
8944        let mut app = test_app();
8945        app.tabs = vec![
8946            Tab {
8947                service: Service::CloudWatchLogGroups,
8948                title: "CloudWatch > Log Groups".to_string(),
8949                breadcrumb: "CloudWatch > Log Groups".to_string(),
8950            },
8951            Tab {
8952                service: Service::CloudWatchInsights,
8953                title: "CloudWatch > Logs Insights".to_string(),
8954                breadcrumb: "CloudWatch > Logs Insights".to_string(),
8955            },
8956            Tab {
8957                service: Service::CloudWatchAlarms,
8958                title: "CloudWatch > Alarms".to_string(),
8959                breadcrumb: "CloudWatch > Alarms".to_string(),
8960            },
8961        ];
8962        app.current_tab = 1;
8963        app.service_selected = true;
8964
8965        app.handle_action(Action::CloseService);
8966
8967        // Tab should be removed
8968        assert_eq!(app.tabs.len(), 2);
8969        // Should switch to next tab (Alarms at index 1)
8970        assert_eq!(app.current_tab, 1);
8971        assert_eq!(app.current_service, Service::CloudWatchAlarms);
8972        // Should stay in service mode, NOT show service picker
8973        assert!(app.service_selected);
8974        assert_eq!(app.mode, Mode::Normal);
8975    }
8976
8977    #[test]
8978    fn test_close_service_last_tab_shows_picker() {
8979        let mut app = test_app();
8980        app.tabs = vec![Tab {
8981            service: Service::CloudWatchLogGroups,
8982            title: "CloudWatch > Log Groups".to_string(),
8983            breadcrumb: "CloudWatch > Log Groups".to_string(),
8984        }];
8985        app.current_tab = 0;
8986        app.service_selected = true;
8987
8988        app.handle_action(Action::CloseService);
8989
8990        // Tab should be removed
8991        assert_eq!(app.tabs.len(), 0);
8992        // Should show service picker
8993        assert!(!app.service_selected);
8994        assert_eq!(app.mode, Mode::ServicePicker);
8995    }
8996
8997    #[test]
8998    fn test_open_tab_picker_with_tabs() {
8999        let mut app = test_app();
9000        app.tabs = vec![
9001            Tab {
9002                service: Service::CloudWatchLogGroups,
9003                title: "CloudWatch > Log Groups".to_string(),
9004                breadcrumb: "CloudWatch > Log Groups".to_string(),
9005            },
9006            Tab {
9007                service: Service::CloudWatchInsights,
9008                title: "CloudWatch > Logs Insights".to_string(),
9009                breadcrumb: "CloudWatch > Logs Insights".to_string(),
9010            },
9011        ];
9012        app.current_tab = 1;
9013
9014        app.handle_action(Action::OpenTabPicker);
9015        assert_eq!(app.mode, Mode::TabPicker);
9016        assert_eq!(app.tab_picker_selected, 1);
9017    }
9018
9019    #[test]
9020    fn test_open_tab_picker_without_tabs() {
9021        let mut app = test_app();
9022        app.tabs = vec![];
9023
9024        app.handle_action(Action::OpenTabPicker);
9025        assert_eq!(app.mode, Mode::Normal);
9026    }
9027
9028    #[test]
9029    fn test_pending_key_state() {
9030        let mut app = test_app();
9031        assert_eq!(app.pending_key, None);
9032
9033        app.pending_key = Some('g');
9034        assert_eq!(app.pending_key, Some('g'));
9035    }
9036
9037    #[test]
9038    fn test_tab_breadcrumb_updates() {
9039        let mut app = test_app();
9040        app.tabs = vec![Tab {
9041            service: Service::CloudWatchLogGroups,
9042            title: "CloudWatch > Log Groups".to_string(),
9043            breadcrumb: "CloudWatch > Log groups".to_string(),
9044        }];
9045        app.current_tab = 0;
9046        app.service_selected = true;
9047        app.current_service = Service::CloudWatchLogGroups;
9048
9049        // Initial breadcrumb
9050        assert_eq!(app.tabs[0].breadcrumb, "CloudWatch > Log groups");
9051
9052        // Add a log group and update breadcrumb
9053        app.log_groups_state
9054            .log_groups
9055            .items
9056            .push(rusticity_core::LogGroup {
9057                name: "/aws/lambda/test".to_string(),
9058                creation_time: None,
9059                stored_bytes: Some(1024),
9060                retention_days: None,
9061                log_class: None,
9062                arn: None,
9063            });
9064        app.log_groups_state.log_groups.reset();
9065        app.view_mode = ViewMode::Detail;
9066        app.update_current_tab_breadcrumb();
9067
9068        // Breadcrumb should now include log group
9069        assert_eq!(
9070            app.tabs[0].breadcrumb,
9071            "CloudWatch > Log groups > /aws/lambda/test"
9072        );
9073    }
9074
9075    #[test]
9076    fn test_s3_bucket_column_selector_navigation() {
9077        let mut app = test_app();
9078        app.current_service = Service::S3Buckets;
9079        app.mode = Mode::ColumnSelector;
9080        app.column_selector_index = 0;
9081
9082        // Should navigate through 3 S3 bucket columns (0, 1, 2)
9083        app.handle_action(Action::NextItem);
9084        assert_eq!(app.column_selector_index, 1);
9085
9086        app.handle_action(Action::NextItem);
9087        assert_eq!(app.column_selector_index, 2);
9088
9089        app.handle_action(Action::NextItem);
9090        assert_eq!(app.column_selector_index, 3);
9091
9092        // Should not go beyond max (now includes page size options: 3 columns + 6 = 9)
9093        for _ in 0..10 {
9094            app.handle_action(Action::NextItem);
9095        }
9096        assert_eq!(app.column_selector_index, 9);
9097
9098        // Navigate back
9099        app.handle_action(Action::PrevItem);
9100        assert_eq!(app.column_selector_index, 8);
9101
9102        app.handle_action(Action::PrevItem);
9103        assert_eq!(app.column_selector_index, 7);
9104
9105        // Should not go below 0
9106        for _ in 0..10 {
9107            app.handle_action(Action::PrevItem);
9108        }
9109        assert_eq!(app.column_selector_index, 0);
9110    }
9111
9112    #[test]
9113    fn test_cloudwatch_alarms_state_initialized() {
9114        let app = test_app();
9115
9116        // Alarms state should be initialized
9117        assert_eq!(app.alarms_state.table.items.len(), 0);
9118        assert_eq!(app.alarms_state.table.selected, 0);
9119        assert_eq!(app.alarms_state.alarm_tab, AlarmTab::AllAlarms);
9120        assert!(!app.alarms_state.table.loading);
9121        assert_eq!(app.alarms_state.view_as, AlarmViewMode::Table);
9122        assert_eq!(app.alarms_state.table.page_size, PageSize::Fifty);
9123    }
9124
9125    #[test]
9126    fn test_cloudwatch_alarms_service_selection() {
9127        let mut app = test_app();
9128
9129        // Switch to alarms service
9130        app.current_service = Service::CloudWatchAlarms;
9131        app.service_selected = true;
9132
9133        assert_eq!(app.current_service, Service::CloudWatchAlarms);
9134        assert!(app.service_selected);
9135    }
9136
9137    #[test]
9138    fn test_cloudwatch_alarms_column_preferences() {
9139        let app = test_app();
9140
9141        // Should have alarm columns defined
9142        assert!(!app.cw_alarm_column_ids.is_empty());
9143        assert!(!app.cw_alarm_visible_column_ids.is_empty());
9144
9145        // Default visible columns
9146        assert!(app
9147            .cw_alarm_visible_column_ids
9148            .contains(&AlarmColumn::Name.id()));
9149        assert!(app
9150            .cw_alarm_visible_column_ids
9151            .contains(&AlarmColumn::State.id()));
9152    }
9153
9154    #[test]
9155    fn test_s3_bucket_navigation_without_expansion() {
9156        let mut app = test_app();
9157        app.current_service = Service::S3Buckets;
9158        app.service_selected = true;
9159        app.mode = Mode::Normal;
9160
9161        // Add 3 buckets
9162        app.s3_state.buckets.items = vec![
9163            S3Bucket {
9164                name: "bucket1".to_string(),
9165                region: "us-east-1".to_string(),
9166                creation_date: "2024-01-01T00:00:00Z".to_string(),
9167            },
9168            S3Bucket {
9169                name: "bucket2".to_string(),
9170                region: "us-east-1".to_string(),
9171                creation_date: "2024-01-02T00:00:00Z".to_string(),
9172            },
9173            S3Bucket {
9174                name: "bucket3".to_string(),
9175                region: "us-east-1".to_string(),
9176                creation_date: "2024-01-03T00:00:00Z".to_string(),
9177            },
9178        ];
9179        app.s3_state.selected_row = 0;
9180
9181        // Navigate down
9182        app.handle_action(Action::NextItem);
9183        assert_eq!(app.s3_state.selected_row, 1);
9184
9185        app.handle_action(Action::NextItem);
9186        assert_eq!(app.s3_state.selected_row, 2);
9187
9188        // Should not go beyond last bucket
9189        app.handle_action(Action::NextItem);
9190        assert_eq!(app.s3_state.selected_row, 2);
9191
9192        // Navigate up
9193        app.handle_action(Action::PrevItem);
9194        assert_eq!(app.s3_state.selected_row, 1);
9195
9196        app.handle_action(Action::PrevItem);
9197        assert_eq!(app.s3_state.selected_row, 0);
9198
9199        // Should not go below 0
9200        app.handle_action(Action::PrevItem);
9201        assert_eq!(app.s3_state.selected_row, 0);
9202    }
9203
9204    #[test]
9205    fn test_s3_bucket_navigation_with_expansion() {
9206        let mut app = test_app();
9207        app.current_service = Service::S3Buckets;
9208        app.service_selected = true;
9209        app.mode = Mode::Normal;
9210
9211        // Add 2 buckets
9212        app.s3_state.buckets.items = vec![
9213            S3Bucket {
9214                name: "bucket1".to_string(),
9215                region: "us-east-1".to_string(),
9216                creation_date: "2024-01-01T00:00:00Z".to_string(),
9217            },
9218            S3Bucket {
9219                name: "bucket2".to_string(),
9220                region: "us-east-1".to_string(),
9221                creation_date: "2024-01-02T00:00:00Z".to_string(),
9222            },
9223        ];
9224
9225        // Expand bucket1 with 2 objects
9226        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
9227        app.s3_state.bucket_preview.insert(
9228            "bucket1".to_string(),
9229            vec![
9230                S3Object {
9231                    key: "file1.txt".to_string(),
9232                    size: 100,
9233                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9234                    is_prefix: false,
9235                    storage_class: "STANDARD".to_string(),
9236                },
9237                S3Object {
9238                    key: "folder/".to_string(),
9239                    size: 0,
9240                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9241                    is_prefix: true,
9242                    storage_class: String::new(),
9243                },
9244            ],
9245        );
9246
9247        app.s3_state.selected_row = 0;
9248
9249        // Total rows: bucket1 (row 0) + file1.txt (row 1) + folder/ (row 2) + bucket2 (row 3) = 4 rows
9250        // Navigate through all rows
9251        app.handle_action(Action::NextItem);
9252        assert_eq!(app.s3_state.selected_row, 1); // file1.txt
9253
9254        app.handle_action(Action::NextItem);
9255        assert_eq!(app.s3_state.selected_row, 2); // folder/
9256
9257        app.handle_action(Action::NextItem);
9258        assert_eq!(app.s3_state.selected_row, 3); // bucket2
9259
9260        // Should not go beyond last row
9261        app.handle_action(Action::NextItem);
9262        assert_eq!(app.s3_state.selected_row, 3);
9263    }
9264
9265    #[test]
9266    fn test_s3_bucket_navigation_with_nested_expansion() {
9267        let mut app = test_app();
9268        app.current_service = Service::S3Buckets;
9269        app.service_selected = true;
9270        app.mode = Mode::Normal;
9271
9272        // Add 1 bucket
9273        app.s3_state.buckets.items = vec![S3Bucket {
9274            name: "bucket1".to_string(),
9275            region: "us-east-1".to_string(),
9276            creation_date: "2024-01-01T00:00:00Z".to_string(),
9277        }];
9278
9279        // Expand bucket1 with a folder
9280        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
9281        app.s3_state.bucket_preview.insert(
9282            "bucket1".to_string(),
9283            vec![S3Object {
9284                key: "folder/".to_string(),
9285                size: 0,
9286                last_modified: "2024-01-01T00:00:00Z".to_string(),
9287                is_prefix: true,
9288                storage_class: String::new(),
9289            }],
9290        );
9291
9292        // Expand the folder with 2 nested objects
9293        app.s3_state.expanded_prefixes.insert("folder/".to_string());
9294        app.s3_state.prefix_preview.insert(
9295            "folder/".to_string(),
9296            vec![
9297                S3Object {
9298                    key: "folder/file1.txt".to_string(),
9299                    size: 100,
9300                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9301                    is_prefix: false,
9302                    storage_class: "STANDARD".to_string(),
9303                },
9304                S3Object {
9305                    key: "folder/file2.txt".to_string(),
9306                    size: 200,
9307                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9308                    is_prefix: false,
9309                    storage_class: "STANDARD".to_string(),
9310                },
9311            ],
9312        );
9313
9314        app.s3_state.selected_row = 0;
9315
9316        // Total rows: bucket1 (0) + folder/ (1) + file1.txt (2) + file2.txt (3) = 4 rows
9317        app.handle_action(Action::NextItem);
9318        assert_eq!(app.s3_state.selected_row, 1); // folder/
9319
9320        app.handle_action(Action::NextItem);
9321        assert_eq!(app.s3_state.selected_row, 2); // folder/file1.txt
9322
9323        app.handle_action(Action::NextItem);
9324        assert_eq!(app.s3_state.selected_row, 3); // folder/file2.txt
9325
9326        // Should not go beyond last row
9327        app.handle_action(Action::NextItem);
9328        assert_eq!(app.s3_state.selected_row, 3);
9329    }
9330
9331    #[test]
9332    fn test_calculate_total_bucket_rows() {
9333        let mut app = test_app();
9334
9335        // No buckets
9336        assert_eq!(app.calculate_total_bucket_rows(), 0);
9337
9338        // 2 buckets, no expansion
9339        app.s3_state.buckets.items = vec![
9340            S3Bucket {
9341                name: "bucket1".to_string(),
9342                region: "us-east-1".to_string(),
9343                creation_date: "2024-01-01T00:00:00Z".to_string(),
9344            },
9345            S3Bucket {
9346                name: "bucket2".to_string(),
9347                region: "us-east-1".to_string(),
9348                creation_date: "2024-01-02T00:00:00Z".to_string(),
9349            },
9350        ];
9351        assert_eq!(app.calculate_total_bucket_rows(), 2);
9352
9353        // Expand bucket1 with 3 objects
9354        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
9355        app.s3_state.bucket_preview.insert(
9356            "bucket1".to_string(),
9357            vec![
9358                S3Object {
9359                    key: "file1.txt".to_string(),
9360                    size: 100,
9361                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9362                    is_prefix: false,
9363                    storage_class: "STANDARD".to_string(),
9364                },
9365                S3Object {
9366                    key: "file2.txt".to_string(),
9367                    size: 200,
9368                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9369                    is_prefix: false,
9370                    storage_class: "STANDARD".to_string(),
9371                },
9372                S3Object {
9373                    key: "folder/".to_string(),
9374                    size: 0,
9375                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9376                    is_prefix: true,
9377                    storage_class: String::new(),
9378                },
9379            ],
9380        );
9381        assert_eq!(app.calculate_total_bucket_rows(), 5); // 2 buckets + 3 objects
9382
9383        // Expand folder/ with 2 nested objects
9384        app.s3_state.expanded_prefixes.insert("folder/".to_string());
9385        app.s3_state.prefix_preview.insert(
9386            "folder/".to_string(),
9387            vec![
9388                S3Object {
9389                    key: "folder/nested1.txt".to_string(),
9390                    size: 50,
9391                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9392                    is_prefix: false,
9393                    storage_class: "STANDARD".to_string(),
9394                },
9395                S3Object {
9396                    key: "folder/nested2.txt".to_string(),
9397                    size: 75,
9398                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9399                    is_prefix: false,
9400                    storage_class: "STANDARD".to_string(),
9401                },
9402            ],
9403        );
9404        assert_eq!(app.calculate_total_bucket_rows(), 7); // 2 buckets + 3 objects + 2 nested
9405    }
9406
9407    #[test]
9408    fn test_calculate_total_object_rows() {
9409        let mut app = test_app();
9410        app.s3_state.current_bucket = Some("test-bucket".to_string());
9411
9412        // No objects
9413        assert_eq!(app.calculate_total_object_rows(), 0);
9414
9415        // 2 objects, no expansion
9416        app.s3_state.objects = vec![
9417            S3Object {
9418                key: "file1.txt".to_string(),
9419                size: 100,
9420                last_modified: "2024-01-01T00:00:00Z".to_string(),
9421                is_prefix: false,
9422                storage_class: "STANDARD".to_string(),
9423            },
9424            S3Object {
9425                key: "folder/".to_string(),
9426                size: 0,
9427                last_modified: "2024-01-01T00:00:00Z".to_string(),
9428                is_prefix: true,
9429                storage_class: String::new(),
9430            },
9431        ];
9432        assert_eq!(app.calculate_total_object_rows(), 2);
9433
9434        // Expand folder/ with 2 items
9435        app.s3_state.expanded_prefixes.insert("folder/".to_string());
9436        app.s3_state.prefix_preview.insert(
9437            "folder/".to_string(),
9438            vec![
9439                S3Object {
9440                    key: "folder/file2.txt".to_string(),
9441                    size: 200,
9442                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9443                    is_prefix: false,
9444                    storage_class: "STANDARD".to_string(),
9445                },
9446                S3Object {
9447                    key: "folder/subfolder/".to_string(),
9448                    size: 0,
9449                    last_modified: "2024-01-01T00:00:00Z".to_string(),
9450                    is_prefix: true,
9451                    storage_class: String::new(),
9452                },
9453            ],
9454        );
9455        assert_eq!(app.calculate_total_object_rows(), 4); // 2 + 2 nested
9456
9457        // Expand subfolder/ with 1 item (3rd level)
9458        app.s3_state
9459            .expanded_prefixes
9460            .insert("folder/subfolder/".to_string());
9461        app.s3_state.prefix_preview.insert(
9462            "folder/subfolder/".to_string(),
9463            vec![S3Object {
9464                key: "folder/subfolder/deep.txt".to_string(),
9465                size: 50,
9466                last_modified: "2024-01-01T00:00:00Z".to_string(),
9467                is_prefix: false,
9468                storage_class: "STANDARD".to_string(),
9469            }],
9470        );
9471        assert_eq!(app.calculate_total_object_rows(), 5); // 2 + 2 nested + 1 deep
9472    }
9473
9474    #[test]
9475    fn test_s3_object_navigation_with_deep_nesting() {
9476        let mut app = test_app();
9477        app.current_service = Service::S3Buckets;
9478        app.service_selected = true;
9479        app.mode = Mode::Normal;
9480        app.s3_state.current_bucket = Some("test-bucket".to_string());
9481
9482        // Add folder structure: folder1/ -> folder2/ -> file.txt
9483        app.s3_state.objects = vec![S3Object {
9484            key: "folder1/".to_string(),
9485            size: 0,
9486            last_modified: "2024-01-01T00:00:00Z".to_string(),
9487            is_prefix: true,
9488            storage_class: String::new(),
9489        }];
9490
9491        // Expand folder1/
9492        app.s3_state
9493            .expanded_prefixes
9494            .insert("folder1/".to_string());
9495        app.s3_state.prefix_preview.insert(
9496            "folder1/".to_string(),
9497            vec![S3Object {
9498                key: "folder1/folder2/".to_string(),
9499                size: 0,
9500                last_modified: "2024-01-01T00:00:00Z".to_string(),
9501                is_prefix: true,
9502                storage_class: String::new(),
9503            }],
9504        );
9505
9506        // Expand folder2/
9507        app.s3_state
9508            .expanded_prefixes
9509            .insert("folder1/folder2/".to_string());
9510        app.s3_state.prefix_preview.insert(
9511            "folder1/folder2/".to_string(),
9512            vec![S3Object {
9513                key: "folder1/folder2/file.txt".to_string(),
9514                size: 100,
9515                last_modified: "2024-01-01T00:00:00Z".to_string(),
9516                is_prefix: false,
9517                storage_class: "STANDARD".to_string(),
9518            }],
9519        );
9520
9521        app.s3_state.selected_object = 0;
9522
9523        // Total: folder1/ (0) + folder2/ (1) + file.txt (2) = 3 rows
9524        app.handle_action(Action::NextItem);
9525        assert_eq!(app.s3_state.selected_object, 1); // folder2/
9526
9527        app.handle_action(Action::NextItem);
9528        assert_eq!(app.s3_state.selected_object, 2); // file.txt
9529
9530        // Should not go beyond
9531        app.handle_action(Action::NextItem);
9532        assert_eq!(app.s3_state.selected_object, 2);
9533    }
9534
9535    #[test]
9536    fn test_s3_expand_nested_folder_in_objects_view() {
9537        let mut app = test_app();
9538        app.current_service = Service::S3Buckets;
9539        app.service_selected = true;
9540        app.mode = Mode::Normal;
9541        app.s3_state.current_bucket = Some("test-bucket".to_string());
9542
9543        // Add parent folder
9544        app.s3_state.objects = vec![S3Object {
9545            key: "parent/".to_string(),
9546            size: 0,
9547            last_modified: "2024-01-01T00:00:00Z".to_string(),
9548            is_prefix: true,
9549            storage_class: String::new(),
9550        }];
9551
9552        // Expand parent
9553        app.s3_state.expanded_prefixes.insert("parent/".to_string());
9554        app.s3_state.prefix_preview.insert(
9555            "parent/".to_string(),
9556            vec![S3Object {
9557                key: "parent/child/".to_string(),
9558                size: 0,
9559                last_modified: "2024-01-01T00:00:00Z".to_string(),
9560                is_prefix: true,
9561                storage_class: String::new(),
9562            }],
9563        );
9564
9565        // Select the nested folder (index 1)
9566        app.s3_state.selected_object = 1;
9567
9568        // Expand it (simulate pressing Enter/Right)
9569        app.handle_action(Action::NextPane);
9570
9571        // Should be expanded now
9572        assert!(app.s3_state.expanded_prefixes.contains("parent/child/"));
9573        assert!(app.s3_state.buckets.loading); // Should trigger load
9574    }
9575
9576    #[test]
9577    fn test_s3_drill_into_nested_folder() {
9578        let mut app = test_app();
9579        app.current_service = Service::S3Buckets;
9580        app.service_selected = true;
9581        app.mode = Mode::Normal;
9582        app.s3_state.current_bucket = Some("test-bucket".to_string());
9583
9584        // Add parent folder
9585        app.s3_state.objects = vec![S3Object {
9586            key: "parent/".to_string(),
9587            size: 0,
9588            last_modified: "2024-01-01T00:00:00Z".to_string(),
9589            is_prefix: true,
9590            storage_class: String::new(),
9591        }];
9592
9593        // Expand parent
9594        app.s3_state.expanded_prefixes.insert("parent/".to_string());
9595        app.s3_state.prefix_preview.insert(
9596            "parent/".to_string(),
9597            vec![S3Object {
9598                key: "parent/child/".to_string(),
9599                size: 0,
9600                last_modified: "2024-01-01T00:00:00Z".to_string(),
9601                is_prefix: true,
9602                storage_class: String::new(),
9603            }],
9604        );
9605
9606        // Select the nested folder (index 1)
9607        app.s3_state.selected_object = 1;
9608
9609        // Drill into it (simulate pressing Enter)
9610        app.handle_action(Action::Select);
9611
9612        // Should navigate into the folder
9613        assert_eq!(app.s3_state.prefix_stack, vec!["parent/child/".to_string()]);
9614        assert!(app.s3_state.buckets.loading); // Should trigger load
9615    }
9616
9617    #[test]
9618    fn test_s3_esc_pops_navigation_stack() {
9619        let mut app = test_app();
9620        app.current_service = Service::S3Buckets;
9621        app.s3_state.current_bucket = Some("test-bucket".to_string());
9622        app.s3_state.prefix_stack = vec!["level1/".to_string(), "level1/level2/".to_string()];
9623
9624        // Press Esc - should pop from stack
9625        app.handle_action(Action::GoBack);
9626        assert_eq!(app.s3_state.prefix_stack, vec!["level1/".to_string()]);
9627        assert!(app.s3_state.buckets.loading);
9628
9629        // Press Esc again - should pop to bucket root
9630        app.s3_state.buckets.loading = false;
9631        app.handle_action(Action::GoBack);
9632        assert_eq!(app.s3_state.prefix_stack, Vec::<String>::new());
9633        assert!(app.s3_state.buckets.loading);
9634
9635        // Press Esc again - should exit bucket
9636        app.s3_state.buckets.loading = false;
9637        app.handle_action(Action::GoBack);
9638        assert_eq!(app.s3_state.current_bucket, None);
9639    }
9640
9641    #[test]
9642    fn test_s3_esc_from_bucket_root_exits() {
9643        let mut app = test_app();
9644        app.current_service = Service::S3Buckets;
9645        app.s3_state.current_bucket = Some("test-bucket".to_string());
9646        app.s3_state.prefix_stack = vec![];
9647
9648        // Press Esc from bucket root - should exit bucket
9649        app.handle_action(Action::GoBack);
9650        assert_eq!(app.s3_state.current_bucket, None);
9651        assert_eq!(app.s3_state.objects.len(), 0);
9652    }
9653
9654    #[test]
9655    fn test_s3_drill_into_nested_prefix_from_bucket_list() {
9656        let mut app = test_app();
9657        app.current_service = Service::S3Buckets;
9658        app.service_selected = true;
9659        app.mode = Mode::Normal;
9660
9661        // Setup bucket with nested preview
9662        app.s3_state.buckets.items = vec![S3Bucket {
9663            name: "test-bucket".to_string(),
9664            region: "us-east-1".to_string(),
9665            creation_date: "2024-01-01".to_string(),
9666        }];
9667
9668        // Expand bucket to show first-level prefix
9669        app.s3_state
9670            .expanded_prefixes
9671            .insert("test-bucket".to_string());
9672        app.s3_state.bucket_preview.insert(
9673            "test-bucket".to_string(),
9674            vec![S3Object {
9675                key: "parent/".to_string(),
9676                size: 0,
9677                last_modified: "2024-01-01".to_string(),
9678                is_prefix: true,
9679                storage_class: String::new(),
9680            }],
9681        );
9682
9683        // Expand parent to show nested prefix
9684        app.s3_state.expanded_prefixes.insert("parent/".to_string());
9685        app.s3_state.prefix_preview.insert(
9686            "parent/".to_string(),
9687            vec![S3Object {
9688                key: "parent/child/".to_string(),
9689                size: 0,
9690                last_modified: "2024-01-01".to_string(),
9691                is_prefix: true,
9692                storage_class: String::new(),
9693            }],
9694        );
9695
9696        // Select nested prefix (row 2: bucket, parent, nested)
9697        app.s3_state.selected_row = 2;
9698
9699        // Drill into nested prefix
9700        app.handle_action(Action::Select);
9701
9702        // Should have both parent and child in stack
9703        assert_eq!(
9704            app.s3_state.prefix_stack,
9705            vec!["parent/".to_string(), "parent/child/".to_string()]
9706        );
9707        assert_eq!(app.s3_state.current_bucket, Some("test-bucket".to_string()));
9708        assert!(app.s3_state.buckets.loading);
9709
9710        // Now press Esc - should go back to parent
9711        app.s3_state.buckets.loading = false;
9712        app.handle_action(Action::GoBack);
9713        assert_eq!(app.s3_state.prefix_stack, vec!["parent/".to_string()]);
9714        assert!(app.s3_state.buckets.loading);
9715
9716        // Press Esc again - should go to bucket root
9717        app.s3_state.buckets.loading = false;
9718        app.handle_action(Action::GoBack);
9719        assert_eq!(app.s3_state.prefix_stack, Vec::<String>::new());
9720        assert!(app.s3_state.buckets.loading);
9721
9722        // Press Esc again - should exit bucket
9723        app.s3_state.buckets.loading = false;
9724        app.handle_action(Action::GoBack);
9725        assert_eq!(app.s3_state.current_bucket, None);
9726    }
9727
9728    #[test]
9729    fn test_region_picker_fuzzy_filter() {
9730        let mut app = test_app();
9731        app.region_latencies.insert("us-east-1".to_string(), 10);
9732        app.region_filter = "vir".to_string();
9733        let filtered = app.get_filtered_regions();
9734        assert!(filtered.iter().any(|r| r.code == "us-east-1"));
9735    }
9736
9737    #[test]
9738    fn test_profile_picker_loads_profiles() {
9739        let profiles = App::load_aws_profiles();
9740        // Should at least have default profile or be empty if no config
9741        assert!(profiles.is_empty() || profiles.iter().any(|p| p.name == "default"));
9742    }
9743
9744    #[test]
9745    fn test_profile_with_region_uses_it() {
9746        let mut app = test_app_no_region();
9747        app.available_profiles = vec![AwsProfile {
9748            name: "test-profile".to_string(),
9749            region: Some("eu-west-1".to_string()),
9750            account: Some("123456789".to_string()),
9751            role_arn: None,
9752            source_profile: None,
9753        }];
9754        app.profile_picker_selected = 0;
9755        app.mode = Mode::ProfilePicker;
9756
9757        // Simulate selecting the profile
9758        let filtered = app.get_filtered_profiles();
9759        if let Some(profile) = filtered.first() {
9760            let profile_name = profile.name.clone();
9761            let profile_region = profile.region.clone();
9762
9763            app.profile = profile_name;
9764            if let Some(region) = profile_region {
9765                app.region = region;
9766            }
9767        }
9768
9769        assert_eq!(app.profile, "test-profile");
9770        assert_eq!(app.region, "eu-west-1");
9771    }
9772
9773    #[test]
9774    fn test_profile_without_region_keeps_unknown() {
9775        let mut app = test_app_no_region();
9776        let initial_region = app.region.clone();
9777
9778        app.available_profiles = vec![AwsProfile {
9779            name: "test-profile".to_string(),
9780            region: None,
9781            account: None,
9782            role_arn: None,
9783            source_profile: None,
9784        }];
9785        app.profile_picker_selected = 0;
9786        app.mode = Mode::ProfilePicker;
9787
9788        let filtered = app.get_filtered_profiles();
9789        if let Some(profile) = filtered.first() {
9790            let profile_name = profile.name.clone();
9791            let profile_region = profile.region.clone();
9792
9793            app.profile = profile_name;
9794            if let Some(region) = profile_region {
9795                app.region = region;
9796            }
9797        }
9798
9799        assert_eq!(app.profile, "test-profile");
9800        assert_eq!(app.region, initial_region); // Should keep initial region
9801    }
9802
9803    #[test]
9804    fn test_region_selection_closes_all_tabs() {
9805        let mut app = test_app();
9806
9807        // Add some tabs
9808        app.tabs.push(Tab {
9809            service: Service::CloudWatchLogGroups,
9810            title: "CloudWatch".to_string(),
9811            breadcrumb: "CloudWatch".to_string(),
9812        });
9813        app.tabs.push(Tab {
9814            service: Service::S3Buckets,
9815            title: "S3".to_string(),
9816            breadcrumb: "S3".to_string(),
9817        });
9818        app.service_selected = true;
9819        app.current_tab = 1;
9820
9821        // Add latency for region
9822        app.region_latencies.insert("eu-west-1".to_string(), 50);
9823
9824        // Simulate selecting a different region
9825        app.mode = Mode::RegionPicker;
9826        app.region_picker_selected = 0;
9827
9828        let filtered = app.get_filtered_regions();
9829        if let Some(region) = filtered.first() {
9830            app.region = region.code.to_string();
9831            app.tabs.clear();
9832            app.current_tab = 0;
9833            app.service_selected = false;
9834            app.mode = Mode::Normal;
9835        }
9836
9837        assert_eq!(app.tabs.len(), 0);
9838        assert_eq!(app.current_tab, 0);
9839        assert!(!app.service_selected);
9840        assert_eq!(app.region, "eu-west-1");
9841    }
9842
9843    #[test]
9844    fn test_region_picker_can_be_closed_without_selection() {
9845        let mut app = test_app();
9846        let initial_region = app.region.clone();
9847
9848        app.mode = Mode::RegionPicker;
9849
9850        // Close without selecting (Esc)
9851        app.mode = Mode::Normal;
9852
9853        // Region should not change
9854        assert_eq!(app.region, initial_region);
9855    }
9856
9857    #[test]
9858    fn test_session_filter_works() {
9859        let mut app = test_app();
9860
9861        app.sessions = vec![
9862            Session {
9863                id: "1".to_string(),
9864                timestamp: "2024-01-01".to_string(),
9865                profile: "prod-profile".to_string(),
9866                region: "us-east-1".to_string(),
9867                account_id: "123456789".to_string(),
9868                role_arn: "arn:aws:iam::123456789:role/admin".to_string(),
9869                tabs: vec![],
9870            },
9871            Session {
9872                id: "2".to_string(),
9873                timestamp: "2024-01-02".to_string(),
9874                profile: "dev-profile".to_string(),
9875                region: "eu-west-1".to_string(),
9876                account_id: "987654321".to_string(),
9877                role_arn: "arn:aws:iam::987654321:role/dev".to_string(),
9878                tabs: vec![],
9879            },
9880        ];
9881
9882        // Filter by profile
9883        app.session_filter = "prod".to_string();
9884        let filtered = app.get_filtered_sessions();
9885        assert_eq!(filtered.len(), 1);
9886        assert_eq!(filtered[0].profile, "prod-profile");
9887
9888        // Filter by region
9889        app.session_filter = "eu".to_string();
9890        let filtered = app.get_filtered_sessions();
9891        assert_eq!(filtered.len(), 1);
9892        assert_eq!(filtered[0].region, "eu-west-1");
9893
9894        // No filter
9895        app.session_filter.clear();
9896        let filtered = app.get_filtered_sessions();
9897        assert_eq!(filtered.len(), 2);
9898    }
9899
9900    #[test]
9901    fn test_profile_picker_shows_account() {
9902        let mut app = test_app_no_region();
9903        app.available_profiles = vec![AwsProfile {
9904            name: "test-profile".to_string(),
9905            region: Some("us-east-1".to_string()),
9906            account: Some("123456789".to_string()),
9907            role_arn: None,
9908            source_profile: None,
9909        }];
9910
9911        let filtered = app.get_filtered_profiles();
9912        assert_eq!(filtered.len(), 1);
9913        assert_eq!(filtered[0].account, Some("123456789".to_string()));
9914    }
9915
9916    #[test]
9917    fn test_profile_without_account() {
9918        let mut app = test_app_no_region();
9919        app.available_profiles = vec![AwsProfile {
9920            name: "test-profile".to_string(),
9921            region: Some("us-east-1".to_string()),
9922            account: None,
9923            role_arn: None,
9924            source_profile: None,
9925        }];
9926
9927        let filtered = app.get_filtered_profiles();
9928        assert_eq!(filtered.len(), 1);
9929        assert_eq!(filtered[0].account, None);
9930    }
9931
9932    #[test]
9933    fn test_profile_with_all_fields() {
9934        let mut app = test_app_no_region();
9935        app.available_profiles = vec![AwsProfile {
9936            name: "prod-profile".to_string(),
9937            region: Some("us-west-2".to_string()),
9938            account: Some("123456789".to_string()),
9939            role_arn: Some("arn:aws:iam::123456789:role/AdminRole".to_string()),
9940            source_profile: Some("base-profile".to_string()),
9941        }];
9942
9943        let filtered = app.get_filtered_profiles();
9944        assert_eq!(filtered.len(), 1);
9945        assert_eq!(filtered[0].name, "prod-profile");
9946        assert_eq!(filtered[0].region, Some("us-west-2".to_string()));
9947        assert_eq!(filtered[0].account, Some("123456789".to_string()));
9948        assert_eq!(
9949            filtered[0].role_arn,
9950            Some("arn:aws:iam::123456789:role/AdminRole".to_string())
9951        );
9952        assert_eq!(filtered[0].source_profile, Some("base-profile".to_string()));
9953    }
9954
9955    #[test]
9956    fn test_profile_filter_by_source_profile() {
9957        let mut app = test_app_no_region();
9958        app.available_profiles = vec![
9959            AwsProfile {
9960                name: "profile1".to_string(),
9961                region: None,
9962                account: None,
9963                role_arn: None,
9964                source_profile: Some("base".to_string()),
9965            },
9966            AwsProfile {
9967                name: "profile2".to_string(),
9968                region: None,
9969                account: None,
9970                role_arn: None,
9971                source_profile: Some("other".to_string()),
9972            },
9973        ];
9974
9975        app.profile_filter = "base".to_string();
9976        let filtered = app.get_filtered_profiles();
9977        assert_eq!(filtered.len(), 1);
9978        assert_eq!(filtered[0].name, "profile1");
9979    }
9980
9981    #[test]
9982    fn test_profile_filter_by_role() {
9983        let mut app = test_app_no_region();
9984        app.available_profiles = vec![
9985            AwsProfile {
9986                name: "admin-profile".to_string(),
9987                region: None,
9988                account: None,
9989                role_arn: Some("arn:aws:iam::123:role/AdminRole".to_string()),
9990                source_profile: None,
9991            },
9992            AwsProfile {
9993                name: "dev-profile".to_string(),
9994                region: None,
9995                account: None,
9996                role_arn: Some("arn:aws:iam::123:role/DevRole".to_string()),
9997                source_profile: None,
9998            },
9999        ];
10000
10001        app.profile_filter = "Admin".to_string();
10002        let filtered = app.get_filtered_profiles();
10003        assert_eq!(filtered.len(), 1);
10004        assert_eq!(filtered[0].name, "admin-profile");
10005    }
10006
10007    #[test]
10008    fn test_profiles_sorted_by_name() {
10009        let mut app = test_app_no_region();
10010        app.available_profiles = vec![
10011            AwsProfile {
10012                name: "zebra-profile".to_string(),
10013                region: None,
10014                account: None,
10015                role_arn: None,
10016                source_profile: None,
10017            },
10018            AwsProfile {
10019                name: "alpha-profile".to_string(),
10020                region: None,
10021                account: None,
10022                role_arn: None,
10023                source_profile: None,
10024            },
10025            AwsProfile {
10026                name: "beta-profile".to_string(),
10027                region: None,
10028                account: None,
10029                role_arn: None,
10030                source_profile: None,
10031            },
10032        ];
10033
10034        let filtered = app.get_filtered_profiles();
10035        assert_eq!(filtered.len(), 3);
10036        assert_eq!(filtered[0].name, "alpha-profile");
10037        assert_eq!(filtered[1].name, "beta-profile");
10038        assert_eq!(filtered[2].name, "zebra-profile");
10039    }
10040
10041    #[test]
10042    fn test_profile_with_role_arn() {
10043        let mut app = test_app_no_region();
10044        app.available_profiles = vec![AwsProfile {
10045            name: "role-profile".to_string(),
10046            region: Some("us-east-1".to_string()),
10047            account: Some("123456789".to_string()),
10048            role_arn: Some("arn:aws:iam::123456789:role/AdminRole".to_string()),
10049            source_profile: None,
10050        }];
10051
10052        let filtered = app.get_filtered_profiles();
10053        assert_eq!(filtered.len(), 1);
10054        assert!(filtered[0].role_arn.as_ref().unwrap().contains(":role/"));
10055    }
10056
10057    #[test]
10058    fn test_profile_with_user_arn() {
10059        let mut app = test_app_no_region();
10060        app.available_profiles = vec![AwsProfile {
10061            name: "user-profile".to_string(),
10062            region: Some("us-east-1".to_string()),
10063            account: Some("123456789".to_string()),
10064            role_arn: Some("arn:aws:iam::123456789:user/john-doe".to_string()),
10065            source_profile: None,
10066        }];
10067
10068        let filtered = app.get_filtered_profiles();
10069        assert_eq!(filtered.len(), 1);
10070        assert!(filtered[0].role_arn.as_ref().unwrap().contains(":user/"));
10071    }
10072
10073    #[test]
10074    fn test_filtered_profiles_also_sorted() {
10075        let mut app = test_app_no_region();
10076        app.available_profiles = vec![
10077            AwsProfile {
10078                name: "prod-zebra".to_string(),
10079                region: Some("us-east-1".to_string()),
10080                account: None,
10081                role_arn: None,
10082                source_profile: None,
10083            },
10084            AwsProfile {
10085                name: "prod-alpha".to_string(),
10086                region: Some("us-east-1".to_string()),
10087                account: None,
10088                role_arn: None,
10089                source_profile: None,
10090            },
10091            AwsProfile {
10092                name: "dev-profile".to_string(),
10093                region: Some("us-west-2".to_string()),
10094                account: None,
10095                role_arn: None,
10096                source_profile: None,
10097            },
10098        ];
10099
10100        app.profile_filter = "prod".to_string();
10101        let filtered = app.get_filtered_profiles();
10102        assert_eq!(filtered.len(), 2);
10103        assert_eq!(filtered[0].name, "prod-alpha");
10104        assert_eq!(filtered[1].name, "prod-zebra");
10105    }
10106
10107    #[test]
10108    fn test_profile_picker_has_all_columns() {
10109        let mut app = test_app_no_region();
10110        app.available_profiles = vec![AwsProfile {
10111            name: "test".to_string(),
10112            region: Some("us-east-1".to_string()),
10113            account: Some("123456789".to_string()),
10114            role_arn: Some("arn:aws:iam::123456789:role/Admin".to_string()),
10115            source_profile: Some("base".to_string()),
10116        }];
10117
10118        let filtered = app.get_filtered_profiles();
10119        assert_eq!(filtered.len(), 1);
10120        assert!(filtered[0].name == "test");
10121        assert!(filtered[0].region.is_some());
10122        assert!(filtered[0].account.is_some());
10123        assert!(filtered[0].role_arn.is_some());
10124        assert!(filtered[0].source_profile.is_some());
10125    }
10126
10127    #[test]
10128    fn test_session_picker_shows_tab_count() {
10129        let mut app = test_app_no_region();
10130        app.sessions = vec![Session {
10131            id: "1".to_string(),
10132            timestamp: "2024-01-01".to_string(),
10133            profile: "test".to_string(),
10134            region: "us-east-1".to_string(),
10135            account_id: "123".to_string(),
10136            role_arn: String::new(),
10137            tabs: vec![
10138                SessionTab {
10139                    service: "CloudWatch".to_string(),
10140                    title: "Logs".to_string(),
10141                    breadcrumb: String::new(),
10142                    filter: None,
10143                    selected_item: None,
10144                },
10145                SessionTab {
10146                    service: "S3".to_string(),
10147                    title: "Buckets".to_string(),
10148                    breadcrumb: String::new(),
10149                    filter: None,
10150                    selected_item: None,
10151                },
10152            ],
10153        }];
10154
10155        let filtered = app.get_filtered_sessions();
10156        assert_eq!(filtered.len(), 1);
10157        assert_eq!(filtered[0].tabs.len(), 2);
10158    }
10159
10160    #[test]
10161    fn test_start_background_data_fetch_loads_profiles() {
10162        let mut app = test_app_no_region();
10163        assert!(app.available_profiles.is_empty());
10164
10165        // Load profiles synchronously
10166        app.available_profiles = App::load_aws_profiles();
10167
10168        // Profiles should be loaded
10169        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
10170    }
10171
10172    #[test]
10173    fn test_refresh_in_profile_picker() {
10174        let mut app = test_app_no_region();
10175        app.mode = Mode::ProfilePicker;
10176        app.available_profiles = vec![AwsProfile {
10177            name: "test".to_string(),
10178            region: None,
10179            account: None,
10180            role_arn: None,
10181            source_profile: None,
10182        }];
10183
10184        app.handle_action(Action::Refresh);
10185
10186        // Should set loading state
10187        assert!(app.log_groups_state.loading);
10188        assert_eq!(app.log_groups_state.loading_message, "Refreshing...");
10189    }
10190
10191    #[test]
10192    fn test_refresh_sets_loading_for_profile_picker() {
10193        let mut app = test_app_no_region();
10194        app.mode = Mode::ProfilePicker;
10195
10196        assert!(!app.log_groups_state.loading);
10197
10198        app.handle_action(Action::Refresh);
10199
10200        assert!(app.log_groups_state.loading);
10201    }
10202
10203    #[test]
10204    fn test_profiles_loaded_on_demand() {
10205        let mut app = test_app_no_region();
10206
10207        // Profiles not loaded by default
10208        assert!(app.available_profiles.is_empty());
10209
10210        // Load on demand
10211        app.available_profiles = App::load_aws_profiles();
10212
10213        // Now loaded
10214        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
10215    }
10216
10217    #[test]
10218    fn test_profile_accounts_not_fetched_automatically() {
10219        let mut app = test_app_no_region();
10220        app.available_profiles = App::load_aws_profiles();
10221
10222        // Accounts should not be populated automatically
10223        for profile in &app.available_profiles {
10224            // Account may or may not be set depending on what's in config
10225            // But we're not fetching them automatically
10226            assert!(profile.account.is_none() || profile.account.is_some());
10227        }
10228    }
10229
10230    #[test]
10231    fn test_ctrl_r_triggers_account_fetch() {
10232        let mut app = test_app_no_region();
10233        app.mode = Mode::ProfilePicker;
10234        app.available_profiles = vec![AwsProfile {
10235            name: "test".to_string(),
10236            region: Some("us-east-1".to_string()),
10237            account: None,
10238            role_arn: None,
10239            source_profile: None,
10240        }];
10241
10242        // Before refresh, account is None
10243        assert!(app.available_profiles[0].account.is_none());
10244
10245        // Trigger refresh
10246        app.handle_action(Action::Refresh);
10247
10248        // Should set loading state (actual fetch happens in main.rs event loop)
10249        assert!(app.log_groups_state.loading);
10250    }
10251
10252    #[test]
10253    fn test_refresh_in_region_picker() {
10254        let mut app = test_app_no_region();
10255        app.mode = Mode::RegionPicker;
10256
10257        let initial_latencies = app.region_latencies.len();
10258        app.handle_action(Action::Refresh);
10259
10260        // Latencies should be cleared and remeasured
10261        assert!(app.region_latencies.is_empty() || app.region_latencies.len() >= initial_latencies);
10262    }
10263
10264    #[test]
10265    fn test_refresh_in_session_picker() {
10266        let mut app = test_app_no_region();
10267        app.mode = Mode::SessionPicker;
10268        app.sessions = vec![];
10269
10270        app.handle_action(Action::Refresh);
10271
10272        // Sessions should be reloaded (may be empty if no saved sessions)
10273        assert!(app.sessions.is_empty() || !app.sessions.is_empty());
10274    }
10275
10276    #[test]
10277    fn test_session_picker_selection() {
10278        let mut app = test_app();
10279
10280        app.sessions = vec![Session {
10281            id: "1".to_string(),
10282            timestamp: "2024-01-01".to_string(),
10283            profile: "prod-profile".to_string(),
10284            region: "us-west-2".to_string(),
10285            account_id: "123456789".to_string(),
10286            role_arn: "arn:aws:iam::123456789:role/admin".to_string(),
10287            tabs: vec![SessionTab {
10288                service: "CloudWatchLogGroups".to_string(),
10289                title: "Log Groups".to_string(),
10290                breadcrumb: "CloudWatch > Log Groups".to_string(),
10291                filter: Some("test".to_string()),
10292                selected_item: None,
10293            }],
10294        }];
10295
10296        app.mode = Mode::SessionPicker;
10297        app.session_picker_selected = 0;
10298
10299        // Simulate selecting the session
10300        app.handle_action(Action::Select);
10301
10302        assert_eq!(app.mode, Mode::Normal);
10303        assert_eq!(app.profile, "prod-profile");
10304        assert_eq!(app.region, "us-west-2");
10305        assert_eq!(app.config.account_id, "123456789");
10306        assert_eq!(app.tabs.len(), 1);
10307        assert_eq!(app.tabs[0].title, "Log Groups");
10308    }
10309
10310    #[test]
10311    fn test_save_session_creates_session() {
10312        let mut app =
10313            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
10314        app.config.account_id = "123456789".to_string();
10315        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
10316
10317        app.tabs.push(Tab {
10318            service: Service::CloudWatchLogGroups,
10319            title: "Log Groups".to_string(),
10320            breadcrumb: "CloudWatch > Log Groups".to_string(),
10321        });
10322
10323        app.save_current_session();
10324
10325        assert!(app.current_session.is_some());
10326        let session = app.current_session.clone().unwrap();
10327        assert_eq!(session.profile, "test-profile");
10328        assert_eq!(session.region, "us-east-1");
10329        assert_eq!(session.account_id, "123456789");
10330        assert_eq!(session.tabs.len(), 1);
10331
10332        // Cleanup
10333        let _ = session.delete();
10334    }
10335
10336    #[test]
10337    fn test_save_session_updates_existing() {
10338        let mut app =
10339            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
10340        app.config.account_id = "123456789".to_string();
10341        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
10342
10343        app.current_session = Some(Session {
10344            id: "existing".to_string(),
10345            timestamp: "2024-01-01".to_string(),
10346            profile: "test-profile".to_string(),
10347            region: "us-east-1".to_string(),
10348            account_id: "123456789".to_string(),
10349            role_arn: "arn:aws:iam::123456789:role/test".to_string(),
10350            tabs: vec![],
10351        });
10352
10353        app.tabs.push(Tab {
10354            service: Service::CloudWatchLogGroups,
10355            title: "Log Groups".to_string(),
10356            breadcrumb: "CloudWatch > Log Groups".to_string(),
10357        });
10358
10359        app.save_current_session();
10360
10361        let session = app.current_session.clone().unwrap();
10362        assert_eq!(session.id, "existing");
10363        assert_eq!(session.tabs.len(), 1);
10364
10365        // Cleanup
10366        let _ = session.delete();
10367    }
10368
10369    #[test]
10370    fn test_save_session_skips_empty_tabs() {
10371        let mut app =
10372            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
10373        app.config.account_id = "123456789".to_string();
10374
10375        app.save_current_session();
10376
10377        assert!(app.current_session.is_none());
10378    }
10379
10380    #[test]
10381    fn test_save_session_deletes_when_tabs_closed() {
10382        let mut app =
10383            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
10384        app.config.account_id = "123456789".to_string();
10385        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
10386
10387        // Create a session with tabs
10388        app.current_session = Some(Session {
10389            id: "test_delete".to_string(),
10390            timestamp: "2024-01-01 10:00:00 UTC".to_string(),
10391            profile: "test-profile".to_string(),
10392            region: "us-east-1".to_string(),
10393            account_id: "123456789".to_string(),
10394            role_arn: "arn:aws:iam::123456789:role/test".to_string(),
10395            tabs: vec![],
10396        });
10397
10398        // Save with no tabs should delete session
10399        app.save_current_session();
10400
10401        assert!(app.current_session.is_none());
10402    }
10403
10404    #[test]
10405    fn test_closing_all_tabs_deletes_session() {
10406        let mut app =
10407            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
10408        app.config.account_id = "123456789".to_string();
10409        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
10410
10411        // Add a tab
10412        app.tabs.push(Tab {
10413            service: Service::CloudWatchLogGroups,
10414            title: "Log Groups".to_string(),
10415            breadcrumb: "CloudWatch > Log Groups".to_string(),
10416        });
10417
10418        // Create session
10419        app.save_current_session();
10420        assert!(app.current_session.is_some());
10421        let session_id = app.current_session.as_ref().unwrap().id.clone();
10422
10423        // Close all tabs
10424        app.tabs.clear();
10425
10426        // Save should delete session
10427        app.save_current_session();
10428        assert!(app.current_session.is_none());
10429
10430        // Cleanup - ensure session file is deleted
10431        let _ = Session::load(&session_id).map(|s| s.delete());
10432    }
10433
10434    #[test]
10435    fn test_credential_error_opens_profile_picker() {
10436        // Simulate what main.rs does on credential error
10437        let mut app = App::new_without_client("default".to_string(), None);
10438        let error_str = "Unable to load credentials from any source";
10439
10440        if error_str.contains("credentials") {
10441            app.available_profiles = App::load_aws_profiles();
10442            app.mode = Mode::ProfilePicker;
10443        }
10444
10445        assert_eq!(app.mode, Mode::ProfilePicker);
10446        // Should have loaded profiles
10447        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
10448    }
10449
10450    #[test]
10451    fn test_non_credential_error_shows_error_modal() {
10452        let mut app = App::new_without_client("default".to_string(), None);
10453        let error_str = "Network timeout";
10454
10455        if !error_str.contains("credentials") {
10456            app.error_message = Some(error_str.to_string());
10457            app.mode = Mode::ErrorModal;
10458        }
10459
10460        assert_eq!(app.mode, Mode::ErrorModal);
10461        assert!(app.error_message.is_some());
10462    }
10463
10464    #[tokio::test]
10465    async fn test_profile_selection_loads_credentials() {
10466        // Set a valid AWS profile if available
10467        std::env::set_var("AWS_PROFILE", "default");
10468
10469        // Try to create app with profile
10470        let result = App::new(Some("default".to_string()), Some("us-east-1".to_string())).await;
10471
10472        if let Ok(app) = result {
10473            // If credentials are available, verify they're loaded
10474            assert!(!app.config.account_id.is_empty());
10475            assert!(!app.config.role_arn.is_empty());
10476            assert_eq!(app.profile, "default");
10477            assert_eq!(app.config.region, "us-east-1");
10478        }
10479        // If no credentials, test passes (can't test without real AWS creds)
10480    }
10481
10482    #[test]
10483    fn test_new_app_shows_service_picker_with_no_tabs() {
10484        let app = App::new_without_client("default".to_string(), Some("us-east-1".to_string()));
10485
10486        // Should start with no service selected
10487        assert!(!app.service_selected);
10488        // Should be in ServicePicker mode (service picker)
10489        assert_eq!(app.mode, Mode::ServicePicker);
10490        // Should have no tabs
10491        assert!(app.tabs.is_empty());
10492    }
10493
10494    #[tokio::test]
10495    async fn test_aws_profile_env_var_read_before_config_load() {
10496        // This test verifies the bug: AWS_PROFILE should be read and used
10497        std::env::set_var("AWS_PROFILE", "test-profile");
10498
10499        // Simulate what happens in App::new
10500        let profile_name = None
10501            .or_else(|| std::env::var("AWS_PROFILE").ok())
10502            .unwrap_or_else(|| "default".to_string());
10503
10504        // Should have read test-profile from env
10505        assert_eq!(profile_name, "test-profile");
10506
10507        // Now set it (redundant but that's what the code does)
10508        std::env::set_var("AWS_PROFILE", &profile_name);
10509
10510        // Verify it's still set
10511        assert_eq!(std::env::var("AWS_PROFILE").unwrap(), "test-profile");
10512
10513        std::env::remove_var("AWS_PROFILE");
10514    }
10515
10516    #[test]
10517    fn test_next_preferences_cloudformation() {
10518        let mut app = test_app();
10519        app.current_service = Service::CloudFormationStacks;
10520        app.mode = Mode::ColumnSelector;
10521        app.column_selector_index = 0;
10522
10523        // Should jump to PageSize section
10524        let page_size_idx = app.cfn_column_ids.len() + 2;
10525        app.handle_action(Action::NextPreferences);
10526        assert_eq!(app.column_selector_index, page_size_idx);
10527
10528        // Should wrap back to Columns
10529        app.handle_action(Action::NextPreferences);
10530        assert_eq!(app.column_selector_index, 0);
10531    }
10532
10533    #[test]
10534    fn test_s3_preferences_tab_cycling() {
10535        let mut app = test_app();
10536        app.current_service = Service::S3Buckets;
10537        app.mode = Mode::ColumnSelector;
10538        app.column_selector_index = 0;
10539
10540        let page_size_idx = app.s3_bucket_column_ids.len() + 2;
10541
10542        // Tab should jump to PageSize
10543        app.handle_action(Action::NextPreferences);
10544        assert_eq!(app.column_selector_index, page_size_idx);
10545
10546        // Tab should wrap back to Columns
10547        app.handle_action(Action::NextPreferences);
10548        assert_eq!(app.column_selector_index, 0);
10549
10550        // Shift+Tab should jump to PageSize
10551        app.handle_action(Action::PrevPreferences);
10552        assert_eq!(app.column_selector_index, page_size_idx);
10553
10554        // Shift+Tab should wrap back to Columns
10555        app.handle_action(Action::PrevPreferences);
10556        assert_eq!(app.column_selector_index, 0);
10557    }
10558
10559    #[test]
10560    fn test_s3_filter_resets_selection() {
10561        let mut app = test_app();
10562        app.current_service = Service::S3Buckets;
10563        app.service_selected = true;
10564
10565        // Add some buckets
10566        app.s3_state.buckets.items = vec![
10567            S3Bucket {
10568                name: "bucket-1".to_string(),
10569                region: "us-east-1".to_string(),
10570                creation_date: "2023-01-01".to_string(),
10571            },
10572            S3Bucket {
10573                name: "bucket-2".to_string(),
10574                region: "us-east-1".to_string(),
10575                creation_date: "2023-01-02".to_string(),
10576            },
10577            S3Bucket {
10578                name: "other-bucket".to_string(),
10579                region: "us-east-1".to_string(),
10580                creation_date: "2023-01-03".to_string(),
10581            },
10582        ];
10583
10584        // Navigate to second bucket
10585        app.s3_state.selected_row = 1;
10586        app.s3_state.bucket_scroll_offset = 1;
10587
10588        // Apply filter
10589        app.mode = Mode::FilterInput;
10590        app.apply_filter_operation(|f| f.push_str("bucket-"));
10591
10592        // Selection should be reset
10593        assert_eq!(app.s3_state.selected_row, 0);
10594        assert_eq!(app.s3_state.bucket_scroll_offset, 0);
10595        assert_eq!(app.s3_state.buckets.filter, "bucket-");
10596    }
10597
10598    #[test]
10599    fn test_s3_navigation_respects_filter() {
10600        let mut app = test_app();
10601        app.current_service = Service::S3Buckets;
10602        app.service_selected = true;
10603        app.mode = Mode::Normal;
10604
10605        // Add buckets
10606        app.s3_state.buckets.items = vec![
10607            S3Bucket {
10608                name: "prod-bucket".to_string(),
10609                region: "us-east-1".to_string(),
10610                creation_date: "2023-01-01".to_string(),
10611            },
10612            S3Bucket {
10613                name: "dev-bucket".to_string(),
10614                region: "us-east-1".to_string(),
10615                creation_date: "2023-01-02".to_string(),
10616            },
10617            S3Bucket {
10618                name: "prod-logs".to_string(),
10619                region: "us-east-1".to_string(),
10620                creation_date: "2023-01-03".to_string(),
10621            },
10622        ];
10623
10624        // Filter to only "prod" buckets (2 results)
10625        app.s3_state.buckets.filter = "prod".to_string();
10626
10627        // Should start at 0
10628        assert_eq!(app.s3_state.selected_row, 0);
10629
10630        // Navigate down - should go to row 1 (prod-logs)
10631        app.handle_action(Action::NextItem);
10632        assert_eq!(app.s3_state.selected_row, 1);
10633
10634        // Navigate down again - should stay at 1 (max for 2 filtered results)
10635        app.handle_action(Action::NextItem);
10636        assert_eq!(app.s3_state.selected_row, 1);
10637
10638        // Navigate up - should go back to 0
10639        app.handle_action(Action::PrevItem);
10640        assert_eq!(app.s3_state.selected_row, 0);
10641    }
10642
10643    #[test]
10644    fn test_next_preferences_lambda_functions() {
10645        let mut app = test_app();
10646        app.current_service = Service::LambdaFunctions;
10647        app.mode = Mode::ColumnSelector;
10648        app.column_selector_index = 0;
10649
10650        let page_size_idx = app.lambda_state.function_column_ids.len() + 2;
10651        app.handle_action(Action::NextPreferences);
10652        assert_eq!(app.column_selector_index, page_size_idx);
10653
10654        app.handle_action(Action::NextPreferences);
10655        assert_eq!(app.column_selector_index, 0);
10656    }
10657
10658    #[test]
10659    fn test_next_preferences_lambda_applications() {
10660        let mut app = test_app();
10661        app.current_service = Service::LambdaApplications;
10662        app.mode = Mode::ColumnSelector;
10663        app.column_selector_index = 0;
10664
10665        let page_size_idx = app.lambda_application_column_ids.len() + 2;
10666        app.handle_action(Action::NextPreferences);
10667        assert_eq!(app.column_selector_index, page_size_idx);
10668
10669        app.handle_action(Action::NextPreferences);
10670        assert_eq!(app.column_selector_index, 0);
10671    }
10672
10673    #[test]
10674    fn test_next_preferences_ecr_images() {
10675        let mut app = test_app();
10676        app.current_service = Service::EcrRepositories;
10677        app.ecr_state.current_repository = Some("test-repo".to_string());
10678        app.mode = Mode::ColumnSelector;
10679        app.column_selector_index = 0;
10680
10681        let page_size_idx = app.ecr_image_column_ids.len() + 2;
10682        app.handle_action(Action::NextPreferences);
10683        assert_eq!(app.column_selector_index, page_size_idx);
10684
10685        app.handle_action(Action::NextPreferences);
10686        assert_eq!(app.column_selector_index, 0);
10687    }
10688
10689    #[test]
10690    fn test_cloudformation_next_item() {
10691        let mut app = test_app();
10692        app.current_service = Service::CloudFormationStacks;
10693        app.service_selected = true;
10694        app.mode = Mode::Normal;
10695        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10696        app.cfn_state.table.items = vec![
10697            CfnStack {
10698                name: "stack1".to_string(),
10699                stack_id: "id1".to_string(),
10700                status: "CREATE_COMPLETE".to_string(),
10701                created_time: "2024-01-01".to_string(),
10702                updated_time: String::new(),
10703                deleted_time: String::new(),
10704                drift_status: String::new(),
10705                last_drift_check_time: String::new(),
10706                status_reason: String::new(),
10707                description: String::new(),
10708                detailed_status: String::new(),
10709                root_stack: String::new(),
10710                parent_stack: String::new(),
10711                termination_protection: false,
10712                iam_role: String::new(),
10713                tags: Vec::new(),
10714                stack_policy: String::new(),
10715                rollback_monitoring_time: String::new(),
10716                rollback_alarms: Vec::new(),
10717                notification_arns: Vec::new(),
10718            },
10719            CfnStack {
10720                name: "stack2".to_string(),
10721                stack_id: "id2".to_string(),
10722                status: "UPDATE_COMPLETE".to_string(),
10723                created_time: "2024-01-02".to_string(),
10724                updated_time: String::new(),
10725                deleted_time: String::new(),
10726                drift_status: String::new(),
10727                last_drift_check_time: String::new(),
10728                status_reason: String::new(),
10729                description: String::new(),
10730                detailed_status: String::new(),
10731                root_stack: String::new(),
10732                parent_stack: String::new(),
10733                termination_protection: false,
10734                iam_role: String::new(),
10735                tags: Vec::new(),
10736                stack_policy: String::new(),
10737                rollback_monitoring_time: String::new(),
10738                rollback_alarms: Vec::new(),
10739                notification_arns: Vec::new(),
10740            },
10741        ];
10742        app.cfn_state.table.reset();
10743
10744        app.handle_action(Action::NextItem);
10745        assert_eq!(app.cfn_state.table.selected, 1);
10746
10747        app.handle_action(Action::NextItem);
10748        assert_eq!(app.cfn_state.table.selected, 1); // At max
10749    }
10750
10751    #[test]
10752    fn test_cloudformation_prev_item() {
10753        let mut app = test_app();
10754        app.current_service = Service::CloudFormationStacks;
10755        app.service_selected = true;
10756        app.mode = Mode::Normal;
10757        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10758        app.cfn_state.table.items = vec![
10759            CfnStack {
10760                name: "stack1".to_string(),
10761                stack_id: "id1".to_string(),
10762                status: "CREATE_COMPLETE".to_string(),
10763                created_time: "2024-01-01".to_string(),
10764                updated_time: String::new(),
10765                deleted_time: String::new(),
10766                drift_status: String::new(),
10767                last_drift_check_time: String::new(),
10768                status_reason: String::new(),
10769                description: String::new(),
10770                detailed_status: String::new(),
10771                root_stack: String::new(),
10772                parent_stack: String::new(),
10773                termination_protection: false,
10774                iam_role: String::new(),
10775                tags: Vec::new(),
10776                stack_policy: String::new(),
10777                rollback_monitoring_time: String::new(),
10778                rollback_alarms: Vec::new(),
10779                notification_arns: Vec::new(),
10780            },
10781            CfnStack {
10782                name: "stack2".to_string(),
10783                stack_id: "id2".to_string(),
10784                status: "UPDATE_COMPLETE".to_string(),
10785                created_time: "2024-01-02".to_string(),
10786                updated_time: String::new(),
10787                deleted_time: String::new(),
10788                drift_status: String::new(),
10789                last_drift_check_time: String::new(),
10790                status_reason: String::new(),
10791                description: String::new(),
10792                detailed_status: String::new(),
10793                root_stack: String::new(),
10794                parent_stack: String::new(),
10795                termination_protection: false,
10796                iam_role: String::new(),
10797                tags: Vec::new(),
10798                stack_policy: String::new(),
10799                rollback_monitoring_time: String::new(),
10800                rollback_alarms: Vec::new(),
10801                notification_arns: Vec::new(),
10802            },
10803        ];
10804        app.cfn_state.table.selected = 1;
10805
10806        app.handle_action(Action::PrevItem);
10807        assert_eq!(app.cfn_state.table.selected, 0);
10808
10809        app.handle_action(Action::PrevItem);
10810        assert_eq!(app.cfn_state.table.selected, 0); // At min
10811    }
10812
10813    #[test]
10814    fn test_cloudformation_page_down() {
10815        let mut app = test_app();
10816        app.current_service = Service::CloudFormationStacks;
10817        app.service_selected = true;
10818        app.mode = Mode::Normal;
10819        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10820
10821        // Create 20 stacks
10822        for i in 0..20 {
10823            app.cfn_state.table.items.push(CfnStack {
10824                name: format!("stack{}", i),
10825                stack_id: format!("id{}", i),
10826                status: "CREATE_COMPLETE".to_string(),
10827                created_time: format!("2024-01-{:02}", i + 1),
10828                updated_time: String::new(),
10829                deleted_time: String::new(),
10830                drift_status: String::new(),
10831                last_drift_check_time: String::new(),
10832                status_reason: String::new(),
10833                description: String::new(),
10834                detailed_status: String::new(),
10835                root_stack: String::new(),
10836                parent_stack: String::new(),
10837                termination_protection: false,
10838                iam_role: String::new(),
10839                tags: Vec::new(),
10840                stack_policy: String::new(),
10841                rollback_monitoring_time: String::new(),
10842                rollback_alarms: Vec::new(),
10843                notification_arns: Vec::new(),
10844            });
10845        }
10846        app.cfn_state.table.reset();
10847
10848        app.handle_action(Action::PageDown);
10849        assert_eq!(app.cfn_state.table.selected, 10);
10850
10851        app.handle_action(Action::PageDown);
10852        assert_eq!(app.cfn_state.table.selected, 19); // Clamped to max
10853    }
10854
10855    #[test]
10856    fn test_cloudformation_page_up() {
10857        let mut app = test_app();
10858        app.current_service = Service::CloudFormationStacks;
10859        app.service_selected = true;
10860        app.mode = Mode::Normal;
10861        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10862
10863        // Create 20 stacks
10864        for i in 0..20 {
10865            app.cfn_state.table.items.push(CfnStack {
10866                name: format!("stack{}", i),
10867                stack_id: format!("id{}", i),
10868                status: "CREATE_COMPLETE".to_string(),
10869                created_time: format!("2024-01-{:02}", i + 1),
10870                updated_time: String::new(),
10871                deleted_time: String::new(),
10872                drift_status: String::new(),
10873                last_drift_check_time: String::new(),
10874                status_reason: String::new(),
10875                description: String::new(),
10876                detailed_status: String::new(),
10877                root_stack: String::new(),
10878                parent_stack: String::new(),
10879                termination_protection: false,
10880                iam_role: String::new(),
10881                tags: Vec::new(),
10882                stack_policy: String::new(),
10883                rollback_monitoring_time: String::new(),
10884                rollback_alarms: Vec::new(),
10885                notification_arns: Vec::new(),
10886            });
10887        }
10888        app.cfn_state.table.selected = 15;
10889
10890        app.handle_action(Action::PageUp);
10891        assert_eq!(app.cfn_state.table.selected, 5);
10892
10893        app.handle_action(Action::PageUp);
10894        assert_eq!(app.cfn_state.table.selected, 0); // Clamped to min
10895    }
10896
10897    #[test]
10898    fn test_cloudformation_filter_input() {
10899        let mut app = test_app();
10900        app.current_service = Service::CloudFormationStacks;
10901        app.service_selected = true;
10902        app.mode = Mode::Normal;
10903
10904        app.handle_action(Action::StartFilter);
10905        assert_eq!(app.mode, Mode::FilterInput);
10906
10907        // Directly set filter (character input is handled in event loop, not actions)
10908        app.cfn_state.table.filter = "test".to_string();
10909        assert_eq!(app.cfn_state.table.filter, "test");
10910    }
10911
10912    #[test]
10913    fn test_cloudformation_filter_applies() {
10914        let mut app = test_app();
10915        app.current_service = Service::CloudFormationStacks;
10916        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10917        app.cfn_state.table.items = vec![
10918            CfnStack {
10919                name: "prod-stack".to_string(),
10920                stack_id: "id1".to_string(),
10921                status: "CREATE_COMPLETE".to_string(),
10922                created_time: "2024-01-01".to_string(),
10923                updated_time: String::new(),
10924                deleted_time: String::new(),
10925                drift_status: String::new(),
10926                last_drift_check_time: String::new(),
10927                status_reason: String::new(),
10928                description: "Production stack".to_string(),
10929                detailed_status: String::new(),
10930                root_stack: String::new(),
10931                parent_stack: String::new(),
10932                termination_protection: false,
10933                iam_role: String::new(),
10934                tags: Vec::new(),
10935                stack_policy: String::new(),
10936                rollback_monitoring_time: String::new(),
10937                rollback_alarms: Vec::new(),
10938                notification_arns: Vec::new(),
10939            },
10940            CfnStack {
10941                name: "dev-stack".to_string(),
10942                stack_id: "id2".to_string(),
10943                status: "UPDATE_COMPLETE".to_string(),
10944                created_time: "2024-01-02".to_string(),
10945                updated_time: String::new(),
10946                deleted_time: String::new(),
10947                drift_status: String::new(),
10948                last_drift_check_time: String::new(),
10949                status_reason: String::new(),
10950                description: "Development stack".to_string(),
10951                detailed_status: String::new(),
10952                root_stack: String::new(),
10953                parent_stack: String::new(),
10954                termination_protection: false,
10955                iam_role: String::new(),
10956                tags: Vec::new(),
10957                stack_policy: String::new(),
10958                rollback_monitoring_time: String::new(),
10959                rollback_alarms: Vec::new(),
10960                notification_arns: Vec::new(),
10961            },
10962        ];
10963        app.cfn_state.table.filter = "prod".to_string();
10964
10965        let filtered = filtered_cloudformation_stacks(&app);
10966        assert_eq!(filtered.len(), 1);
10967        assert_eq!(filtered[0].name, "prod-stack");
10968    }
10969
10970    #[test]
10971    fn test_cloudformation_right_arrow_expands() {
10972        let mut app = test_app();
10973        app.current_service = Service::CloudFormationStacks;
10974        app.service_selected = true;
10975        app.cfn_state.status_filter = CfnStatusFilter::Complete;
10976        app.cfn_state.table.items = vec![CfnStack {
10977            name: "test-stack".to_string(),
10978            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
10979                .to_string(),
10980            status: "CREATE_COMPLETE".to_string(),
10981            created_time: "2024-01-01".to_string(),
10982            updated_time: String::new(),
10983            deleted_time: String::new(),
10984            drift_status: String::new(),
10985            last_drift_check_time: String::new(),
10986            status_reason: String::new(),
10987            description: "Test stack".to_string(),
10988            detailed_status: String::new(),
10989            root_stack: String::new(),
10990            parent_stack: String::new(),
10991            termination_protection: false,
10992            iam_role: String::new(),
10993            tags: Vec::new(),
10994            stack_policy: String::new(),
10995            rollback_monitoring_time: String::new(),
10996            rollback_alarms: Vec::new(),
10997            notification_arns: Vec::new(),
10998        }];
10999        app.cfn_state.table.reset();
11000
11001        assert_eq!(app.cfn_state.table.expanded_item, None);
11002
11003        app.handle_action(Action::NextPane);
11004        assert_eq!(app.cfn_state.table.expanded_item, Some(0));
11005    }
11006
11007    #[test]
11008    fn test_cloudformation_left_arrow_collapses() {
11009        let mut app = test_app();
11010        app.current_service = Service::CloudFormationStacks;
11011        app.service_selected = true;
11012        app.cfn_state.status_filter = CfnStatusFilter::Complete;
11013        app.cfn_state.table.items = vec![CfnStack {
11014            name: "test-stack".to_string(),
11015            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
11016                .to_string(),
11017            status: "CREATE_COMPLETE".to_string(),
11018            created_time: "2024-01-01".to_string(),
11019            updated_time: String::new(),
11020            deleted_time: String::new(),
11021            drift_status: String::new(),
11022            last_drift_check_time: String::new(),
11023            status_reason: String::new(),
11024            description: "Test stack".to_string(),
11025            detailed_status: String::new(),
11026            root_stack: String::new(),
11027            parent_stack: String::new(),
11028            termination_protection: false,
11029            iam_role: String::new(),
11030            tags: Vec::new(),
11031            stack_policy: String::new(),
11032            rollback_monitoring_time: String::new(),
11033            rollback_alarms: Vec::new(),
11034            notification_arns: Vec::new(),
11035        }];
11036        app.cfn_state.table.reset();
11037        app.cfn_state.table.expanded_item = Some(0);
11038
11039        app.handle_action(Action::PrevPane);
11040        assert_eq!(app.cfn_state.table.expanded_item, None);
11041    }
11042
11043    #[test]
11044    fn test_cloudformation_enter_drills_into_stack() {
11045        let mut app = test_app();
11046        app.current_service = Service::CloudFormationStacks;
11047        app.service_selected = true;
11048        app.mode = Mode::Normal;
11049        app.tabs = vec![Tab {
11050            service: Service::CloudFormationStacks,
11051            title: "CloudFormation > Stacks".to_string(),
11052            breadcrumb: "CloudFormation > Stacks".to_string(),
11053        }];
11054        app.current_tab = 0;
11055        app.cfn_state.status_filter = CfnStatusFilter::Complete;
11056        app.cfn_state.table.items = vec![CfnStack {
11057            name: "test-stack".to_string(),
11058            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
11059                .to_string(),
11060            status: "CREATE_COMPLETE".to_string(),
11061            created_time: "2024-01-01".to_string(),
11062            updated_time: String::new(),
11063            deleted_time: String::new(),
11064            drift_status: String::new(),
11065            last_drift_check_time: String::new(),
11066            status_reason: String::new(),
11067            description: "Test stack".to_string(),
11068            detailed_status: String::new(),
11069            root_stack: String::new(),
11070            parent_stack: String::new(),
11071            termination_protection: false,
11072            iam_role: String::new(),
11073            tags: Vec::new(),
11074            stack_policy: String::new(),
11075            rollback_monitoring_time: String::new(),
11076            rollback_alarms: Vec::new(),
11077            notification_arns: Vec::new(),
11078        }];
11079        app.cfn_state.table.reset();
11080
11081        // Verify filtering works
11082        let filtered = filtered_cloudformation_stacks(&app);
11083        assert_eq!(filtered.len(), 1);
11084        assert_eq!(filtered[0].name, "test-stack");
11085
11086        assert_eq!(app.cfn_state.current_stack, None);
11087
11088        // Enter drills into stack detail view
11089        app.handle_action(Action::Select);
11090        assert_eq!(app.cfn_state.current_stack, Some("test-stack".to_string()));
11091    }
11092
11093    #[test]
11094    fn test_cloudformation_copy_to_clipboard() {
11095        let mut app = test_app();
11096        app.current_service = Service::CloudFormationStacks;
11097        app.service_selected = true;
11098        app.mode = Mode::Normal;
11099        app.cfn_state.status_filter = CfnStatusFilter::Complete;
11100        app.cfn_state.table.items = vec![
11101            CfnStack {
11102                name: "stack1".to_string(),
11103                stack_id: "id1".to_string(),
11104                status: "CREATE_COMPLETE".to_string(),
11105                created_time: "2024-01-01".to_string(),
11106                updated_time: String::new(),
11107                deleted_time: String::new(),
11108                drift_status: String::new(),
11109                last_drift_check_time: String::new(),
11110                status_reason: String::new(),
11111                description: String::new(),
11112                detailed_status: String::new(),
11113                root_stack: String::new(),
11114                parent_stack: String::new(),
11115                termination_protection: false,
11116                iam_role: String::new(),
11117                tags: Vec::new(),
11118                stack_policy: String::new(),
11119                rollback_monitoring_time: String::new(),
11120                rollback_alarms: Vec::new(),
11121                notification_arns: Vec::new(),
11122            },
11123            CfnStack {
11124                name: "stack2".to_string(),
11125                stack_id: "id2".to_string(),
11126                status: "UPDATE_COMPLETE".to_string(),
11127                created_time: "2024-01-02".to_string(),
11128                updated_time: String::new(),
11129                deleted_time: String::new(),
11130                drift_status: String::new(),
11131                last_drift_check_time: String::new(),
11132                status_reason: String::new(),
11133                description: String::new(),
11134                detailed_status: String::new(),
11135                root_stack: String::new(),
11136                parent_stack: String::new(),
11137                termination_protection: false,
11138                iam_role: String::new(),
11139                tags: Vec::new(),
11140                stack_policy: String::new(),
11141                rollback_monitoring_time: String::new(),
11142                rollback_alarms: Vec::new(),
11143                notification_arns: Vec::new(),
11144            },
11145        ];
11146
11147        assert!(!app.snapshot_requested);
11148        app.handle_action(Action::CopyToClipboard);
11149
11150        // Should set snapshot_requested flag
11151        assert!(app.snapshot_requested);
11152    }
11153
11154    #[test]
11155    fn test_cloudformation_expansion_shows_all_visible_columns() {
11156        let mut app = test_app();
11157        app.current_service = Service::CloudFormationStacks;
11158        app.cfn_state.status_filter = CfnStatusFilter::Complete;
11159        app.cfn_state.table.items = vec![CfnStack {
11160            name: "test-stack".to_string(),
11161            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
11162                .to_string(),
11163            status: "CREATE_COMPLETE".to_string(),
11164            created_time: "2024-01-01".to_string(),
11165            updated_time: "2024-01-02".to_string(),
11166            deleted_time: String::new(),
11167            drift_status: "IN_SYNC".to_string(),
11168            last_drift_check_time: "2024-01-03".to_string(),
11169            status_reason: String::new(),
11170            description: "Test description".to_string(),
11171            detailed_status: String::new(),
11172            root_stack: String::new(),
11173            parent_stack: String::new(),
11174            termination_protection: false,
11175            iam_role: String::new(),
11176            tags: Vec::new(),
11177            stack_policy: String::new(),
11178            rollback_monitoring_time: String::new(),
11179            rollback_alarms: Vec::new(),
11180            notification_arns: Vec::new(),
11181        }];
11182
11183        // Set visible columns
11184        app.cfn_visible_column_ids = [
11185            CfnColumn::Name,
11186            CfnColumn::Status,
11187            CfnColumn::CreatedTime,
11188            CfnColumn::Description,
11189        ]
11190        .iter()
11191        .map(|c| c.id())
11192        .collect();
11193
11194        app.cfn_state.table.expanded_item = Some(0);
11195
11196        // Verify all visible columns would be shown in expansion
11197        // (This is a structural test - actual rendering is in UI layer)
11198        assert_eq!(app.cfn_visible_column_ids.len(), 4);
11199        assert!(app.cfn_state.table.has_expanded_item());
11200    }
11201
11202    #[test]
11203    fn test_cloudformation_empty_list_shows_page_1() {
11204        let mut app = test_app();
11205        app.current_service = Service::CloudFormationStacks;
11206        app.cfn_state.table.items = vec![];
11207
11208        let filtered = filtered_cloudformation_stacks(&app);
11209        assert_eq!(filtered.len(), 0);
11210
11211        // Pagination should still show [1] even with 0 items
11212        let page_size = app.cfn_state.table.page_size.value();
11213        let total_pages = filtered.len().div_ceil(page_size);
11214        assert_eq!(total_pages, 0);
11215
11216        // render_pagination_text(0, 0) should return "[1]"
11217        // This is tested in UI layer
11218    }
11219}
11220
11221impl App {
11222    pub fn get_filtered_regions(&self) -> Vec<AwsRegion> {
11223        let mut all = AwsRegion::all();
11224
11225        // Add latencies to regions
11226        for region in &mut all {
11227            region.latency_ms = self.region_latencies.get(region.code).copied();
11228        }
11229
11230        // Filter by search term
11231        let filtered: Vec<AwsRegion> = if self.region_filter.is_empty() {
11232            all
11233        } else {
11234            let filter_lower = self.region_filter.to_lowercase();
11235            all.into_iter()
11236                .filter(|r| {
11237                    r.name.to_lowercase().contains(&filter_lower)
11238                        || r.code.to_lowercase().contains(&filter_lower)
11239                        || r.group.to_lowercase().contains(&filter_lower)
11240                })
11241                .collect()
11242        };
11243
11244        // Sort by latency (lowest first), treat None as 1000ms
11245        let mut sorted = filtered;
11246        sorted.sort_by_key(|r| r.latency_ms.unwrap_or(1000));
11247        sorted
11248    }
11249
11250    pub fn measure_region_latencies(&mut self) {
11251        use std::time::Instant;
11252        self.region_latencies.clear();
11253
11254        let regions = AwsRegion::all();
11255        let start_all = Instant::now();
11256        tracing::info!("Starting latency measurement for {} regions", regions.len());
11257
11258        let handles: Vec<_> = regions
11259            .iter()
11260            .map(|region| {
11261                let code = region.code.to_string();
11262                std::thread::spawn(move || {
11263                    // Use STS endpoint - fastest and most reliable
11264                    let endpoint = format!("https://sts.{}.amazonaws.com", code);
11265                    let start = Instant::now();
11266
11267                    match ureq::get(&endpoint)
11268                        .timeout(std::time::Duration::from_secs(2))
11269                        .call()
11270                    {
11271                        Ok(_) => {
11272                            let latency = start.elapsed().as_millis() as u64;
11273                            Some((code, latency))
11274                        }
11275                        Err(e) => {
11276                            tracing::debug!("Failed to measure {}: {}", code, e);
11277                            Some((code, 9999))
11278                        }
11279                    }
11280                })
11281            })
11282            .collect();
11283
11284        for handle in handles {
11285            if let Ok(Some((code, latency))) = handle.join() {
11286                self.region_latencies.insert(code, latency);
11287            }
11288        }
11289
11290        tracing::info!(
11291            "Measured {} regions in {:?}",
11292            self.region_latencies.len(),
11293            start_all.elapsed()
11294        );
11295    }
11296
11297    pub fn get_filtered_profiles(&self) -> Vec<&AwsProfile> {
11298        filter_profiles(&self.available_profiles, &self.profile_filter)
11299    }
11300
11301    pub fn get_filtered_sessions(&self) -> Vec<&Session> {
11302        if self.session_filter.is_empty() {
11303            return self.sessions.iter().collect();
11304        }
11305        let filter_lower = self.session_filter.to_lowercase();
11306        self.sessions
11307            .iter()
11308            .filter(|s| {
11309                s.profile.to_lowercase().contains(&filter_lower)
11310                    || s.region.to_lowercase().contains(&filter_lower)
11311                    || s.account_id.to_lowercase().contains(&filter_lower)
11312                    || s.role_arn.to_lowercase().contains(&filter_lower)
11313            })
11314            .collect()
11315    }
11316
11317    pub fn get_filtered_tabs(&self) -> Vec<(usize, &Tab)> {
11318        if self.tab_filter.is_empty() {
11319            return self.tabs.iter().enumerate().collect();
11320        }
11321        let filter_lower = self.tab_filter.to_lowercase();
11322        self.tabs
11323            .iter()
11324            .enumerate()
11325            .filter(|(_, tab)| {
11326                tab.title.to_lowercase().contains(&filter_lower)
11327                    || tab.breadcrumb.to_lowercase().contains(&filter_lower)
11328            })
11329            .collect()
11330    }
11331
11332    pub fn load_aws_profiles() -> Vec<AwsProfile> {
11333        AwsProfile::load_all()
11334    }
11335
11336    pub async fn fetch_profile_accounts(&mut self) {
11337        for profile in &mut self.available_profiles {
11338            if profile.account.is_none() {
11339                let region = profile
11340                    .region
11341                    .clone()
11342                    .unwrap_or_else(|| "us-east-1".to_string());
11343                if let Ok(account) =
11344                    rusticity_core::AwsConfig::get_account_for_profile(&profile.name, &region).await
11345                {
11346                    profile.account = Some(account);
11347                }
11348            }
11349        }
11350    }
11351
11352    fn save_current_session(&mut self) {
11353        // If no tabs, delete the session if it exists
11354        if self.tabs.is_empty() {
11355            if let Some(ref session) = self.current_session {
11356                let _ = session.delete();
11357                self.current_session = None;
11358            }
11359            return;
11360        }
11361
11362        let session = if let Some(ref mut current) = self.current_session {
11363            // Update existing session
11364            current.tabs = self
11365                .tabs
11366                .iter()
11367                .map(|t| SessionTab {
11368                    service: format!("{:?}", t.service),
11369                    title: t.title.clone(),
11370                    breadcrumb: t.breadcrumb.clone(),
11371                    filter: match t.service {
11372                        Service::CloudWatchLogGroups => {
11373                            Some(self.log_groups_state.log_groups.filter.clone())
11374                        }
11375                        _ => None,
11376                    },
11377                    selected_item: None,
11378                })
11379                .collect();
11380            current.clone()
11381        } else {
11382            // Create new session
11383            let mut session = Session::new(
11384                self.profile.clone(),
11385                self.region.clone(),
11386                self.config.account_id.clone(),
11387                self.config.role_arn.clone(),
11388            );
11389            session.tabs = self
11390                .tabs
11391                .iter()
11392                .map(|t| SessionTab {
11393                    service: format!("{:?}", t.service),
11394                    title: t.title.clone(),
11395                    breadcrumb: t.breadcrumb.clone(),
11396                    filter: match t.service {
11397                        Service::CloudWatchLogGroups => {
11398                            Some(self.log_groups_state.log_groups.filter.clone())
11399                        }
11400                        _ => None,
11401                    },
11402                    selected_item: None,
11403                })
11404                .collect();
11405            self.current_session = Some(session.clone());
11406            session
11407        };
11408
11409        let _ = session.save();
11410    }
11411}
11412
11413#[cfg(test)]
11414mod iam_policy_view_tests {
11415    use super::*;
11416    use test_helpers::*;
11417
11418    #[test]
11419    fn test_enter_opens_policy_view() {
11420        let mut app = test_app();
11421        app.current_service = Service::IamRoles;
11422        app.service_selected = true;
11423        app.mode = Mode::Normal;
11424        app.view_mode = ViewMode::Detail;
11425        app.iam_state.current_role = Some("TestRole".to_string());
11426        app.iam_state.policies.items = vec![IamPolicy {
11427            policy_name: "TestPolicy".to_string(),
11428            policy_type: "Inline".to_string(),
11429            attached_via: "Direct".to_string(),
11430            attached_entities: "1".to_string(),
11431            description: "Test".to_string(),
11432            creation_time: "2023-01-01".to_string(),
11433            edited_time: "2023-01-01".to_string(),
11434            policy_arn: None,
11435        }];
11436        app.iam_state.policies.reset();
11437
11438        app.handle_action(Action::Select);
11439
11440        assert_eq!(app.view_mode, ViewMode::PolicyView);
11441        assert_eq!(app.iam_state.current_policy, Some("TestPolicy".to_string()));
11442        assert_eq!(app.iam_state.policy_scroll, 0);
11443        assert!(app.iam_state.policies.loading);
11444    }
11445
11446    #[test]
11447    fn test_escape_closes_policy_view() {
11448        let mut app = test_app();
11449        app.current_service = Service::IamRoles;
11450        app.service_selected = true;
11451        app.mode = Mode::Normal;
11452        app.view_mode = ViewMode::PolicyView;
11453        app.iam_state.current_role = Some("TestRole".to_string());
11454        app.iam_state.current_policy = Some("TestPolicy".to_string());
11455        app.iam_state.policy_document = "{\n  \"test\": \"value\"\n}".to_string();
11456        app.iam_state.policy_scroll = 5;
11457
11458        app.handle_action(Action::PrevPane);
11459
11460        assert_eq!(app.view_mode, ViewMode::Detail);
11461        assert_eq!(app.iam_state.current_policy, None);
11462        assert_eq!(app.iam_state.policy_document, "");
11463        assert_eq!(app.iam_state.policy_scroll, 0);
11464    }
11465
11466    #[test]
11467    fn test_ctrl_d_scrolls_down_in_policy_view() {
11468        let mut app = test_app();
11469        app.current_service = Service::IamRoles;
11470        app.service_selected = true;
11471        app.mode = Mode::Normal;
11472        app.view_mode = ViewMode::PolicyView;
11473        app.iam_state.current_role = Some("TestRole".to_string());
11474        app.iam_state.current_policy = Some("TestPolicy".to_string());
11475        app.iam_state.policy_document = (0..100)
11476            .map(|i| format!("line {}", i))
11477            .collect::<Vec<_>>()
11478            .join("\n");
11479        app.iam_state.policy_scroll = 0;
11480
11481        app.handle_action(Action::ScrollDown);
11482
11483        assert_eq!(app.iam_state.policy_scroll, 10);
11484
11485        app.handle_action(Action::ScrollDown);
11486
11487        assert_eq!(app.iam_state.policy_scroll, 20);
11488    }
11489
11490    #[test]
11491    fn test_ctrl_u_scrolls_up_in_policy_view() {
11492        let mut app = test_app();
11493        app.current_service = Service::IamRoles;
11494        app.service_selected = true;
11495        app.mode = Mode::Normal;
11496        app.view_mode = ViewMode::PolicyView;
11497        app.iam_state.current_role = Some("TestRole".to_string());
11498        app.iam_state.current_policy = Some("TestPolicy".to_string());
11499        app.iam_state.policy_document = (0..100)
11500            .map(|i| format!("line {}", i))
11501            .collect::<Vec<_>>()
11502            .join("\n");
11503        app.iam_state.policy_scroll = 30;
11504
11505        app.handle_action(Action::ScrollUp);
11506
11507        assert_eq!(app.iam_state.policy_scroll, 20);
11508
11509        app.handle_action(Action::ScrollUp);
11510
11511        assert_eq!(app.iam_state.policy_scroll, 10);
11512    }
11513
11514    #[test]
11515    fn test_scroll_does_not_go_negative() {
11516        let mut app = test_app();
11517        app.current_service = Service::IamRoles;
11518        app.service_selected = true;
11519        app.mode = Mode::Normal;
11520        app.view_mode = ViewMode::PolicyView;
11521        app.iam_state.current_role = Some("TestRole".to_string());
11522        app.iam_state.current_policy = Some("TestPolicy".to_string());
11523        app.iam_state.policy_document = "line 1\nline 2\nline 3".to_string();
11524        app.iam_state.policy_scroll = 0;
11525
11526        app.handle_action(Action::ScrollUp);
11527
11528        assert_eq!(app.iam_state.policy_scroll, 0);
11529    }
11530
11531    #[test]
11532    fn test_scroll_does_not_exceed_max() {
11533        let mut app = test_app();
11534        app.current_service = Service::IamRoles;
11535        app.service_selected = true;
11536        app.mode = Mode::Normal;
11537        app.view_mode = ViewMode::PolicyView;
11538        app.iam_state.current_role = Some("TestRole".to_string());
11539        app.iam_state.current_policy = Some("TestPolicy".to_string());
11540        app.iam_state.policy_document = "line 1\nline 2\nline 3".to_string();
11541        app.iam_state.policy_scroll = 0;
11542
11543        app.handle_action(Action::ScrollDown);
11544
11545        assert_eq!(app.iam_state.policy_scroll, 2); // Max is 2 (3 lines - 1)
11546    }
11547
11548    #[test]
11549    fn test_policy_view_console_url() {
11550        let mut app = test_app();
11551        app.current_service = Service::IamRoles;
11552        app.service_selected = true;
11553        app.view_mode = ViewMode::PolicyView;
11554        app.iam_state.current_role = Some("TestRole".to_string());
11555        app.iam_state.current_policy = Some("TestPolicy".to_string());
11556
11557        let url = app.get_console_url();
11558
11559        assert!(url.contains("us-east-1.console.aws.amazon.com"));
11560        assert!(url.contains("/roles/details/TestRole"));
11561        assert!(url.contains("/editPolicy/TestPolicy"));
11562        assert!(url.contains("step=addPermissions"));
11563    }
11564
11565    #[test]
11566    fn test_esc_from_policy_view_goes_to_role_detail() {
11567        let mut app = test_app();
11568        app.current_service = Service::IamRoles;
11569        app.service_selected = true;
11570        app.mode = Mode::Normal;
11571        app.view_mode = ViewMode::PolicyView;
11572        app.iam_state.current_role = Some("TestRole".to_string());
11573        app.iam_state.current_policy = Some("TestPolicy".to_string());
11574        app.iam_state.policy_document = "test".to_string();
11575        app.iam_state.policy_scroll = 5;
11576
11577        app.handle_action(Action::GoBack);
11578
11579        assert_eq!(app.view_mode, ViewMode::Detail);
11580        assert_eq!(app.iam_state.current_policy, None);
11581        assert_eq!(app.iam_state.policy_document, "");
11582        assert_eq!(app.iam_state.policy_scroll, 0);
11583        assert_eq!(app.iam_state.current_role, Some("TestRole".to_string()));
11584    }
11585
11586    #[test]
11587    fn test_esc_from_role_detail_goes_to_role_list() {
11588        let mut app = test_app();
11589        app.current_service = Service::IamRoles;
11590        app.service_selected = true;
11591        app.mode = Mode::Normal;
11592        app.view_mode = ViewMode::Detail;
11593        app.iam_state.current_role = Some("TestRole".to_string());
11594
11595        app.handle_action(Action::GoBack);
11596
11597        assert_eq!(app.iam_state.current_role, None);
11598    }
11599
11600    #[test]
11601    fn test_right_arrow_expands_policy_row() {
11602        let mut app = test_app();
11603        app.current_service = Service::IamRoles;
11604        app.service_selected = true;
11605        app.mode = Mode::Normal;
11606        app.view_mode = ViewMode::Detail;
11607        app.iam_state.current_role = Some("TestRole".to_string());
11608        app.iam_state.policies.items = vec![IamPolicy {
11609            policy_name: "TestPolicy".to_string(),
11610            policy_type: "Inline".to_string(),
11611            attached_via: "Direct".to_string(),
11612            attached_entities: "1".to_string(),
11613            description: "Test".to_string(),
11614            creation_time: "2023-01-01".to_string(),
11615            edited_time: "2023-01-01".to_string(),
11616            policy_arn: None,
11617        }];
11618        app.iam_state.policies.reset();
11619
11620        app.handle_action(Action::NextPane);
11621
11622        // Should expand, not drill down
11623        assert_eq!(app.view_mode, ViewMode::Detail);
11624        assert_eq!(app.iam_state.current_policy, None);
11625        assert_eq!(app.iam_state.policies.expanded_item, Some(0));
11626    }
11627}
11628
11629#[cfg(test)]
11630mod tab_filter_tests {
11631    use super::*;
11632    use test_helpers::*;
11633
11634    #[test]
11635    fn test_space_t_opens_tab_picker() {
11636        let mut app = test_app();
11637        app.tabs = vec![
11638            Tab {
11639                service: Service::CloudWatchLogGroups,
11640                title: "Tab 1".to_string(),
11641                breadcrumb: "CloudWatch > Log groups".to_string(),
11642            },
11643            Tab {
11644                service: Service::S3Buckets,
11645                title: "Tab 2".to_string(),
11646                breadcrumb: "S3 > Buckets".to_string(),
11647            },
11648        ];
11649        app.current_tab = 0;
11650
11651        app.handle_action(Action::OpenTabPicker);
11652
11653        assert_eq!(app.mode, Mode::TabPicker);
11654        assert_eq!(app.tab_picker_selected, 0);
11655    }
11656
11657    #[test]
11658    fn test_tab_filter_works() {
11659        let mut app = test_app();
11660        app.tabs = vec![
11661            Tab {
11662                service: Service::CloudWatchLogGroups,
11663                title: "CloudWatch Logs".to_string(),
11664                breadcrumb: "CloudWatch > Log groups".to_string(),
11665            },
11666            Tab {
11667                service: Service::S3Buckets,
11668                title: "S3 Buckets".to_string(),
11669                breadcrumb: "S3 > Buckets".to_string(),
11670            },
11671            Tab {
11672                service: Service::CloudWatchAlarms,
11673                title: "CloudWatch Alarms".to_string(),
11674                breadcrumb: "CloudWatch > Alarms".to_string(),
11675            },
11676        ];
11677        app.mode = Mode::TabPicker;
11678
11679        // Filter for "s3"
11680        app.handle_action(Action::FilterInput('s'));
11681        app.handle_action(Action::FilterInput('3'));
11682
11683        let filtered = app.get_filtered_tabs();
11684        assert_eq!(filtered.len(), 1);
11685        assert_eq!(filtered[0].1.title, "S3 Buckets");
11686    }
11687
11688    #[test]
11689    fn test_tab_filter_by_breadcrumb() {
11690        let mut app = test_app();
11691        app.tabs = vec![
11692            Tab {
11693                service: Service::CloudWatchLogGroups,
11694                title: "Tab 1".to_string(),
11695                breadcrumb: "CloudWatch > Log groups".to_string(),
11696            },
11697            Tab {
11698                service: Service::S3Buckets,
11699                title: "Tab 2".to_string(),
11700                breadcrumb: "S3 > Buckets".to_string(),
11701            },
11702        ];
11703        app.mode = Mode::TabPicker;
11704
11705        // Filter for "cloudwatch"
11706        app.handle_action(Action::FilterInput('c'));
11707        app.handle_action(Action::FilterInput('l'));
11708        app.handle_action(Action::FilterInput('o'));
11709        app.handle_action(Action::FilterInput('u'));
11710        app.handle_action(Action::FilterInput('d'));
11711
11712        let filtered = app.get_filtered_tabs();
11713        assert_eq!(filtered.len(), 1);
11714        assert_eq!(filtered[0].1.breadcrumb, "CloudWatch > Log groups");
11715    }
11716
11717    #[test]
11718    fn test_tab_filter_backspace() {
11719        let mut app = test_app();
11720        app.tabs = vec![
11721            Tab {
11722                service: Service::CloudWatchLogGroups,
11723                title: "CloudWatch Logs".to_string(),
11724                breadcrumb: "CloudWatch > Log groups".to_string(),
11725            },
11726            Tab {
11727                service: Service::S3Buckets,
11728                title: "S3 Buckets".to_string(),
11729                breadcrumb: "S3 > Buckets".to_string(),
11730            },
11731        ];
11732        app.mode = Mode::TabPicker;
11733
11734        app.handle_action(Action::FilterInput('s'));
11735        app.handle_action(Action::FilterInput('3'));
11736        assert_eq!(app.tab_filter, "s3");
11737
11738        app.handle_action(Action::FilterBackspace);
11739        assert_eq!(app.tab_filter, "s");
11740
11741        let filtered = app.get_filtered_tabs();
11742        assert_eq!(filtered.len(), 2); // Both match "s"
11743    }
11744
11745    #[test]
11746    fn test_tab_selection_with_filter() {
11747        let mut app = test_app();
11748        app.tabs = vec![
11749            Tab {
11750                service: Service::CloudWatchLogGroups,
11751                title: "CloudWatch Logs".to_string(),
11752                breadcrumb: "CloudWatch > Log groups".to_string(),
11753            },
11754            Tab {
11755                service: Service::S3Buckets,
11756                title: "S3 Buckets".to_string(),
11757                breadcrumb: "S3 > Buckets".to_string(),
11758            },
11759        ];
11760        app.mode = Mode::TabPicker;
11761        app.current_tab = 0;
11762
11763        // Filter for "s3"
11764        app.handle_action(Action::FilterInput('s'));
11765        app.handle_action(Action::FilterInput('3'));
11766
11767        // Select the filtered tab
11768        app.handle_action(Action::Select);
11769
11770        assert_eq!(app.current_tab, 1); // Should select the S3 tab (index 1)
11771        assert_eq!(app.mode, Mode::Normal);
11772        assert_eq!(app.tab_filter, ""); // Filter should be cleared
11773    }
11774}
11775
11776#[cfg(test)]
11777mod region_latency_tests {
11778    use super::*;
11779    use test_helpers::*;
11780
11781    #[test]
11782    fn test_regions_sorted_by_latency() {
11783        let mut app = test_app();
11784
11785        // Add some latencies
11786        app.region_latencies.insert("us-west-2".to_string(), 50);
11787        app.region_latencies.insert("us-east-1".to_string(), 10);
11788        app.region_latencies.insert("eu-west-1".to_string(), 100);
11789
11790        let filtered = app.get_filtered_regions();
11791
11792        // Should be sorted by latency (lowest first)
11793        let with_latency: Vec<_> = filtered.iter().filter(|r| r.latency_ms.is_some()).collect();
11794
11795        assert!(with_latency.len() >= 3);
11796        assert_eq!(with_latency[0].code, "us-east-1");
11797        assert_eq!(with_latency[0].latency_ms, Some(10));
11798        assert_eq!(with_latency[1].code, "us-west-2");
11799        assert_eq!(with_latency[1].latency_ms, Some(50));
11800        assert_eq!(with_latency[2].code, "eu-west-1");
11801        assert_eq!(with_latency[2].latency_ms, Some(100));
11802    }
11803
11804    #[test]
11805    fn test_regions_with_latency_before_without() {
11806        let mut app = test_app();
11807
11808        // Only add latency for one region
11809        app.region_latencies.insert("eu-west-1".to_string(), 100);
11810
11811        let filtered = app.get_filtered_regions();
11812
11813        // Region with latency should come first
11814        assert_eq!(filtered[0].code, "eu-west-1");
11815        assert_eq!(filtered[0].latency_ms, Some(100));
11816
11817        // Rest should be sorted by name
11818        for region in &filtered[1..] {
11819            assert!(region.latency_ms.is_none());
11820        }
11821    }
11822
11823    #[test]
11824    fn test_region_filter_with_latency() {
11825        let mut app = test_app();
11826
11827        app.region_latencies.insert("us-east-1".to_string(), 10);
11828        app.region_latencies.insert("us-west-2".to_string(), 50);
11829        app.region_filter = "us".to_string();
11830
11831        let filtered = app.get_filtered_regions();
11832
11833        // Should only have US regions, sorted by latency
11834        assert!(filtered.iter().all(|r| r.code.starts_with("us-")));
11835        assert_eq!(filtered[0].code, "us-east-1");
11836        assert_eq!(filtered[1].code, "us-west-2");
11837    }
11838
11839    #[test]
11840    fn test_latency_persists_across_filters() {
11841        let mut app = test_app();
11842
11843        app.region_latencies.insert("us-east-1".to_string(), 10);
11844
11845        // Filter to something else
11846        app.region_filter = "eu".to_string();
11847        let filtered = app.get_filtered_regions();
11848        assert!(filtered.iter().all(|r| !r.code.starts_with("us-")));
11849
11850        // Clear filter
11851        app.region_filter.clear();
11852        let all = app.get_filtered_regions();
11853
11854        // Latency should still be there
11855        let us_east = all.iter().find(|r| r.code == "us-east-1").unwrap();
11856        assert_eq!(us_east.latency_ms, Some(10));
11857    }
11858
11859    #[test]
11860    fn test_measure_region_latencies_clears_previous() {
11861        let mut app = test_app();
11862
11863        // Add some fake latencies
11864        app.region_latencies.insert("us-east-1".to_string(), 100);
11865        app.region_latencies.insert("eu-west-1".to_string(), 200);
11866
11867        // Measure again (will fail to connect but should clear)
11868        app.measure_region_latencies();
11869
11870        // Old latencies should be cleared
11871        assert!(
11872            app.region_latencies.is_empty() || !app.region_latencies.contains_key("fake-region")
11873        );
11874    }
11875
11876    #[test]
11877    fn test_regions_with_latency_sorted_first() {
11878        let mut app = test_app();
11879
11880        // Add latencies: one fast, one slow (>1000ms would be treated as >1s)
11881        app.region_latencies.insert("us-east-1".to_string(), 50);
11882        app.region_latencies.insert("eu-west-1".to_string(), 500);
11883
11884        let filtered = app.get_filtered_regions();
11885
11886        // Should show all regions
11887        assert!(filtered.len() > 2);
11888
11889        // Fast regions first
11890        assert_eq!(filtered[0].code, "us-east-1");
11891        assert_eq!(filtered[0].latency_ms, Some(50));
11892        assert_eq!(filtered[1].code, "eu-west-1");
11893        assert_eq!(filtered[1].latency_ms, Some(500));
11894
11895        // Regions without latency treated as 1000ms, so they come after 500ms
11896        for region in &filtered[2..] {
11897            assert!(region.latency_ms.is_none());
11898        }
11899    }
11900
11901    #[test]
11902    fn test_regions_without_latency_sorted_as_1000ms() {
11903        let mut app = test_app();
11904
11905        // Add one region with 1500ms (slower than default 1000ms)
11906        app.region_latencies
11907            .insert("ap-southeast-2".to_string(), 1500);
11908        // Add one region with 50ms (faster)
11909        app.region_latencies.insert("us-east-1".to_string(), 50);
11910
11911        let filtered = app.get_filtered_regions();
11912
11913        // Fast region first
11914        assert_eq!(filtered[0].code, "us-east-1");
11915        assert_eq!(filtered[0].latency_ms, Some(50));
11916
11917        // Regions without latency (treated as 1000ms) come before 1500ms
11918        let slow_region_idx = filtered
11919            .iter()
11920            .position(|r| r.code == "ap-southeast-2")
11921            .unwrap();
11922        assert!(slow_region_idx > 1); // Should be after fast region and regions without latency
11923
11924        // All regions between index 1 and slow_region_idx should have no latency
11925        for region in filtered.iter().take(slow_region_idx).skip(1) {
11926            assert!(region.latency_ms.is_none());
11927        }
11928    }
11929
11930    #[test]
11931    fn test_region_picker_opens_with_latencies() {
11932        let mut app = test_app();
11933
11934        // Simulate opening region picker
11935        app.region_filter.clear();
11936        app.region_picker_selected = 0;
11937        app.measure_region_latencies();
11938
11939        // Should have attempted to measure (even if all fail in test env)
11940        // The map should be initialized
11941        assert!(app.region_latencies.is_empty() || !app.region_latencies.is_empty());
11942    }
11943
11944    #[test]
11945    fn test_ecr_tab_next() {
11946        assert_eq!(EcrTab::Private.next(), EcrTab::Public);
11947        assert_eq!(EcrTab::Public.next(), EcrTab::Private);
11948    }
11949
11950    #[test]
11951    fn test_ecr_tab_switching() {
11952        let mut app = test_app();
11953        app.current_service = Service::EcrRepositories;
11954        app.service_selected = true;
11955        app.ecr_state.tab = EcrTab::Private;
11956
11957        app.handle_action(Action::NextDetailTab);
11958        assert_eq!(app.ecr_state.tab, EcrTab::Public);
11959        assert_eq!(app.ecr_state.repositories.selected, 0);
11960
11961        app.handle_action(Action::NextDetailTab);
11962        assert_eq!(app.ecr_state.tab, EcrTab::Private);
11963    }
11964
11965    #[test]
11966    fn test_ecr_navigation() {
11967        let mut app = test_app();
11968        app.current_service = Service::EcrRepositories;
11969        app.service_selected = true;
11970        app.mode = Mode::Normal;
11971        app.ecr_state.repositories.items = vec![
11972            EcrRepository {
11973                name: "repo1".to_string(),
11974                uri: "uri1".to_string(),
11975                created_at: "2023-01-01".to_string(),
11976                tag_immutability: "MUTABLE".to_string(),
11977                encryption_type: "AES256".to_string(),
11978            },
11979            EcrRepository {
11980                name: "repo2".to_string(),
11981                uri: "uri2".to_string(),
11982                created_at: "2023-01-02".to_string(),
11983                tag_immutability: "IMMUTABLE".to_string(),
11984                encryption_type: "KMS".to_string(),
11985            },
11986        ];
11987
11988        app.handle_action(Action::NextItem);
11989        assert_eq!(app.ecr_state.repositories.selected, 1);
11990
11991        app.handle_action(Action::PrevItem);
11992        assert_eq!(app.ecr_state.repositories.selected, 0);
11993    }
11994
11995    #[test]
11996    fn test_ecr_filter() {
11997        let mut app = test_app();
11998        app.current_service = Service::EcrRepositories;
11999        app.service_selected = true;
12000        app.ecr_state.repositories.items = vec![
12001            EcrRepository {
12002                name: "my-app".to_string(),
12003                uri: "uri1".to_string(),
12004                created_at: "2023-01-01".to_string(),
12005                tag_immutability: "MUTABLE".to_string(),
12006                encryption_type: "AES256".to_string(),
12007            },
12008            EcrRepository {
12009                name: "other-service".to_string(),
12010                uri: "uri2".to_string(),
12011                created_at: "2023-01-02".to_string(),
12012                tag_immutability: "IMMUTABLE".to_string(),
12013                encryption_type: "KMS".to_string(),
12014            },
12015        ];
12016
12017        app.ecr_state.repositories.filter = "app".to_string();
12018        let filtered = filtered_ecr_repositories(&app);
12019        assert_eq!(filtered.len(), 1);
12020        assert_eq!(filtered[0].name, "my-app");
12021    }
12022
12023    #[test]
12024    fn test_ecr_filter_input() {
12025        let mut app = test_app();
12026        app.current_service = Service::EcrRepositories;
12027        app.service_selected = true;
12028        app.mode = Mode::FilterInput;
12029
12030        app.handle_action(Action::FilterInput('t'));
12031        app.handle_action(Action::FilterInput('e'));
12032        app.handle_action(Action::FilterInput('s'));
12033        app.handle_action(Action::FilterInput('t'));
12034        assert_eq!(app.ecr_state.repositories.filter, "test");
12035
12036        app.handle_action(Action::FilterBackspace);
12037        assert_eq!(app.ecr_state.repositories.filter, "tes");
12038    }
12039
12040    #[test]
12041    fn test_ecr_filter_resets_selection() {
12042        let mut app = test_app();
12043        app.current_service = Service::EcrRepositories;
12044        app.service_selected = true;
12045        app.mode = Mode::FilterInput;
12046        app.ecr_state.repositories.items = vec![
12047            EcrRepository {
12048                name: "repo1".to_string(),
12049                uri: "uri1".to_string(),
12050                created_at: "2023-01-01".to_string(),
12051                tag_immutability: "MUTABLE".to_string(),
12052                encryption_type: "AES256".to_string(),
12053            },
12054            EcrRepository {
12055                name: "repo2".to_string(),
12056                uri: "uri2".to_string(),
12057                created_at: "2023-01-02".to_string(),
12058                tag_immutability: "IMMUTABLE".to_string(),
12059                encryption_type: "KMS".to_string(),
12060            },
12061            EcrRepository {
12062                name: "repo3".to_string(),
12063                uri: "uri3".to_string(),
12064                created_at: "2023-01-03".to_string(),
12065                tag_immutability: "MUTABLE".to_string(),
12066                encryption_type: "AES256".to_string(),
12067            },
12068        ];
12069
12070        // Move selection to second item
12071        app.ecr_state.repositories.selected = 2;
12072        assert_eq!(app.ecr_state.repositories.selected, 2);
12073
12074        // Apply filter - selection should reset to 0
12075        app.handle_action(Action::FilterInput('t'));
12076        assert_eq!(app.ecr_state.repositories.filter, "t");
12077        assert_eq!(app.ecr_state.repositories.selected, 0);
12078
12079        // Move selection again
12080        app.ecr_state.repositories.selected = 1;
12081
12082        // Backspace filter - selection should reset to 0
12083        app.handle_action(Action::FilterBackspace);
12084        assert_eq!(app.ecr_state.repositories.filter, "");
12085        assert_eq!(app.ecr_state.repositories.selected, 0);
12086    }
12087
12088    #[test]
12089    fn test_ecr_images_filter_resets_selection() {
12090        let mut app = test_app();
12091        app.current_service = Service::EcrRepositories;
12092        app.service_selected = true;
12093        app.mode = Mode::FilterInput;
12094        app.ecr_state.current_repository = Some("test-repo".to_string());
12095        app.ecr_state.images.items = vec![
12096            EcrImage {
12097                tag: "v1.0.0".to_string(),
12098                artifact_type: "container".to_string(),
12099                digest: "sha256:abc123".to_string(),
12100                pushed_at: "2023-01-01".to_string(),
12101                size_bytes: 1000,
12102                uri: "uri1".to_string(),
12103                last_pull_time: "".to_string(),
12104            },
12105            EcrImage {
12106                tag: "v2.0.0".to_string(),
12107                artifact_type: "container".to_string(),
12108                digest: "sha256:def456".to_string(),
12109                pushed_at: "2023-01-02".to_string(),
12110                size_bytes: 2000,
12111                uri: "uri2".to_string(),
12112                last_pull_time: "".to_string(),
12113            },
12114        ];
12115
12116        // Move selection to second item
12117        app.ecr_state.images.selected = 1;
12118        assert_eq!(app.ecr_state.images.selected, 1);
12119
12120        // Apply filter - selection should reset to 0
12121        app.handle_action(Action::FilterInput('v'));
12122        assert_eq!(app.ecr_state.images.filter, "v");
12123        assert_eq!(app.ecr_state.images.selected, 0);
12124    }
12125
12126    #[test]
12127    fn test_iam_users_filter_input() {
12128        let mut app = test_app();
12129        app.current_service = Service::IamUsers;
12130        app.service_selected = true;
12131        app.mode = Mode::FilterInput;
12132
12133        app.handle_action(Action::FilterInput('a'));
12134        app.handle_action(Action::FilterInput('d'));
12135        app.handle_action(Action::FilterInput('m'));
12136        app.handle_action(Action::FilterInput('i'));
12137        app.handle_action(Action::FilterInput('n'));
12138        assert_eq!(app.iam_state.users.filter, "admin");
12139
12140        app.handle_action(Action::FilterBackspace);
12141        assert_eq!(app.iam_state.users.filter, "admi");
12142    }
12143
12144    #[test]
12145    fn test_iam_policies_filter_input() {
12146        let mut app = test_app();
12147        app.current_service = Service::IamUsers;
12148        app.service_selected = true;
12149        app.iam_state.current_user = Some("testuser".to_string());
12150        app.mode = Mode::FilterInput;
12151
12152        app.handle_action(Action::FilterInput('r'));
12153        app.handle_action(Action::FilterInput('e'));
12154        app.handle_action(Action::FilterInput('a'));
12155        app.handle_action(Action::FilterInput('d'));
12156        assert_eq!(app.iam_state.policies.filter, "read");
12157
12158        app.handle_action(Action::FilterBackspace);
12159        assert_eq!(app.iam_state.policies.filter, "rea");
12160    }
12161
12162    #[test]
12163    fn test_iam_start_filter() {
12164        let mut app = test_app();
12165        app.current_service = Service::IamUsers;
12166        app.service_selected = true;
12167        app.mode = Mode::Normal;
12168
12169        app.handle_action(Action::StartFilter);
12170        assert_eq!(app.mode, Mode::FilterInput);
12171    }
12172
12173    #[test]
12174    fn test_iam_roles_filter_input() {
12175        let mut app = test_app();
12176        app.current_service = Service::IamRoles;
12177        app.service_selected = true;
12178        app.mode = Mode::FilterInput;
12179
12180        app.handle_action(Action::FilterInput('a'));
12181        app.handle_action(Action::FilterInput('d'));
12182        app.handle_action(Action::FilterInput('m'));
12183        app.handle_action(Action::FilterInput('i'));
12184        app.handle_action(Action::FilterInput('n'));
12185        assert_eq!(app.iam_state.roles.filter, "admin");
12186
12187        app.handle_action(Action::FilterBackspace);
12188        assert_eq!(app.iam_state.roles.filter, "admi");
12189    }
12190
12191    #[test]
12192    fn test_iam_roles_start_filter() {
12193        let mut app = test_app();
12194        app.current_service = Service::IamRoles;
12195        app.service_selected = true;
12196        app.mode = Mode::Normal;
12197
12198        app.handle_action(Action::StartFilter);
12199        assert_eq!(app.mode, Mode::FilterInput);
12200    }
12201
12202    #[test]
12203    fn test_iam_roles_navigation() {
12204        let mut app = test_app();
12205        app.current_service = Service::IamRoles;
12206        app.service_selected = true;
12207        app.mode = Mode::Normal;
12208        app.iam_state.roles.items = (0..10)
12209            .map(|i| IamRole {
12210                role_name: format!("role{}", i),
12211                path: "/".to_string(),
12212                trusted_entities: String::new(),
12213                last_activity: String::new(),
12214                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
12215                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
12216                description: String::new(),
12217                max_session_duration: Some(3600),
12218            })
12219            .collect();
12220
12221        assert_eq!(app.iam_state.roles.selected, 0);
12222
12223        app.handle_action(Action::NextItem);
12224        assert_eq!(app.iam_state.roles.selected, 1);
12225
12226        app.handle_action(Action::NextItem);
12227        assert_eq!(app.iam_state.roles.selected, 2);
12228
12229        app.handle_action(Action::PrevItem);
12230        assert_eq!(app.iam_state.roles.selected, 1);
12231    }
12232
12233    #[test]
12234    fn test_iam_roles_page_hotkey() {
12235        let mut app = test_app();
12236        app.current_service = Service::IamRoles;
12237        app.service_selected = true;
12238        app.mode = Mode::Normal;
12239        app.iam_state.roles.page_size = PageSize::Ten;
12240        app.iam_state.roles.items = (0..100)
12241            .map(|i| IamRole {
12242                role_name: format!("role{}", i),
12243                path: "/".to_string(),
12244                trusted_entities: String::new(),
12245                last_activity: String::new(),
12246                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
12247                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
12248                description: String::new(),
12249                max_session_duration: Some(3600),
12250            })
12251            .collect();
12252
12253        app.handle_action(Action::FilterInput('2'));
12254        app.handle_action(Action::OpenColumnSelector);
12255        assert_eq!(app.iam_state.roles.selected, 10); // Page 2 = index 10 (with page size 10)
12256    }
12257
12258    #[test]
12259    fn test_iam_users_page_hotkey() {
12260        let mut app = test_app();
12261        app.current_service = Service::IamUsers;
12262        app.service_selected = true;
12263        app.mode = Mode::Normal;
12264        app.iam_state.users.page_size = PageSize::Ten;
12265        app.iam_state.users.items = (0..100)
12266            .map(|i| IamUser {
12267                user_name: format!("user{}", i),
12268                path: "/".to_string(),
12269                groups: String::new(),
12270                last_activity: String::new(),
12271                mfa: String::new(),
12272                password_age: String::new(),
12273                console_last_sign_in: String::new(),
12274                access_key_id: String::new(),
12275                active_key_age: String::new(),
12276                access_key_last_used: String::new(),
12277                arn: format!("arn:aws:iam::123456789012:user/user{}", i),
12278                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
12279                console_access: String::new(),
12280                signing_certs: String::new(),
12281            })
12282            .collect();
12283
12284        app.handle_action(Action::FilterInput('3'));
12285        app.handle_action(Action::OpenColumnSelector);
12286        assert_eq!(app.iam_state.users.selected, 20); // Page 3 = index 20 (with page size 10)
12287    }
12288
12289    #[test]
12290    fn test_ecr_scroll_navigation() {
12291        let mut app = test_app();
12292        app.current_service = Service::EcrRepositories;
12293        app.service_selected = true;
12294        app.ecr_state.repositories.items = (0..20)
12295            .map(|i| EcrRepository {
12296                name: format!("repo{}", i),
12297                uri: format!("uri{}", i),
12298                created_at: "2023-01-01".to_string(),
12299                tag_immutability: "MUTABLE".to_string(),
12300                encryption_type: "AES256".to_string(),
12301            })
12302            .collect();
12303
12304        app.handle_action(Action::ScrollDown);
12305        assert_eq!(app.ecr_state.repositories.selected, 10);
12306
12307        app.handle_action(Action::ScrollUp);
12308        assert_eq!(app.ecr_state.repositories.selected, 0);
12309    }
12310
12311    #[test]
12312    fn test_ecr_tab_switching_triggers_reload() {
12313        let mut app = test_app();
12314        app.current_service = Service::EcrRepositories;
12315        app.service_selected = true;
12316        app.ecr_state.tab = EcrTab::Private;
12317        app.ecr_state.repositories.loading = false;
12318        app.ecr_state.repositories.items = vec![EcrRepository {
12319            name: "private-repo".to_string(),
12320            uri: "uri".to_string(),
12321            created_at: "2023-01-01".to_string(),
12322            tag_immutability: "MUTABLE".to_string(),
12323            encryption_type: "AES256".to_string(),
12324        }];
12325
12326        app.handle_action(Action::NextDetailTab);
12327        assert_eq!(app.ecr_state.tab, EcrTab::Public);
12328        assert!(app.ecr_state.repositories.loading);
12329        assert_eq!(app.ecr_state.repositories.selected, 0);
12330    }
12331
12332    #[test]
12333    fn test_ecr_tab_cycles_between_private_and_public() {
12334        let mut app = test_app();
12335        app.current_service = Service::EcrRepositories;
12336        app.service_selected = true;
12337        app.ecr_state.tab = EcrTab::Private;
12338
12339        app.handle_action(Action::NextDetailTab);
12340        assert_eq!(app.ecr_state.tab, EcrTab::Public);
12341
12342        app.handle_action(Action::NextDetailTab);
12343        assert_eq!(app.ecr_state.tab, EcrTab::Private);
12344    }
12345
12346    #[test]
12347    fn test_page_size_values() {
12348        assert_eq!(PageSize::Ten.value(), 10);
12349        assert_eq!(PageSize::TwentyFive.value(), 25);
12350        assert_eq!(PageSize::Fifty.value(), 50);
12351        assert_eq!(PageSize::OneHundred.value(), 100);
12352    }
12353
12354    #[test]
12355    fn test_page_size_next() {
12356        assert_eq!(PageSize::Ten.next(), PageSize::TwentyFive);
12357        assert_eq!(PageSize::TwentyFive.next(), PageSize::Fifty);
12358        assert_eq!(PageSize::Fifty.next(), PageSize::OneHundred);
12359        assert_eq!(PageSize::OneHundred.next(), PageSize::Ten);
12360    }
12361
12362    #[test]
12363    fn test_ecr_enter_drills_into_repository() {
12364        let mut app = test_app();
12365        app.current_service = Service::EcrRepositories;
12366        app.service_selected = true;
12367        app.mode = Mode::Normal;
12368        app.ecr_state.repositories.items = vec![EcrRepository {
12369            name: "my-repo".to_string(),
12370            uri: "uri".to_string(),
12371            created_at: "2023-01-01".to_string(),
12372            tag_immutability: "MUTABLE".to_string(),
12373            encryption_type: "AES256".to_string(),
12374        }];
12375
12376        app.handle_action(Action::Select);
12377        assert_eq!(
12378            app.ecr_state.current_repository,
12379            Some("my-repo".to_string())
12380        );
12381        assert!(app.ecr_state.repositories.loading);
12382    }
12383
12384    #[test]
12385    fn test_ecr_repository_expansion() {
12386        let mut app = test_app();
12387        app.current_service = Service::EcrRepositories;
12388        app.service_selected = true;
12389        app.ecr_state.repositories.items = vec![EcrRepository {
12390            name: "my-repo".to_string(),
12391            uri: "uri".to_string(),
12392            created_at: "2023-01-01".to_string(),
12393            tag_immutability: "MUTABLE".to_string(),
12394            encryption_type: "AES256".to_string(),
12395        }];
12396        app.ecr_state.repositories.selected = 0;
12397
12398        assert_eq!(app.ecr_state.repositories.expanded_item, None);
12399
12400        app.handle_action(Action::NextPane);
12401        assert_eq!(app.ecr_state.repositories.expanded_item, Some(0));
12402
12403        app.handle_action(Action::PrevPane);
12404        assert_eq!(app.ecr_state.repositories.expanded_item, None);
12405    }
12406
12407    #[test]
12408    fn test_ecr_ctrl_d_scrolls_down() {
12409        let mut app = test_app();
12410        app.current_service = Service::EcrRepositories;
12411        app.service_selected = true;
12412        app.mode = Mode::Normal;
12413        app.ecr_state.repositories.items = (0..30)
12414            .map(|i| EcrRepository {
12415                name: format!("repo{}", i),
12416                uri: format!("uri{}", i),
12417                created_at: "2023-01-01".to_string(),
12418                tag_immutability: "MUTABLE".to_string(),
12419                encryption_type: "AES256".to_string(),
12420            })
12421            .collect();
12422        app.ecr_state.repositories.selected = 0;
12423
12424        app.handle_action(Action::PageDown);
12425        assert_eq!(app.ecr_state.repositories.selected, 10);
12426    }
12427
12428    #[test]
12429    fn test_ecr_ctrl_u_scrolls_up() {
12430        let mut app = test_app();
12431        app.current_service = Service::EcrRepositories;
12432        app.service_selected = true;
12433        app.mode = Mode::Normal;
12434        app.ecr_state.repositories.items = (0..30)
12435            .map(|i| EcrRepository {
12436                name: format!("repo{}", i),
12437                uri: format!("uri{}", i),
12438                created_at: "2023-01-01".to_string(),
12439                tag_immutability: "MUTABLE".to_string(),
12440                encryption_type: "AES256".to_string(),
12441            })
12442            .collect();
12443        app.ecr_state.repositories.selected = 15;
12444
12445        app.handle_action(Action::PageUp);
12446        assert_eq!(app.ecr_state.repositories.selected, 5);
12447    }
12448
12449    #[test]
12450    fn test_ecr_images_ctrl_d_scrolls_down() {
12451        let mut app = test_app();
12452        app.current_service = Service::EcrRepositories;
12453        app.service_selected = true;
12454        app.mode = Mode::Normal;
12455        app.ecr_state.current_repository = Some("repo".to_string());
12456        app.ecr_state.images.items = (0..30)
12457            .map(|i| EcrImage {
12458                tag: format!("tag{}", i),
12459                artifact_type: "container".to_string(),
12460                pushed_at: "2023-01-01T12:00:00Z".to_string(),
12461                size_bytes: 104857600,
12462                uri: format!("uri{}", i),
12463                digest: format!("sha256:{}", i),
12464                last_pull_time: String::new(),
12465            })
12466            .collect();
12467        app.ecr_state.images.selected = 0;
12468
12469        app.handle_action(Action::PageDown);
12470        assert_eq!(app.ecr_state.images.selected, 10);
12471    }
12472
12473    #[test]
12474    fn test_ecr_esc_goes_back_from_images_to_repos() {
12475        let mut app = test_app();
12476        app.current_service = Service::EcrRepositories;
12477        app.service_selected = true;
12478        app.mode = Mode::Normal;
12479        app.ecr_state.current_repository = Some("my-repo".to_string());
12480        app.ecr_state.images.items = vec![EcrImage {
12481            tag: "latest".to_string(),
12482            artifact_type: "container".to_string(),
12483            pushed_at: "2023-01-01T12:00:00Z".to_string(),
12484            size_bytes: 104857600,
12485            uri: "uri".to_string(),
12486            digest: "sha256:abc".to_string(),
12487            last_pull_time: String::new(),
12488        }];
12489
12490        app.handle_action(Action::GoBack);
12491        assert_eq!(app.ecr_state.current_repository, None);
12492        assert!(app.ecr_state.images.items.is_empty());
12493    }
12494
12495    #[test]
12496    fn test_ecr_esc_collapses_expanded_image_first() {
12497        let mut app = test_app();
12498        app.current_service = Service::EcrRepositories;
12499        app.service_selected = true;
12500        app.mode = Mode::Normal;
12501        app.ecr_state.current_repository = Some("my-repo".to_string());
12502        app.ecr_state.images.expanded_item = Some(0);
12503
12504        app.handle_action(Action::GoBack);
12505        assert_eq!(app.ecr_state.images.expanded_item, None);
12506        assert_eq!(
12507            app.ecr_state.current_repository,
12508            Some("my-repo".to_string())
12509        );
12510    }
12511
12512    #[test]
12513    fn test_pagination_with_lowercase_p() {
12514        let mut app = test_app();
12515        app.current_service = Service::EcrRepositories;
12516        app.service_selected = true;
12517        app.mode = Mode::Normal;
12518        app.ecr_state.repositories.items = (0..100)
12519            .map(|i| EcrRepository {
12520                name: format!("repo{}", i),
12521                uri: format!("uri{}", i),
12522                created_at: "2023-01-01".to_string(),
12523                tag_immutability: "MUTABLE".to_string(),
12524                encryption_type: "AES256".to_string(),
12525            })
12526            .collect();
12527
12528        // Type "2" then "p" to go to page 2
12529        app.handle_action(Action::FilterInput('2'));
12530        assert_eq!(app.page_input, "2");
12531
12532        app.handle_action(Action::OpenColumnSelector); // 'p' key
12533        assert_eq!(app.ecr_state.repositories.selected, 50); // Page 2 starts at index 50
12534        assert_eq!(app.page_input, ""); // Should be cleared
12535    }
12536
12537    #[test]
12538    fn test_lowercase_p_without_number_opens_preferences() {
12539        let mut app = test_app();
12540        app.current_service = Service::EcrRepositories;
12541        app.service_selected = true;
12542        app.mode = Mode::Normal;
12543
12544        app.handle_action(Action::OpenColumnSelector); // 'p' key without number
12545        assert_eq!(app.mode, Mode::ColumnSelector);
12546    }
12547
12548    #[test]
12549    fn test_ctrl_o_generates_correct_console_url() {
12550        let mut app = test_app();
12551        app.current_service = Service::EcrRepositories;
12552        app.service_selected = true;
12553        app.mode = Mode::Normal;
12554        app.config.account_id = "123456789012".to_string();
12555
12556        // Test repository list URL
12557        let url = app.get_console_url();
12558        assert!(url.contains("ecr/private-registry/repositories"));
12559        assert!(url.contains("region=us-east-1"));
12560
12561        // Test images URL
12562        app.ecr_state.current_repository = Some("my-repo".to_string());
12563        let url = app.get_console_url();
12564        assert!(url.contains("ecr/repositories/private/123456789012/my-repo"));
12565        assert!(url.contains("region=us-east-1"));
12566    }
12567
12568    #[test]
12569    fn test_page_input_display_and_reset() {
12570        let mut app = test_app();
12571        app.current_service = Service::EcrRepositories;
12572        app.service_selected = true;
12573        app.mode = Mode::Normal;
12574        app.ecr_state.repositories.items = (0..100)
12575            .map(|i| EcrRepository {
12576                name: format!("repo{}", i),
12577                uri: format!("uri{}", i),
12578                created_at: "2023-01-01".to_string(),
12579                tag_immutability: "MUTABLE".to_string(),
12580                encryption_type: "AES256".to_string(),
12581            })
12582            .collect();
12583
12584        // Type "2"
12585        app.handle_action(Action::FilterInput('2'));
12586        assert_eq!(app.page_input, "2");
12587
12588        // Press 'p' to go to page 2
12589        app.handle_action(Action::OpenColumnSelector);
12590        assert_eq!(app.page_input, ""); // Should be cleared
12591        assert_eq!(app.ecr_state.repositories.selected, 50); // Page 2 starts at index 50
12592    }
12593
12594    #[test]
12595    fn test_page_navigation_updates_scroll_offset_for_cfn() {
12596        let mut app = test_app();
12597        app.current_service = Service::CloudFormationStacks;
12598        app.service_selected = true;
12599        app.mode = Mode::Normal;
12600        app.cfn_state.table.items = (0..100)
12601            .map(|i| CfnStack {
12602                name: format!("stack-{}", i),
12603                stack_id: format!(
12604                    "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-{}/id",
12605                    i
12606                ),
12607                status: "CREATE_COMPLETE".to_string(),
12608                created_time: "2023-01-01T00:00:00Z".to_string(),
12609                updated_time: "2023-01-01T00:00:00Z".to_string(),
12610                deleted_time: String::new(),
12611                drift_status: "IN_SYNC".to_string(),
12612                last_drift_check_time: String::new(),
12613                status_reason: String::new(),
12614                description: String::new(),
12615                detailed_status: String::new(),
12616                root_stack: String::new(),
12617                parent_stack: String::new(),
12618                termination_protection: false,
12619                iam_role: String::new(),
12620                tags: vec![],
12621                stack_policy: String::new(),
12622                rollback_monitoring_time: String::new(),
12623                rollback_alarms: vec![],
12624                notification_arns: vec![],
12625            })
12626            .collect();
12627
12628        // Type "2" then "p" to go to page 2
12629        app.handle_action(Action::FilterInput('2'));
12630        assert_eq!(app.page_input, "2");
12631
12632        app.handle_action(Action::OpenColumnSelector); // 'p' key
12633        assert_eq!(app.page_input, ""); // Should be cleared
12634
12635        // Verify both selected and scroll_offset are updated
12636        let page_size = app.cfn_state.table.page_size.value();
12637        let expected_offset = page_size; // Page 2 starts at page_size
12638        assert_eq!(app.cfn_state.table.selected, expected_offset);
12639        assert_eq!(app.cfn_state.table.scroll_offset, expected_offset);
12640
12641        // Verify pagination display shows page 2
12642        let current_page = app.cfn_state.table.scroll_offset / page_size;
12643        assert_eq!(
12644            current_page, 1,
12645            "2p should go to page 2 (0-indexed as 1), not page 3"
12646        ); // 0-indexed, so page 2 is index 1
12647    }
12648
12649    #[test]
12650    fn test_3p_goes_to_page_3_not_page_5() {
12651        let mut app = test_app();
12652        app.current_service = Service::CloudFormationStacks;
12653        app.service_selected = true;
12654        app.mode = Mode::Normal;
12655        app.cfn_state.table.items = (0..200)
12656            .map(|i| CfnStack {
12657                name: format!("stack-{}", i),
12658                stack_id: format!(
12659                    "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-{}/id",
12660                    i
12661                ),
12662                status: "CREATE_COMPLETE".to_string(),
12663                created_time: "2023-01-01T00:00:00Z".to_string(),
12664                updated_time: "2023-01-01T00:00:00Z".to_string(),
12665                deleted_time: String::new(),
12666                drift_status: "IN_SYNC".to_string(),
12667                last_drift_check_time: String::new(),
12668                status_reason: String::new(),
12669                description: String::new(),
12670                detailed_status: String::new(),
12671                root_stack: String::new(),
12672                parent_stack: String::new(),
12673                termination_protection: false,
12674                iam_role: String::new(),
12675                tags: vec![],
12676                stack_policy: String::new(),
12677                rollback_monitoring_time: String::new(),
12678                rollback_alarms: vec![],
12679                notification_arns: vec![],
12680            })
12681            .collect();
12682
12683        // Type "3" then "p" to go to page 3
12684        app.handle_action(Action::FilterInput('3'));
12685        app.handle_action(Action::OpenColumnSelector);
12686
12687        let page_size = app.cfn_state.table.page_size.value();
12688        let current_page = app.cfn_state.table.scroll_offset / page_size;
12689        assert_eq!(
12690            current_page, 2,
12691            "3p should go to page 3 (0-indexed as 2), not page 5"
12692        );
12693        assert_eq!(app.cfn_state.table.scroll_offset, 2 * page_size);
12694    }
12695
12696    #[test]
12697    fn test_log_streams_page_navigation_uses_correct_page_size() {
12698        let mut app = test_app();
12699        app.current_service = Service::CloudWatchLogGroups;
12700        app.view_mode = ViewMode::Detail;
12701        app.service_selected = true;
12702        app.mode = Mode::Normal;
12703        app.log_groups_state.log_streams = (0..100)
12704            .map(|i| LogStream {
12705                name: format!("stream-{}", i),
12706                creation_time: None,
12707                last_event_time: None,
12708            })
12709            .collect();
12710
12711        // Type "2" then "p" to go to page 2
12712        app.handle_action(Action::FilterInput('2'));
12713        app.handle_action(Action::OpenColumnSelector);
12714
12715        // Should go to page 2 (page index 1)
12716        assert_eq!(app.log_groups_state.stream_current_page, 1);
12717        assert_eq!(app.log_groups_state.selected_stream, 0);
12718
12719        // Verify pagination display shows page 2 (not page 3)
12720        assert_eq!(
12721            app.log_groups_state.stream_current_page, 1,
12722            "2p should go to page 2 (0-indexed as 1), not page 3"
12723        );
12724    }
12725
12726    #[test]
12727    fn test_ecr_repositories_page_navigation_uses_configurable_page_size() {
12728        let mut app = test_app();
12729        app.current_service = Service::EcrRepositories;
12730        app.service_selected = true;
12731        app.mode = Mode::Normal;
12732        app.ecr_state.repositories.page_size = PageSize::TwentyFive; // Set to 25
12733        app.ecr_state.repositories.items = (0..100)
12734            .map(|i| EcrRepository {
12735                name: format!("repo{}", i),
12736                uri: format!("uri{}", i),
12737                created_at: "2023-01-01".to_string(),
12738                tag_immutability: "MUTABLE".to_string(),
12739                encryption_type: "AES256".to_string(),
12740            })
12741            .collect();
12742
12743        // Type "3" then "p" to go to page 3
12744        app.handle_action(Action::FilterInput('3'));
12745        app.handle_action(Action::OpenColumnSelector);
12746
12747        // With page_size=25, page 3 starts at index 50
12748        assert_eq!(app.ecr_state.repositories.selected, 50);
12749
12750        let page_size = app.ecr_state.repositories.page_size.value();
12751        let current_page = app.ecr_state.repositories.selected / page_size;
12752        assert_eq!(
12753            current_page, 2,
12754            "3p with page_size=25 should go to page 3 (0-indexed as 2)"
12755        );
12756    }
12757
12758    #[test]
12759    fn test_page_navigation_updates_scroll_offset_for_alarms() {
12760        let mut app = test_app();
12761        app.current_service = Service::CloudWatchAlarms;
12762        app.service_selected = true;
12763        app.mode = Mode::Normal;
12764        app.alarms_state.table.items = (0..100)
12765            .map(|i| Alarm {
12766                name: format!("alarm-{}", i),
12767                state: "OK".to_string(),
12768                state_updated_timestamp: "2023-01-01T00:00:00Z".to_string(),
12769                description: String::new(),
12770                metric_name: "CPUUtilization".to_string(),
12771                namespace: "AWS/EC2".to_string(),
12772                statistic: "Average".to_string(),
12773                period: 300,
12774                comparison_operator: "GreaterThanThreshold".to_string(),
12775                threshold: 80.0,
12776                actions_enabled: true,
12777                state_reason: String::new(),
12778                resource: String::new(),
12779                dimensions: String::new(),
12780                expression: String::new(),
12781                alarm_type: "MetricAlarm".to_string(),
12782                cross_account: String::new(),
12783            })
12784            .collect();
12785
12786        // Type "2" then "p" to go to page 2
12787        app.handle_action(Action::FilterInput('2'));
12788        app.handle_action(Action::OpenColumnSelector);
12789
12790        // Verify both selected and scroll_offset are updated
12791        let page_size = app.alarms_state.table.page_size.value();
12792        let expected_offset = page_size; // Page 2 starts at page_size
12793        assert_eq!(app.alarms_state.table.selected, expected_offset);
12794        assert_eq!(app.alarms_state.table.scroll_offset, expected_offset);
12795    }
12796
12797    #[test]
12798    fn test_ecr_pagination_with_65_repos() {
12799        let mut app = test_app();
12800        app.current_service = Service::EcrRepositories;
12801        app.service_selected = true;
12802        app.mode = Mode::Normal;
12803        app.ecr_state.repositories.items = (0..65)
12804            .map(|i| EcrRepository {
12805                name: format!("repo{:02}", i),
12806                uri: format!("uri{}", i),
12807                created_at: "2023-01-01".to_string(),
12808                tag_immutability: "MUTABLE".to_string(),
12809                encryption_type: "AES256".to_string(),
12810            })
12811            .collect();
12812
12813        // Page 1: items 0-49 (50 items)
12814        assert_eq!(app.ecr_state.repositories.selected, 0);
12815        let page_size = 50;
12816        let current_page = app.ecr_state.repositories.selected / page_size;
12817        assert_eq!(current_page, 0);
12818
12819        // Go to page 2
12820        app.handle_action(Action::FilterInput('2'));
12821        app.handle_action(Action::OpenColumnSelector);
12822        assert_eq!(app.ecr_state.repositories.selected, 50);
12823
12824        // Page 2: items 50-64 (15 items)
12825        let current_page = app.ecr_state.repositories.selected / page_size;
12826        assert_eq!(current_page, 1);
12827    }
12828
12829    #[test]
12830    fn test_ecr_repos_input_focus_tab_cycling() {
12831        let mut app = test_app();
12832        app.current_service = Service::EcrRepositories;
12833        app.service_selected = true;
12834        app.mode = Mode::FilterInput;
12835        app.ecr_state.input_focus = InputFocus::Filter;
12836
12837        // Tab should cycle to Pagination
12838        app.handle_action(Action::NextFilterFocus);
12839        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
12840
12841        // Tab again should cycle back to Input
12842        app.handle_action(Action::NextFilterFocus);
12843        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
12844
12845        // Shift+Tab should cycle backwards to Pagination
12846        app.handle_action(Action::PrevFilterFocus);
12847        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
12848
12849        // Shift+Tab again should cycle back to Input
12850        app.handle_action(Action::PrevFilterFocus);
12851        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
12852    }
12853
12854    #[test]
12855    fn test_ecr_images_column_toggle_not_off_by_one() {
12856        use crate::ecr::image::Column as ImageColumn;
12857        let mut app = test_app();
12858        app.current_service = Service::EcrRepositories;
12859        app.service_selected = true;
12860        app.mode = Mode::ColumnSelector;
12861        app.ecr_state.current_repository = Some("test-repo".to_string());
12862
12863        // Start with all columns visible
12864        app.ecr_image_visible_column_ids = ImageColumn::ids();
12865        let initial_count = app.ecr_image_visible_column_ids.len();
12866
12867        // Select first column (index 0) and toggle it
12868        app.column_selector_index = 0;
12869        app.handle_action(Action::ToggleColumn);
12870
12871        // First column should be removed
12872        assert_eq!(app.ecr_image_visible_column_ids.len(), initial_count - 1);
12873        assert!(!app
12874            .ecr_image_visible_column_ids
12875            .contains(&ImageColumn::Tag.id()));
12876
12877        // Toggle it back
12878        app.handle_action(Action::ToggleColumn);
12879        assert_eq!(app.ecr_image_visible_column_ids.len(), initial_count);
12880        assert!(app
12881            .ecr_image_visible_column_ids
12882            .contains(&ImageColumn::Tag.id()));
12883    }
12884
12885    #[test]
12886    fn test_ecr_repos_column_toggle_works() {
12887        let mut app = test_app();
12888        app.current_service = Service::EcrRepositories;
12889        app.service_selected = true;
12890        app.mode = Mode::ColumnSelector;
12891        app.ecr_state.current_repository = None;
12892
12893        // Start with all columns visible
12894        app.ecr_repo_visible_column_ids = EcrColumn::ids();
12895        let initial_count = app.ecr_repo_visible_column_ids.len();
12896
12897        // Select first column (index 1, since 0 is header) and toggle it
12898        app.column_selector_index = 1;
12899        app.handle_action(Action::ToggleColumn);
12900
12901        // First column should be removed
12902        assert_eq!(app.ecr_repo_visible_column_ids.len(), initial_count - 1);
12903        assert!(!app
12904            .ecr_repo_visible_column_ids
12905            .contains(&EcrColumn::Name.id()));
12906
12907        // Toggle it back
12908        app.handle_action(Action::ToggleColumn);
12909        assert_eq!(app.ecr_repo_visible_column_ids.len(), initial_count);
12910        assert!(app
12911            .ecr_repo_visible_column_ids
12912            .contains(&EcrColumn::Name.id()));
12913    }
12914
12915    #[test]
12916    fn test_ecr_repos_pagination_left_right_navigation() {
12917        use crate::ecr::repo::Repository as EcrRepository;
12918        let mut app = test_app();
12919        app.current_service = Service::EcrRepositories;
12920        app.service_selected = true;
12921        app.mode = Mode::FilterInput;
12922        app.ecr_state.input_focus = InputFocus::Pagination;
12923
12924        // Create 150 repos (3 pages with page size 50)
12925        app.ecr_state.repositories.items = (0..150)
12926            .map(|i| EcrRepository {
12927                name: format!("repo{:03}", i),
12928                uri: format!("uri{}", i),
12929                created_at: "2023-01-01".to_string(),
12930                tag_immutability: "MUTABLE".to_string(),
12931                encryption_type: "AES256".to_string(),
12932            })
12933            .collect();
12934
12935        // Start on page 1 (index 0)
12936        app.ecr_state.repositories.selected = 0;
12937        eprintln!(
12938            "Initial: selected={}, focus={:?}, mode={:?}",
12939            app.ecr_state.repositories.selected, app.ecr_state.input_focus, app.mode
12940        );
12941
12942        // Right arrow (PageDown) should go to page 2
12943        app.handle_action(Action::PageDown);
12944        eprintln!(
12945            "After PageDown: selected={}",
12946            app.ecr_state.repositories.selected
12947        );
12948        assert_eq!(app.ecr_state.repositories.selected, 50);
12949
12950        // Right arrow again should go to page 3
12951        app.handle_action(Action::PageDown);
12952        eprintln!(
12953            "After 2nd PageDown: selected={}",
12954            app.ecr_state.repositories.selected
12955        );
12956        assert_eq!(app.ecr_state.repositories.selected, 100);
12957
12958        // Right arrow at last page should stay at last page
12959        app.handle_action(Action::PageDown);
12960        eprintln!(
12961            "After 3rd PageDown: selected={}",
12962            app.ecr_state.repositories.selected
12963        );
12964        assert_eq!(app.ecr_state.repositories.selected, 100);
12965
12966        // Left arrow (PageUp) should go back to page 2
12967        app.handle_action(Action::PageUp);
12968        eprintln!(
12969            "After PageUp: selected={}",
12970            app.ecr_state.repositories.selected
12971        );
12972        assert_eq!(app.ecr_state.repositories.selected, 50);
12973
12974        // Left arrow again should go to page 1
12975        app.handle_action(Action::PageUp);
12976        eprintln!(
12977            "After 2nd PageUp: selected={}",
12978            app.ecr_state.repositories.selected
12979        );
12980        assert_eq!(app.ecr_state.repositories.selected, 0);
12981
12982        // Left arrow at first page should stay at first page
12983        app.handle_action(Action::PageUp);
12984        eprintln!(
12985            "After 3rd PageUp: selected={}",
12986            app.ecr_state.repositories.selected
12987        );
12988        assert_eq!(app.ecr_state.repositories.selected, 0);
12989    }
12990
12991    #[test]
12992    fn test_ecr_repos_filter_input_when_input_focused() {
12993        use crate::ecr::repo::Repository as EcrRepository;
12994        let mut app = test_app();
12995        app.current_service = Service::EcrRepositories;
12996        app.service_selected = true;
12997        app.mode = Mode::FilterInput;
12998        app.ecr_state.input_focus = InputFocus::Filter;
12999
13000        // Create some repos
13001        app.ecr_state.repositories.items = vec![
13002            EcrRepository {
13003                name: "test-repo".to_string(),
13004                uri: "uri1".to_string(),
13005                created_at: "2023-01-01".to_string(),
13006                tag_immutability: "MUTABLE".to_string(),
13007                encryption_type: "AES256".to_string(),
13008            },
13009            EcrRepository {
13010                name: "prod-repo".to_string(),
13011                uri: "uri2".to_string(),
13012                created_at: "2023-01-01".to_string(),
13013                tag_immutability: "MUTABLE".to_string(),
13014                encryption_type: "AES256".to_string(),
13015            },
13016        ];
13017
13018        // When input is focused, typing should add to filter
13019        assert_eq!(app.ecr_state.repositories.filter, "");
13020        app.handle_action(Action::FilterInput('t'));
13021        assert_eq!(app.ecr_state.repositories.filter, "t");
13022        app.handle_action(Action::FilterInput('e'));
13023        assert_eq!(app.ecr_state.repositories.filter, "te");
13024        app.handle_action(Action::FilterInput('s'));
13025        assert_eq!(app.ecr_state.repositories.filter, "tes");
13026        app.handle_action(Action::FilterInput('t'));
13027        assert_eq!(app.ecr_state.repositories.filter, "test");
13028    }
13029
13030    #[test]
13031    fn test_ecr_repos_digit_input_when_pagination_focused() {
13032        use crate::ecr::repo::Repository as EcrRepository;
13033        let mut app = test_app();
13034        app.current_service = Service::EcrRepositories;
13035        app.service_selected = true;
13036        app.mode = Mode::FilterInput;
13037        app.ecr_state.input_focus = InputFocus::Pagination;
13038
13039        // Create some repos
13040        app.ecr_state.repositories.items = vec![EcrRepository {
13041            name: "test-repo".to_string(),
13042            uri: "uri1".to_string(),
13043            created_at: "2023-01-01".to_string(),
13044            tag_immutability: "MUTABLE".to_string(),
13045            encryption_type: "AES256".to_string(),
13046        }];
13047
13048        // When pagination is focused, digits should go to page_input, not filter
13049        assert_eq!(app.ecr_state.repositories.filter, "");
13050        assert_eq!(app.page_input, "");
13051        app.handle_action(Action::FilterInput('2'));
13052        assert_eq!(app.ecr_state.repositories.filter, "");
13053        assert_eq!(app.page_input, "2");
13054
13055        // Non-digits should not be added to either
13056        app.handle_action(Action::FilterInput('a'));
13057        assert_eq!(app.ecr_state.repositories.filter, "");
13058        assert_eq!(app.page_input, "2");
13059    }
13060
13061    #[test]
13062    fn test_ecr_repos_left_right_scrolls_table_when_input_focused() {
13063        use crate::ecr::repo::Repository as EcrRepository;
13064        let mut app = test_app();
13065        app.current_service = Service::EcrRepositories;
13066        app.service_selected = true;
13067        app.mode = Mode::FilterInput;
13068        app.ecr_state.input_focus = InputFocus::Filter;
13069
13070        // Create 150 repos (3 pages)
13071        app.ecr_state.repositories.items = (0..150)
13072            .map(|i| EcrRepository {
13073                name: format!("repo{:03}", i),
13074                uri: format!("uri{}", i),
13075                created_at: "2023-01-01".to_string(),
13076                tag_immutability: "MUTABLE".to_string(),
13077                encryption_type: "AES256".to_string(),
13078            })
13079            .collect();
13080
13081        // Start on page 1
13082        app.ecr_state.repositories.selected = 0;
13083
13084        // When input is focused, left/right should scroll table (not change pages)
13085        app.handle_action(Action::PageDown);
13086        assert_eq!(
13087            app.ecr_state.repositories.selected, 10,
13088            "Should scroll down by 10"
13089        );
13090
13091        app.handle_action(Action::PageUp);
13092        assert_eq!(
13093            app.ecr_state.repositories.selected, 0,
13094            "Should scroll back up"
13095        );
13096    }
13097
13098    #[test]
13099    fn test_ecr_repos_pagination_control_actually_works() {
13100        use crate::ecr::repo::Repository as EcrRepository;
13101
13102        // Test that verifies the exact conditions needed for pagination to work
13103        let mut app = test_app();
13104        app.current_service = Service::EcrRepositories;
13105        app.service_selected = true;
13106        app.mode = Mode::FilterInput;
13107        app.ecr_state.current_repository = None;
13108        app.ecr_state.input_focus = InputFocus::Pagination;
13109
13110        // Create 100 repos (2 pages with page size 50)
13111        app.ecr_state.repositories.items = (0..100)
13112            .map(|i| EcrRepository {
13113                name: format!("repo{:03}", i),
13114                uri: format!("uri{}", i),
13115                created_at: "2023-01-01".to_string(),
13116                tag_immutability: "MUTABLE".to_string(),
13117                encryption_type: "AES256".to_string(),
13118            })
13119            .collect();
13120
13121        app.ecr_state.repositories.selected = 0;
13122
13123        // Verify all conditions are met
13124        assert_eq!(app.mode, Mode::FilterInput);
13125        assert_eq!(app.current_service, Service::EcrRepositories);
13126        assert_eq!(app.ecr_state.current_repository, None);
13127        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
13128
13129        // Now test pagination
13130        app.handle_action(Action::PageDown);
13131        assert_eq!(
13132            app.ecr_state.repositories.selected, 50,
13133            "PageDown should move to page 2"
13134        );
13135
13136        app.handle_action(Action::PageUp);
13137        assert_eq!(
13138            app.ecr_state.repositories.selected, 0,
13139            "PageUp should move back to page 1"
13140        );
13141    }
13142
13143    #[test]
13144    fn test_ecr_repos_start_filter_resets_focus_to_input() {
13145        let mut app = test_app();
13146        app.current_service = Service::EcrRepositories;
13147        app.service_selected = true;
13148        app.mode = Mode::Normal;
13149        app.ecr_state.current_repository = None;
13150
13151        // Set focus to Pagination
13152        app.ecr_state.input_focus = InputFocus::Pagination;
13153
13154        // Start filter mode
13155        app.handle_action(Action::StartFilter);
13156
13157        // Should reset to Input focus
13158        assert_eq!(app.mode, Mode::FilterInput);
13159        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
13160    }
13161
13162    #[test]
13163    fn test_ecr_repos_exact_user_flow_i_tab_arrow() {
13164        use crate::ecr::repo::Repository as EcrRepository;
13165
13166        let mut app = test_app();
13167        app.current_service = Service::EcrRepositories;
13168        app.service_selected = true;
13169        app.mode = Mode::Normal;
13170        app.ecr_state.current_repository = None;
13171
13172        // Create 100 repos (2 pages)
13173        app.ecr_state.repositories.items = (0..100)
13174            .map(|i| EcrRepository {
13175                name: format!("repo{:03}", i),
13176                uri: format!("uri{}", i),
13177                created_at: "2023-01-01".to_string(),
13178                tag_immutability: "MUTABLE".to_string(),
13179                encryption_type: "AES256".to_string(),
13180            })
13181            .collect();
13182
13183        app.ecr_state.repositories.selected = 0;
13184
13185        // User presses 'i' to enter filter mode
13186        app.handle_action(Action::StartFilter);
13187        assert_eq!(app.mode, Mode::FilterInput);
13188        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
13189
13190        // User presses Tab to switch to pagination
13191        app.handle_action(Action::NextFilterFocus);
13192        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
13193
13194        // User presses right arrow (PageDown)
13195        eprintln!("Before PageDown: mode={:?}, service={:?}, current_repo={:?}, input_focus={:?}, selected={}",
13196            app.mode, app.current_service, app.ecr_state.current_repository, app.ecr_state.input_focus, app.ecr_state.repositories.selected);
13197        app.handle_action(Action::PageDown);
13198        eprintln!(
13199            "After PageDown: selected={}",
13200            app.ecr_state.repositories.selected
13201        );
13202
13203        // Should move to page 2
13204        assert_eq!(
13205            app.ecr_state.repositories.selected, 50,
13206            "Right arrow should move to page 2"
13207        );
13208
13209        // User presses left arrow (PageUp)
13210        app.handle_action(Action::PageUp);
13211        assert_eq!(
13212            app.ecr_state.repositories.selected, 0,
13213            "Left arrow should move back to page 1"
13214        );
13215    }
13216
13217    #[test]
13218    fn test_service_picker_i_key_activates_filter() {
13219        let mut app = test_app();
13220
13221        // Start in ServicePicker mode (service picker)
13222        assert_eq!(app.mode, Mode::ServicePicker);
13223        assert!(app.service_picker.filter.is_empty());
13224
13225        // Press 'i' to start filtering
13226        app.handle_action(Action::FilterInput('i'));
13227
13228        // Should still be in ServicePicker mode and filter should have 'i'
13229        assert_eq!(app.mode, Mode::ServicePicker);
13230        assert_eq!(app.service_picker.filter, "i");
13231    }
13232
13233    #[test]
13234    fn test_service_picker_typing_filters_services() {
13235        let mut app = test_app();
13236
13237        // Start in ServicePicker mode
13238        assert_eq!(app.mode, Mode::ServicePicker);
13239
13240        // Type "s3" to filter
13241        app.handle_action(Action::FilterInput('s'));
13242        app.handle_action(Action::FilterInput('3'));
13243
13244        assert_eq!(app.service_picker.filter, "s3");
13245        assert_eq!(app.mode, Mode::ServicePicker);
13246    }
13247
13248    #[test]
13249    fn test_service_picker_resets_on_open() {
13250        let mut app = test_app();
13251
13252        // Select a service to get into Normal mode
13253        app.service_selected = true;
13254        app.mode = Mode::Normal;
13255
13256        // Simulate having previous filter and selection
13257        app.service_picker.filter = "previous".to_string();
13258        app.service_picker.selected = 5;
13259
13260        // Open space menu (service picker)
13261        app.handle_action(Action::OpenSpaceMenu);
13262
13263        // Filter and selection should be reset
13264        assert_eq!(app.mode, Mode::SpaceMenu);
13265        assert!(app.service_picker.filter.is_empty());
13266        assert_eq!(app.service_picker.selected, 0);
13267    }
13268
13269    #[test]
13270    fn test_no_pii_in_test_data() {
13271        // Ensure test data uses placeholder account IDs, not real ones
13272        let test_repo = EcrRepository {
13273            name: "test-repo".to_string(),
13274            uri: "123456789012.dkr.ecr.us-east-1.amazonaws.com/test-repo".to_string(),
13275            created_at: "2024-01-01".to_string(),
13276            tag_immutability: "MUTABLE".to_string(),
13277            encryption_type: "AES256".to_string(),
13278        };
13279
13280        // Verify placeholder account ID is used
13281        assert!(test_repo.uri.starts_with("123456789012"));
13282        assert!(!test_repo.uri.contains("123456789013")); // Not a real account
13283    }
13284
13285    #[test]
13286    fn test_lambda_versions_tab_triggers_loading() {
13287        let mut app = test_app();
13288        app.current_service = Service::LambdaFunctions;
13289        app.service_selected = true;
13290
13291        // Simulate selecting a function
13292        app.lambda_state.current_function = Some("test-function".to_string());
13293        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13294
13295        // Initially no versions
13296        assert!(app.lambda_state.version_table.items.is_empty());
13297
13298        // Switch to Versions tab
13299        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13300
13301        // The main loop should detect this change and load versions
13302        // We verify the state is set up correctly for loading
13303        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
13304        assert!(app.lambda_state.current_function.is_some());
13305    }
13306
13307    #[test]
13308    fn test_lambda_versions_navigation() {
13309        let mut app = test_app();
13310        app.current_service = Service::LambdaFunctions;
13311        app.service_selected = true;
13312        app.lambda_state.current_function = Some("test-function".to_string());
13313        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13314
13315        // Add test versions
13316        app.lambda_state.version_table.items = vec![
13317            LambdaVersion {
13318                version: "3".to_string(),
13319                aliases: "prod".to_string(),
13320                description: "".to_string(),
13321                last_modified: "".to_string(),
13322                architecture: "X86_64".to_string(),
13323            },
13324            LambdaVersion {
13325                version: "2".to_string(),
13326                aliases: "".to_string(),
13327                description: "".to_string(),
13328                last_modified: "".to_string(),
13329                architecture: "X86_64".to_string(),
13330            },
13331            LambdaVersion {
13332                version: "1".to_string(),
13333                aliases: "".to_string(),
13334                description: "".to_string(),
13335                last_modified: "".to_string(),
13336                architecture: "X86_64".to_string(),
13337            },
13338        ];
13339
13340        // Verify versions are loaded
13341        assert_eq!(app.lambda_state.version_table.items.len(), 3);
13342        assert_eq!(app.lambda_state.version_table.items[0].version, "3");
13343        assert_eq!(app.lambda_state.version_table.items[0].aliases, "prod");
13344
13345        // Verify selection can be changed
13346        app.lambda_state.version_table.selected = 1;
13347        assert_eq!(app.lambda_state.version_table.selected, 1);
13348    }
13349
13350    #[test]
13351    fn test_lambda_versions_with_aliases() {
13352        let version = LambdaVersion {
13353            version: "35".to_string(),
13354            aliases: "prod, staging".to_string(),
13355            description: "Production version".to_string(),
13356            last_modified: "2024-01-01".to_string(),
13357            architecture: "X86_64".to_string(),
13358        };
13359
13360        assert_eq!(version.aliases, "prod, staging");
13361        assert!(!version.aliases.is_empty());
13362    }
13363
13364    #[test]
13365    fn test_lambda_versions_expansion() {
13366        let mut app = test_app();
13367        app.current_service = Service::LambdaFunctions;
13368        app.service_selected = true;
13369        app.lambda_state.current_function = Some("test-function".to_string());
13370        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13371
13372        // Add test versions
13373        app.lambda_state.version_table.items = vec![
13374            LambdaVersion {
13375                version: "2".to_string(),
13376                aliases: "prod".to_string(),
13377                description: "Production".to_string(),
13378                last_modified: "2024-01-01".to_string(),
13379                architecture: "X86_64".to_string(),
13380            },
13381            LambdaVersion {
13382                version: "1".to_string(),
13383                aliases: "".to_string(),
13384                description: "".to_string(),
13385                last_modified: "2024-01-01".to_string(),
13386                architecture: "Arm64".to_string(),
13387            },
13388        ];
13389
13390        app.lambda_state.version_table.selected = 0;
13391
13392        // Verify expansion can be set
13393        app.lambda_state.version_table.expanded_item = Some(0);
13394        assert_eq!(app.lambda_state.version_table.expanded_item, Some(0));
13395
13396        // Select different version
13397        app.lambda_state.version_table.selected = 1;
13398        app.lambda_state.version_table.expanded_item = Some(1);
13399        assert_eq!(app.lambda_state.version_table.expanded_item, Some(1));
13400    }
13401
13402    #[test]
13403    fn test_lambda_versions_page_navigation() {
13404        let mut app = test_app();
13405        app.current_service = Service::LambdaFunctions;
13406        app.service_selected = true;
13407        app.lambda_state.current_function = Some("test-function".to_string());
13408        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13409
13410        // Add 30 test versions
13411        app.lambda_state.version_table.items = (1..=30)
13412            .map(|i| LambdaVersion {
13413                version: i.to_string(),
13414                aliases: "".to_string(),
13415                description: "".to_string(),
13416                last_modified: "".to_string(),
13417                architecture: "X86_64".to_string(),
13418            })
13419            .collect();
13420
13421        app.lambda_state.version_table.page_size = PageSize::Ten;
13422        app.lambda_state.version_table.selected = 0;
13423
13424        // Go to page 2
13425        app.page_input = "2".to_string();
13426        app.handle_action(Action::OpenColumnSelector);
13427
13428        // Should be at index 10 (start of page 2)
13429        assert_eq!(app.lambda_state.version_table.selected, 10);
13430    }
13431
13432    #[test]
13433    fn test_lambda_versions_pagination_arrow_keys() {
13434        let mut app = test_app();
13435        app.current_service = Service::LambdaFunctions;
13436        app.service_selected = true;
13437        app.lambda_state.current_function = Some("test-function".to_string());
13438        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13439        app.mode = Mode::FilterInput;
13440        app.lambda_state.version_input_focus = InputFocus::Pagination;
13441
13442        // Add 30 test versions
13443        app.lambda_state.version_table.items = (1..=30)
13444            .map(|i| LambdaVersion {
13445                version: i.to_string(),
13446                aliases: "".to_string(),
13447                description: "".to_string(),
13448                last_modified: "".to_string(),
13449                architecture: "X86_64".to_string(),
13450            })
13451            .collect();
13452
13453        app.lambda_state.version_table.page_size = PageSize::Ten;
13454        app.lambda_state.version_table.selected = 0;
13455
13456        // Right arrow (PageDown) should go to next page
13457        app.handle_action(Action::PageDown);
13458        assert_eq!(app.lambda_state.version_table.selected, 10);
13459
13460        // Left arrow (PageUp) should go back
13461        app.handle_action(Action::PageUp);
13462        assert_eq!(app.lambda_state.version_table.selected, 0);
13463    }
13464
13465    #[test]
13466    fn test_lambda_versions_page_input_in_filter_mode() {
13467        let mut app = test_app();
13468        app.current_service = Service::LambdaFunctions;
13469        app.service_selected = true;
13470        app.lambda_state.current_function = Some("test-function".to_string());
13471        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13472        app.mode = Mode::FilterInput;
13473        app.lambda_state.version_input_focus = InputFocus::Pagination;
13474
13475        // Add 30 test versions
13476        app.lambda_state.version_table.items = (1..=30)
13477            .map(|i| LambdaVersion {
13478                version: i.to_string(),
13479                aliases: "".to_string(),
13480                description: "".to_string(),
13481                last_modified: "".to_string(),
13482                architecture: "X86_64".to_string(),
13483            })
13484            .collect();
13485
13486        app.lambda_state.version_table.page_size = PageSize::Ten;
13487        app.lambda_state.version_table.selected = 0;
13488
13489        // Type "2" when focused on Pagination
13490        app.handle_action(Action::FilterInput('2'));
13491        assert_eq!(app.page_input, "2");
13492        assert_eq!(app.lambda_state.version_table.filter, ""); // Should not go to filter
13493
13494        // Press 'p' to go to page 2
13495        app.handle_action(Action::OpenColumnSelector);
13496        assert_eq!(app.lambda_state.version_table.selected, 10);
13497        assert_eq!(app.page_input, ""); // Should be cleared
13498    }
13499
13500    #[test]
13501    fn test_lambda_versions_filter_input() {
13502        let mut app = test_app();
13503        app.current_service = Service::LambdaFunctions;
13504        app.service_selected = true;
13505        app.lambda_state.current_function = Some("test-function".to_string());
13506        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13507        app.mode = Mode::FilterInput;
13508        app.lambda_state.version_input_focus = InputFocus::Filter;
13509
13510        // Add test versions
13511        app.lambda_state.version_table.items = vec![
13512            LambdaVersion {
13513                version: "1".to_string(),
13514                aliases: "prod".to_string(),
13515                description: "Production".to_string(),
13516                last_modified: "".to_string(),
13517                architecture: "X86_64".to_string(),
13518            },
13519            LambdaVersion {
13520                version: "2".to_string(),
13521                aliases: "staging".to_string(),
13522                description: "Staging".to_string(),
13523                last_modified: "".to_string(),
13524                architecture: "X86_64".to_string(),
13525            },
13526        ];
13527
13528        // Type filter text
13529        app.handle_action(Action::FilterInput('p'));
13530        app.handle_action(Action::FilterInput('r'));
13531        app.handle_action(Action::FilterInput('o'));
13532        app.handle_action(Action::FilterInput('d'));
13533        assert_eq!(app.lambda_state.version_table.filter, "prod");
13534
13535        // Backspace should work
13536        app.handle_action(Action::FilterBackspace);
13537        assert_eq!(app.lambda_state.version_table.filter, "pro");
13538    }
13539
13540    #[test]
13541    fn test_lambda_aliases_table_expansion() {
13542        use crate::lambda::Alias;
13543
13544        let mut app = test_app();
13545        app.current_service = Service::LambdaFunctions;
13546        app.service_selected = true;
13547        app.lambda_state.current_function = Some("test-function".to_string());
13548        app.lambda_state.detail_tab = LambdaDetailTab::Aliases;
13549        app.mode = Mode::Normal;
13550
13551        app.lambda_state.alias_table.items = vec![
13552            Alias {
13553                name: "prod".to_string(),
13554                versions: "1".to_string(),
13555                description: "Production alias".to_string(),
13556            },
13557            Alias {
13558                name: "staging".to_string(),
13559                versions: "2".to_string(),
13560                description: "Staging alias".to_string(),
13561            },
13562        ];
13563
13564        app.lambda_state.alias_table.selected = 0;
13565
13566        // Select first alias - should open alias detail view (no tab change)
13567        app.handle_action(Action::Select);
13568        assert_eq!(app.lambda_state.current_alias, Some("prod".to_string()));
13569        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
13570
13571        // Go back
13572        app.handle_action(Action::GoBack);
13573        assert_eq!(app.lambda_state.current_alias, None);
13574        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
13575
13576        // Select second alias
13577        app.lambda_state.alias_table.selected = 1;
13578        app.handle_action(Action::Select);
13579        assert_eq!(app.lambda_state.current_alias, Some("staging".to_string()));
13580    }
13581
13582    #[test]
13583    fn test_lambda_versions_arrow_key_expansion() {
13584        let mut app = test_app();
13585        app.current_service = Service::LambdaFunctions;
13586        app.service_selected = true;
13587        app.lambda_state.current_function = Some("test-function".to_string());
13588        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13589        app.mode = Mode::Normal;
13590
13591        app.lambda_state.version_table.items = vec![LambdaVersion {
13592            version: "1".to_string(),
13593            aliases: "prod".to_string(),
13594            description: "Production".to_string(),
13595            last_modified: "2024-01-01".to_string(),
13596            architecture: "X86_64".to_string(),
13597        }];
13598
13599        app.lambda_state.version_table.selected = 0;
13600
13601        // Right arrow expands
13602        app.handle_action(Action::NextPane);
13603        assert_eq!(app.lambda_state.version_table.expanded_item, Some(0));
13604
13605        // Left arrow collapses
13606        app.handle_action(Action::PrevPane);
13607        assert_eq!(app.lambda_state.version_table.expanded_item, None);
13608    }
13609
13610    #[test]
13611    fn test_lambda_version_detail_view() {
13612        use crate::lambda::Function;
13613
13614        let mut app = test_app();
13615        app.current_service = Service::LambdaFunctions;
13616        app.service_selected = true;
13617        app.lambda_state.current_function = Some("test-function".to_string());
13618        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
13619        app.mode = Mode::Normal;
13620
13621        app.lambda_state.table.items = vec![Function {
13622            name: "test-function".to_string(),
13623            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
13624            application: None,
13625            description: "Test".to_string(),
13626            package_type: "Zip".to_string(),
13627            runtime: "python3.12".to_string(),
13628            architecture: "X86_64".to_string(),
13629            code_size: 1024,
13630            code_sha256: "hash".to_string(),
13631            memory_mb: 128,
13632            timeout_seconds: 30,
13633            last_modified: "2024-01-01".to_string(),
13634            layers: vec![],
13635        }];
13636
13637        app.lambda_state.version_table.items = vec![LambdaVersion {
13638            version: "1".to_string(),
13639            aliases: "prod".to_string(),
13640            description: "Production".to_string(),
13641            last_modified: "2024-01-01".to_string(),
13642            architecture: "X86_64".to_string(),
13643        }];
13644
13645        app.lambda_state.version_table.selected = 0;
13646
13647        // Select version to open detail view
13648        app.handle_action(Action::Select);
13649        assert_eq!(app.lambda_state.current_version, Some("1".to_string()));
13650        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
13651
13652        // GoBack should go back to versions list
13653        app.handle_action(Action::GoBack);
13654        assert_eq!(app.lambda_state.current_version, None);
13655        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
13656    }
13657
13658    #[test]
13659    fn test_lambda_version_detail_tabs() {
13660        use crate::lambda::Function;
13661
13662        let mut app = test_app();
13663        app.current_service = Service::LambdaFunctions;
13664        app.service_selected = true;
13665        app.lambda_state.current_function = Some("test-function".to_string());
13666        app.lambda_state.current_version = Some("1".to_string());
13667        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13668        app.mode = Mode::Normal;
13669
13670        app.lambda_state.table.items = vec![Function {
13671            name: "test-function".to_string(),
13672            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
13673            application: None,
13674            description: "Test".to_string(),
13675            package_type: "Zip".to_string(),
13676            runtime: "python3.12".to_string(),
13677            architecture: "X86_64".to_string(),
13678            code_size: 1024,
13679            code_sha256: "hash".to_string(),
13680            memory_mb: 128,
13681            timeout_seconds: 30,
13682            last_modified: "2024-01-01".to_string(),
13683            layers: vec![],
13684        }];
13685
13686        // Tab should cycle between Code, Monitor, and Configuration
13687        app.handle_action(Action::NextDetailTab);
13688        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
13689
13690        app.handle_action(Action::NextDetailTab);
13691        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
13692
13693        app.handle_action(Action::NextDetailTab);
13694        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
13695
13696        // BackTab should cycle backward
13697        app.handle_action(Action::PrevDetailTab);
13698        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
13699
13700        app.handle_action(Action::PrevDetailTab);
13701        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
13702    }
13703
13704    #[test]
13705    fn test_lambda_aliases_arrow_key_expansion() {
13706        use crate::lambda::Alias;
13707
13708        let mut app = test_app();
13709        app.current_service = Service::LambdaFunctions;
13710        app.service_selected = true;
13711        app.lambda_state.current_function = Some("test-function".to_string());
13712        app.lambda_state.detail_tab = LambdaDetailTab::Aliases;
13713        app.mode = Mode::Normal;
13714
13715        app.lambda_state.alias_table.items = vec![Alias {
13716            name: "prod".to_string(),
13717            versions: "1".to_string(),
13718            description: "Production alias".to_string(),
13719        }];
13720
13721        app.lambda_state.alias_table.selected = 0;
13722
13723        // Right arrow expands
13724        app.handle_action(Action::NextPane);
13725        assert_eq!(app.lambda_state.alias_table.expanded_item, Some(0));
13726
13727        // Left arrow collapses
13728        app.handle_action(Action::PrevPane);
13729        assert_eq!(app.lambda_state.alias_table.expanded_item, None);
13730    }
13731
13732    #[test]
13733    fn test_lambda_functions_arrow_key_expansion() {
13734        use crate::lambda::Function;
13735
13736        let mut app = test_app();
13737        app.current_service = Service::LambdaFunctions;
13738        app.service_selected = true;
13739        app.mode = Mode::Normal;
13740
13741        app.lambda_state.table.items = vec![Function {
13742            name: "test-function".to_string(),
13743            arn: "arn".to_string(),
13744            application: None,
13745            description: "Test".to_string(),
13746            package_type: "Zip".to_string(),
13747            runtime: "python3.12".to_string(),
13748            architecture: "X86_64".to_string(),
13749            code_size: 1024,
13750            code_sha256: "hash".to_string(),
13751            memory_mb: 128,
13752            timeout_seconds: 30,
13753            last_modified: "2024-01-01".to_string(),
13754            layers: vec![],
13755        }];
13756
13757        app.lambda_state.table.selected = 0;
13758
13759        // Right arrow expands
13760        app.handle_action(Action::NextPane);
13761        assert_eq!(app.lambda_state.table.expanded_item, Some(0));
13762
13763        // Left arrow collapses
13764        app.handle_action(Action::PrevPane);
13765        assert_eq!(app.lambda_state.table.expanded_item, None);
13766    }
13767
13768    #[test]
13769    fn test_lambda_version_detail_with_application() {
13770        use crate::lambda::Function;
13771
13772        let mut app = test_app();
13773        app.current_service = Service::LambdaFunctions;
13774        app.service_selected = true;
13775        app.lambda_state.current_function = Some("storefront-studio-beta-api".to_string());
13776        app.lambda_state.current_version = Some("1".to_string());
13777        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13778        app.mode = Mode::Normal;
13779
13780        app.lambda_state.table.items = vec![Function {
13781            name: "storefront-studio-beta-api".to_string(),
13782            arn: "arn:aws:lambda:us-east-1:123456789012:function:storefront-studio-beta-api"
13783                .to_string(),
13784            application: Some("storefront-studio-beta".to_string()),
13785            description: "API function".to_string(),
13786            package_type: "Zip".to_string(),
13787            runtime: "python3.12".to_string(),
13788            architecture: "X86_64".to_string(),
13789            code_size: 1024,
13790            code_sha256: "hash".to_string(),
13791            memory_mb: 128,
13792            timeout_seconds: 30,
13793            last_modified: "2024-01-01".to_string(),
13794            layers: vec![],
13795        }];
13796
13797        // Verify function has application extracted
13798        assert_eq!(
13799            app.lambda_state.table.items[0].application,
13800            Some("storefront-studio-beta".to_string())
13801        );
13802        assert_eq!(app.lambda_state.current_version, Some("1".to_string()));
13803    }
13804
13805    #[test]
13806    fn test_lambda_layer_navigation() {
13807        use crate::lambda::{Function, Layer};
13808
13809        let mut app = test_app();
13810        app.current_service = Service::LambdaFunctions;
13811        app.service_selected = true;
13812        app.lambda_state.current_function = Some("test-function".to_string());
13813        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13814        app.mode = Mode::Normal;
13815
13816        app.lambda_state.table.items = vec![Function {
13817            name: "test-function".to_string(),
13818            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
13819            application: None,
13820            description: "Test".to_string(),
13821            package_type: "Zip".to_string(),
13822            runtime: "python3.12".to_string(),
13823            architecture: "X86_64".to_string(),
13824            code_size: 1024,
13825            code_sha256: "hash".to_string(),
13826            memory_mb: 128,
13827            timeout_seconds: 30,
13828            last_modified: "2024-01-01".to_string(),
13829            layers: vec![
13830                Layer {
13831                    merge_order: "1".to_string(),
13832                    name: "layer1".to_string(),
13833                    layer_version: "1".to_string(),
13834                    compatible_runtimes: "python3.9".to_string(),
13835                    compatible_architectures: "x86_64".to_string(),
13836                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer1:1".to_string(),
13837                },
13838                Layer {
13839                    merge_order: "2".to_string(),
13840                    name: "layer2".to_string(),
13841                    layer_version: "2".to_string(),
13842                    compatible_runtimes: "python3.9".to_string(),
13843                    compatible_architectures: "x86_64".to_string(),
13844                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer2:2".to_string(),
13845                },
13846                Layer {
13847                    merge_order: "3".to_string(),
13848                    name: "layer3".to_string(),
13849                    layer_version: "3".to_string(),
13850                    compatible_runtimes: "python3.9".to_string(),
13851                    compatible_architectures: "x86_64".to_string(),
13852                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer3:3".to_string(),
13853                },
13854            ],
13855        }];
13856
13857        assert_eq!(app.lambda_state.layer_selected, 0);
13858
13859        app.handle_action(Action::NextItem);
13860        assert_eq!(app.lambda_state.layer_selected, 1);
13861
13862        app.handle_action(Action::NextItem);
13863        assert_eq!(app.lambda_state.layer_selected, 2);
13864
13865        app.handle_action(Action::NextItem);
13866        assert_eq!(app.lambda_state.layer_selected, 2);
13867
13868        app.handle_action(Action::PrevItem);
13869        assert_eq!(app.lambda_state.layer_selected, 1);
13870
13871        app.handle_action(Action::PrevItem);
13872        assert_eq!(app.lambda_state.layer_selected, 0);
13873
13874        app.handle_action(Action::PrevItem);
13875        assert_eq!(app.lambda_state.layer_selected, 0);
13876    }
13877
13878    #[test]
13879    fn test_lambda_layer_expansion() {
13880        use crate::lambda::{Function, Layer};
13881
13882        let mut app = test_app();
13883        app.current_service = Service::LambdaFunctions;
13884        app.service_selected = true;
13885        app.lambda_state.current_function = Some("test-function".to_string());
13886        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13887        app.mode = Mode::Normal;
13888
13889        app.lambda_state.table.items = vec![Function {
13890            name: "test-function".to_string(),
13891            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
13892            application: None,
13893            description: "Test".to_string(),
13894            package_type: "Zip".to_string(),
13895            runtime: "python3.12".to_string(),
13896            architecture: "X86_64".to_string(),
13897            code_size: 1024,
13898            code_sha256: "hash".to_string(),
13899            memory_mb: 128,
13900            timeout_seconds: 30,
13901            last_modified: "2024-01-01".to_string(),
13902            layers: vec![Layer {
13903                merge_order: "1".to_string(),
13904                name: "test-layer".to_string(),
13905                layer_version: "1".to_string(),
13906                compatible_runtimes: "python3.9".to_string(),
13907                compatible_architectures: "x86_64".to_string(),
13908                version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1".to_string(),
13909            }],
13910        }];
13911
13912        assert_eq!(app.lambda_state.layer_expanded, None);
13913
13914        app.handle_action(Action::NextPane);
13915        assert_eq!(app.lambda_state.layer_expanded, Some(0));
13916
13917        app.handle_action(Action::PrevPane);
13918        assert_eq!(app.lambda_state.layer_expanded, None);
13919
13920        app.handle_action(Action::NextPane);
13921        assert_eq!(app.lambda_state.layer_expanded, Some(0));
13922
13923        app.handle_action(Action::NextPane);
13924        assert_eq!(app.lambda_state.layer_expanded, None);
13925    }
13926
13927    #[test]
13928    fn test_lambda_layer_selection_and_expansion_workflow() {
13929        use crate::lambda::{Function, Layer};
13930
13931        let mut app = test_app();
13932        app.current_service = Service::LambdaFunctions;
13933        app.service_selected = true;
13934        app.lambda_state.current_function = Some("test-function".to_string());
13935        app.lambda_state.detail_tab = LambdaDetailTab::Code;
13936        app.mode = Mode::Normal;
13937
13938        app.lambda_state.table.items = vec![Function {
13939            name: "test-function".to_string(),
13940            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
13941            application: None,
13942            description: "Test".to_string(),
13943            package_type: "Zip".to_string(),
13944            runtime: "python3.12".to_string(),
13945            architecture: "X86_64".to_string(),
13946            code_size: 1024,
13947            code_sha256: "hash".to_string(),
13948            memory_mb: 128,
13949            timeout_seconds: 30,
13950            last_modified: "2024-01-01".to_string(),
13951            layers: vec![
13952                Layer {
13953                    merge_order: "1".to_string(),
13954                    name: "layer1".to_string(),
13955                    layer_version: "1".to_string(),
13956                    compatible_runtimes: "python3.9".to_string(),
13957                    compatible_architectures: "x86_64".to_string(),
13958                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer1:1".to_string(),
13959                },
13960                Layer {
13961                    merge_order: "2".to_string(),
13962                    name: "layer2".to_string(),
13963                    layer_version: "2".to_string(),
13964                    compatible_runtimes: "python3.9".to_string(),
13965                    compatible_architectures: "x86_64".to_string(),
13966                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer2:2".to_string(),
13967                },
13968            ],
13969        }];
13970
13971        // Start at layer 0
13972        assert_eq!(app.lambda_state.layer_selected, 0);
13973        assert_eq!(app.lambda_state.layer_expanded, None);
13974
13975        // Expand layer 0
13976        app.handle_action(Action::NextPane);
13977        assert_eq!(app.lambda_state.layer_selected, 0);
13978        assert_eq!(app.lambda_state.layer_expanded, Some(0));
13979
13980        // Navigate to layer 1 while layer 0 is expanded
13981        app.handle_action(Action::NextItem);
13982        assert_eq!(app.lambda_state.layer_selected, 1);
13983        assert_eq!(app.lambda_state.layer_expanded, Some(0)); // Still expanded
13984
13985        // Expand layer 1 (should collapse layer 0 and expand layer 1)
13986        app.handle_action(Action::NextPane);
13987        assert_eq!(app.lambda_state.layer_selected, 1);
13988        assert_eq!(app.lambda_state.layer_expanded, Some(1));
13989
13990        // Collapse layer 1
13991        app.handle_action(Action::PrevPane);
13992        assert_eq!(app.lambda_state.layer_selected, 1);
13993        assert_eq!(app.lambda_state.layer_expanded, None);
13994
13995        // Navigate back to layer 0
13996        app.handle_action(Action::PrevItem);
13997        assert_eq!(app.lambda_state.layer_selected, 0);
13998        assert_eq!(app.lambda_state.layer_expanded, None);
13999    }
14000
14001    #[test]
14002    fn test_backtab_cycles_detail_tabs_backward() {
14003        let mut app = test_app();
14004        app.mode = Mode::Normal;
14005
14006        // Test Lambda detail tabs
14007        app.current_service = Service::LambdaFunctions;
14008        app.service_selected = true;
14009        app.lambda_state.current_function = Some("test-function".to_string());
14010        app.lambda_state.detail_tab = LambdaDetailTab::Code;
14011
14012        app.handle_action(Action::PrevDetailTab);
14013        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
14014
14015        app.handle_action(Action::PrevDetailTab);
14016        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
14017
14018        // Test IAM Roles detail tabs
14019        app.current_service = Service::IamRoles;
14020        app.iam_state.current_role = Some("test-role".to_string());
14021        app.iam_state.role_tab = RoleTab::Permissions;
14022
14023        app.handle_action(Action::PrevDetailTab);
14024        assert_eq!(app.iam_state.role_tab, RoleTab::RevokeSessions);
14025
14026        // Test IAM Users detail tabs
14027        app.current_service = Service::IamUsers;
14028        app.iam_state.current_user = Some("test-user".to_string());
14029        app.iam_state.user_tab = UserTab::Permissions;
14030
14031        app.handle_action(Action::PrevDetailTab);
14032        assert_eq!(app.iam_state.user_tab, UserTab::LastAccessed);
14033
14034        // Test IAM Groups detail tabs
14035        app.current_service = Service::IamUserGroups;
14036        app.iam_state.current_group = Some("test-group".to_string());
14037        app.iam_state.group_tab = GroupTab::Permissions;
14038
14039        app.handle_action(Action::PrevDetailTab);
14040        assert_eq!(app.iam_state.group_tab, GroupTab::Users);
14041
14042        // Test S3 object tabs
14043        app.current_service = Service::S3Buckets;
14044        app.s3_state.current_bucket = Some("test-bucket".to_string());
14045        app.s3_state.object_tab = S3ObjectTab::Properties;
14046
14047        app.handle_action(Action::PrevDetailTab);
14048        assert_eq!(app.s3_state.object_tab, S3ObjectTab::Objects);
14049
14050        // Test ECR repository tabs (Private/Public)
14051        app.current_service = Service::EcrRepositories;
14052        app.ecr_state.current_repository = None;
14053        app.ecr_state.tab = EcrTab::Private;
14054
14055        app.handle_action(Action::PrevDetailTab);
14056        assert_eq!(app.ecr_state.tab, EcrTab::Public);
14057
14058        // Test CloudFormation detail tabs
14059        app.current_service = Service::CloudFormationStacks;
14060        app.cfn_state.current_stack = Some("test-stack".to_string());
14061        app.cfn_state.detail_tab = CfnDetailTab::Resources;
14062    }
14063
14064    #[test]
14065    fn test_cloudformation_status_filter_active() {
14066        let filter = CfnStatusFilter::Active;
14067        assert!(filter.matches("CREATE_IN_PROGRESS"));
14068        assert!(filter.matches("UPDATE_IN_PROGRESS"));
14069        assert!(!filter.matches("CREATE_COMPLETE"));
14070        assert!(!filter.matches("DELETE_COMPLETE"));
14071        assert!(!filter.matches("CREATE_FAILED"));
14072    }
14073
14074    #[test]
14075    fn test_cloudformation_status_filter_complete() {
14076        let filter = CfnStatusFilter::Complete;
14077        assert!(filter.matches("CREATE_COMPLETE"));
14078        assert!(filter.matches("UPDATE_COMPLETE"));
14079        assert!(!filter.matches("DELETE_COMPLETE"));
14080        assert!(!filter.matches("CREATE_IN_PROGRESS"));
14081    }
14082
14083    #[test]
14084    fn test_cloudformation_status_filter_failed() {
14085        let filter = CfnStatusFilter::Failed;
14086        assert!(filter.matches("CREATE_FAILED"));
14087        assert!(filter.matches("UPDATE_FAILED"));
14088        assert!(!filter.matches("CREATE_COMPLETE"));
14089    }
14090
14091    #[test]
14092    fn test_cloudformation_status_filter_deleted() {
14093        let filter = CfnStatusFilter::Deleted;
14094        assert!(filter.matches("DELETE_COMPLETE"));
14095        assert!(filter.matches("DELETE_IN_PROGRESS"));
14096        assert!(!filter.matches("CREATE_COMPLETE"));
14097    }
14098
14099    #[test]
14100    fn test_cloudformation_status_filter_in_progress() {
14101        let filter = CfnStatusFilter::InProgress;
14102        assert!(filter.matches("CREATE_IN_PROGRESS"));
14103        assert!(filter.matches("UPDATE_IN_PROGRESS"));
14104        assert!(filter.matches("DELETE_IN_PROGRESS"));
14105        assert!(!filter.matches("CREATE_COMPLETE"));
14106    }
14107
14108    #[test]
14109    fn test_cloudformation_status_filter_cycle() {
14110        let filter = CfnStatusFilter::All;
14111        assert_eq!(filter.next(), CfnStatusFilter::Active);
14112        assert_eq!(filter.next().next(), CfnStatusFilter::Complete);
14113        assert_eq!(filter.next().next().next(), CfnStatusFilter::Failed);
14114        assert_eq!(filter.next().next().next().next(), CfnStatusFilter::Deleted);
14115        assert_eq!(
14116            filter.next().next().next().next().next(),
14117            CfnStatusFilter::InProgress
14118        );
14119        assert_eq!(
14120            filter.next().next().next().next().next().next(),
14121            CfnStatusFilter::All
14122        );
14123    }
14124
14125    #[test]
14126    fn test_cloudformation_default_columns() {
14127        let app = test_app();
14128        assert_eq!(app.cfn_visible_column_ids.len(), 4);
14129        assert!(app.cfn_visible_column_ids.contains(&CfnColumn::Name.id()));
14130        assert!(app.cfn_visible_column_ids.contains(&CfnColumn::Status.id()));
14131        assert!(app
14132            .cfn_visible_column_ids
14133            .contains(&CfnColumn::CreatedTime.id()));
14134        assert!(app
14135            .cfn_visible_column_ids
14136            .contains(&CfnColumn::Description.id()));
14137    }
14138
14139    #[test]
14140    fn test_cloudformation_all_columns() {
14141        let app = test_app();
14142        assert_eq!(app.cfn_column_ids.len(), 10);
14143    }
14144
14145    #[test]
14146    fn test_cloudformation_filter_by_name() {
14147        let mut app = test_app();
14148        app.cfn_state.status_filter = CfnStatusFilter::Complete;
14149        app.cfn_state.table.items = vec![
14150            CfnStack {
14151                name: "my-stack".to_string(),
14152                stack_id: "id1".to_string(),
14153                status: "CREATE_COMPLETE".to_string(),
14154                created_time: "2024-01-01".to_string(),
14155                updated_time: String::new(),
14156                deleted_time: String::new(),
14157                drift_status: String::new(),
14158                last_drift_check_time: String::new(),
14159                status_reason: String::new(),
14160                description: String::new(),
14161                detailed_status: String::new(),
14162                root_stack: String::new(),
14163                parent_stack: String::new(),
14164                termination_protection: false,
14165                iam_role: String::new(),
14166                tags: Vec::new(),
14167                stack_policy: String::new(),
14168                rollback_monitoring_time: String::new(),
14169                rollback_alarms: Vec::new(),
14170                notification_arns: Vec::new(),
14171            },
14172            CfnStack {
14173                name: "other-stack".to_string(),
14174                stack_id: "id2".to_string(),
14175                status: "CREATE_COMPLETE".to_string(),
14176                created_time: "2024-01-02".to_string(),
14177                updated_time: String::new(),
14178                deleted_time: String::new(),
14179                drift_status: String::new(),
14180                last_drift_check_time: String::new(),
14181                status_reason: String::new(),
14182                description: String::new(),
14183                detailed_status: String::new(),
14184                root_stack: String::new(),
14185                parent_stack: String::new(),
14186                termination_protection: false,
14187                iam_role: String::new(),
14188                tags: Vec::new(),
14189                stack_policy: String::new(),
14190                rollback_monitoring_time: String::new(),
14191                rollback_alarms: Vec::new(),
14192                notification_arns: Vec::new(),
14193            },
14194        ];
14195
14196        app.cfn_state.table.filter = "my".to_string();
14197        let filtered = filtered_cloudformation_stacks(&app);
14198        assert_eq!(filtered.len(), 1);
14199        assert_eq!(filtered[0].name, "my-stack");
14200    }
14201
14202    #[test]
14203    fn test_cloudformation_filter_by_description() {
14204        let mut app = test_app();
14205        app.cfn_state.status_filter = CfnStatusFilter::Complete;
14206        app.cfn_state.table.items = vec![CfnStack {
14207            name: "stack1".to_string(),
14208            stack_id: "id1".to_string(),
14209            status: "CREATE_COMPLETE".to_string(),
14210            created_time: "2024-01-01".to_string(),
14211            updated_time: String::new(),
14212            deleted_time: String::new(),
14213            drift_status: String::new(),
14214            last_drift_check_time: String::new(),
14215            status_reason: String::new(),
14216            description: "production stack".to_string(),
14217            detailed_status: String::new(),
14218            root_stack: String::new(),
14219            parent_stack: String::new(),
14220            termination_protection: false,
14221            iam_role: String::new(),
14222            tags: Vec::new(),
14223            stack_policy: String::new(),
14224            rollback_monitoring_time: String::new(),
14225            rollback_alarms: Vec::new(),
14226            notification_arns: Vec::new(),
14227        }];
14228
14229        app.cfn_state.table.filter = "production".to_string();
14230        let filtered = filtered_cloudformation_stacks(&app);
14231        assert_eq!(filtered.len(), 1);
14232    }
14233
14234    #[test]
14235    fn test_cloudformation_status_filter_applied() {
14236        let mut app = test_app();
14237        app.cfn_state.table.items = vec![
14238            CfnStack {
14239                name: "complete-stack".to_string(),
14240                stack_id: "id1".to_string(),
14241                status: "CREATE_COMPLETE".to_string(),
14242                created_time: "2024-01-01".to_string(),
14243                updated_time: String::new(),
14244                deleted_time: String::new(),
14245                drift_status: String::new(),
14246                last_drift_check_time: String::new(),
14247                status_reason: String::new(),
14248                description: String::new(),
14249                detailed_status: String::new(),
14250                root_stack: String::new(),
14251                parent_stack: String::new(),
14252                termination_protection: false,
14253                iam_role: String::new(),
14254                tags: Vec::new(),
14255                stack_policy: String::new(),
14256                rollback_monitoring_time: String::new(),
14257                rollback_alarms: Vec::new(),
14258                notification_arns: Vec::new(),
14259            },
14260            CfnStack {
14261                name: "failed-stack".to_string(),
14262                stack_id: "id2".to_string(),
14263                status: "CREATE_FAILED".to_string(),
14264                created_time: "2024-01-02".to_string(),
14265                updated_time: String::new(),
14266                deleted_time: String::new(),
14267                drift_status: String::new(),
14268                last_drift_check_time: String::new(),
14269                status_reason: String::new(),
14270                description: String::new(),
14271                detailed_status: String::new(),
14272                root_stack: String::new(),
14273                parent_stack: String::new(),
14274                termination_protection: false,
14275                iam_role: String::new(),
14276                tags: Vec::new(),
14277                stack_policy: String::new(),
14278                rollback_monitoring_time: String::new(),
14279                rollback_alarms: Vec::new(),
14280                notification_arns: Vec::new(),
14281            },
14282        ];
14283
14284        app.cfn_state.status_filter = CfnStatusFilter::Complete;
14285        let filtered = filtered_cloudformation_stacks(&app);
14286        assert_eq!(filtered.len(), 1);
14287        assert_eq!(filtered[0].name, "complete-stack");
14288
14289        app.cfn_state.status_filter = CfnStatusFilter::Failed;
14290        let filtered = filtered_cloudformation_stacks(&app);
14291        assert_eq!(filtered.len(), 1);
14292        assert_eq!(filtered[0].name, "failed-stack");
14293    }
14294
14295    #[test]
14296    fn test_cloudformation_default_page_size() {
14297        let app = test_app();
14298        assert_eq!(app.cfn_state.table.page_size, PageSize::Fifty);
14299    }
14300
14301    #[test]
14302    fn test_cloudformation_default_status_filter() {
14303        let app = test_app();
14304        assert_eq!(app.cfn_state.status_filter, CfnStatusFilter::All);
14305    }
14306
14307    #[test]
14308    fn test_cloudformation_view_nested_default_false() {
14309        let app = test_app();
14310        assert!(!app.cfn_state.view_nested);
14311    }
14312
14313    #[test]
14314    fn test_cloudformation_pagination_hotkeys() {
14315        let mut app = test_app();
14316        app.current_service = Service::CloudFormationStacks;
14317        app.service_selected = true;
14318        app.cfn_state.status_filter = CfnStatusFilter::All;
14319
14320        // Add 150 stacks
14321        for i in 0..150 {
14322            app.cfn_state.table.items.push(CfnStack {
14323                name: format!("stack-{}", i),
14324                stack_id: format!("id-{}", i),
14325                status: "CREATE_COMPLETE".to_string(),
14326                created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
14327                updated_time: String::new(),
14328                deleted_time: String::new(),
14329                drift_status: String::new(),
14330                last_drift_check_time: String::new(),
14331                status_reason: String::new(),
14332                description: String::new(),
14333                detailed_status: String::new(),
14334                root_stack: String::new(),
14335                parent_stack: String::new(),
14336                termination_protection: false,
14337                iam_role: String::new(),
14338                tags: vec![],
14339                stack_policy: String::new(),
14340                rollback_monitoring_time: String::new(),
14341                rollback_alarms: vec![],
14342                notification_arns: vec![],
14343            });
14344        }
14345
14346        // Go to page 2
14347        app.go_to_page(2);
14348        assert_eq!(app.cfn_state.table.selected, 50);
14349
14350        // Go to page 3
14351        app.go_to_page(3);
14352        assert_eq!(app.cfn_state.table.selected, 100);
14353
14354        // Go to page 1
14355        app.go_to_page(1);
14356        assert_eq!(app.cfn_state.table.selected, 0);
14357    }
14358
14359    #[test]
14360    fn test_cloudformation_tab_cycling_in_filter_mode() {
14361        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
14362        let mut app = test_app();
14363        app.current_service = Service::CloudFormationStacks;
14364        app.service_selected = true;
14365        app.mode = Mode::FilterInput;
14366        app.cfn_state.input_focus = InputFocus::Filter;
14367
14368        // Tab to StatusFilter
14369        app.handle_action(Action::NextFilterFocus);
14370        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
14371
14372        // Tab to ViewNested
14373        app.handle_action(Action::NextFilterFocus);
14374        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
14375
14376        // Tab to Pagination
14377        app.handle_action(Action::NextFilterFocus);
14378        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
14379
14380        // Tab back to Filter
14381        app.handle_action(Action::NextFilterFocus);
14382        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14383    }
14384
14385    #[test]
14386    fn test_cloudformation_timestamp_format_includes_utc() {
14387        let stack = CfnStack {
14388            name: "test-stack".to_string(),
14389            stack_id: "id-123".to_string(),
14390            status: "CREATE_COMPLETE".to_string(),
14391            created_time: "2025-08-07 15:38:02 (UTC)".to_string(),
14392            updated_time: "2025-08-08 10:00:00 (UTC)".to_string(),
14393            deleted_time: String::new(),
14394            drift_status: String::new(),
14395            last_drift_check_time: "2025-08-09 12:00:00 (UTC)".to_string(),
14396            status_reason: String::new(),
14397            description: String::new(),
14398            detailed_status: String::new(),
14399            root_stack: String::new(),
14400            parent_stack: String::new(),
14401            termination_protection: false,
14402            iam_role: String::new(),
14403            tags: vec![],
14404            stack_policy: String::new(),
14405            rollback_monitoring_time: String::new(),
14406            rollback_alarms: vec![],
14407            notification_arns: vec![],
14408        };
14409
14410        assert!(stack.created_time.contains("(UTC)"));
14411        assert!(stack.updated_time.contains("(UTC)"));
14412        assert!(stack.last_drift_check_time.contains("(UTC)"));
14413        assert_eq!(stack.created_time.len(), 25);
14414    }
14415
14416    #[test]
14417    fn test_cloudformation_enter_drills_into_stack_view() {
14418        let mut app = test_app();
14419        app.current_service = Service::CloudFormationStacks;
14420        app.service_selected = true;
14421        app.mode = Mode::Normal;
14422        app.cfn_state.status_filter = CfnStatusFilter::All;
14423        app.tabs = vec![Tab {
14424            service: Service::CloudFormationStacks,
14425            title: "CloudFormation > Stacks".to_string(),
14426            breadcrumb: "CloudFormation > Stacks".to_string(),
14427        }];
14428        app.current_tab = 0;
14429
14430        app.cfn_state.table.items.push(CfnStack {
14431            name: "test-stack".to_string(),
14432            stack_id: "id-123".to_string(),
14433            status: "CREATE_COMPLETE".to_string(),
14434            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
14435            updated_time: String::new(),
14436            deleted_time: String::new(),
14437            drift_status: String::new(),
14438            last_drift_check_time: String::new(),
14439            status_reason: String::new(),
14440            description: String::new(),
14441            detailed_status: String::new(),
14442            root_stack: String::new(),
14443            parent_stack: String::new(),
14444            termination_protection: false,
14445            iam_role: String::new(),
14446            tags: vec![],
14447            stack_policy: String::new(),
14448            rollback_monitoring_time: String::new(),
14449            rollback_alarms: vec![],
14450            notification_arns: vec![],
14451        });
14452
14453        app.cfn_state.table.reset();
14454        assert_eq!(app.cfn_state.current_stack, None);
14455
14456        // Press Enter - should drill into stack detail view
14457        app.handle_action(Action::Select);
14458        assert_eq!(app.cfn_state.current_stack, Some("test-stack".to_string()));
14459    }
14460
14461    #[test]
14462    fn test_cloudformation_arrow_keys_expand_collapse() {
14463        let mut app = test_app();
14464        app.current_service = Service::CloudFormationStacks;
14465        app.service_selected = true;
14466        app.mode = Mode::Normal;
14467        app.cfn_state.status_filter = CfnStatusFilter::All;
14468
14469        app.cfn_state.table.items.push(CfnStack {
14470            name: "test-stack".to_string(),
14471            stack_id: "id-123".to_string(),
14472            status: "CREATE_COMPLETE".to_string(),
14473            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
14474            updated_time: String::new(),
14475            deleted_time: String::new(),
14476            drift_status: String::new(),
14477            last_drift_check_time: String::new(),
14478            status_reason: String::new(),
14479            description: String::new(),
14480            detailed_status: String::new(),
14481            root_stack: String::new(),
14482            parent_stack: String::new(),
14483            termination_protection: false,
14484            iam_role: String::new(),
14485            tags: vec![],
14486            stack_policy: String::new(),
14487            rollback_monitoring_time: String::new(),
14488            rollback_alarms: vec![],
14489            notification_arns: vec![],
14490        });
14491
14492        app.cfn_state.table.reset();
14493        assert_eq!(app.cfn_state.table.expanded_item, None);
14494
14495        // Right arrow - should expand
14496        app.handle_action(Action::NextPane);
14497        assert_eq!(app.cfn_state.table.expanded_item, Some(0));
14498
14499        // Left arrow - should collapse
14500        app.handle_action(Action::PrevPane);
14501        assert_eq!(app.cfn_state.table.expanded_item, None);
14502
14503        // Verify current_stack is still None (not drilled in)
14504        assert_eq!(app.cfn_state.current_stack, None);
14505    }
14506
14507    #[test]
14508    fn test_cloudformation_tab_cycling() {
14509        use crate::ui::cfn::DetailTab;
14510        let mut app = test_app();
14511        app.current_service = Service::CloudFormationStacks;
14512        app.service_selected = true;
14513        app.mode = Mode::Normal;
14514        app.cfn_state.status_filter = CfnStatusFilter::All;
14515        app.cfn_state.current_stack = Some("test-stack".to_string());
14516
14517        assert_eq!(app.cfn_state.detail_tab, DetailTab::StackInfo);
14518    }
14519
14520    #[test]
14521    fn test_cloudformation_console_url() {
14522        use crate::ui::cfn::DetailTab;
14523        let mut app = test_app();
14524        app.current_service = Service::CloudFormationStacks;
14525        app.service_selected = true;
14526        app.cfn_state.status_filter = CfnStatusFilter::All;
14527
14528        app.cfn_state.table.items.push(CfnStack {
14529            name: "test-stack".to_string(),
14530            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
14531                .to_string(),
14532            status: "CREATE_COMPLETE".to_string(),
14533            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
14534            updated_time: String::new(),
14535            deleted_time: String::new(),
14536            drift_status: String::new(),
14537            last_drift_check_time: String::new(),
14538            status_reason: String::new(),
14539            description: String::new(),
14540            detailed_status: String::new(),
14541            root_stack: String::new(),
14542            parent_stack: String::new(),
14543            termination_protection: false,
14544            iam_role: String::new(),
14545            tags: vec![],
14546            stack_policy: String::new(),
14547            rollback_monitoring_time: String::new(),
14548            rollback_alarms: vec![],
14549            notification_arns: vec![],
14550        });
14551
14552        app.cfn_state.current_stack = Some("test-stack".to_string());
14553
14554        // Stack info URL
14555        app.cfn_state.detail_tab = DetailTab::StackInfo;
14556        let url = app.get_console_url();
14557        assert!(url.contains("stackinfo"));
14558        assert!(url.contains("arn%3Aaws%3Acloudformation"));
14559
14560        // Events URL
14561        app.cfn_state.detail_tab = DetailTab::Events;
14562        let url = app.get_console_url();
14563        assert!(url.contains("events"));
14564        assert!(url.contains("arn%3Aaws%3Acloudformation"));
14565    }
14566
14567    #[test]
14568    fn test_iam_role_select() {
14569        let mut app = test_app();
14570        app.current_service = Service::IamRoles;
14571        app.service_selected = true;
14572        app.mode = Mode::Normal;
14573
14574        app.iam_state.roles.items = vec![
14575            IamRole {
14576                role_name: "role1".to_string(),
14577                path: "/".to_string(),
14578                trusted_entities: "AWS Service: ec2".to_string(),
14579                last_activity: "-".to_string(),
14580                arn: "arn:aws:iam::123456789012:role/role1".to_string(),
14581                creation_time: "2025-01-01".to_string(),
14582                description: "Test role 1".to_string(),
14583                max_session_duration: Some(3600),
14584            },
14585            IamRole {
14586                role_name: "role2".to_string(),
14587                path: "/".to_string(),
14588                trusted_entities: "AWS Service: lambda".to_string(),
14589                last_activity: "-".to_string(),
14590                arn: "arn:aws:iam::123456789012:role/role2".to_string(),
14591                creation_time: "2025-01-02".to_string(),
14592                description: "Test role 2".to_string(),
14593                max_session_duration: Some(7200),
14594            },
14595        ];
14596
14597        // Select first role
14598        app.iam_state.roles.selected = 0;
14599        app.handle_action(Action::Select);
14600
14601        assert_eq!(
14602            app.iam_state.current_role,
14603            Some("role1".to_string()),
14604            "Should open role detail view"
14605        );
14606        assert_eq!(
14607            app.iam_state.role_tab,
14608            RoleTab::Permissions,
14609            "Should default to Permissions tab"
14610        );
14611    }
14612
14613    #[test]
14614    fn test_iam_role_back_navigation() {
14615        let mut app = test_app();
14616        app.current_service = Service::IamRoles;
14617        app.service_selected = true;
14618        app.iam_state.current_role = Some("test-role".to_string());
14619
14620        app.handle_action(Action::GoBack);
14621
14622        assert_eq!(
14623            app.iam_state.current_role, None,
14624            "Should return to roles list"
14625        );
14626    }
14627
14628    #[test]
14629    fn test_iam_role_tab_navigation() {
14630        let mut app = test_app();
14631        app.current_service = Service::IamRoles;
14632        app.service_selected = true;
14633        app.iam_state.current_role = Some("test-role".to_string());
14634        app.iam_state.role_tab = RoleTab::Permissions;
14635
14636        app.handle_action(Action::NextDetailTab);
14637
14638        assert_eq!(
14639            app.iam_state.role_tab,
14640            RoleTab::TrustRelationships,
14641            "Should move to next tab"
14642        );
14643    }
14644
14645    #[test]
14646    fn test_iam_role_tab_cycle_order() {
14647        let mut app = test_app();
14648        app.current_service = Service::IamRoles;
14649        app.service_selected = true;
14650        app.iam_state.current_role = Some("test-role".to_string());
14651        app.iam_state.role_tab = RoleTab::Permissions;
14652
14653        app.handle_action(Action::NextDetailTab);
14654        assert_eq!(app.iam_state.role_tab, RoleTab::TrustRelationships);
14655
14656        app.handle_action(Action::NextDetailTab);
14657        assert_eq!(app.iam_state.role_tab, RoleTab::Tags);
14658
14659        app.handle_action(Action::NextDetailTab);
14660        assert_eq!(app.iam_state.role_tab, RoleTab::LastAccessed);
14661
14662        app.handle_action(Action::NextDetailTab);
14663        assert_eq!(app.iam_state.role_tab, RoleTab::RevokeSessions);
14664
14665        app.handle_action(Action::NextDetailTab);
14666        assert_eq!(
14667            app.iam_state.role_tab,
14668            RoleTab::Permissions,
14669            "Should cycle back to first tab"
14670        );
14671    }
14672
14673    #[test]
14674    fn test_iam_role_pagination() {
14675        let mut app = test_app();
14676        app.current_service = Service::IamRoles;
14677        app.service_selected = true;
14678        app.iam_state.roles.page_size = PageSize::Ten;
14679
14680        app.iam_state.roles.items = (0..25)
14681            .map(|i| IamRole {
14682                role_name: format!("role{}", i),
14683                path: "/".to_string(),
14684                trusted_entities: "AWS Service: ec2".to_string(),
14685                last_activity: "-".to_string(),
14686                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
14687                creation_time: "2025-01-01".to_string(),
14688                description: format!("Test role {}", i),
14689                max_session_duration: Some(3600),
14690            })
14691            .collect();
14692
14693        // Jump to page 2
14694        app.go_to_page(2);
14695
14696        assert_eq!(
14697            app.iam_state.roles.selected, 10,
14698            "Should select first item of page 2"
14699        );
14700        assert_eq!(
14701            app.iam_state.roles.scroll_offset, 10,
14702            "Should update scroll offset"
14703        );
14704    }
14705
14706    #[test]
14707    fn test_tags_table_populated_on_role_detail() {
14708        let mut app = test_app();
14709        app.current_service = Service::IamRoles;
14710        app.service_selected = true;
14711        app.mode = Mode::Normal;
14712        app.iam_state.roles.items = vec![IamRole {
14713            role_name: "TestRole".to_string(),
14714            path: "/".to_string(),
14715            trusted_entities: String::new(),
14716            last_activity: String::new(),
14717            arn: "arn:aws:iam::123456789012:role/TestRole".to_string(),
14718            creation_time: "2025-01-01".to_string(),
14719            description: String::new(),
14720            max_session_duration: Some(3600),
14721        }];
14722
14723        // Manually populate tags to test table rendering
14724        app.iam_state.tags.items = vec![
14725            IamRoleTag {
14726                key: "Environment".to_string(),
14727                value: "Production".to_string(),
14728            },
14729            IamRoleTag {
14730                key: "Team".to_string(),
14731                value: "Platform".to_string(),
14732            },
14733        ];
14734
14735        assert_eq!(app.iam_state.tags.items.len(), 2);
14736        assert_eq!(app.iam_state.tags.items[0].key, "Environment");
14737        assert_eq!(app.iam_state.tags.items[0].value, "Production");
14738        assert_eq!(app.iam_state.tags.selected, 0);
14739    }
14740
14741    #[test]
14742    fn test_tags_table_navigation() {
14743        let mut app = test_app();
14744        app.current_service = Service::IamRoles;
14745        app.service_selected = true;
14746        app.mode = Mode::Normal;
14747        app.iam_state.current_role = Some("TestRole".to_string());
14748        app.iam_state.role_tab = RoleTab::Tags;
14749        app.iam_state.tags.items = vec![
14750            IamRoleTag {
14751                key: "Tag1".to_string(),
14752                value: "Value1".to_string(),
14753            },
14754            IamRoleTag {
14755                key: "Tag2".to_string(),
14756                value: "Value2".to_string(),
14757            },
14758        ];
14759
14760        app.handle_action(Action::NextItem);
14761        assert_eq!(app.iam_state.tags.selected, 1);
14762
14763        app.handle_action(Action::PrevItem);
14764        assert_eq!(app.iam_state.tags.selected, 0);
14765    }
14766
14767    #[test]
14768    fn test_last_accessed_table_navigation() {
14769        let mut app = test_app();
14770        app.current_service = Service::IamRoles;
14771        app.service_selected = true;
14772        app.mode = Mode::Normal;
14773        app.iam_state.current_role = Some("TestRole".to_string());
14774        app.iam_state.role_tab = RoleTab::LastAccessed;
14775        app.iam_state.last_accessed_services.items = vec![
14776            LastAccessedService {
14777                service: "S3".to_string(),
14778                policies_granting: "Policy1".to_string(),
14779                last_accessed: "2025-01-01".to_string(),
14780            },
14781            LastAccessedService {
14782                service: "EC2".to_string(),
14783                policies_granting: "Policy2".to_string(),
14784                last_accessed: "2025-01-02".to_string(),
14785            },
14786        ];
14787
14788        app.handle_action(Action::NextItem);
14789        assert_eq!(app.iam_state.last_accessed_services.selected, 1);
14790
14791        app.handle_action(Action::PrevItem);
14792        assert_eq!(app.iam_state.last_accessed_services.selected, 0);
14793    }
14794
14795    #[test]
14796    fn test_cfn_input_focus_next() {
14797        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
14798        let mut app = test_app();
14799        app.current_service = Service::CloudFormationStacks;
14800        app.mode = Mode::FilterInput;
14801        app.cfn_state.input_focus = InputFocus::Filter;
14802
14803        app.handle_action(Action::NextFilterFocus);
14804        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
14805
14806        app.handle_action(Action::NextFilterFocus);
14807        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
14808
14809        app.handle_action(Action::NextFilterFocus);
14810        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
14811
14812        app.handle_action(Action::NextFilterFocus);
14813        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14814    }
14815
14816    #[test]
14817    fn test_cfn_input_focus_prev() {
14818        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
14819        let mut app = test_app();
14820        app.current_service = Service::CloudFormationStacks;
14821        app.mode = Mode::FilterInput;
14822        app.cfn_state.input_focus = InputFocus::Filter;
14823
14824        app.handle_action(Action::PrevFilterFocus);
14825        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
14826
14827        app.handle_action(Action::PrevFilterFocus);
14828        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
14829
14830        app.handle_action(Action::PrevFilterFocus);
14831        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
14832
14833        app.handle_action(Action::PrevFilterFocus);
14834        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14835    }
14836
14837    #[test]
14838    fn test_cw_logs_input_focus_prev() {
14839        let mut app = test_app();
14840        app.current_service = Service::CloudWatchLogGroups;
14841        app.mode = Mode::FilterInput;
14842        app.view_mode = ViewMode::Detail;
14843        app.log_groups_state.detail_tab = CwLogsDetailTab::LogStreams;
14844        app.log_groups_state.input_focus = InputFocus::Filter;
14845
14846        app.handle_action(Action::PrevFilterFocus);
14847        assert_eq!(app.log_groups_state.input_focus, InputFocus::Pagination);
14848
14849        app.handle_action(Action::PrevFilterFocus);
14850        assert_eq!(
14851            app.log_groups_state.input_focus,
14852            InputFocus::Checkbox("ShowExpired")
14853        );
14854
14855        app.handle_action(Action::PrevFilterFocus);
14856        assert_eq!(
14857            app.log_groups_state.input_focus,
14858            InputFocus::Checkbox("ExactMatch")
14859        );
14860
14861        app.handle_action(Action::PrevFilterFocus);
14862        assert_eq!(app.log_groups_state.input_focus, InputFocus::Filter);
14863    }
14864
14865    #[test]
14866    fn test_cw_events_input_focus_prev() {
14867        use crate::ui::cw::logs::EventFilterFocus;
14868        let mut app = test_app();
14869        app.mode = Mode::EventFilterInput;
14870        app.log_groups_state.event_input_focus = EventFilterFocus::Filter;
14871
14872        app.handle_action(Action::PrevFilterFocus);
14873        assert_eq!(
14874            app.log_groups_state.event_input_focus,
14875            EventFilterFocus::DateRange
14876        );
14877
14878        app.handle_action(Action::PrevFilterFocus);
14879        assert_eq!(
14880            app.log_groups_state.event_input_focus,
14881            EventFilterFocus::Filter
14882        );
14883    }
14884
14885    #[test]
14886    fn test_cfn_input_focus_cycle_complete() {
14887        let mut app = test_app();
14888        app.current_service = Service::CloudFormationStacks;
14889        app.mode = Mode::FilterInput;
14890        app.cfn_state.input_focus = InputFocus::Filter;
14891
14892        // Cycle forward through all controls
14893        for _ in 0..4 {
14894            app.handle_action(Action::NextFilterFocus);
14895        }
14896        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14897
14898        // Cycle backward through all controls
14899        for _ in 0..4 {
14900            app.handle_action(Action::PrevFilterFocus);
14901        }
14902        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14903    }
14904
14905    #[test]
14906    fn test_cfn_filter_status_arrow_keys() {
14907        use crate::ui::cfn::STATUS_FILTER;
14908        let mut app = test_app();
14909        app.current_service = Service::CloudFormationStacks;
14910        app.mode = Mode::FilterInput;
14911        app.cfn_state.input_focus = STATUS_FILTER;
14912        app.cfn_state.status_filter = CfnStatusFilter::All;
14913
14914        app.handle_action(Action::NextItem);
14915        assert_eq!(app.cfn_state.status_filter, CfnStatusFilter::Active);
14916
14917        app.handle_action(Action::PrevItem);
14918        assert_eq!(app.cfn_state.status_filter, CfnStatusFilter::All);
14919    }
14920
14921    #[test]
14922    fn test_cfn_filter_shift_tab_cycles_backward() {
14923        use crate::ui::cfn::STATUS_FILTER;
14924        let mut app = test_app();
14925        app.current_service = Service::CloudFormationStacks;
14926        app.mode = Mode::FilterInput;
14927        app.cfn_state.input_focus = STATUS_FILTER;
14928
14929        // Shift+Tab should go backward
14930        app.handle_action(Action::PrevFilterFocus);
14931        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
14932
14933        // From Input, Shift+Tab should wrap to Pagination
14934        app.handle_action(Action::PrevFilterFocus);
14935        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
14936    }
14937
14938    #[test]
14939    fn test_cfn_pagination_arrow_keys() {
14940        let mut app = test_app();
14941        app.current_service = Service::CloudFormationStacks;
14942        app.mode = Mode::FilterInput;
14943        app.cfn_state.input_focus = InputFocus::Pagination;
14944        app.cfn_state.table.scroll_offset = 0;
14945        app.cfn_state.table.page_size = PageSize::Ten;
14946
14947        // Add some test stacks
14948        app.cfn_state.table.items = (0..30)
14949            .map(|i| CfnStack {
14950                name: format!("stack-{}", i),
14951                stack_id: format!("id-{}", i),
14952                status: "CREATE_COMPLETE".to_string(),
14953                created_time: "2024-01-01".to_string(),
14954                updated_time: String::new(),
14955                deleted_time: String::new(),
14956                drift_status: String::new(),
14957                last_drift_check_time: String::new(),
14958                status_reason: String::new(),
14959                description: String::new(),
14960                detailed_status: String::new(),
14961                root_stack: String::new(),
14962                parent_stack: String::new(),
14963                termination_protection: false,
14964                iam_role: String::new(),
14965                tags: Vec::new(),
14966                stack_policy: String::new(),
14967                rollback_monitoring_time: String::new(),
14968                rollback_alarms: Vec::new(),
14969                notification_arns: Vec::new(),
14970            })
14971            .collect();
14972
14973        // Right arrow should page forward
14974        app.handle_action(Action::PageDown);
14975        assert_eq!(app.cfn_state.table.scroll_offset, 10);
14976        // Verify page number calculation
14977        let page_size = app.cfn_state.table.page_size.value();
14978        let current_page = app.cfn_state.table.scroll_offset / page_size;
14979        assert_eq!(current_page, 1);
14980
14981        // Left arrow should page backward
14982        app.handle_action(Action::PageUp);
14983        assert_eq!(app.cfn_state.table.scroll_offset, 0);
14984        let current_page = app.cfn_state.table.scroll_offset / page_size;
14985        assert_eq!(current_page, 0);
14986    }
14987
14988    #[test]
14989    fn test_cfn_page_navigation_updates_selection() {
14990        let mut app = test_app();
14991        app.current_service = Service::CloudFormationStacks;
14992        app.mode = Mode::Normal;
14993
14994        // Add 30 test stacks
14995        app.cfn_state.table.items = (0..30)
14996            .map(|i| CfnStack {
14997                name: format!("stack-{}", i),
14998                stack_id: format!("id-{}", i),
14999                status: "CREATE_COMPLETE".to_string(),
15000                created_time: "2024-01-01".to_string(),
15001                updated_time: String::new(),
15002                deleted_time: String::new(),
15003                drift_status: String::new(),
15004                last_drift_check_time: String::new(),
15005                status_reason: String::new(),
15006                description: String::new(),
15007                detailed_status: String::new(),
15008                root_stack: String::new(),
15009                parent_stack: String::new(),
15010                termination_protection: false,
15011                iam_role: String::new(),
15012                tags: Vec::new(),
15013                stack_policy: String::new(),
15014                rollback_monitoring_time: String::new(),
15015                rollback_alarms: Vec::new(),
15016                notification_arns: Vec::new(),
15017            })
15018            .collect();
15019
15020        app.cfn_state.table.reset();
15021        app.cfn_state.table.scroll_offset = 0;
15022
15023        // Page down should update selection
15024        app.handle_action(Action::PageDown);
15025        assert_eq!(app.cfn_state.table.selected, 10);
15026
15027        // Page down again
15028        app.handle_action(Action::PageDown);
15029        assert_eq!(app.cfn_state.table.selected, 20);
15030
15031        // Page up should update selection
15032        app.handle_action(Action::PageUp);
15033        assert_eq!(app.cfn_state.table.selected, 10);
15034    }
15035
15036    #[test]
15037    fn test_cfn_filter_input_only_when_focused() {
15038        use crate::ui::cfn::STATUS_FILTER;
15039        let mut app = test_app();
15040        app.current_service = Service::CloudFormationStacks;
15041        app.mode = Mode::FilterInput;
15042        app.cfn_state.input_focus = STATUS_FILTER;
15043        app.cfn_state.table.filter = String::new();
15044
15045        // Typing should not add to filter when focus is not on Input
15046        app.handle_action(Action::FilterInput('t'));
15047        app.handle_action(Action::FilterInput('e'));
15048        app.handle_action(Action::FilterInput('s'));
15049        app.handle_action(Action::FilterInput('t'));
15050        assert_eq!(app.cfn_state.table.filter, "");
15051
15052        // Switch to Input focus
15053        app.cfn_state.input_focus = InputFocus::Filter;
15054        app.handle_action(Action::FilterInput('t'));
15055        app.handle_action(Action::FilterInput('e'));
15056        app.handle_action(Action::FilterInput('s'));
15057        app.handle_action(Action::FilterInput('t'));
15058        assert_eq!(app.cfn_state.table.filter, "test");
15059    }
15060
15061    #[test]
15062    fn test_cfn_input_focus_resets_on_start() {
15063        let mut app = test_app();
15064        app.current_service = Service::CloudFormationStacks;
15065        app.service_selected = true;
15066        app.mode = Mode::Normal;
15067        app.cfn_state.input_focus = InputFocus::Pagination;
15068
15069        // Start filter should reset focus to Input
15070        app.handle_action(Action::StartFilter);
15071        assert_eq!(app.mode, Mode::FilterInput);
15072        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
15073    }
15074
15075    #[test]
15076    fn test_iam_roles_input_focus_cycles_forward() {
15077        let mut app = test_app();
15078        app.current_service = Service::IamRoles;
15079        app.mode = Mode::FilterInput;
15080        app.iam_state.role_input_focus = InputFocus::Filter;
15081
15082        app.handle_action(Action::NextFilterFocus);
15083        assert_eq!(app.iam_state.role_input_focus, InputFocus::Pagination);
15084
15085        app.handle_action(Action::NextFilterFocus);
15086        assert_eq!(app.iam_state.role_input_focus, InputFocus::Filter);
15087    }
15088
15089    #[test]
15090    fn test_iam_roles_input_focus_cycles_backward() {
15091        let mut app = test_app();
15092        app.current_service = Service::IamRoles;
15093        app.mode = Mode::FilterInput;
15094        app.iam_state.role_input_focus = InputFocus::Filter;
15095
15096        app.handle_action(Action::PrevFilterFocus);
15097        assert_eq!(app.iam_state.role_input_focus, InputFocus::Pagination);
15098
15099        app.handle_action(Action::PrevFilterFocus);
15100        assert_eq!(app.iam_state.role_input_focus, InputFocus::Filter);
15101    }
15102
15103    #[test]
15104    fn test_iam_roles_filter_input_only_when_focused() {
15105        let mut app = test_app();
15106        app.current_service = Service::IamRoles;
15107        app.mode = Mode::FilterInput;
15108        app.iam_state.role_input_focus = InputFocus::Pagination;
15109        app.iam_state.roles.filter = String::new();
15110
15111        // Typing should not add to filter when focus is on Pagination
15112        app.handle_action(Action::FilterInput('t'));
15113        app.handle_action(Action::FilterInput('e'));
15114        app.handle_action(Action::FilterInput('s'));
15115        app.handle_action(Action::FilterInput('t'));
15116        assert_eq!(app.iam_state.roles.filter, "");
15117
15118        // Switch to Input focus
15119        app.iam_state.role_input_focus = InputFocus::Filter;
15120        app.handle_action(Action::FilterInput('t'));
15121        app.handle_action(Action::FilterInput('e'));
15122        app.handle_action(Action::FilterInput('s'));
15123        app.handle_action(Action::FilterInput('t'));
15124        assert_eq!(app.iam_state.roles.filter, "test");
15125    }
15126
15127    #[test]
15128    fn test_iam_roles_page_down_updates_scroll_offset() {
15129        let mut app = test_app();
15130        app.current_service = Service::IamRoles;
15131        app.mode = Mode::Normal;
15132        app.iam_state.roles.items = (0..50)
15133            .map(|i| IamRole {
15134                role_name: format!("role-{}", i),
15135                path: "/".to_string(),
15136                trusted_entities: "AWS Service".to_string(),
15137                last_activity: "N/A".to_string(),
15138                arn: format!("arn:aws:iam::123456789012:role/role-{}", i),
15139                creation_time: "2024-01-01".to_string(),
15140                description: String::new(),
15141                max_session_duration: Some(3600),
15142            })
15143            .collect();
15144
15145        app.iam_state.roles.selected = 0;
15146        app.iam_state.roles.scroll_offset = 0;
15147
15148        // Page down should update both selected and scroll_offset
15149        app.handle_action(Action::PageDown);
15150        assert_eq!(app.iam_state.roles.selected, 10);
15151        // scroll_offset should be updated to keep selection visible
15152        assert!(app.iam_state.roles.scroll_offset <= app.iam_state.roles.selected);
15153
15154        // Page down again
15155        app.handle_action(Action::PageDown);
15156        assert_eq!(app.iam_state.roles.selected, 20);
15157        assert!(app.iam_state.roles.scroll_offset <= app.iam_state.roles.selected);
15158    }
15159
15160    #[test]
15161    fn test_application_selection_and_deployments_tab() {
15162        use crate::lambda::Application as LambdaApplication;
15163        use LambdaApplicationDetailTab;
15164
15165        let mut app = test_app();
15166        app.current_service = Service::LambdaApplications;
15167        app.service_selected = true;
15168        app.mode = Mode::Normal;
15169
15170        app.lambda_application_state.table.items = vec![LambdaApplication {
15171            name: "test-app".to_string(),
15172            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
15173            description: "Test application".to_string(),
15174            status: "CREATE_COMPLETE".to_string(),
15175            last_modified: "2024-01-01".to_string(),
15176        }];
15177
15178        // Select application
15179        app.handle_action(Action::Select);
15180        assert_eq!(
15181            app.lambda_application_state.current_application,
15182            Some("test-app".to_string())
15183        );
15184        assert_eq!(
15185            app.lambda_application_state.detail_tab,
15186            LambdaApplicationDetailTab::Overview
15187        );
15188
15189        // Switch to Deployments tab
15190        app.handle_action(Action::NextDetailTab);
15191        assert_eq!(
15192            app.lambda_application_state.detail_tab,
15193            LambdaApplicationDetailTab::Deployments
15194        );
15195
15196        // Go back
15197        app.handle_action(Action::GoBack);
15198        assert_eq!(app.lambda_application_state.current_application, None);
15199    }
15200
15201    #[test]
15202    fn test_application_resources_filter_and_pagination() {
15203        use crate::lambda::Application as LambdaApplication;
15204        use LambdaApplicationDetailTab;
15205
15206        let mut app = test_app();
15207        app.current_service = Service::LambdaApplications;
15208        app.service_selected = true;
15209        app.mode = Mode::Normal;
15210
15211        app.lambda_application_state.table.items = vec![LambdaApplication {
15212            name: "test-app".to_string(),
15213            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
15214            description: "Test application".to_string(),
15215            status: "CREATE_COMPLETE".to_string(),
15216            last_modified: "2024-01-01".to_string(),
15217        }];
15218
15219        // Select application
15220        app.handle_action(Action::Select);
15221        assert_eq!(
15222            app.lambda_application_state.detail_tab,
15223            LambdaApplicationDetailTab::Overview
15224        );
15225
15226        // Verify resources were loaded
15227        assert!(!app.lambda_application_state.resources.items.is_empty());
15228
15229        // Test filter focus cycling
15230        app.mode = Mode::FilterInput;
15231        assert_eq!(
15232            app.lambda_application_state.resource_input_focus,
15233            InputFocus::Filter
15234        );
15235
15236        app.handle_action(Action::NextFilterFocus);
15237        assert_eq!(
15238            app.lambda_application_state.resource_input_focus,
15239            InputFocus::Pagination
15240        );
15241
15242        app.handle_action(Action::PrevFilterFocus);
15243        assert_eq!(
15244            app.lambda_application_state.resource_input_focus,
15245            InputFocus::Filter
15246        );
15247    }
15248
15249    #[test]
15250    fn test_application_deployments_filter_and_pagination() {
15251        use crate::lambda::Application as LambdaApplication;
15252        use LambdaApplicationDetailTab;
15253
15254        let mut app = test_app();
15255        app.current_service = Service::LambdaApplications;
15256        app.service_selected = true;
15257        app.mode = Mode::Normal;
15258
15259        app.lambda_application_state.table.items = vec![LambdaApplication {
15260            name: "test-app".to_string(),
15261            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
15262            description: "Test application".to_string(),
15263            status: "CREATE_COMPLETE".to_string(),
15264            last_modified: "2024-01-01".to_string(),
15265        }];
15266
15267        // Select application and switch to Deployments tab
15268        app.handle_action(Action::Select);
15269        app.handle_action(Action::NextDetailTab);
15270        assert_eq!(
15271            app.lambda_application_state.detail_tab,
15272            LambdaApplicationDetailTab::Deployments
15273        );
15274
15275        // Verify deployments were loaded
15276        assert!(!app.lambda_application_state.deployments.items.is_empty());
15277
15278        // Test filter focus cycling
15279        app.mode = Mode::FilterInput;
15280        assert_eq!(
15281            app.lambda_application_state.deployment_input_focus,
15282            InputFocus::Filter
15283        );
15284
15285        app.handle_action(Action::NextFilterFocus);
15286        assert_eq!(
15287            app.lambda_application_state.deployment_input_focus,
15288            InputFocus::Pagination
15289        );
15290
15291        app.handle_action(Action::PrevFilterFocus);
15292        assert_eq!(
15293            app.lambda_application_state.deployment_input_focus,
15294            InputFocus::Filter
15295        );
15296    }
15297
15298    #[test]
15299    fn test_application_resource_expansion() {
15300        use crate::lambda::Application as LambdaApplication;
15301        use LambdaApplicationDetailTab;
15302
15303        let mut app = test_app();
15304        app.current_service = Service::LambdaApplications;
15305        app.service_selected = true;
15306        app.mode = Mode::Normal;
15307
15308        app.lambda_application_state.table.items = vec![LambdaApplication {
15309            name: "test-app".to_string(),
15310            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
15311            description: "Test application".to_string(),
15312            status: "CREATE_COMPLETE".to_string(),
15313            last_modified: "2024-01-01".to_string(),
15314        }];
15315
15316        // Select application (Overview tab by default)
15317        app.handle_action(Action::Select);
15318        assert_eq!(
15319            app.lambda_application_state.detail_tab,
15320            LambdaApplicationDetailTab::Overview
15321        );
15322
15323        // Expand resource
15324        app.handle_action(Action::NextPane);
15325        assert_eq!(
15326            app.lambda_application_state.resources.expanded_item,
15327            Some(0)
15328        );
15329
15330        // Collapse resource
15331        app.handle_action(Action::PrevPane);
15332        assert_eq!(app.lambda_application_state.resources.expanded_item, None);
15333    }
15334
15335    #[test]
15336    fn test_application_deployment_expansion() {
15337        use crate::lambda::Application as LambdaApplication;
15338        use LambdaApplicationDetailTab;
15339
15340        let mut app = test_app();
15341        app.current_service = Service::LambdaApplications;
15342        app.service_selected = true;
15343        app.mode = Mode::Normal;
15344
15345        app.lambda_application_state.table.items = vec![LambdaApplication {
15346            name: "test-app".to_string(),
15347            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
15348            description: "Test application".to_string(),
15349            status: "CREATE_COMPLETE".to_string(),
15350            last_modified: "2024-01-01".to_string(),
15351        }];
15352
15353        // Select application and switch to Deployments tab
15354        app.handle_action(Action::Select);
15355        app.handle_action(Action::NextDetailTab);
15356        assert_eq!(
15357            app.lambda_application_state.detail_tab,
15358            LambdaApplicationDetailTab::Deployments
15359        );
15360
15361        // Expand deployment
15362        app.handle_action(Action::NextPane);
15363        assert_eq!(
15364            app.lambda_application_state.deployments.expanded_item,
15365            Some(0)
15366        );
15367
15368        // Collapse deployment
15369        app.handle_action(Action::PrevPane);
15370        assert_eq!(app.lambda_application_state.deployments.expanded_item, None);
15371    }
15372
15373    #[test]
15374    fn test_s3_nested_prefix_expansion() {
15375        use crate::s3::Bucket;
15376        use S3Object;
15377
15378        let mut app = test_app();
15379        app.current_service = Service::S3Buckets;
15380        app.service_selected = true;
15381        app.mode = Mode::Normal;
15382
15383        // Setup bucket with nested prefixes (2 levels)
15384        app.s3_state.buckets.items = vec![Bucket {
15385            name: "test-bucket".to_string(),
15386            region: "us-east-1".to_string(),
15387            creation_date: "2024-01-01".to_string(),
15388        }];
15389
15390        // Level 1: bucket preview
15391        app.s3_state.bucket_preview.insert(
15392            "test-bucket".to_string(),
15393            vec![S3Object {
15394                key: "level1/".to_string(),
15395                size: 0,
15396                last_modified: "".to_string(),
15397                is_prefix: true,
15398                storage_class: "".to_string(),
15399            }],
15400        );
15401
15402        // Level 2: nested prefix
15403        app.s3_state.prefix_preview.insert(
15404            "level1/".to_string(),
15405            vec![S3Object {
15406                key: "level1/level2/".to_string(),
15407                size: 0,
15408                last_modified: "".to_string(),
15409                is_prefix: true,
15410                storage_class: "".to_string(),
15411            }],
15412        );
15413
15414        // Expand bucket (row 0)
15415        app.s3_state.selected_row = 0;
15416        app.handle_action(Action::NextPane);
15417        assert!(app.s3_state.expanded_prefixes.contains("test-bucket"));
15418
15419        // Expand level1/ (row 1)
15420        app.s3_state.selected_row = 1;
15421        app.handle_action(Action::NextPane);
15422        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
15423
15424        // Expand level2/ (row 2) - verifies nested expansion works
15425        app.s3_state.selected_row = 2;
15426        app.handle_action(Action::NextPane);
15427        assert!(app.s3_state.expanded_prefixes.contains("level1/level2/"));
15428
15429        // Verify all are still expanded
15430        assert!(app.s3_state.expanded_prefixes.contains("test-bucket"));
15431        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
15432    }
15433
15434    #[test]
15435    fn test_s3_nested_prefix_collapse() {
15436        use crate::s3::Bucket;
15437        use S3Object;
15438
15439        let mut app = test_app();
15440        app.current_service = Service::S3Buckets;
15441        app.service_selected = true;
15442        app.mode = Mode::Normal;
15443
15444        app.s3_state.buckets.items = vec![Bucket {
15445            name: "test-bucket".to_string(),
15446            region: "us-east-1".to_string(),
15447            creation_date: "2024-01-01".to_string(),
15448        }];
15449
15450        app.s3_state.bucket_preview.insert(
15451            "test-bucket".to_string(),
15452            vec![S3Object {
15453                key: "level1/".to_string(),
15454                size: 0,
15455                last_modified: "".to_string(),
15456                is_prefix: true,
15457                storage_class: "".to_string(),
15458            }],
15459        );
15460
15461        app.s3_state.prefix_preview.insert(
15462            "level1/".to_string(),
15463            vec![S3Object {
15464                key: "level1/level2/".to_string(),
15465                size: 0,
15466                last_modified: "".to_string(),
15467                is_prefix: true,
15468                storage_class: "".to_string(),
15469            }],
15470        );
15471
15472        // Pre-expand all levels
15473        app.s3_state
15474            .expanded_prefixes
15475            .insert("test-bucket".to_string());
15476        app.s3_state.expanded_prefixes.insert("level1/".to_string());
15477        app.s3_state
15478            .expanded_prefixes
15479            .insert("level1/level2/".to_string());
15480
15481        // Collapse level2/ (row 2)
15482        app.s3_state.selected_row = 2;
15483        app.handle_action(Action::PrevPane);
15484        assert!(!app.s3_state.expanded_prefixes.contains("level1/level2/"));
15485        assert!(app.s3_state.expanded_prefixes.contains("level1/")); // Parent still expanded
15486
15487        // Collapse level1/ (row 1)
15488        app.s3_state.selected_row = 1;
15489        app.handle_action(Action::PrevPane);
15490        assert!(!app.s3_state.expanded_prefixes.contains("level1/"));
15491        assert!(app.s3_state.expanded_prefixes.contains("test-bucket")); // Bucket still expanded
15492
15493        // Collapse bucket (row 0)
15494        app.s3_state.selected_row = 0;
15495        app.handle_action(Action::PrevPane);
15496        assert!(!app.s3_state.expanded_prefixes.contains("test-bucket"));
15497    }
15498}
15499
15500#[cfg(test)]
15501mod sqs_tests {
15502    use super::*;
15503    use test_helpers::*;
15504
15505    #[test]
15506    fn test_sqs_filter_input() {
15507        let mut app = test_app();
15508        app.current_service = Service::SqsQueues;
15509        app.service_selected = true;
15510        app.mode = Mode::FilterInput;
15511
15512        app.handle_action(Action::FilterInput('t'));
15513        app.handle_action(Action::FilterInput('e'));
15514        app.handle_action(Action::FilterInput('s'));
15515        app.handle_action(Action::FilterInput('t'));
15516        assert_eq!(app.sqs_state.queues.filter, "test");
15517
15518        app.handle_action(Action::FilterBackspace);
15519        assert_eq!(app.sqs_state.queues.filter, "tes");
15520    }
15521
15522    #[test]
15523    fn test_sqs_start_filter() {
15524        let mut app = test_app();
15525        app.current_service = Service::SqsQueues;
15526        app.service_selected = true;
15527        app.mode = Mode::Normal;
15528
15529        app.handle_action(Action::StartFilter);
15530        assert_eq!(app.mode, Mode::FilterInput);
15531        assert_eq!(app.sqs_state.input_focus, InputFocus::Filter);
15532    }
15533
15534    #[test]
15535    fn test_sqs_filter_focus_cycling() {
15536        let mut app = test_app();
15537        app.current_service = Service::SqsQueues;
15538        app.service_selected = true;
15539        app.mode = Mode::FilterInput;
15540        app.sqs_state.input_focus = InputFocus::Filter;
15541
15542        app.handle_action(Action::NextFilterFocus);
15543        assert_eq!(app.sqs_state.input_focus, InputFocus::Pagination);
15544
15545        app.handle_action(Action::NextFilterFocus);
15546        assert_eq!(app.sqs_state.input_focus, InputFocus::Filter);
15547
15548        app.handle_action(Action::PrevFilterFocus);
15549        assert_eq!(app.sqs_state.input_focus, InputFocus::Pagination);
15550    }
15551
15552    #[test]
15553    fn test_sqs_navigation() {
15554        let mut app = test_app();
15555        app.current_service = Service::SqsQueues;
15556        app.service_selected = true;
15557        app.mode = Mode::Normal;
15558        app.sqs_state.queues.items = (0..10)
15559            .map(|i| SqsQueue {
15560                name: format!("queue{}", i),
15561                url: String::new(),
15562                queue_type: "Standard".to_string(),
15563                created_timestamp: String::new(),
15564                messages_available: "0".to_string(),
15565                messages_in_flight: "0".to_string(),
15566                encryption: "Disabled".to_string(),
15567                content_based_deduplication: "Disabled".to_string(),
15568                last_modified_timestamp: String::new(),
15569                visibility_timeout: String::new(),
15570                message_retention_period: String::new(),
15571                maximum_message_size: String::new(),
15572                delivery_delay: String::new(),
15573                receive_message_wait_time: String::new(),
15574                high_throughput_fifo: "N/A".to_string(),
15575                deduplication_scope: "N/A".to_string(),
15576                fifo_throughput_limit: "N/A".to_string(),
15577                dead_letter_queue: "-".to_string(),
15578                messages_delayed: "0".to_string(),
15579                redrive_allow_policy: "-".to_string(),
15580                redrive_policy: "".to_string(),
15581                redrive_task_id: "-".to_string(),
15582                redrive_task_start_time: "-".to_string(),
15583                redrive_task_status: "-".to_string(),
15584                redrive_task_percent: "-".to_string(),
15585                redrive_task_destination: "-".to_string(),
15586            })
15587            .collect();
15588
15589        app.handle_action(Action::NextItem);
15590        assert_eq!(app.sqs_state.queues.selected, 1);
15591
15592        app.handle_action(Action::PrevItem);
15593        assert_eq!(app.sqs_state.queues.selected, 0);
15594    }
15595
15596    #[test]
15597    fn test_sqs_page_navigation() {
15598        let mut app = test_app();
15599        app.current_service = Service::SqsQueues;
15600        app.service_selected = true;
15601        app.mode = Mode::Normal;
15602        app.sqs_state.queues.items = (0..100)
15603            .map(|i| SqsQueue {
15604                name: format!("queue{}", i),
15605                url: String::new(),
15606                queue_type: "Standard".to_string(),
15607                created_timestamp: String::new(),
15608                messages_available: "0".to_string(),
15609                messages_in_flight: "0".to_string(),
15610                encryption: "Disabled".to_string(),
15611                content_based_deduplication: "Disabled".to_string(),
15612                last_modified_timestamp: String::new(),
15613                visibility_timeout: String::new(),
15614                message_retention_period: String::new(),
15615                maximum_message_size: String::new(),
15616                delivery_delay: String::new(),
15617                receive_message_wait_time: String::new(),
15618                high_throughput_fifo: "N/A".to_string(),
15619                deduplication_scope: "N/A".to_string(),
15620                fifo_throughput_limit: "N/A".to_string(),
15621                dead_letter_queue: "-".to_string(),
15622                messages_delayed: "0".to_string(),
15623                redrive_allow_policy: "-".to_string(),
15624                redrive_policy: "".to_string(),
15625                redrive_task_id: "-".to_string(),
15626                redrive_task_start_time: "-".to_string(),
15627                redrive_task_status: "-".to_string(),
15628                redrive_task_percent: "-".to_string(),
15629                redrive_task_destination: "-".to_string(),
15630            })
15631            .collect();
15632
15633        app.handle_action(Action::PageDown);
15634        assert_eq!(app.sqs_state.queues.selected, 10);
15635
15636        app.handle_action(Action::PageUp);
15637        assert_eq!(app.sqs_state.queues.selected, 0);
15638    }
15639
15640    #[test]
15641    fn test_sqs_queue_expansion() {
15642        let mut app = test_app();
15643        app.current_service = Service::SqsQueues;
15644        app.service_selected = true;
15645        app.sqs_state.queues.items = vec![SqsQueue {
15646            name: "my-queue".to_string(),
15647            url: "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string(),
15648            queue_type: "Standard".to_string(),
15649            created_timestamp: "2023-01-01".to_string(),
15650            messages_available: "5".to_string(),
15651            messages_in_flight: "2".to_string(),
15652            encryption: "Enabled".to_string(),
15653            content_based_deduplication: "Disabled".to_string(),
15654            last_modified_timestamp: "2023-01-02".to_string(),
15655            visibility_timeout: "30".to_string(),
15656            message_retention_period: "345600".to_string(),
15657            maximum_message_size: "262144".to_string(),
15658            delivery_delay: "0".to_string(),
15659            receive_message_wait_time: "0".to_string(),
15660            high_throughput_fifo: "N/A".to_string(),
15661            deduplication_scope: "N/A".to_string(),
15662            fifo_throughput_limit: "N/A".to_string(),
15663            dead_letter_queue: "-".to_string(),
15664            messages_delayed: "0".to_string(),
15665            redrive_allow_policy: "-".to_string(),
15666            redrive_policy: "".to_string(),
15667            redrive_task_id: "-".to_string(),
15668            redrive_task_start_time: "-".to_string(),
15669            redrive_task_status: "-".to_string(),
15670            redrive_task_percent: "-".to_string(),
15671            redrive_task_destination: "-".to_string(),
15672        }];
15673        app.sqs_state.queues.selected = 0;
15674
15675        assert_eq!(app.sqs_state.queues.expanded_item, None);
15676
15677        // Right arrow expands
15678        app.handle_action(Action::NextPane);
15679        assert_eq!(app.sqs_state.queues.expanded_item, Some(0));
15680
15681        // Right arrow again keeps it expanded
15682        app.handle_action(Action::NextPane);
15683        assert_eq!(app.sqs_state.queues.expanded_item, Some(0));
15684
15685        // Left arrow collapses
15686        app.handle_action(Action::PrevPane);
15687        assert_eq!(app.sqs_state.queues.expanded_item, None);
15688
15689        // Left arrow again keeps it collapsed
15690        app.handle_action(Action::PrevPane);
15691        assert_eq!(app.sqs_state.queues.expanded_item, None);
15692    }
15693
15694    #[test]
15695    fn test_sqs_column_toggle() {
15696        use crate::sqs::queue::Column as SqsColumn;
15697        let mut app = test_app();
15698        app.current_service = Service::SqsQueues;
15699        app.service_selected = true;
15700        app.mode = Mode::ColumnSelector;
15701
15702        // Start with all columns visible
15703        app.sqs_visible_column_ids = SqsColumn::ids();
15704        let initial_count = app.sqs_visible_column_ids.len();
15705
15706        // Select first column (index 0) and toggle it
15707        app.column_selector_index = 0;
15708        app.handle_action(Action::ToggleColumn);
15709
15710        // First column should be removed
15711        assert_eq!(app.sqs_visible_column_ids.len(), initial_count - 1);
15712        assert!(!app.sqs_visible_column_ids.contains(&SqsColumn::Name.id()));
15713
15714        // Toggle it back
15715        app.handle_action(Action::ToggleColumn);
15716        assert_eq!(app.sqs_visible_column_ids.len(), initial_count);
15717        assert!(app.sqs_visible_column_ids.contains(&SqsColumn::Name.id()));
15718    }
15719
15720    #[test]
15721    fn test_sqs_column_selector_navigation() {
15722        let mut app = test_app();
15723        app.current_service = Service::SqsQueues;
15724        app.service_selected = true;
15725        app.mode = Mode::ColumnSelector;
15726        app.column_selector_index = 0;
15727
15728        // Should be able to navigate through all columns
15729        let max_index = app.sqs_column_ids.len() - 1;
15730
15731        // Navigate to last column
15732        for _ in 0..max_index {
15733            app.handle_action(Action::NextItem);
15734        }
15735        assert_eq!(app.column_selector_index, max_index);
15736
15737        // Navigate back to first
15738        for _ in 0..max_index {
15739            app.handle_action(Action::PrevItem);
15740        }
15741        assert_eq!(app.column_selector_index, 0);
15742    }
15743
15744    #[test]
15745    fn test_sqs_queue_selection() {
15746        let mut app = test_app();
15747        app.current_service = Service::SqsQueues;
15748        app.service_selected = true;
15749        app.mode = Mode::Normal;
15750        app.sqs_state.queues.items = vec![SqsQueue {
15751            name: "my-queue".to_string(),
15752            url: "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string(),
15753            queue_type: "Standard".to_string(),
15754            created_timestamp: "2023-01-01".to_string(),
15755            messages_available: "5".to_string(),
15756            messages_in_flight: "2".to_string(),
15757            encryption: "Enabled".to_string(),
15758            content_based_deduplication: "Disabled".to_string(),
15759            last_modified_timestamp: "2023-01-02".to_string(),
15760            visibility_timeout: "30".to_string(),
15761            message_retention_period: "345600".to_string(),
15762            maximum_message_size: "262144".to_string(),
15763            delivery_delay: "0".to_string(),
15764            receive_message_wait_time: "0".to_string(),
15765            high_throughput_fifo: "N/A".to_string(),
15766            deduplication_scope: "N/A".to_string(),
15767            fifo_throughput_limit: "N/A".to_string(),
15768            dead_letter_queue: "-".to_string(),
15769            messages_delayed: "0".to_string(),
15770            redrive_allow_policy: "-".to_string(),
15771            redrive_policy: "".to_string(),
15772            redrive_task_id: "-".to_string(),
15773            redrive_task_start_time: "-".to_string(),
15774            redrive_task_status: "-".to_string(),
15775            redrive_task_percent: "-".to_string(),
15776            redrive_task_destination: "-".to_string(),
15777        }];
15778        app.sqs_state.queues.selected = 0;
15779
15780        assert_eq!(app.sqs_state.current_queue, None);
15781
15782        // Select queue
15783        app.handle_action(Action::Select);
15784        assert_eq!(
15785            app.sqs_state.current_queue,
15786            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string())
15787        );
15788
15789        // Go back
15790        app.handle_action(Action::GoBack);
15791        assert_eq!(app.sqs_state.current_queue, None);
15792    }
15793
15794    #[test]
15795    fn test_sqs_lambda_triggers_expand_collapse() {
15796        let mut app = test_app();
15797        app.current_service = Service::SqsQueues;
15798        app.service_selected = true;
15799        app.sqs_state.current_queue =
15800            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
15801        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15802        app.sqs_state.triggers.items = vec![LambdaTrigger {
15803            uuid: "test-uuid".to_string(),
15804            arn: "arn:aws:lambda:us-east-1:123456789012:function:test".to_string(),
15805            status: "Enabled".to_string(),
15806            last_modified: "2024-01-01T00:00:00Z".to_string(),
15807        }];
15808        app.sqs_state.triggers.selected = 0;
15809
15810        assert_eq!(app.sqs_state.triggers.expanded_item, None);
15811
15812        // Right arrow expands
15813        app.handle_action(Action::NextPane);
15814        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
15815
15816        // Left arrow collapses
15817        app.handle_action(Action::PrevPane);
15818        assert_eq!(app.sqs_state.triggers.expanded_item, None);
15819    }
15820
15821    #[test]
15822    fn test_sqs_lambda_triggers_expand_toggle() {
15823        let mut app = test_app();
15824        app.current_service = Service::SqsQueues;
15825        app.service_selected = true;
15826        app.sqs_state.current_queue =
15827            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
15828        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15829        app.sqs_state.triggers.items = vec![LambdaTrigger {
15830            uuid: "test-uuid".to_string(),
15831            arn: "arn:aws:lambda:us-east-1:123456789012:function:test".to_string(),
15832            status: "Enabled".to_string(),
15833            last_modified: "2024-01-01T00:00:00Z".to_string(),
15834        }];
15835        app.sqs_state.triggers.selected = 0;
15836
15837        // Expand
15838        app.handle_action(Action::NextPane);
15839        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
15840
15841        // Toggle collapses
15842        app.handle_action(Action::NextPane);
15843        assert_eq!(app.sqs_state.triggers.expanded_item, None);
15844
15845        // Toggle expands again
15846        app.handle_action(Action::NextPane);
15847        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
15848    }
15849
15850    #[test]
15851    fn test_sqs_lambda_triggers_sorted_by_last_modified_asc() {
15852        use crate::ui::sqs::filtered_lambda_triggers;
15853
15854        let mut app = test_app();
15855        app.current_service = Service::SqsQueues;
15856        app.service_selected = true;
15857        app.sqs_state.current_queue =
15858            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
15859        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15860        app.sqs_state.triggers.items = vec![
15861            LambdaTrigger {
15862                uuid: "uuid-3".to_string(),
15863                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-3".to_string(),
15864                status: "Enabled".to_string(),
15865                last_modified: "2024-03-01T00:00:00Z".to_string(),
15866            },
15867            LambdaTrigger {
15868                uuid: "uuid-1".to_string(),
15869                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-1".to_string(),
15870                status: "Enabled".to_string(),
15871                last_modified: "2024-01-01T00:00:00Z".to_string(),
15872            },
15873            LambdaTrigger {
15874                uuid: "uuid-2".to_string(),
15875                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-2".to_string(),
15876                status: "Enabled".to_string(),
15877                last_modified: "2024-02-01T00:00:00Z".to_string(),
15878            },
15879        ];
15880
15881        let sorted = filtered_lambda_triggers(&app);
15882
15883        // Should be sorted by last_modified ASC
15884        assert_eq!(sorted.len(), 3);
15885        assert_eq!(sorted[0].uuid, "uuid-1");
15886        assert_eq!(sorted[0].last_modified, "2024-01-01T00:00:00Z");
15887        assert_eq!(sorted[1].uuid, "uuid-2");
15888        assert_eq!(sorted[1].last_modified, "2024-02-01T00:00:00Z");
15889        assert_eq!(sorted[2].uuid, "uuid-3");
15890        assert_eq!(sorted[2].last_modified, "2024-03-01T00:00:00Z");
15891    }
15892
15893    #[test]
15894    fn test_sqs_lambda_triggers_filter_input() {
15895        let mut app = test_app();
15896        app.current_service = Service::SqsQueues;
15897        app.service_selected = true;
15898        app.mode = Mode::FilterInput;
15899        app.sqs_state.current_queue =
15900            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
15901        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15902        app.sqs_state.input_focus = InputFocus::Filter;
15903
15904        assert_eq!(app.sqs_state.triggers.filter, "");
15905
15906        // Type characters
15907        app.handle_action(Action::FilterInput('t'));
15908        assert_eq!(app.sqs_state.triggers.filter, "t");
15909
15910        app.handle_action(Action::FilterInput('e'));
15911        assert_eq!(app.sqs_state.triggers.filter, "te");
15912
15913        app.handle_action(Action::FilterInput('s'));
15914        assert_eq!(app.sqs_state.triggers.filter, "tes");
15915
15916        app.handle_action(Action::FilterInput('t'));
15917        assert_eq!(app.sqs_state.triggers.filter, "test");
15918
15919        // Backspace
15920        app.handle_action(Action::FilterBackspace);
15921        assert_eq!(app.sqs_state.triggers.filter, "tes");
15922    }
15923
15924    #[test]
15925    fn test_sqs_lambda_triggers_filter_applied() {
15926        use crate::ui::sqs::filtered_lambda_triggers;
15927
15928        let mut app = test_app();
15929        app.current_service = Service::SqsQueues;
15930        app.service_selected = true;
15931        app.sqs_state.current_queue =
15932            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
15933        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15934        app.sqs_state.triggers.items = vec![
15935            LambdaTrigger {
15936                uuid: "uuid-1".to_string(),
15937                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-alpha".to_string(),
15938                status: "Enabled".to_string(),
15939                last_modified: "2024-01-01T00:00:00Z".to_string(),
15940            },
15941            LambdaTrigger {
15942                uuid: "uuid-2".to_string(),
15943                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-beta".to_string(),
15944                status: "Enabled".to_string(),
15945                last_modified: "2024-02-01T00:00:00Z".to_string(),
15946            },
15947            LambdaTrigger {
15948                uuid: "uuid-3".to_string(),
15949                arn: "arn:aws:lambda:us-east-1:123456789012:function:prod-gamma".to_string(),
15950                status: "Enabled".to_string(),
15951                last_modified: "2024-03-01T00:00:00Z".to_string(),
15952            },
15953        ];
15954
15955        // No filter - all items
15956        let filtered = filtered_lambda_triggers(&app);
15957        assert_eq!(filtered.len(), 3);
15958
15959        // Filter by "alpha"
15960        app.sqs_state.triggers.filter = "alpha".to_string();
15961        let filtered = filtered_lambda_triggers(&app);
15962        assert_eq!(filtered.len(), 1);
15963        assert_eq!(
15964            filtered[0].arn,
15965            "arn:aws:lambda:us-east-1:123456789012:function:test-alpha"
15966        );
15967
15968        // Filter by "test" - matches 2
15969        app.sqs_state.triggers.filter = "test".to_string();
15970        let filtered = filtered_lambda_triggers(&app);
15971        assert_eq!(filtered.len(), 2);
15972        assert_eq!(
15973            filtered[0].arn,
15974            "arn:aws:lambda:us-east-1:123456789012:function:test-alpha"
15975        );
15976        assert_eq!(
15977            filtered[1].arn,
15978            "arn:aws:lambda:us-east-1:123456789012:function:test-beta"
15979        );
15980
15981        // Filter by uuid
15982        app.sqs_state.triggers.filter = "uuid-3".to_string();
15983        let filtered = filtered_lambda_triggers(&app);
15984        assert_eq!(filtered.len(), 1);
15985        assert_eq!(filtered[0].uuid, "uuid-3");
15986    }
15987
15988    #[test]
15989    fn test_sqs_triggers_navigation() {
15990        let mut app = test_app();
15991        app.service_selected = true;
15992        app.mode = Mode::Normal;
15993        app.current_service = Service::SqsQueues;
15994        app.sqs_state.current_queue = Some("test-queue".to_string());
15995        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
15996        app.sqs_state.triggers.items = vec![
15997            LambdaTrigger {
15998                uuid: "1".to_string(),
15999                arn: "arn1".to_string(),
16000                status: "Enabled".to_string(),
16001                last_modified: "2024-01-01".to_string(),
16002            },
16003            LambdaTrigger {
16004                uuid: "2".to_string(),
16005                arn: "arn2".to_string(),
16006                status: "Enabled".to_string(),
16007                last_modified: "2024-01-02".to_string(),
16008            },
16009        ];
16010
16011        assert_eq!(app.sqs_state.triggers.selected, 0);
16012        app.next_item();
16013        assert_eq!(app.sqs_state.triggers.selected, 1);
16014        app.prev_item();
16015        assert_eq!(app.sqs_state.triggers.selected, 0);
16016    }
16017
16018    #[test]
16019    fn test_sqs_pipes_navigation() {
16020        let mut app = test_app();
16021        app.service_selected = true;
16022        app.mode = Mode::Normal;
16023        app.current_service = Service::SqsQueues;
16024        app.sqs_state.current_queue = Some("test-queue".to_string());
16025        app.sqs_state.detail_tab = SqsQueueDetailTab::EventBridgePipes;
16026        app.sqs_state.pipes.items = vec![
16027            EventBridgePipe {
16028                name: "pipe1".to_string(),
16029                status: "RUNNING".to_string(),
16030                target: "target1".to_string(),
16031                last_modified: "2024-01-01".to_string(),
16032            },
16033            EventBridgePipe {
16034                name: "pipe2".to_string(),
16035                status: "RUNNING".to_string(),
16036                target: "target2".to_string(),
16037                last_modified: "2024-01-02".to_string(),
16038            },
16039        ];
16040
16041        assert_eq!(app.sqs_state.pipes.selected, 0);
16042        app.next_item();
16043        assert_eq!(app.sqs_state.pipes.selected, 1);
16044        app.prev_item();
16045        assert_eq!(app.sqs_state.pipes.selected, 0);
16046    }
16047
16048    #[test]
16049    fn test_sqs_tags_navigation() {
16050        let mut app = test_app();
16051        app.service_selected = true;
16052        app.mode = Mode::Normal;
16053        app.current_service = Service::SqsQueues;
16054        app.sqs_state.current_queue = Some("test-queue".to_string());
16055        app.sqs_state.detail_tab = SqsQueueDetailTab::Tagging;
16056        app.sqs_state.tags.items = vec![
16057            SqsQueueTag {
16058                key: "Env".to_string(),
16059                value: "prod".to_string(),
16060            },
16061            SqsQueueTag {
16062                key: "Team".to_string(),
16063                value: "backend".to_string(),
16064            },
16065        ];
16066
16067        assert_eq!(app.sqs_state.tags.selected, 0);
16068        app.next_item();
16069        assert_eq!(app.sqs_state.tags.selected, 1);
16070        app.prev_item();
16071        assert_eq!(app.sqs_state.tags.selected, 0);
16072    }
16073
16074    #[test]
16075    fn test_sqs_queues_navigation() {
16076        let mut app = test_app();
16077        app.service_selected = true;
16078        app.mode = Mode::Normal;
16079        app.current_service = Service::SqsQueues;
16080        app.sqs_state.queues.items = vec![
16081            SqsQueue {
16082                name: "queue1".to_string(),
16083                url: "url1".to_string(),
16084                queue_type: "Standard".to_string(),
16085                created_timestamp: "".to_string(),
16086                messages_available: "0".to_string(),
16087                messages_in_flight: "0".to_string(),
16088                encryption: "Disabled".to_string(),
16089                content_based_deduplication: "Disabled".to_string(),
16090                last_modified_timestamp: "".to_string(),
16091                visibility_timeout: "".to_string(),
16092                message_retention_period: "".to_string(),
16093                maximum_message_size: "".to_string(),
16094                delivery_delay: "".to_string(),
16095                receive_message_wait_time: "".to_string(),
16096                high_throughput_fifo: "-".to_string(),
16097                deduplication_scope: "-".to_string(),
16098                fifo_throughput_limit: "-".to_string(),
16099                dead_letter_queue: "-".to_string(),
16100                messages_delayed: "0".to_string(),
16101                redrive_allow_policy: "-".to_string(),
16102                redrive_policy: "".to_string(),
16103                redrive_task_id: "-".to_string(),
16104                redrive_task_start_time: "-".to_string(),
16105                redrive_task_status: "-".to_string(),
16106                redrive_task_percent: "-".to_string(),
16107                redrive_task_destination: "-".to_string(),
16108            },
16109            SqsQueue {
16110                name: "queue2".to_string(),
16111                url: "url2".to_string(),
16112                queue_type: "Standard".to_string(),
16113                created_timestamp: "".to_string(),
16114                messages_available: "0".to_string(),
16115                messages_in_flight: "0".to_string(),
16116                encryption: "Disabled".to_string(),
16117                content_based_deduplication: "Disabled".to_string(),
16118                last_modified_timestamp: "".to_string(),
16119                visibility_timeout: "".to_string(),
16120                message_retention_period: "".to_string(),
16121                maximum_message_size: "".to_string(),
16122                delivery_delay: "".to_string(),
16123                receive_message_wait_time: "".to_string(),
16124                high_throughput_fifo: "-".to_string(),
16125                deduplication_scope: "-".to_string(),
16126                fifo_throughput_limit: "-".to_string(),
16127                dead_letter_queue: "-".to_string(),
16128                messages_delayed: "0".to_string(),
16129                redrive_allow_policy: "-".to_string(),
16130                redrive_policy: "".to_string(),
16131                redrive_task_id: "-".to_string(),
16132                redrive_task_start_time: "-".to_string(),
16133                redrive_task_status: "-".to_string(),
16134                redrive_task_percent: "-".to_string(),
16135                redrive_task_destination: "-".to_string(),
16136            },
16137        ];
16138
16139        assert_eq!(app.sqs_state.queues.selected, 0);
16140        app.next_item();
16141        assert_eq!(app.sqs_state.queues.selected, 1);
16142        app.prev_item();
16143        assert_eq!(app.sqs_state.queues.selected, 0);
16144    }
16145
16146    #[test]
16147    fn test_sqs_subscriptions_navigation() {
16148        let mut app = test_app();
16149        app.service_selected = true;
16150        app.mode = Mode::Normal;
16151        app.current_service = Service::SqsQueues;
16152        app.sqs_state.current_queue = Some("test-queue".to_string());
16153        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
16154        app.sqs_state.subscriptions.items = vec![
16155            SnsSubscription {
16156                subscription_arn: "arn:aws:sns:us-east-1:123:sub1".to_string(),
16157                topic_arn: "arn:aws:sns:us-east-1:123:topic1".to_string(),
16158            },
16159            SnsSubscription {
16160                subscription_arn: "arn:aws:sns:us-east-1:123:sub2".to_string(),
16161                topic_arn: "arn:aws:sns:us-east-1:123:topic2".to_string(),
16162            },
16163        ];
16164
16165        assert_eq!(app.sqs_state.subscriptions.selected, 0);
16166        app.next_item();
16167        assert_eq!(app.sqs_state.subscriptions.selected, 1);
16168        app.prev_item();
16169        assert_eq!(app.sqs_state.subscriptions.selected, 0);
16170    }
16171
16172    #[test]
16173    fn test_sqs_subscription_region_dropdown_navigation() {
16174        let mut app = test_app();
16175        app.service_selected = true;
16176        app.mode = Mode::FilterInput;
16177        app.current_service = Service::SqsQueues;
16178        app.sqs_state.current_queue = Some("test-queue".to_string());
16179        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
16180        app.sqs_state.input_focus = InputFocus::Dropdown("SubscriptionRegion");
16181
16182        assert_eq!(app.sqs_state.subscription_region_selected, 0);
16183        app.next_item();
16184        assert_eq!(app.sqs_state.subscription_region_selected, 1);
16185        app.next_item();
16186        assert_eq!(app.sqs_state.subscription_region_selected, 2);
16187        app.prev_item();
16188        assert_eq!(app.sqs_state.subscription_region_selected, 1);
16189        app.prev_item();
16190        assert_eq!(app.sqs_state.subscription_region_selected, 0);
16191    }
16192
16193    #[test]
16194    fn test_sqs_subscription_region_selection() {
16195        let mut app = test_app();
16196        app.service_selected = true;
16197        app.mode = Mode::FilterInput;
16198        app.current_service = Service::SqsQueues;
16199        app.sqs_state.current_queue = Some("test-queue".to_string());
16200        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
16201        app.sqs_state.input_focus = InputFocus::Dropdown("SubscriptionRegion");
16202        app.sqs_state.subscription_region_selected = 2; // us-west-1
16203
16204        assert_eq!(app.sqs_state.subscription_region_filter, "");
16205        app.handle_action(Action::ApplyFilter);
16206        assert_eq!(app.sqs_state.subscription_region_filter, "us-west-1");
16207        assert_eq!(app.mode, Mode::Normal);
16208    }
16209
16210    #[test]
16211    fn test_sqs_subscription_region_change_resets_selection() {
16212        let mut app = test_app();
16213        app.service_selected = true;
16214        app.mode = Mode::FilterInput;
16215        app.current_service = Service::SqsQueues;
16216        app.sqs_state.current_queue = Some("test-queue".to_string());
16217        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
16218        app.sqs_state.input_focus = InputFocus::Dropdown("SubscriptionRegion");
16219        app.sqs_state.subscription_region_selected = 0;
16220        app.sqs_state.subscriptions.selected = 5;
16221
16222        app.handle_action(Action::NextItem);
16223
16224        assert_eq!(app.sqs_state.subscription_region_selected, 1);
16225        assert_eq!(app.sqs_state.subscriptions.selected, 0);
16226    }
16227
16228    #[test]
16229    fn test_s3_object_filter_resets_selection() {
16230        let mut app = test_app();
16231        app.service_selected = true;
16232        app.current_service = Service::S3Buckets;
16233        app.s3_state.current_bucket = Some("test-bucket".to_string());
16234        app.s3_state.selected_row = 5;
16235        app.mode = Mode::FilterInput;
16236
16237        app.handle_action(Action::CloseMenu);
16238
16239        assert_eq!(app.s3_state.selected_row, 0);
16240        assert_eq!(app.mode, Mode::Normal);
16241    }
16242
16243    #[test]
16244    fn test_s3_bucket_filter_resets_selection() {
16245        let mut app = test_app();
16246        app.service_selected = true;
16247        app.current_service = Service::S3Buckets;
16248        app.s3_state.selected_row = 10;
16249        app.mode = Mode::FilterInput;
16250
16251        app.handle_action(Action::CloseMenu);
16252
16253        assert_eq!(app.s3_state.selected_row, 0);
16254        assert_eq!(app.mode, Mode::Normal);
16255    }
16256
16257    #[test]
16258    fn test_s3_selection_stays_in_bounds() {
16259        let mut app = test_app();
16260        app.service_selected = true;
16261        app.current_service = Service::S3Buckets;
16262        app.s3_state.selected_row = 0;
16263        app.s3_state.selected_object = 0;
16264
16265        // Simulate going up from row 0
16266        app.prev_item();
16267
16268        // Should stay at 0, not wrap to negative
16269        assert_eq!(app.s3_state.selected_row, 0);
16270        assert_eq!(app.s3_state.selected_object, 0);
16271    }
16272
16273    #[test]
16274    fn test_cfn_filter_resets_selection() {
16275        let mut app = test_app();
16276        app.service_selected = true;
16277        app.current_service = Service::CloudFormationStacks;
16278        app.cfn_state.table.selected = 10;
16279        app.mode = Mode::FilterInput;
16280
16281        app.handle_action(Action::CloseMenu);
16282
16283        assert_eq!(app.cfn_state.table.selected, 0);
16284        assert_eq!(app.mode, Mode::Normal);
16285    }
16286
16287    #[test]
16288    fn test_lambda_filter_resets_selection() {
16289        let mut app = test_app();
16290        app.service_selected = true;
16291        app.current_service = Service::LambdaFunctions;
16292        app.lambda_state.table.selected = 8;
16293        app.mode = Mode::FilterInput;
16294
16295        app.handle_action(Action::CloseMenu);
16296
16297        assert_eq!(app.lambda_state.table.selected, 0);
16298        assert_eq!(app.mode, Mode::Normal);
16299    }
16300
16301    #[test]
16302    fn test_sqs_filter_resets_selection() {
16303        let mut app = test_app();
16304        app.service_selected = true;
16305        app.current_service = Service::SqsQueues;
16306        app.sqs_state.queues.selected = 7;
16307        app.mode = Mode::FilterInput;
16308
16309        app.handle_action(Action::CloseMenu);
16310
16311        assert_eq!(app.sqs_state.queues.selected, 0);
16312        assert_eq!(app.mode, Mode::Normal);
16313    }
16314
16315    #[test]
16316    fn test_sqs_queues_list_shows_preferences() {
16317        let mut app = test_app();
16318        app.service_selected = true;
16319        app.current_service = Service::SqsQueues;
16320        app.mode = Mode::Normal;
16321
16322        app.handle_action(Action::OpenColumnSelector);
16323
16324        assert_eq!(app.mode, Mode::ColumnSelector);
16325    }
16326
16327    #[test]
16328    fn test_sqs_queue_policies_tab_no_preferences() {
16329        let mut app = test_app();
16330        app.service_selected = true;
16331        app.current_service = Service::SqsQueues;
16332        app.sqs_state.current_queue = Some("test-queue".to_string());
16333        app.sqs_state.detail_tab = SqsQueueDetailTab::QueuePolicies;
16334        app.mode = Mode::Normal;
16335
16336        app.handle_action(Action::OpenColumnSelector);
16337
16338        assert_eq!(app.mode, Mode::Normal);
16339    }
16340
16341    #[test]
16342    fn test_sqs_sns_subscriptions_tab_shows_preferences() {
16343        let mut app = test_app();
16344        app.service_selected = true;
16345        app.current_service = Service::SqsQueues;
16346        app.sqs_state.current_queue = Some("test-queue".to_string());
16347        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
16348        app.mode = Mode::Normal;
16349
16350        app.handle_action(Action::OpenColumnSelector);
16351
16352        assert_eq!(app.mode, Mode::ColumnSelector);
16353    }
16354
16355    #[test]
16356    fn test_sqs_monitoring_tab_no_preferences() {
16357        let mut app = test_app();
16358        app.service_selected = true;
16359        app.current_service = Service::SqsQueues;
16360        app.sqs_state.current_queue = Some("test-queue".to_string());
16361        app.sqs_state.detail_tab = SqsQueueDetailTab::Monitoring;
16362        app.mode = Mode::Normal;
16363
16364        app.handle_action(Action::OpenColumnSelector);
16365
16366        assert_eq!(app.mode, Mode::Normal);
16367    }
16368
16369    #[test]
16370    fn test_cfn_status_filter_change_resets_selection() {
16371        use crate::ui::cfn::STATUS_FILTER;
16372        let mut app = test_app();
16373        app.service_selected = true;
16374        app.current_service = Service::CloudFormationStacks;
16375        app.mode = Mode::FilterInput;
16376        app.cfn_state.input_focus = STATUS_FILTER;
16377        app.cfn_state.status_filter = CfnStatusFilter::All;
16378        app.cfn_state.table.items = vec![
16379            CfnStack {
16380                name: "stack1".to_string(),
16381                stack_id: "id1".to_string(),
16382                status: "CREATE_COMPLETE".to_string(),
16383                created_time: "2024-01-01".to_string(),
16384                updated_time: String::new(),
16385                deleted_time: String::new(),
16386                drift_status: String::new(),
16387                last_drift_check_time: String::new(),
16388                status_reason: String::new(),
16389                description: String::new(),
16390                detailed_status: String::new(),
16391                root_stack: String::new(),
16392                parent_stack: String::new(),
16393                termination_protection: false,
16394                iam_role: String::new(),
16395                tags: Vec::new(),
16396                stack_policy: String::new(),
16397                rollback_monitoring_time: String::new(),
16398                rollback_alarms: Vec::new(),
16399                notification_arns: Vec::new(),
16400            },
16401            CfnStack {
16402                name: "stack2".to_string(),
16403                stack_id: "id2".to_string(),
16404                status: "UPDATE_IN_PROGRESS".to_string(),
16405                created_time: "2024-01-02".to_string(),
16406                updated_time: String::new(),
16407                deleted_time: String::new(),
16408                drift_status: String::new(),
16409                last_drift_check_time: String::new(),
16410                status_reason: String::new(),
16411                description: String::new(),
16412                detailed_status: String::new(),
16413                root_stack: String::new(),
16414                parent_stack: String::new(),
16415                termination_protection: false,
16416                iam_role: String::new(),
16417                tags: Vec::new(),
16418                stack_policy: String::new(),
16419                rollback_monitoring_time: String::new(),
16420                rollback_alarms: Vec::new(),
16421                notification_arns: Vec::new(),
16422            },
16423        ];
16424        app.cfn_state.table.selected = 1;
16425
16426        app.handle_action(Action::NextItem);
16427
16428        assert_eq!(app.cfn_state.status_filter, CfnStatusFilter::Active);
16429        assert_eq!(app.cfn_state.table.selected, 0);
16430    }
16431
16432    #[test]
16433    fn test_cfn_view_nested_toggle_resets_selection() {
16434        use crate::ui::cfn::VIEW_NESTED;
16435        let mut app = test_app();
16436        app.service_selected = true;
16437        app.current_service = Service::CloudFormationStacks;
16438        app.mode = Mode::FilterInput;
16439        app.cfn_state.input_focus = VIEW_NESTED;
16440        app.cfn_state.view_nested = false;
16441        app.cfn_state.table.items = vec![CfnStack {
16442            name: "stack1".to_string(),
16443            stack_id: "id1".to_string(),
16444            status: "CREATE_COMPLETE".to_string(),
16445            created_time: "2024-01-01".to_string(),
16446            updated_time: String::new(),
16447            deleted_time: String::new(),
16448            drift_status: String::new(),
16449            last_drift_check_time: String::new(),
16450            status_reason: String::new(),
16451            description: String::new(),
16452            detailed_status: String::new(),
16453            root_stack: String::new(),
16454            parent_stack: String::new(),
16455            termination_protection: false,
16456            iam_role: String::new(),
16457            tags: Vec::new(),
16458            stack_policy: String::new(),
16459            rollback_monitoring_time: String::new(),
16460            rollback_alarms: Vec::new(),
16461            notification_arns: Vec::new(),
16462        }];
16463        app.cfn_state.table.selected = 5;
16464
16465        app.handle_action(Action::ToggleFilterCheckbox);
16466
16467        assert!(app.cfn_state.view_nested);
16468        assert_eq!(app.cfn_state.table.selected, 0);
16469    }
16470
16471    #[test]
16472    fn test_cfn_template_scroll_up() {
16473        let mut app = test_app();
16474        app.service_selected = true;
16475        app.current_service = Service::CloudFormationStacks;
16476        app.cfn_state.current_stack = Some("test-stack".to_string());
16477        app.cfn_state.detail_tab = CfnDetailTab::Template;
16478        app.cfn_state.template_scroll = 20;
16479
16480        app.page_up();
16481
16482        assert_eq!(app.cfn_state.template_scroll, 10);
16483    }
16484
16485    #[test]
16486    fn test_cfn_template_scroll_down() {
16487        let mut app = test_app();
16488        app.service_selected = true;
16489        app.current_service = Service::CloudFormationStacks;
16490        app.cfn_state.current_stack = Some("test-stack".to_string());
16491        app.cfn_state.detail_tab = CfnDetailTab::Template;
16492        app.cfn_state.template_body = "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\nline14\nline15".to_string();
16493        app.cfn_state.template_scroll = 0;
16494
16495        app.page_down();
16496
16497        assert_eq!(app.cfn_state.template_scroll, 10);
16498    }
16499
16500    #[test]
16501    fn test_cfn_template_scroll_down_respects_max() {
16502        let mut app = test_app();
16503        app.service_selected = true;
16504        app.current_service = Service::CloudFormationStacks;
16505        app.cfn_state.current_stack = Some("test-stack".to_string());
16506        app.cfn_state.detail_tab = CfnDetailTab::Template;
16507        app.cfn_state.template_body = "line1\nline2\nline3".to_string();
16508        app.cfn_state.template_scroll = 0;
16509
16510        app.page_down();
16511
16512        // Should not scroll past the last line (3 lines = max scroll of 2)
16513        assert_eq!(app.cfn_state.template_scroll, 2);
16514    }
16515
16516    #[test]
16517    fn test_cfn_template_arrow_up() {
16518        let mut app = test_app();
16519        app.service_selected = true;
16520        app.current_service = Service::CloudFormationStacks;
16521        app.mode = Mode::Normal;
16522        app.cfn_state.current_stack = Some("test-stack".to_string());
16523        app.cfn_state.detail_tab = CfnDetailTab::Template;
16524        app.cfn_state.template_scroll = 5;
16525
16526        app.prev_item();
16527
16528        assert_eq!(app.cfn_state.template_scroll, 4);
16529    }
16530
16531    #[test]
16532    fn test_cfn_template_arrow_down() {
16533        let mut app = test_app();
16534        app.service_selected = true;
16535        app.current_service = Service::CloudFormationStacks;
16536        app.mode = Mode::Normal;
16537        app.cfn_state.current_stack = Some("test-stack".to_string());
16538        app.cfn_state.detail_tab = CfnDetailTab::Template;
16539        app.cfn_state.template_body = "line1\nline2\nline3\nline4\nline5".to_string();
16540        app.cfn_state.template_scroll = 2;
16541
16542        app.next_item();
16543
16544        assert_eq!(app.cfn_state.template_scroll, 3);
16545    }
16546
16547    #[test]
16548    fn test_cfn_template_arrow_down_respects_max() {
16549        let mut app = test_app();
16550        app.service_selected = true;
16551        app.current_service = Service::CloudFormationStacks;
16552        app.mode = Mode::Normal;
16553        app.cfn_state.current_stack = Some("test-stack".to_string());
16554        app.cfn_state.detail_tab = CfnDetailTab::Template;
16555        app.cfn_state.template_body = "line1\nline2".to_string();
16556        app.cfn_state.template_scroll = 1;
16557
16558        app.next_item();
16559
16560        // Should stay at max (2 lines = max scroll of 1)
16561        assert_eq!(app.cfn_state.template_scroll, 1);
16562    }
16563}
16564
16565#[cfg(test)]
16566mod lambda_version_tab_tests {
16567    use super::*;
16568    use crate::ui::iam::POLICY_TYPE_DROPDOWN;
16569    use test_helpers::*;
16570
16571    #[test]
16572    fn test_lambda_version_tab_cycling_next() {
16573        let mut app = test_app();
16574        app.current_service = Service::LambdaFunctions;
16575        app.lambda_state.current_function = Some("test-function".to_string());
16576        app.lambda_state.current_version = Some("1".to_string());
16577        app.lambda_state.detail_tab = LambdaDetailTab::Code;
16578
16579        // Code -> Monitor
16580        app.handle_action(Action::NextDetailTab);
16581        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
16582        assert!(app.lambda_state.metrics_loading);
16583
16584        // Monitor -> Configuration
16585        app.lambda_state.metrics_loading = false;
16586        app.handle_action(Action::NextDetailTab);
16587        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
16588
16589        // Configuration -> Code
16590        app.handle_action(Action::NextDetailTab);
16591        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
16592    }
16593
16594    #[test]
16595    fn test_lambda_version_tab_cycling_prev() {
16596        let mut app = test_app();
16597        app.current_service = Service::LambdaFunctions;
16598        app.lambda_state.current_function = Some("test-function".to_string());
16599        app.lambda_state.current_version = Some("1".to_string());
16600        app.lambda_state.detail_tab = LambdaDetailTab::Code;
16601
16602        // Code -> Configuration
16603        app.handle_action(Action::PrevDetailTab);
16604        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
16605
16606        // Configuration -> Monitor
16607        app.handle_action(Action::PrevDetailTab);
16608        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
16609        assert!(app.lambda_state.metrics_loading);
16610
16611        // Monitor -> Code
16612        app.lambda_state.metrics_loading = false;
16613        app.handle_action(Action::PrevDetailTab);
16614        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
16615    }
16616
16617    #[test]
16618    fn test_lambda_version_monitor_clears_metrics() {
16619        let mut app = test_app();
16620        app.current_service = Service::LambdaFunctions;
16621        app.lambda_state.current_function = Some("test-function".to_string());
16622        app.lambda_state.current_version = Some("1".to_string());
16623        app.lambda_state.detail_tab = LambdaDetailTab::Code;
16624
16625        // Add some fake metric data
16626        app.lambda_state.metric_data_invocations = vec![(1, 10.0), (2, 20.0)];
16627        app.lambda_state.monitoring_scroll = 5;
16628
16629        // Switch to Monitor tab
16630        app.handle_action(Action::NextDetailTab);
16631
16632        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
16633        assert!(app.lambda_state.metrics_loading);
16634        assert_eq!(app.lambda_state.monitoring_scroll, 0);
16635        assert!(app.lambda_state.metric_data_invocations.is_empty());
16636    }
16637
16638    #[test]
16639    fn test_cfn_parameters_expand_collapse() {
16640        let mut app = test_app();
16641        app.current_service = Service::CloudFormationStacks;
16642        app.service_selected = true;
16643        app.cfn_state.current_stack = Some("test-stack".to_string());
16644        app.cfn_state.detail_tab = CfnDetailTab::Parameters;
16645        app.cfn_state.parameters.items = vec![rusticity_core::cfn::StackParameter {
16646            key: "Param1".to_string(),
16647            value: "Value1".to_string(),
16648            resolved_value: "Resolved1".to_string(),
16649        }];
16650        app.cfn_state.parameters.reset();
16651
16652        assert_eq!(app.cfn_state.parameters.expanded_item, None);
16653
16654        // Right arrow expands
16655        app.handle_action(Action::NextPane);
16656        assert_eq!(app.cfn_state.parameters.expanded_item, Some(0));
16657
16658        // Left arrow collapses
16659        app.handle_action(Action::PrevPane);
16660        assert_eq!(app.cfn_state.parameters.expanded_item, None);
16661    }
16662
16663    #[test]
16664    fn test_cfn_parameters_filter_resets_selection() {
16665        let mut app = test_app();
16666        app.current_service = Service::CloudFormationStacks;
16667        app.service_selected = true;
16668        app.cfn_state.current_stack = Some("test-stack".to_string());
16669        app.cfn_state.detail_tab = CfnDetailTab::Parameters;
16670        app.cfn_state.parameters.items = vec![
16671            rusticity_core::cfn::StackParameter {
16672                key: "DatabaseName".to_string(),
16673                value: "mydb".to_string(),
16674                resolved_value: "mydb".to_string(),
16675            },
16676            rusticity_core::cfn::StackParameter {
16677                key: "InstanceType".to_string(),
16678                value: "t2.micro".to_string(),
16679                resolved_value: "t2.micro".to_string(),
16680            },
16681            rusticity_core::cfn::StackParameter {
16682                key: "Environment".to_string(),
16683                value: "production".to_string(),
16684                resolved_value: "production".to_string(),
16685            },
16686        ];
16687        app.cfn_state.parameters.selected = 2; // Select third item
16688        app.mode = Mode::FilterInput;
16689        app.cfn_state.parameters_input_focus = InputFocus::Filter;
16690
16691        // Type a filter character - should reset selection
16692        app.handle_action(Action::FilterInput('D'));
16693        assert_eq!(app.cfn_state.parameters.selected, 0);
16694        assert_eq!(app.cfn_state.parameters.filter, "D");
16695
16696        // Select another item
16697        app.cfn_state.parameters.selected = 1;
16698
16699        // Type another character - should reset again
16700        app.handle_action(Action::FilterInput('a'));
16701        assert_eq!(app.cfn_state.parameters.selected, 0);
16702        assert_eq!(app.cfn_state.parameters.filter, "Da");
16703
16704        // Select another item
16705        app.cfn_state.parameters.selected = 1;
16706
16707        // Backspace - should also reset
16708        app.handle_action(Action::FilterBackspace);
16709        assert_eq!(app.cfn_state.parameters.selected, 0);
16710        assert_eq!(app.cfn_state.parameters.filter, "D");
16711    }
16712
16713    #[test]
16714    fn test_cfn_template_tab_no_preferences() {
16715        let mut app = test_app();
16716        app.current_service = Service::CloudFormationStacks;
16717        app.service_selected = true;
16718        app.cfn_state.current_stack = Some("test-stack".to_string());
16719        app.cfn_state.detail_tab = CfnDetailTab::Template;
16720        app.mode = Mode::Normal;
16721
16722        // Try to open preferences - should be ignored
16723        app.handle_action(Action::OpenColumnSelector);
16724        assert_eq!(app.mode, Mode::Normal); // Should stay in Normal mode
16725
16726        // GitSync tab should also not allow preferences
16727        app.cfn_state.detail_tab = CfnDetailTab::GitSync;
16728        app.handle_action(Action::OpenColumnSelector);
16729        assert_eq!(app.mode, Mode::Normal); // Should stay in Normal mode
16730
16731        // Parameters tab should allow preferences
16732        app.cfn_state.detail_tab = CfnDetailTab::Parameters;
16733        app.handle_action(Action::OpenColumnSelector);
16734        assert_eq!(app.mode, Mode::ColumnSelector); // Should open preferences
16735
16736        // Outputs tab should allow preferences
16737        app.mode = Mode::Normal;
16738        app.cfn_state.detail_tab = CfnDetailTab::Outputs;
16739        app.handle_action(Action::OpenColumnSelector);
16740        assert_eq!(app.mode, Mode::ColumnSelector); // Should open preferences
16741    }
16742
16743    #[test]
16744    fn test_iam_user_groups_tab_shows_preferences() {
16745        let mut app = App::new_without_client("test".to_string(), Some("us-east-1".to_string()));
16746        app.current_service = Service::IamUsers;
16747        app.service_selected = true;
16748        app.mode = Mode::Normal;
16749        app.iam_state.current_user = Some("test-user".to_string());
16750        app.iam_state.user_tab = UserTab::Groups;
16751
16752        // Should allow opening preferences
16753        app.handle_action(Action::OpenColumnSelector);
16754        assert_eq!(app.mode, Mode::ColumnSelector);
16755    }
16756
16757    #[test]
16758    fn test_iam_user_tags_tab_shows_preferences() {
16759        let mut app = App::new_without_client("test".to_string(), Some("us-east-1".to_string()));
16760        app.current_service = Service::IamUsers;
16761        app.service_selected = true;
16762        app.mode = Mode::Normal;
16763        app.iam_state.current_user = Some("test-user".to_string());
16764        app.iam_state.user_tab = UserTab::Tags;
16765
16766        // Should allow opening preferences
16767        app.handle_action(Action::OpenColumnSelector);
16768        assert_eq!(app.mode, Mode::ColumnSelector);
16769    }
16770
16771    #[test]
16772    fn test_iam_user_last_accessed_tab_shows_preferences() {
16773        let mut app = App::new_without_client("test".to_string(), Some("us-east-1".to_string()));
16774        app.current_service = Service::IamUsers;
16775        app.service_selected = true;
16776        app.mode = Mode::Normal;
16777        app.iam_state.current_user = Some("test-user".to_string());
16778        app.iam_state.user_tab = UserTab::LastAccessed;
16779
16780        // Should allow opening preferences
16781        app.handle_action(Action::OpenColumnSelector);
16782        assert_eq!(app.mode, Mode::ColumnSelector);
16783    }
16784
16785    #[test]
16786    fn test_iam_user_security_credentials_tab_no_preferences() {
16787        let mut app = App::new_without_client("test".to_string(), Some("us-east-1".to_string()));
16788        app.current_service = Service::IamUsers;
16789        app.service_selected = true;
16790        app.mode = Mode::Normal;
16791        app.iam_state.current_user = Some("test-user".to_string());
16792        app.iam_state.user_tab = UserTab::SecurityCredentials;
16793
16794        // Should NOT allow opening preferences
16795        app.handle_action(Action::OpenColumnSelector);
16796        assert_eq!(app.mode, Mode::Normal);
16797    }
16798
16799    #[test]
16800    fn test_iam_user_tabs_without_column_preferences() {
16801        let mut app = test_app();
16802        app.current_service = Service::IamUsers;
16803        app.service_selected = true;
16804        app.iam_state.current_user = Some("test-user".to_string());
16805        app.mode = Mode::Normal;
16806
16807        // Groups tab should allow preferences (page size)
16808        app.iam_state.user_tab = UserTab::Groups;
16809        app.handle_action(Action::OpenColumnSelector);
16810        assert_eq!(app.mode, Mode::ColumnSelector);
16811        app.mode = Mode::Normal;
16812
16813        // Tags tab should allow preferences (page size)
16814        app.iam_state.user_tab = UserTab::Tags;
16815        app.handle_action(Action::OpenColumnSelector);
16816        assert_eq!(app.mode, Mode::ColumnSelector);
16817        app.mode = Mode::Normal;
16818
16819        // SecurityCredentials tab should not allow preferences
16820        app.iam_state.user_tab = UserTab::SecurityCredentials;
16821        app.handle_action(Action::OpenColumnSelector);
16822        assert_eq!(app.mode, Mode::Normal);
16823
16824        // LastAccessed tab should allow preferences (page size)
16825        app.iam_state.user_tab = UserTab::LastAccessed;
16826        app.handle_action(Action::OpenColumnSelector);
16827        assert_eq!(app.mode, Mode::ColumnSelector);
16828        app.mode = Mode::Normal;
16829
16830        // Permissions tab should allow preferences
16831        app.iam_state.user_tab = UserTab::Permissions;
16832        app.handle_action(Action::OpenColumnSelector);
16833        assert_eq!(app.mode, Mode::ColumnSelector);
16834
16835        // User list (no current_user) should allow preferences
16836        app.mode = Mode::Normal;
16837        app.iam_state.current_user = None;
16838        app.handle_action(Action::OpenColumnSelector);
16839        assert_eq!(app.mode, Mode::ColumnSelector);
16840    }
16841
16842    #[test]
16843    fn test_iam_role_policies_dropdown_cycling() {
16844        let mut app = test_app();
16845        app.current_service = Service::IamRoles;
16846        app.service_selected = true;
16847        app.iam_state.current_role = Some("test-role".to_string());
16848        app.iam_state.role_tab = RoleTab::Permissions;
16849        app.mode = Mode::FilterInput;
16850        app.iam_state.policy_input_focus = POLICY_TYPE_DROPDOWN;
16851        app.iam_state.policy_type_filter = "All types".to_string();
16852
16853        // Test next cycling
16854        app.next_item();
16855        assert_eq!(app.iam_state.policy_type_filter, "AWS managed");
16856        app.next_item();
16857        assert_eq!(app.iam_state.policy_type_filter, "Customer managed");
16858        app.next_item();
16859        assert_eq!(app.iam_state.policy_type_filter, "All types");
16860
16861        // Test prev cycling
16862        app.prev_item();
16863        assert_eq!(app.iam_state.policy_type_filter, "Customer managed");
16864        app.prev_item();
16865        assert_eq!(app.iam_state.policy_type_filter, "AWS managed");
16866        app.prev_item();
16867        assert_eq!(app.iam_state.policy_type_filter, "All types");
16868    }
16869
16870    #[test]
16871    fn test_iam_user_policies_dropdown_cycling() {
16872        let mut app = test_app();
16873        app.current_service = Service::IamUsers;
16874        app.service_selected = true;
16875        app.iam_state.current_user = Some("test-user".to_string());
16876        app.iam_state.user_tab = UserTab::Permissions;
16877        app.mode = Mode::FilterInput;
16878        app.iam_state.policy_input_focus = POLICY_TYPE_DROPDOWN;
16879        app.iam_state.policy_type_filter = "All types".to_string();
16880
16881        // Test next cycling
16882        app.next_item();
16883        assert_eq!(app.iam_state.policy_type_filter, "AWS managed");
16884        app.next_item();
16885        assert_eq!(app.iam_state.policy_type_filter, "Customer managed");
16886        app.next_item();
16887        assert_eq!(app.iam_state.policy_type_filter, "All types");
16888
16889        // Test prev cycling
16890        app.prev_item();
16891        assert_eq!(app.iam_state.policy_type_filter, "Customer managed");
16892        app.prev_item();
16893        assert_eq!(app.iam_state.policy_type_filter, "AWS managed");
16894        app.prev_item();
16895        assert_eq!(app.iam_state.policy_type_filter, "All types");
16896    }
16897
16898    #[test]
16899    fn test_iam_role_tabs_without_column_preferences() {
16900        let mut app = test_app();
16901        app.current_service = Service::IamRoles;
16902        app.service_selected = true;
16903        app.iam_state.current_role = Some("test-role".to_string());
16904        app.mode = Mode::Normal;
16905
16906        // TrustRelationships tab should not allow preferences
16907        app.iam_state.role_tab = RoleTab::TrustRelationships;
16908        app.handle_action(Action::OpenColumnSelector);
16909        assert_eq!(app.mode, Mode::Normal);
16910
16911        // RevokeSessions tab should not allow preferences
16912        app.iam_state.role_tab = RoleTab::RevokeSessions;
16913        app.handle_action(Action::OpenColumnSelector);
16914        assert_eq!(app.mode, Mode::Normal);
16915
16916        // LastAccessed tab should allow preferences
16917        app.iam_state.role_tab = RoleTab::LastAccessed;
16918        app.handle_action(Action::OpenColumnSelector);
16919        assert_eq!(app.mode, Mode::ColumnSelector);
16920
16921        // Permissions tab should allow preferences
16922        app.mode = Mode::Normal;
16923        app.iam_state.role_tab = RoleTab::Permissions;
16924        app.handle_action(Action::OpenColumnSelector);
16925        assert_eq!(app.mode, Mode::ColumnSelector);
16926
16927        // Tags tab should allow preferences
16928        app.mode = Mode::Normal;
16929        app.iam_state.role_tab = RoleTab::Tags;
16930        app.handle_action(Action::OpenColumnSelector);
16931        assert_eq!(app.mode, Mode::ColumnSelector);
16932
16933        // Role list (no current_role) should allow preferences
16934        app.mode = Mode::Normal;
16935        app.iam_state.current_role = None;
16936        app.handle_action(Action::OpenColumnSelector);
16937        assert_eq!(app.mode, Mode::ColumnSelector);
16938    }
16939
16940    #[test]
16941    fn test_iam_role_tags_tab_cycling() {
16942        let mut app = test_app();
16943        app.current_service = Service::IamRoles;
16944        app.service_selected = true;
16945        app.iam_state.current_role = Some("test-role".to_string());
16946        app.iam_state.role_tab = RoleTab::Tags;
16947        app.mode = Mode::ColumnSelector;
16948        app.column_selector_index = 0;
16949
16950        // NextPreferences from column section -> page size
16951        app.handle_action(Action::NextPreferences);
16952        assert_eq!(app.column_selector_index, 4);
16953
16954        // NextPreferences from page size -> column section
16955        app.handle_action(Action::NextPreferences);
16956        assert_eq!(app.column_selector_index, 0);
16957
16958        // PrevPreferences from column section -> page size
16959        app.handle_action(Action::PrevPreferences);
16960        assert_eq!(app.column_selector_index, 4);
16961
16962        // PrevPreferences from page size -> column section
16963        app.handle_action(Action::PrevPreferences);
16964        assert_eq!(app.column_selector_index, 0);
16965    }
16966
16967    #[test]
16968    fn test_cfn_outputs_expand_collapse() {
16969        let mut app = test_app();
16970        app.current_service = Service::CloudFormationStacks;
16971        app.service_selected = true;
16972        app.cfn_state.current_stack = Some("test-stack".to_string());
16973        app.cfn_state.detail_tab = CfnDetailTab::Outputs;
16974        app.cfn_state.outputs.items = vec![rusticity_core::cfn::StackOutput {
16975            key: "Output1".to_string(),
16976            value: "Value1".to_string(),
16977            description: "Description1".to_string(),
16978            export_name: "Export1".to_string(),
16979        }];
16980        app.cfn_state.outputs.reset();
16981
16982        assert_eq!(app.cfn_state.outputs.expanded_item, None);
16983
16984        // Right arrow expands
16985        app.handle_action(Action::NextPane);
16986        assert_eq!(app.cfn_state.outputs.expanded_item, Some(0));
16987
16988        // Left arrow collapses
16989        app.handle_action(Action::PrevPane);
16990        assert_eq!(app.cfn_state.outputs.expanded_item, None);
16991    }
16992
16993    #[test]
16994    fn test_cfn_outputs_filter_resets_selection() {
16995        let mut app = test_app();
16996        app.current_service = Service::CloudFormationStacks;
16997        app.service_selected = true;
16998        app.cfn_state.current_stack = Some("test-stack".to_string());
16999        app.cfn_state.detail_tab = CfnDetailTab::Outputs;
17000        app.cfn_state.outputs.items = vec![
17001            rusticity_core::cfn::StackOutput {
17002                key: "ApiUrl".to_string(),
17003                value: "https://api.example.com".to_string(),
17004                description: "API endpoint".to_string(),
17005                export_name: "MyApiUrl".to_string(),
17006            },
17007            rusticity_core::cfn::StackOutput {
17008                key: "BucketName".to_string(),
17009                value: "my-bucket".to_string(),
17010                description: "S3 bucket".to_string(),
17011                export_name: "MyBucket".to_string(),
17012            },
17013        ];
17014        app.cfn_state.outputs.reset();
17015        app.cfn_state.outputs.selected = 1;
17016
17017        // Start filter mode
17018        app.handle_action(Action::StartFilter);
17019        assert_eq!(app.mode, Mode::FilterInput);
17020
17021        // Type a character - should reset selection
17022        app.handle_action(Action::FilterInput('A'));
17023        assert_eq!(app.cfn_state.outputs.selected, 0);
17024        assert_eq!(app.cfn_state.outputs.filter, "A");
17025
17026        // Type more
17027        app.cfn_state.outputs.selected = 1;
17028        app.handle_action(Action::FilterInput('p'));
17029        assert_eq!(app.cfn_state.outputs.selected, 0);
17030
17031        // Backspace should also reset selection
17032        app.cfn_state.outputs.selected = 1;
17033        app.handle_action(Action::FilterBackspace);
17034        assert_eq!(app.cfn_state.outputs.selected, 0);
17035    }
17036
17037    #[test]
17038    fn test_ec2_service_in_picker() {
17039        let app = test_app();
17040        assert!(app.service_picker.services.contains(&"EC2 > Instances"));
17041    }
17042
17043    #[test]
17044    fn test_ec2_state_filter_cycles() {
17045        let mut app = test_app();
17046        app.current_service = Service::Ec2Instances;
17047        app.service_selected = true;
17048        app.mode = Mode::FilterInput;
17049        app.ec2_state.input_focus = EC2_STATE_FILTER;
17050
17051        let initial = app.ec2_state.state_filter;
17052        assert_eq!(initial, Ec2StateFilter::AllStates);
17053
17054        // Cycle through filters using ToggleFilterCheckbox
17055        app.handle_action(Action::ToggleFilterCheckbox);
17056        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
17057
17058        app.handle_action(Action::ToggleFilterCheckbox);
17059        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopped);
17060
17061        app.handle_action(Action::ToggleFilterCheckbox);
17062        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Terminated);
17063
17064        app.handle_action(Action::ToggleFilterCheckbox);
17065        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Pending);
17066
17067        app.handle_action(Action::ToggleFilterCheckbox);
17068        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::ShuttingDown);
17069
17070        app.handle_action(Action::ToggleFilterCheckbox);
17071        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopping);
17072
17073        app.handle_action(Action::ToggleFilterCheckbox);
17074        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
17075    }
17076
17077    #[test]
17078    fn test_ec2_filter_resets_table() {
17079        let mut app = test_app();
17080        app.current_service = Service::Ec2Instances;
17081        app.service_selected = true;
17082        app.mode = Mode::FilterInput;
17083        app.ec2_state.input_focus = EC2_STATE_FILTER;
17084        app.ec2_state.table.selected = 5;
17085
17086        app.handle_action(Action::ToggleFilterCheckbox);
17087        assert_eq!(app.ec2_state.table.selected, 0);
17088    }
17089
17090    #[test]
17091    fn test_ec2_columns_visible() {
17092        let app = test_app();
17093        assert_eq!(app.ec2_visible_column_ids.len(), 16); // Default visible columns
17094        assert_eq!(app.ec2_column_ids.len(), 52); // Total available columns
17095    }
17096
17097    #[test]
17098    fn test_ec2_breadcrumbs() {
17099        let mut app = test_app();
17100        app.current_service = Service::Ec2Instances;
17101        app.service_selected = true;
17102        let breadcrumb = app.breadcrumbs();
17103        assert_eq!(breadcrumb, "EC2 > Instances");
17104    }
17105
17106    #[test]
17107    fn test_ec2_console_url() {
17108        let mut app = test_app();
17109        app.current_service = Service::Ec2Instances;
17110        app.service_selected = true;
17111        let url = app.get_console_url();
17112        assert!(url.contains("ec2"));
17113        assert!(url.contains("Instances"));
17114    }
17115
17116    #[test]
17117    fn test_ec2_filter_handling() {
17118        let mut app = test_app();
17119        app.current_service = Service::Ec2Instances;
17120        app.service_selected = true;
17121        app.mode = Mode::FilterInput;
17122
17123        app.handle_action(Action::FilterInput('t'));
17124        app.handle_action(Action::FilterInput('e'));
17125        app.handle_action(Action::FilterInput('s'));
17126        app.handle_action(Action::FilterInput('t'));
17127
17128        assert_eq!(app.ec2_state.table.filter, "test");
17129
17130        app.handle_action(Action::FilterBackspace);
17131        assert_eq!(app.ec2_state.table.filter, "tes");
17132    }
17133
17134    #[test]
17135    fn test_column_selector_page_down_ec2() {
17136        let mut app = test_app();
17137        app.current_service = Service::Ec2Instances;
17138        app.service_selected = true;
17139        app.mode = Mode::ColumnSelector;
17140        app.column_selector_index = 0;
17141
17142        app.handle_action(Action::PageDown);
17143        assert_eq!(app.column_selector_index, 10);
17144
17145        app.handle_action(Action::PageDown);
17146        assert_eq!(app.column_selector_index, 20);
17147    }
17148
17149    #[test]
17150    fn test_column_selector_page_up_ec2() {
17151        let mut app = test_app();
17152        app.current_service = Service::Ec2Instances;
17153        app.service_selected = true;
17154        app.mode = Mode::ColumnSelector;
17155        app.column_selector_index = 30;
17156
17157        app.handle_action(Action::PageUp);
17158        assert_eq!(app.column_selector_index, 20);
17159
17160        app.handle_action(Action::PageUp);
17161        assert_eq!(app.column_selector_index, 10);
17162    }
17163
17164    #[test]
17165    fn test_ec2_state_filter_dropdown_focus() {
17166        let mut app = test_app();
17167        app.current_service = Service::Ec2Instances;
17168        app.service_selected = true;
17169        app.mode = Mode::FilterInput;
17170
17171        // Tab to state filter
17172        app.handle_action(Action::NextFilterFocus);
17173        assert_eq!(app.ec2_state.input_focus, EC2_STATE_FILTER);
17174
17175        // Dropdown should show when focused (tested in render)
17176        // Verify we can cycle the filter
17177        app.handle_action(Action::ToggleFilterCheckbox);
17178        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
17179    }
17180
17181    #[test]
17182    fn test_column_selector_ctrl_d_scrolling() {
17183        let mut app = test_app();
17184        app.current_service = Service::LambdaFunctions;
17185        app.mode = Mode::ColumnSelector;
17186        app.column_selector_index = 0;
17187
17188        app.handle_action(Action::PageDown);
17189        assert_eq!(app.column_selector_index, 10);
17190
17191        // Second PageDown should be capped at max
17192        let max = app.get_column_selector_max();
17193        app.handle_action(Action::PageDown);
17194        assert_eq!(app.column_selector_index, max);
17195    }
17196
17197    #[test]
17198    fn test_column_selector_ctrl_u_scrolling() {
17199        let mut app = test_app();
17200        app.current_service = Service::CloudFormationStacks;
17201        app.mode = Mode::ColumnSelector;
17202        app.column_selector_index = 25;
17203
17204        app.handle_action(Action::PageUp);
17205        assert_eq!(app.column_selector_index, 15);
17206
17207        app.handle_action(Action::PageUp);
17208        assert_eq!(app.column_selector_index, 5);
17209    }
17210
17211    #[test]
17212    fn test_prev_preferences_lambda() {
17213        let mut app = test_app();
17214        app.current_service = Service::LambdaFunctions;
17215        app.mode = Mode::ColumnSelector;
17216        let page_size_idx = app.lambda_state.function_column_ids.len() + 2;
17217        app.column_selector_index = page_size_idx;
17218
17219        app.handle_action(Action::PrevPreferences);
17220        assert_eq!(app.column_selector_index, 0);
17221
17222        app.handle_action(Action::PrevPreferences);
17223        assert_eq!(app.column_selector_index, page_size_idx);
17224    }
17225
17226    #[test]
17227    fn test_prev_preferences_cloudformation() {
17228        let mut app = test_app();
17229        app.current_service = Service::CloudFormationStacks;
17230        app.mode = Mode::ColumnSelector;
17231        let page_size_idx = app.cfn_column_ids.len() + 2;
17232        app.column_selector_index = page_size_idx;
17233
17234        app.handle_action(Action::PrevPreferences);
17235        assert_eq!(app.column_selector_index, 0);
17236
17237        app.handle_action(Action::PrevPreferences);
17238        assert_eq!(app.column_selector_index, page_size_idx);
17239    }
17240
17241    #[test]
17242    fn test_prev_preferences_alarms() {
17243        let mut app = test_app();
17244        app.current_service = Service::CloudWatchAlarms;
17245        app.mode = Mode::ColumnSelector;
17246        app.column_selector_index = 28; // WrapLines
17247
17248        app.handle_action(Action::PrevPreferences);
17249        assert_eq!(app.column_selector_index, 22); // PageSize
17250
17251        app.handle_action(Action::PrevPreferences);
17252        assert_eq!(app.column_selector_index, 18); // ViewAs
17253
17254        app.handle_action(Action::PrevPreferences);
17255        assert_eq!(app.column_selector_index, 0); // Columns
17256
17257        app.handle_action(Action::PrevPreferences);
17258        assert_eq!(app.column_selector_index, 28); // Wrap to WrapLines
17259    }
17260
17261    #[test]
17262    fn test_ec2_page_size_in_preferences() {
17263        let mut app = test_app();
17264        app.current_service = Service::Ec2Instances;
17265        app.mode = Mode::ColumnSelector;
17266        app.ec2_state.table.page_size = PageSize::Fifty;
17267
17268        // Navigate to page size section
17269        let page_size_idx = app.ec2_column_ids.len() + 3; // First page size option (10)
17270        app.column_selector_index = page_size_idx;
17271        app.handle_action(Action::ToggleColumn);
17272
17273        assert_eq!(app.ec2_state.table.page_size, PageSize::Ten);
17274    }
17275
17276    #[test]
17277    fn test_ec2_next_preferences_with_page_size() {
17278        let mut app = test_app();
17279        app.current_service = Service::Ec2Instances;
17280        app.mode = Mode::ColumnSelector;
17281        app.column_selector_index = 0;
17282
17283        let page_size_idx = app.ec2_column_ids.len() + 2;
17284        app.handle_action(Action::NextPreferences);
17285        assert_eq!(app.column_selector_index, page_size_idx);
17286
17287        app.handle_action(Action::NextPreferences);
17288        assert_eq!(app.column_selector_index, 0);
17289    }
17290
17291    #[test]
17292    fn test_ec2_dropdown_next_item() {
17293        let mut app = test_app();
17294        app.current_service = Service::Ec2Instances;
17295        app.mode = Mode::FilterInput;
17296        app.ec2_state.input_focus = EC2_STATE_FILTER;
17297        app.ec2_state.state_filter = Ec2StateFilter::AllStates;
17298
17299        app.handle_action(Action::NextItem);
17300        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
17301
17302        app.handle_action(Action::NextItem);
17303        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopped);
17304    }
17305
17306    #[test]
17307    fn test_ec2_dropdown_prev_item() {
17308        let mut app = test_app();
17309        app.current_service = Service::Ec2Instances;
17310        app.mode = Mode::FilterInput;
17311        app.ec2_state.input_focus = EC2_STATE_FILTER;
17312        app.ec2_state.state_filter = Ec2StateFilter::Stopped;
17313
17314        app.handle_action(Action::PrevItem);
17315        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
17316
17317        app.handle_action(Action::PrevItem);
17318        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
17319    }
17320
17321    #[test]
17322    fn test_ec2_dropdown_cycles_with_arrows() {
17323        let mut app = test_app();
17324        app.current_service = Service::Ec2Instances;
17325        app.mode = Mode::FilterInput;
17326        app.ec2_state.input_focus = EC2_STATE_FILTER;
17327        app.ec2_state.state_filter = Ec2StateFilter::Stopping;
17328
17329        // Next wraps to AllStates
17330        app.handle_action(Action::NextItem);
17331        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
17332
17333        // Prev wraps to Stopping
17334        app.handle_action(Action::PrevItem);
17335        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopping);
17336    }
17337
17338    #[test]
17339    fn test_collapse_row_ec2_instances() {
17340        let mut app = test_app();
17341        app.current_service = Service::Ec2Instances;
17342        app.ec2_state.table.expanded_item = Some(0);
17343
17344        app.handle_action(Action::CollapseRow);
17345        assert_eq!(app.ec2_state.table.expanded_item, None);
17346    }
17347
17348    #[test]
17349    fn test_collapse_row_ec2_tags() {
17350        let mut app = test_app();
17351        app.current_service = Service::Ec2Instances;
17352        app.ec2_state.current_instance = Some("i-123".to_string());
17353        app.ec2_state.detail_tab = Ec2DetailTab::Tags;
17354        app.ec2_state.tags.expanded_item = Some(1);
17355
17356        app.handle_action(Action::CollapseRow);
17357        assert_eq!(app.ec2_state.tags.expanded_item, None);
17358    }
17359
17360    #[test]
17361    fn test_collapse_row_cloudwatch_log_groups() {
17362        let mut app = test_app();
17363        app.current_service = Service::CloudWatchLogGroups;
17364        app.log_groups_state.log_groups.expanded_item = Some(2);
17365
17366        app.handle_action(Action::CollapseRow);
17367        assert_eq!(app.log_groups_state.log_groups.expanded_item, None);
17368    }
17369
17370    #[test]
17371    fn test_collapse_row_cloudwatch_alarms() {
17372        let mut app = test_app();
17373        app.current_service = Service::CloudWatchAlarms;
17374        app.alarms_state.table.expanded_item = Some(0);
17375
17376        app.handle_action(Action::CollapseRow);
17377        assert_eq!(app.alarms_state.table.expanded_item, None);
17378    }
17379
17380    #[test]
17381    fn test_collapse_row_lambda_functions() {
17382        let mut app = test_app();
17383        app.current_service = Service::LambdaFunctions;
17384        app.lambda_state.table.expanded_item = Some(1);
17385
17386        app.handle_action(Action::CollapseRow);
17387        assert_eq!(app.lambda_state.table.expanded_item, None);
17388    }
17389
17390    #[test]
17391    fn test_collapse_row_cfn_stacks() {
17392        let mut app = test_app();
17393        app.current_service = Service::CloudFormationStacks;
17394        app.cfn_state.table.expanded_item = Some(0);
17395
17396        app.handle_action(Action::CollapseRow);
17397        assert_eq!(app.cfn_state.table.expanded_item, None);
17398    }
17399
17400    #[test]
17401    fn test_collapse_row_cfn_resources() {
17402        let mut app = test_app();
17403        app.current_service = Service::CloudFormationStacks;
17404        app.cfn_state.current_stack = Some("test-stack".to_string());
17405        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Resources;
17406        app.cfn_state.resources.expanded_item = Some(2);
17407
17408        app.handle_action(Action::CollapseRow);
17409        assert_eq!(app.cfn_state.resources.expanded_item, None);
17410    }
17411
17412    #[test]
17413    fn test_collapse_row_iam_users() {
17414        let mut app = test_app();
17415        app.current_service = Service::IamUsers;
17416        app.iam_state.users.expanded_item = Some(1);
17417
17418        app.handle_action(Action::CollapseRow);
17419        assert_eq!(app.iam_state.users.expanded_item, None);
17420    }
17421
17422    #[test]
17423    fn test_collapse_row_does_nothing_when_not_expanded() {
17424        let mut app = test_app();
17425        app.current_service = Service::Ec2Instances;
17426        app.ec2_state.table.expanded_item = None;
17427
17428        app.handle_action(Action::CollapseRow);
17429        assert_eq!(app.ec2_state.table.expanded_item, None);
17430    }
17431
17432    #[test]
17433    fn test_s3_collapse_expanded_folder_moves_to_parent() {
17434        let mut app = test_app();
17435        app.current_service = Service::S3Buckets;
17436        app.service_selected = true;
17437        app.mode = Mode::Normal;
17438
17439        // Add bucket with folder
17440        app.s3_state.buckets.items = vec![S3Bucket {
17441            name: "bucket1".to_string(),
17442            region: "us-east-1".to_string(),
17443            creation_date: "2024-01-01T00:00:00Z".to_string(),
17444        }];
17445
17446        // Expand bucket
17447        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
17448        app.s3_state.bucket_preview.insert(
17449            "bucket1".to_string(),
17450            vec![S3Object {
17451                key: "folder1/".to_string(),
17452                size: 0,
17453                last_modified: "2024-01-01T00:00:00Z".to_string(),
17454                is_prefix: true,
17455                storage_class: String::new(),
17456            }],
17457        );
17458
17459        // Expand folder
17460        app.s3_state
17461            .expanded_prefixes
17462            .insert("folder1/".to_string());
17463        app.s3_state.prefix_preview.insert(
17464            "folder1/".to_string(),
17465            vec![S3Object {
17466                key: "folder1/file.txt".to_string(),
17467                size: 0,
17468                last_modified: "2024-01-01T00:00:00Z".to_string(),
17469                is_prefix: false,
17470                storage_class: String::new(),
17471            }],
17472        );
17473
17474        // Select the expanded folder (row 1)
17475        app.s3_state.selected_row = 1;
17476
17477        // Press Left to collapse
17478        app.handle_action(Action::PrevPane);
17479
17480        // Folder should be collapsed
17481        assert!(!app.s3_state.expanded_prefixes.contains("folder1/"));
17482        // Selection should move to parent (bucket at row 0)
17483        assert_eq!(app.s3_state.selected_row, 0);
17484    }
17485
17486    #[test]
17487    fn test_s3_collapse_hierarchy_level_by_level() {
17488        let mut app = test_app();
17489        app.current_service = Service::S3Buckets;
17490        app.service_selected = true;
17491        app.mode = Mode::Normal;
17492
17493        // Add bucket with 3-level hierarchy
17494        app.s3_state.buckets.items = vec![S3Bucket {
17495            name: "bucket1".to_string(),
17496            region: "us-east-1".to_string(),
17497            creation_date: "2024-01-01T00:00:00Z".to_string(),
17498        }];
17499
17500        // Level 1: bucket -> level1/
17501        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
17502        app.s3_state.bucket_preview.insert(
17503            "bucket1".to_string(),
17504            vec![S3Object {
17505                key: "level1/".to_string(),
17506                size: 0,
17507                last_modified: "2024-01-01T00:00:00Z".to_string(),
17508                is_prefix: true,
17509                storage_class: String::new(),
17510            }],
17511        );
17512
17513        // Level 2: level1/ -> level2/
17514        app.s3_state.expanded_prefixes.insert("level1/".to_string());
17515        app.s3_state.prefix_preview.insert(
17516            "level1/".to_string(),
17517            vec![S3Object {
17518                key: "level1/level2/".to_string(),
17519                size: 0,
17520                last_modified: "2024-01-01T00:00:00Z".to_string(),
17521                is_prefix: true,
17522                storage_class: String::new(),
17523            }],
17524        );
17525
17526        // Level 3: level2/ -> file
17527        app.s3_state
17528            .expanded_prefixes
17529            .insert("level1/level2/".to_string());
17530        app.s3_state.prefix_preview.insert(
17531            "level1/level2/".to_string(),
17532            vec![S3Object {
17533                key: "level1/level2/file.txt".to_string(),
17534                size: 100,
17535                last_modified: "2024-01-01T00:00:00Z".to_string(),
17536                is_prefix: false,
17537                storage_class: String::new(),
17538            }],
17539        );
17540
17541        // Select deepest level (row 3: file)
17542        app.s3_state.selected_row = 3;
17543
17544        // First Left: move to parent (level2/ at row 2)
17545        app.handle_action(Action::PrevPane);
17546        assert_eq!(app.s3_state.selected_row, 2);
17547
17548        // Second Left: collapse level2/ and move to parent (level1/ at row 1)
17549        app.handle_action(Action::PrevPane);
17550        assert!(!app.s3_state.expanded_prefixes.contains("level1/level2/"));
17551        assert_eq!(app.s3_state.selected_row, 1);
17552
17553        // Third Left: collapse level1/ and move to parent (bucket at row 0)
17554        app.handle_action(Action::PrevPane);
17555        assert!(!app.s3_state.expanded_prefixes.contains("level1/"));
17556        assert_eq!(app.s3_state.selected_row, 0);
17557
17558        // Fourth Left: collapse bucket (stays at row 0)
17559        app.handle_action(Action::PrevPane);
17560        assert!(!app.s3_state.expanded_prefixes.contains("bucket1"));
17561        assert_eq!(app.s3_state.selected_row, 0);
17562    }
17563
17564    #[test]
17565    fn test_ec2_instance_detail_tabs_no_preferences() {
17566        let mut app = test_app();
17567        app.current_service = Service::Ec2Instances;
17568        app.service_selected = true;
17569        app.ec2_state.table.expanded_item = Some(0);
17570        app.mode = Mode::Normal;
17571
17572        // Details tab should NOT allow preferences
17573        app.ec2_state.detail_tab = Ec2DetailTab::Details;
17574        app.handle_action(Action::OpenColumnSelector);
17575        assert_eq!(app.mode, Mode::Normal);
17576
17577        // StatusAndAlarms tab should NOT allow preferences
17578        app.ec2_state.detail_tab = Ec2DetailTab::StatusAndAlarms;
17579        app.handle_action(Action::OpenColumnSelector);
17580        assert_eq!(app.mode, Mode::Normal);
17581
17582        // Monitoring tab should NOT allow preferences
17583        app.ec2_state.detail_tab = Ec2DetailTab::Monitoring;
17584        app.handle_action(Action::OpenColumnSelector);
17585        assert_eq!(app.mode, Mode::Normal);
17586
17587        // Security tab should NOT allow preferences
17588        app.ec2_state.detail_tab = Ec2DetailTab::Security;
17589        app.handle_action(Action::OpenColumnSelector);
17590        assert_eq!(app.mode, Mode::Normal);
17591
17592        // Networking tab should NOT allow preferences
17593        app.ec2_state.detail_tab = Ec2DetailTab::Networking;
17594        app.handle_action(Action::OpenColumnSelector);
17595        assert_eq!(app.mode, Mode::Normal);
17596
17597        // Storage tab should NOT allow preferences
17598        app.ec2_state.detail_tab = Ec2DetailTab::Storage;
17599        app.handle_action(Action::OpenColumnSelector);
17600        assert_eq!(app.mode, Mode::Normal);
17601
17602        // Tags tab SHOULD allow preferences
17603        app.ec2_state.detail_tab = Ec2DetailTab::Tags;
17604        app.handle_action(Action::OpenColumnSelector);
17605        assert_eq!(app.mode, Mode::ColumnSelector);
17606    }
17607
17608    #[test]
17609    fn test_log_streams_filter_only_updates_when_focused() {
17610        let mut app = test_app();
17611        app.current_service = Service::CloudWatchLogGroups;
17612        app.service_selected = true;
17613        app.view_mode = ViewMode::Detail;
17614        app.mode = Mode::FilterInput;
17615        app.log_groups_state.stream_filter = "test".to_string();
17616
17617        // When filter is focused, typing should update filter
17618        app.log_groups_state.input_focus = InputFocus::Filter;
17619        app.handle_action(Action::FilterInput('x'));
17620        assert_eq!(app.log_groups_state.stream_filter, "testx");
17621
17622        // When pagination is focused, typing should NOT update filter
17623        app.log_groups_state.input_focus = InputFocus::Pagination;
17624        app.handle_action(Action::FilterInput('y'));
17625        assert_eq!(app.log_groups_state.stream_filter, "testx"); // unchanged
17626    }
17627
17628    #[test]
17629    fn test_log_streams_backspace_only_updates_when_focused() {
17630        let mut app = test_app();
17631        app.current_service = Service::CloudWatchLogGroups;
17632        app.service_selected = true;
17633        app.view_mode = ViewMode::Detail;
17634        app.mode = Mode::FilterInput;
17635        app.log_groups_state.stream_filter = "test".to_string();
17636
17637        // When filter is focused, backspace should update filter
17638        app.log_groups_state.input_focus = InputFocus::Filter;
17639        app.handle_action(Action::FilterBackspace);
17640        assert_eq!(app.log_groups_state.stream_filter, "tes");
17641
17642        // When pagination is focused, backspace should NOT update filter
17643        app.log_groups_state.input_focus = InputFocus::Pagination;
17644        app.handle_action(Action::FilterBackspace);
17645        assert_eq!(app.log_groups_state.stream_filter, "tes"); // unchanged
17646    }
17647
17648    #[test]
17649    fn test_log_groups_filter_only_updates_when_focused() {
17650        let mut app = test_app();
17651        app.current_service = Service::CloudWatchLogGroups;
17652        app.service_selected = true;
17653        app.view_mode = ViewMode::List;
17654        app.mode = Mode::FilterInput;
17655        app.log_groups_state.log_groups.filter = "test".to_string();
17656
17657        // When filter is focused, typing should update filter
17658        app.log_groups_state.input_focus = InputFocus::Filter;
17659        app.handle_action(Action::FilterInput('x'));
17660        assert_eq!(app.log_groups_state.log_groups.filter, "testx");
17661
17662        // When pagination is focused, typing should NOT update filter
17663        app.log_groups_state.input_focus = InputFocus::Pagination;
17664        app.handle_action(Action::FilterInput('y'));
17665        assert_eq!(app.log_groups_state.log_groups.filter, "testx"); // unchanged
17666    }
17667
17668    #[test]
17669    fn test_s3_bucket_collapse_nested_prefix_jumps_to_parent() {
17670        use S3Bucket;
17671        use S3Object;
17672
17673        let mut app = test_app();
17674        app.current_service = Service::S3Buckets;
17675        app.service_selected = true;
17676
17677        // Create bucket with nested prefixes
17678        app.s3_state.buckets.items = vec![S3Bucket {
17679            name: "test-bucket".to_string(),
17680            region: "us-east-1".to_string(),
17681            creation_date: String::new(),
17682        }];
17683
17684        // Expand bucket with folder1/
17685        app.s3_state
17686            .expanded_prefixes
17687            .insert("test-bucket".to_string());
17688        app.s3_state.bucket_preview.insert(
17689            "test-bucket".to_string(),
17690            vec![S3Object {
17691                key: "folder1/".to_string(),
17692                is_prefix: true,
17693                size: 0,
17694                last_modified: String::new(),
17695                storage_class: String::new(),
17696            }],
17697        );
17698
17699        // Expand folder1/ with folder2/
17700        app.s3_state
17701            .expanded_prefixes
17702            .insert("folder1/".to_string());
17703        app.s3_state.prefix_preview.insert(
17704            "folder1/".to_string(),
17705            vec![S3Object {
17706                key: "folder1/folder2/".to_string(),
17707                is_prefix: true,
17708                size: 0,
17709                last_modified: String::new(),
17710                storage_class: String::new(),
17711            }],
17712        );
17713
17714        // Select folder2/ (row 0: bucket, row 1: folder1, row 2: folder2)
17715        app.s3_state.selected_row = 2;
17716
17717        // Press left arrow - should collapse folder2 and jump to folder1
17718        app.handle_action(Action::CollapseRow);
17719
17720        // folder2 should be collapsed
17721        assert!(!app.s3_state.expanded_prefixes.contains("folder1/folder2/"));
17722        // Selection should move to folder1 (row 1)
17723        assert_eq!(app.s3_state.selected_row, 1);
17724    }
17725
17726    #[test]
17727    fn test_s3_bucket_collapse_expanded_folder_moves_to_parent() {
17728        use S3Bucket;
17729        use S3Object;
17730
17731        let mut app = test_app();
17732        app.current_service = Service::S3Buckets;
17733        app.service_selected = true;
17734
17735        // Create bucket with folder
17736        app.s3_state.buckets.items = vec![S3Bucket {
17737            name: "test-bucket".to_string(),
17738            region: "us-east-1".to_string(),
17739            creation_date: String::new(),
17740        }];
17741
17742        // Expand bucket with folder1/
17743        app.s3_state
17744            .expanded_prefixes
17745            .insert("test-bucket".to_string());
17746        app.s3_state.bucket_preview.insert(
17747            "test-bucket".to_string(),
17748            vec![S3Object {
17749                key: "folder1/".to_string(),
17750                is_prefix: true,
17751                size: 0,
17752                last_modified: String::new(),
17753                storage_class: String::new(),
17754            }],
17755        );
17756
17757        // Expand folder1/
17758        app.s3_state
17759            .expanded_prefixes
17760            .insert("folder1/".to_string());
17761        app.s3_state.prefix_preview.insert(
17762            "folder1/".to_string(),
17763            vec![S3Object {
17764                key: "folder1/file.txt".to_string(),
17765                is_prefix: false,
17766                size: 100,
17767                last_modified: String::new(),
17768                storage_class: String::new(),
17769            }],
17770        );
17771
17772        // Select folder1/ (row 0: bucket, row 1: folder1)
17773        app.s3_state.selected_row = 1;
17774
17775        // Press left arrow - should collapse folder1 and jump to bucket
17776        app.handle_action(Action::CollapseRow);
17777
17778        // folder1 should be collapsed
17779        assert!(!app.s3_state.expanded_prefixes.contains("folder1/"));
17780        // Selection should move to bucket (row 0)
17781        assert_eq!(app.s3_state.selected_row, 0);
17782    }
17783
17784    #[test]
17785    fn test_log_streams_pagination_limits_table_content() {
17786        let mut app = test_app();
17787        app.current_service = Service::CloudWatchLogGroups;
17788        app.service_selected = true;
17789        app.view_mode = ViewMode::Detail;
17790
17791        // Create 50 log streams
17792        app.log_groups_state.log_streams = (0..50)
17793            .map(|i| rusticity_core::LogStream {
17794                name: format!("stream-{}", i),
17795                creation_time: None,
17796                last_event_time: None,
17797            })
17798            .collect();
17799
17800        // Set page size to 10
17801        app.log_groups_state.stream_page_size = 10;
17802        app.log_groups_state.stream_current_page = 0;
17803
17804        // First page should show streams 0-9
17805        // (This is tested by rendering, but we can verify pagination logic)
17806        assert_eq!(app.log_groups_state.stream_page_size, 10);
17807        assert_eq!(app.log_groups_state.stream_current_page, 0);
17808
17809        // Navigate to page 2
17810        app.log_groups_state.stream_current_page = 1;
17811        assert_eq!(app.log_groups_state.stream_current_page, 1);
17812    }
17813
17814    #[test]
17815    fn test_log_streams_page_size_change_resets_page() {
17816        let mut app = test_app();
17817        app.current_service = Service::CloudWatchLogGroups;
17818        app.service_selected = true;
17819        app.view_mode = ViewMode::Detail;
17820        app.mode = Mode::ColumnSelector;
17821
17822        app.log_groups_state.stream_page_size = 10;
17823        app.log_groups_state.stream_current_page = 3;
17824
17825        // Change page size - should reset to page 0
17826        app.column_selector_index = app.cw_log_stream_column_ids.len() + 4; // 25 items
17827        app.handle_action(Action::ToggleColumn);
17828
17829        assert_eq!(app.log_groups_state.stream_page_size, 25);
17830        assert_eq!(app.log_groups_state.stream_current_page, 0);
17831    }
17832
17833    #[test]
17834    fn test_s3_objects_expanded_rows_stay_visible() {
17835        use S3Object;
17836
17837        let mut app = test_app();
17838        app.current_service = Service::S3Buckets;
17839        app.service_selected = true;
17840        app.mode = Mode::Normal;
17841        app.s3_state.current_bucket = Some("test-bucket".to_string());
17842
17843        // Create a folder with many nested items
17844        app.s3_state.objects = vec![S3Object {
17845            key: "folder1/".to_string(),
17846            is_prefix: true,
17847            size: 0,
17848            last_modified: String::new(),
17849            storage_class: String::new(),
17850        }];
17851
17852        // Expand folder1 with 20 files
17853        app.s3_state
17854            .expanded_prefixes
17855            .insert("folder1/".to_string());
17856        app.s3_state.prefix_preview.insert(
17857            "folder1/".to_string(),
17858            (0..20)
17859                .map(|i| S3Object {
17860                    key: format!("folder1/file{}.txt", i),
17861                    is_prefix: false,
17862                    size: 100,
17863                    last_modified: String::new(),
17864                    storage_class: String::new(),
17865                })
17866                .collect(),
17867        );
17868
17869        // Set viewport to show 10 rows
17870        app.s3_state.object_visible_rows.set(10);
17871        app.s3_state.object_scroll_offset = 0;
17872        app.s3_state.selected_object = 0; // folder1
17873
17874        // Navigate down through all items
17875        for i in 1..=20 {
17876            app.handle_action(Action::NextItem);
17877            assert_eq!(app.s3_state.selected_object, i);
17878
17879            // Check that selection is within visible range
17880            let visible_start = app.s3_state.object_scroll_offset;
17881            let visible_end = visible_start + app.s3_state.object_visible_rows.get();
17882            assert!(
17883                app.s3_state.selected_object >= visible_start
17884                    && app.s3_state.selected_object < visible_end,
17885                "Selection {} should be visible in range [{}, {})",
17886                app.s3_state.selected_object,
17887                visible_start,
17888                visible_end
17889            );
17890        }
17891    }
17892
17893    #[test]
17894    fn test_s3_bucket_error_rows_counted_in_total() {
17895        use S3Bucket;
17896
17897        let mut app = test_app();
17898        app.current_service = Service::S3Buckets;
17899        app.service_selected = true;
17900
17901        // Create buckets
17902        app.s3_state.buckets.items = vec![
17903            S3Bucket {
17904                name: "bucket1".to_string(),
17905                region: "us-east-1".to_string(),
17906                creation_date: String::new(),
17907            },
17908            S3Bucket {
17909                name: "bucket2".to_string(),
17910                region: "us-east-1".to_string(),
17911                creation_date: String::new(),
17912            },
17913        ];
17914
17915        // Expand bucket1 with error (long error message that will wrap)
17916        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
17917        let long_error = "service error: unhandled error (PermanentRedirect): Error { code: PermanentRedirect, message: The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint., request_id: 6D5VJ9TXYEMXSMXG, s3_extended_request_id: CGSwddO9ummjFYFHKyqNEU= }".to_string();
17918        app.s3_state
17919            .bucket_errors
17920            .insert("bucket1".to_string(), long_error.clone());
17921
17922        // Calculate total rows
17923        let total = app.calculate_total_bucket_rows();
17924
17925        // Should be: 2 buckets + error rows (long_error.len() / 120 rounded up)
17926        let error_rows = long_error.len().div_ceil(120);
17927        assert_eq!(total, 2 + error_rows);
17928    }
17929
17930    #[test]
17931    fn test_s3_bucket_with_error_can_be_collapsed() {
17932        use S3Bucket;
17933
17934        let mut app = test_app();
17935        app.current_service = Service::S3Buckets;
17936        app.service_selected = true;
17937        app.mode = Mode::Normal;
17938
17939        // Create bucket
17940        app.s3_state.buckets.items = vec![S3Bucket {
17941            name: "bucket1".to_string(),
17942            region: "us-east-1".to_string(),
17943            creation_date: String::new(),
17944        }];
17945
17946        // Expand bucket with error
17947        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
17948        let error = "service error: PermanentRedirect".to_string();
17949        app.s3_state
17950            .bucket_errors
17951            .insert("bucket1".to_string(), error);
17952
17953        // Select the bucket row (row 0) - error rows are not selectable
17954        app.s3_state.selected_row = 0;
17955
17956        // Press left arrow to collapse
17957        app.handle_action(Action::CollapseRow);
17958
17959        // Bucket should be collapsed
17960        assert!(!app.s3_state.expanded_prefixes.contains("bucket1"));
17961        // Selection should stay on bucket
17962        assert_eq!(app.s3_state.selected_row, 0);
17963    }
17964
17965    #[test]
17966    fn test_s3_bucket_collapse_on_bucket_row() {
17967        use S3Bucket;
17968
17969        let mut app = test_app();
17970        app.current_service = Service::S3Buckets;
17971        app.service_selected = true;
17972        app.mode = Mode::Normal;
17973
17974        // Create bucket
17975        app.s3_state.buckets.items = vec![S3Bucket {
17976            name: "bucket1".to_string(),
17977            region: "us-east-1".to_string(),
17978            creation_date: String::new(),
17979        }];
17980
17981        // Expand bucket with error
17982        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
17983        let error = "service error: PermanentRedirect".to_string();
17984        app.s3_state
17985            .bucket_errors
17986            .insert("bucket1".to_string(), error);
17987
17988        // Select the bucket row itself (row 0)
17989        app.s3_state.selected_row = 0;
17990
17991        // Press left arrow to collapse
17992        app.handle_action(Action::CollapseRow);
17993
17994        // Bucket should be collapsed
17995        assert!(!app.s3_state.expanded_prefixes.contains("bucket1"));
17996        // Selection should stay on bucket
17997        assert_eq!(app.s3_state.selected_row, 0);
17998    }
17999
18000    #[test]
18001    fn test_s3_bucket_collapse_adjusts_scroll_offset() {
18002        use S3Bucket;
18003
18004        let mut app = test_app();
18005        app.current_service = Service::S3Buckets;
18006        app.service_selected = true;
18007        app.mode = Mode::Normal;
18008
18009        // Create multiple buckets
18010        app.s3_state.buckets.items = (0..20)
18011            .map(|i| S3Bucket {
18012                name: format!("bucket{}", i),
18013                region: "us-east-1".to_string(),
18014                creation_date: String::new(),
18015            })
18016            .collect();
18017
18018        // Expand bucket 10 with a long error
18019        app.s3_state
18020            .expanded_prefixes
18021            .insert("bucket10".to_string());
18022        let long_error = "service error: unhandled error (PermanentRedirect): Error { code: PermanentRedirect, message: The bucket you are attempting to access must be addressed using the specified endpoint. Please send all future requests to this endpoint., request_id: 6D5VJ9TXYEMXSMXG }".to_string();
18023        app.s3_state
18024            .bucket_errors
18025            .insert("bucket10".to_string(), long_error.clone());
18026
18027        // Set viewport to 10 rows and scroll so bucket10 is at top
18028        app.s3_state.bucket_visible_rows.set(10);
18029        app.s3_state.bucket_scroll_offset = 10; // bucket10 is at row 10
18030
18031        // Select bucket10 (row 10) - error rows are not selectable
18032        app.s3_state.selected_row = 10;
18033
18034        // Press left arrow to collapse
18035        app.handle_action(Action::CollapseRow);
18036
18037        // Bucket should be collapsed
18038        assert!(!app.s3_state.expanded_prefixes.contains("bucket10"));
18039        // Selection should stay on bucket10 (row 10)
18040        assert_eq!(app.s3_state.selected_row, 10);
18041        // Scroll offset should be adjusted to show bucket10
18042        assert!(app.s3_state.selected_row >= app.s3_state.bucket_scroll_offset);
18043        assert!(
18044            app.s3_state.selected_row
18045                < app.s3_state.bucket_scroll_offset + app.s3_state.bucket_visible_rows.get()
18046        );
18047    }
18048
18049    #[test]
18050    fn test_s3_collapse_second_to_last_bucket_with_last_having_error() {
18051        use S3Bucket;
18052
18053        let mut app = test_app();
18054        app.current_service = Service::S3Buckets;
18055        app.service_selected = true;
18056        app.mode = Mode::Normal;
18057
18058        // Create 3 buckets
18059        app.s3_state.buckets.items = vec![
18060            S3Bucket {
18061                name: "bucket1".to_string(),
18062                region: "us-east-1".to_string(),
18063                creation_date: String::new(),
18064            },
18065            S3Bucket {
18066                name: "bucket2".to_string(),
18067                region: "us-east-1".to_string(),
18068                creation_date: String::new(),
18069            },
18070            S3Bucket {
18071                name: "bucket3".to_string(),
18072                region: "us-east-1".to_string(),
18073                creation_date: String::new(),
18074            },
18075        ];
18076
18077        // Expand bucket2 (second to last) with preview
18078        app.s3_state.expanded_prefixes.insert("bucket2".to_string());
18079        app.s3_state.bucket_preview.insert(
18080            "bucket2".to_string(),
18081            vec![
18082                S3Object {
18083                    key: "folder1/".to_string(),
18084                    is_prefix: true,
18085                    size: 0,
18086                    last_modified: String::new(),
18087                    storage_class: String::new(),
18088                },
18089                S3Object {
18090                    key: "file1.txt".to_string(),
18091                    is_prefix: false,
18092                    size: 100,
18093                    last_modified: String::new(),
18094                    storage_class: String::new(),
18095                },
18096            ],
18097        );
18098
18099        // Expand bucket3 (last) with error
18100        app.s3_state.expanded_prefixes.insert("bucket3".to_string());
18101        let error = "service error: PermanentRedirect".to_string();
18102        app.s3_state
18103            .bucket_errors
18104            .insert("bucket3".to_string(), error);
18105
18106        // Set viewport
18107        app.s3_state.bucket_visible_rows.set(10);
18108        app.s3_state.bucket_scroll_offset = 0;
18109
18110        // Select last item in bucket2 (row 3: bucket1, bucket2, folder1, file1)
18111        app.s3_state.selected_row = 3;
18112
18113        // Collapse - should move to parent (bucket2)
18114        app.handle_action(Action::CollapseRow);
18115
18116        // bucket2 should still be expanded (we only moved to parent, didn't collapse)
18117        assert!(app.s3_state.expanded_prefixes.contains("bucket2"));
18118        // Selection should move to bucket2
18119        assert_eq!(app.s3_state.selected_row, 1);
18120        // Selection should be visible
18121        assert!(app.s3_state.selected_row >= app.s3_state.bucket_scroll_offset);
18122        assert!(
18123            app.s3_state.selected_row
18124                < app.s3_state.bucket_scroll_offset + app.s3_state.bucket_visible_rows.get()
18125        );
18126    }
18127
18128    #[test]
18129    fn test_s3_collapse_bucket_with_error() {
18130        use S3Bucket;
18131
18132        let mut app = test_app();
18133        app.current_service = Service::S3Buckets;
18134        app.service_selected = true;
18135        app.mode = Mode::Normal;
18136
18137        app.s3_state.buckets.items = vec![
18138            S3Bucket {
18139                name: "bucket1".to_string(),
18140                region: "us-east-1".to_string(),
18141                creation_date: String::new(),
18142            },
18143            S3Bucket {
18144                name: "bucket2".to_string(),
18145                region: "us-east-1".to_string(),
18146                creation_date: String::new(),
18147            },
18148        ];
18149
18150        let error = "service error: unhandled error (PermanentRedirect)".to_string();
18151        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
18152        app.s3_state
18153            .bucket_errors
18154            .insert("bucket1".to_string(), error);
18155
18156        app.s3_state.bucket_visible_rows.set(10);
18157        app.s3_state.bucket_scroll_offset = 0;
18158
18159        // Select bucket1 (row 0) - error rows are not selectable
18160        app.s3_state.selected_row = 0;
18161
18162        // Collapse
18163        app.handle_action(Action::CollapseRow);
18164
18165        // bucket1 should be collapsed
18166        assert!(!app.s3_state.expanded_prefixes.contains("bucket1"));
18167        assert_eq!(app.s3_state.selected_row, 0);
18168    }
18169
18170    #[test]
18171    fn test_s3_collapse_row_with_multiple_error_buckets() {
18172        use S3Bucket;
18173
18174        let mut app = test_app();
18175        app.current_service = Service::S3Buckets;
18176        app.service_selected = true;
18177        app.mode = Mode::Normal;
18178
18179        app.s3_state.buckets.items = vec![
18180            S3Bucket {
18181                name: "bucket1".to_string(),
18182                region: "us-east-1".to_string(),
18183                creation_date: String::new(),
18184            },
18185            S3Bucket {
18186                name: "bucket2".to_string(),
18187                region: "us-east-1".to_string(),
18188                creation_date: String::new(),
18189            },
18190            S3Bucket {
18191                name: "bucket3".to_string(),
18192                region: "us-east-1".to_string(),
18193                creation_date: String::new(),
18194            },
18195        ];
18196
18197        let error = "service error: unhandled error (PermanentRedirect)".to_string();
18198
18199        // Expand bucket1 with error
18200        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
18201        app.s3_state
18202            .bucket_errors
18203            .insert("bucket1".to_string(), error.clone());
18204
18205        // Expand bucket3 with error
18206        app.s3_state.expanded_prefixes.insert("bucket3".to_string());
18207        app.s3_state
18208            .bucket_errors
18209            .insert("bucket3".to_string(), error.clone());
18210
18211        app.s3_state.bucket_visible_rows.set(30);
18212        app.s3_state.bucket_scroll_offset = 0;
18213
18214        // Row 0: bucket1 (expanded with error - error rows not selectable)
18215        // Row 1: bucket2
18216        // Row 2: bucket3 (expanded with error - error rows not selectable)
18217        // Select bucket3
18218        app.s3_state.selected_row = 2;
18219
18220        app.handle_action(Action::CollapseRow);
18221
18222        // bucket3 should be collapsed, NOT bucket1
18223        assert!(
18224            !app.s3_state.expanded_prefixes.contains("bucket3"),
18225            "bucket3 should be collapsed"
18226        );
18227        assert!(
18228            app.s3_state.expanded_prefixes.contains("bucket1"),
18229            "bucket1 should still be expanded"
18230        );
18231        assert_eq!(app.s3_state.selected_row, 2);
18232    }
18233
18234    #[test]
18235    fn test_s3_collapse_row_nested_only_collapses_one_level() {
18236        use S3Bucket;
18237
18238        let mut app = test_app();
18239        app.current_service = Service::S3Buckets;
18240        app.service_selected = true;
18241        app.mode = Mode::Normal;
18242
18243        app.s3_state.buckets.items = vec![S3Bucket {
18244            name: "bucket1".to_string(),
18245            region: "us-east-1".to_string(),
18246            creation_date: String::new(),
18247        }];
18248
18249        // Level 1: bucket -> level1/
18250        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
18251        app.s3_state.bucket_preview.insert(
18252            "bucket1".to_string(),
18253            vec![S3Object {
18254                key: "level1/".to_string(),
18255                size: 0,
18256                last_modified: "2024-01-01T00:00:00Z".to_string(),
18257                is_prefix: true,
18258                storage_class: String::new(),
18259            }],
18260        );
18261
18262        // Level 2: level1/ -> level2/
18263        app.s3_state.expanded_prefixes.insert("level1/".to_string());
18264        app.s3_state.prefix_preview.insert(
18265            "level1/".to_string(),
18266            vec![S3Object {
18267                key: "level1/level2/".to_string(),
18268                size: 0,
18269                last_modified: "2024-01-01T00:00:00Z".to_string(),
18270                is_prefix: true,
18271                storage_class: String::new(),
18272            }],
18273        );
18274
18275        // Level 3: level2/ -> file
18276        app.s3_state
18277            .expanded_prefixes
18278            .insert("level1/level2/".to_string());
18279        app.s3_state.prefix_preview.insert(
18280            "level1/level2/".to_string(),
18281            vec![S3Object {
18282                key: "level1/level2/file.txt".to_string(),
18283                size: 100,
18284                last_modified: "2024-01-01T00:00:00Z".to_string(),
18285                is_prefix: false,
18286                storage_class: String::new(),
18287            }],
18288        );
18289
18290        app.s3_state.bucket_visible_rows.set(10);
18291
18292        // Select level2/ (row 2: bucket, level1, level2)
18293        app.s3_state.selected_row = 2;
18294
18295        // Collapse - should only collapse level2/, not the entire bucket
18296        app.handle_action(Action::CollapseRow);
18297
18298        // level2/ should be collapsed
18299        assert!(!app.s3_state.expanded_prefixes.contains("level1/level2/"));
18300        // level1/ should still be expanded
18301        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
18302        // bucket should still be expanded
18303        assert!(app.s3_state.expanded_prefixes.contains("bucket1"));
18304        // Selection should move to parent (level1/ at row 1)
18305        assert_eq!(app.s3_state.selected_row, 1);
18306    }
18307
18308    #[test]
18309    fn test_s3_collapse_row_deeply_nested_file() {
18310        use S3Bucket;
18311
18312        let mut app = test_app();
18313        app.current_service = Service::S3Buckets;
18314        app.service_selected = true;
18315        app.mode = Mode::Normal;
18316
18317        app.s3_state.buckets.items = vec![S3Bucket {
18318            name: "bucket1".to_string(),
18319            region: "us-east-1".to_string(),
18320            creation_date: String::new(),
18321        }];
18322
18323        // Level 1: bucket -> level1/
18324        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
18325        app.s3_state.bucket_preview.insert(
18326            "bucket1".to_string(),
18327            vec![S3Object {
18328                key: "level1/".to_string(),
18329                size: 0,
18330                last_modified: "2024-01-01T00:00:00Z".to_string(),
18331                is_prefix: true,
18332                storage_class: String::new(),
18333            }],
18334        );
18335
18336        // Level 2: level1/ -> level2/
18337        app.s3_state.expanded_prefixes.insert("level1/".to_string());
18338        app.s3_state.prefix_preview.insert(
18339            "level1/".to_string(),
18340            vec![S3Object {
18341                key: "level1/level2/".to_string(),
18342                size: 0,
18343                last_modified: "2024-01-01T00:00:00Z".to_string(),
18344                is_prefix: true,
18345                storage_class: String::new(),
18346            }],
18347        );
18348
18349        // Level 3: level2/ -> file
18350        app.s3_state
18351            .expanded_prefixes
18352            .insert("level1/level2/".to_string());
18353        app.s3_state.prefix_preview.insert(
18354            "level1/level2/".to_string(),
18355            vec![S3Object {
18356                key: "level1/level2/file.txt".to_string(),
18357                size: 100,
18358                last_modified: "2024-01-01T00:00:00Z".to_string(),
18359                is_prefix: false,
18360                storage_class: String::new(),
18361            }],
18362        );
18363
18364        app.s3_state.bucket_visible_rows.set(10);
18365
18366        // Select file (row 3: bucket, level1, level2, file)
18367        app.s3_state.selected_row = 3;
18368
18369        // Collapse - should move to parent (level2/)
18370        app.handle_action(Action::CollapseRow);
18371
18372        // All levels should still be expanded
18373        assert!(app.s3_state.expanded_prefixes.contains("level1/level2/"));
18374        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
18375        assert!(app.s3_state.expanded_prefixes.contains("bucket1"));
18376        // Selection should move to parent (level2/ at row 2)
18377        assert_eq!(app.s3_state.selected_row, 2);
18378    }
18379
18380    #[test]
18381    fn test_s3_bucket_pagination_adjusts_scroll() {
18382        use S3Bucket;
18383
18384        let mut app = test_app();
18385        app.current_service = Service::S3Buckets;
18386        app.service_selected = true;
18387        app.mode = Mode::Normal;
18388
18389        // Create 150 buckets (3 pages with page size 50)
18390        app.s3_state.buckets.items = (0..150)
18391            .map(|i| S3Bucket {
18392                name: format!("bucket{:03}", i),
18393                region: "us-east-1".to_string(),
18394                creation_date: String::new(),
18395            })
18396            .collect();
18397
18398        app.s3_state.bucket_visible_rows.set(20);
18399        app.s3_state.selected_row = 0;
18400        app.s3_state.bucket_scroll_offset = 0;
18401
18402        // Go to page 2 (should select row 50 and scroll to show it)
18403        app.go_to_page(2);
18404
18405        assert_eq!(app.s3_state.selected_row, 50);
18406        // Scroll offset should be adjusted to show page 2
18407        assert_eq!(app.s3_state.bucket_scroll_offset, 50);
18408
18409        // Go to page 3 (should select row 100 and scroll to show it)
18410        app.go_to_page(3);
18411
18412        assert_eq!(app.s3_state.selected_row, 100);
18413        assert_eq!(app.s3_state.bucket_scroll_offset, 100);
18414
18415        // Go to page 1 (should select row 0 and scroll to top)
18416        app.go_to_page(1);
18417
18418        assert_eq!(app.s3_state.selected_row, 0);
18419        assert_eq!(app.s3_state.bucket_scroll_offset, 0);
18420    }
18421
18422    #[test]
18423    fn test_s3_bucket_pagination_uses_page_size() {
18424        use S3Bucket;
18425
18426        let mut app = test_app();
18427        app.current_service = Service::S3Buckets;
18428        app.service_selected = true;
18429        app.mode = Mode::Normal;
18430
18431        // Create 100 buckets
18432        app.s3_state.buckets.items = (0..100)
18433            .map(|i| S3Bucket {
18434                name: format!("bucket{:03}", i),
18435                region: "us-east-1".to_string(),
18436                creation_date: String::new(),
18437            })
18438            .collect();
18439
18440        app.s3_state.bucket_visible_rows.set(20);
18441        app.s3_state.selected_row = 0;
18442
18443        // Default page size is 50
18444        assert_eq!(app.s3_state.buckets.page_size.value(), 50);
18445
18446        // Go to page 2 with default page size (50)
18447        app.go_to_page(2);
18448        assert_eq!(app.s3_state.selected_row, 50);
18449        assert_eq!(app.s3_state.bucket_scroll_offset, 50);
18450
18451        // Change page size to 25
18452        app.s3_state.buckets.page_size = crate::common::PageSize::TwentyFive;
18453        assert_eq!(app.s3_state.buckets.page_size.value(), 25);
18454
18455        // Go to page 2 with new page size (25)
18456        app.go_to_page(2);
18457        assert_eq!(app.s3_state.selected_row, 25);
18458        assert_eq!(app.s3_state.bucket_scroll_offset, 25);
18459    }
18460
18461    #[test]
18462    fn test_s3_bucket_page_size_limits_visible_rows() {
18463        use S3Bucket;
18464
18465        let mut app = test_app();
18466        app.current_service = Service::S3Buckets;
18467        app.service_selected = true;
18468        app.mode = Mode::Normal;
18469
18470        // Create 100 buckets
18471        app.s3_state.buckets.items = (0..100)
18472            .map(|i| S3Bucket {
18473                name: format!("bucket{:03}", i),
18474                region: "us-east-1".to_string(),
18475                creation_date: String::new(),
18476            })
18477            .collect();
18478
18479        // Set page size to 10
18480        app.s3_state.buckets.page_size = crate::common::PageSize::Ten;
18481        assert_eq!(app.s3_state.buckets.page_size.value(), 10);
18482
18483        // Calculate total rows - should only count buckets on current page
18484        let total_rows = app.calculate_total_bucket_rows();
18485        // With 100 buckets and page size 10, we should see 10 buckets per page
18486        // But calculate_total_bucket_rows returns ALL rows, not just current page
18487        // This is the issue - we need to paginate the display
18488        assert!(total_rows >= 10, "Should have at least 10 rows");
18489    }
18490
18491    #[test]
18492    fn test_s3_bucket_tab_cycling_in_filter() {
18493        use crate::common::InputFocus;
18494
18495        let mut app = test_app();
18496        app.current_service = Service::S3Buckets;
18497        app.mode = Mode::FilterInput;
18498
18499        // Start at Filter
18500        assert_eq!(app.s3_state.input_focus, InputFocus::Filter);
18501
18502        // Tab to Pagination
18503        app.handle_action(Action::NextFilterFocus);
18504        assert_eq!(app.s3_state.input_focus, InputFocus::Pagination);
18505
18506        // Tab back to Filter
18507        app.handle_action(Action::NextFilterFocus);
18508        assert_eq!(app.s3_state.input_focus, InputFocus::Filter);
18509    }
18510
18511    #[test]
18512    fn test_s3_bucket_pagination_navigation_with_arrows() {
18513        use S3Bucket;
18514
18515        let mut app = test_app();
18516        app.current_service = Service::S3Buckets;
18517        app.mode = Mode::FilterInput;
18518
18519        // Create 100 buckets
18520        app.s3_state.buckets.items = (0..100)
18521            .map(|i| S3Bucket {
18522                name: format!("bucket{:03}", i),
18523                region: "us-east-1".to_string(),
18524                creation_date: String::new(),
18525            })
18526            .collect();
18527
18528        app.s3_state.buckets.page_size = crate::common::PageSize::Ten;
18529        app.s3_state.selected_row = 0;
18530
18531        // Focus pagination
18532        app.s3_state.input_focus = crate::common::InputFocus::Pagination;
18533
18534        // Right arrow should go to next page (row 10)
18535        app.handle_action(Action::NextItem);
18536        assert_eq!(app.s3_state.selected_row, 10);
18537
18538        // Right arrow again (row 20)
18539        app.handle_action(Action::NextItem);
18540        assert_eq!(app.s3_state.selected_row, 20);
18541
18542        // Left arrow should go back (row 10)
18543        app.handle_action(Action::PrevItem);
18544        assert_eq!(app.s3_state.selected_row, 10);
18545    }
18546
18547    #[test]
18548    fn test_s3_bucket_go_to_page_shows_correct_buckets() {
18549        use S3Bucket;
18550
18551        let mut app = test_app();
18552        app.current_service = Service::S3Buckets;
18553        app.service_selected = true;
18554        app.mode = Mode::Normal;
18555
18556        // Create 100 buckets
18557        app.s3_state.buckets.items = (0..100)
18558            .map(|i| S3Bucket {
18559                name: format!("bucket{:03}", i),
18560                region: "us-east-1".to_string(),
18561                creation_date: String::new(),
18562            })
18563            .collect();
18564
18565        app.s3_state.buckets.page_size = crate::common::PageSize::Ten;
18566
18567        // Go to page 2 (should show buckets 10-19)
18568        app.go_to_page(2);
18569        assert_eq!(app.s3_state.selected_row, 10);
18570
18571        // Go to page 5 (should show buckets 40-49)
18572        app.go_to_page(5);
18573        assert_eq!(app.s3_state.selected_row, 40);
18574    }
18575
18576    #[test]
18577    fn test_s3_bucket_left_right_arrows_change_pages() {
18578        use S3Bucket;
18579
18580        let mut app = test_app();
18581        app.current_service = Service::S3Buckets;
18582        app.mode = Mode::FilterInput;
18583
18584        // Create 100 buckets
18585        app.s3_state.buckets.items = (0..100)
18586            .map(|i| S3Bucket {
18587                name: format!("bucket{:03}", i),
18588                region: "us-east-1".to_string(),
18589                creation_date: String::new(),
18590            })
18591            .collect();
18592
18593        app.s3_state.buckets.page_size = crate::common::PageSize::Ten;
18594        app.s3_state.selected_row = 0;
18595        app.s3_state.input_focus = crate::common::InputFocus::Pagination;
18596
18597        // Right arrow (PageDown) should go to next page
18598        app.handle_action(Action::PageDown);
18599        assert_eq!(app.s3_state.selected_row, 10);
18600
18601        // Right arrow again
18602        app.handle_action(Action::PageDown);
18603        assert_eq!(app.s3_state.selected_row, 20);
18604
18605        // Left arrow (PageUp) should go back
18606        app.handle_action(Action::PageUp);
18607        assert_eq!(app.s3_state.selected_row, 10);
18608    }
18609}