rusticity_term/
app.rs

1pub use crate::aws::{Profile as AwsProfile, Region as AwsRegion};
2use crate::cfn::{Column as CfnColumn, Stack as CfnStack};
3use crate::common::{ColumnId, CyclicEnum, InputFocus, PageSize, SortDirection};
4use crate::cw::insights::{InsightsFocus, InsightsState};
5pub use crate::cw::{Alarm, AlarmColumn};
6pub use crate::ec2::{Column as Ec2Column, Instance as Ec2Instance};
7use crate::ecr::image::{Column as EcrImageColumn, Image as EcrImage};
8use crate::ecr::repo::{Column as EcrColumn, Repository as EcrRepository};
9use crate::iam::{self, UserColumn};
10use crate::keymap::{Action, Mode};
11pub use crate::lambda::DeploymentColumn;
12pub use crate::lambda::ResourceColumn;
13pub use crate::lambda::{
14    Application as LambdaApplication, ApplicationColumn as LambdaApplicationColumn,
15    Function as LambdaFunction, FunctionColumn as LambdaColumn,
16};
17pub use crate::s3::{Bucket as S3Bucket, BucketColumn as S3BucketColumn, Object as S3Object};
18use crate::session::{Session, SessionTab};
19pub use crate::sqs::queue::Column as SqsColumn;
20pub use crate::sqs::trigger::Column as SqsTriggerColumn;
21use crate::table::TableState;
22use crate::ui::cfn::State as CfnStateConstants;
23pub use crate::ui::cfn::{
24    filtered_outputs, filtered_parameters, filtered_resources, DetailTab as CfnDetailTab,
25    State as CfnState, StatusFilter as CfnStatusFilter,
26};
27pub use crate::ui::cw::alarms::{AlarmTab, AlarmViewMode};
28use crate::ui::ec2;
29pub use crate::ui::ec2::{
30    State as Ec2State, StateFilter as Ec2StateFilter, STATE_FILTER as EC2_STATE_FILTER,
31};
32pub use crate::ui::ecr::{State as EcrState, Tab as EcrTab};
33use crate::ui::iam::{GroupTab, RoleTab, State as IamState, UserTab};
34pub use crate::ui::lambda::{
35    ApplicationDetailTab as LambdaApplicationDetailTab, ApplicationState as LambdaApplicationState,
36    DetailTab as LambdaDetailTab, State as LambdaState,
37};
38use crate::ui::monitoring::MonitoringState;
39pub use crate::ui::s3::{BucketType as S3BucketType, ObjectTab as S3ObjectTab, State as S3State};
40pub use crate::ui::sqs::{QueueDetailTab as SqsQueueDetailTab, State as SqsState};
41pub use crate::ui::{
42    CloudWatchLogGroupsState, DateRangeType, DetailTab, EventColumn, EventFilterFocus,
43    LogGroupColumn, Preferences, StreamColumn, StreamSort, TimeUnit,
44};
45use rusticity_core::{
46    AlarmsClient, AwsConfig, CloudFormationClient, CloudWatchClient, Ec2Client, EcrClient,
47    IamClient, LambdaClient, LogEvent, LogGroup, LogStream, S3Client, SqsClient,
48};
49
50#[derive(Clone)]
51pub struct Tab {
52    pub service: Service,
53    pub title: String,
54    pub breadcrumb: String,
55}
56
57pub struct App {
58    pub running: bool,
59    pub mode: Mode,
60    pub config: AwsConfig,
61    pub cloudwatch_client: CloudWatchClient,
62    pub s3_client: S3Client,
63    pub sqs_client: SqsClient,
64    pub alarms_client: AlarmsClient,
65    pub ec2_client: Ec2Client,
66    pub ecr_client: EcrClient,
67    pub iam_client: IamClient,
68    pub lambda_client: LambdaClient,
69    pub cloudformation_client: CloudFormationClient,
70    pub current_service: Service,
71    pub tabs: Vec<Tab>,
72    pub current_tab: usize,
73    pub tab_picker_selected: usize,
74    pub tab_filter: String,
75    pub pending_key: Option<char>,
76    pub log_groups_state: CloudWatchLogGroupsState,
77    pub insights_state: CloudWatchInsightsState,
78    pub alarms_state: CloudWatchAlarmsState,
79    pub s3_state: S3State,
80    pub sqs_state: SqsState,
81    pub ec2_state: Ec2State,
82    pub ecr_state: EcrState,
83    pub lambda_state: LambdaState,
84    pub lambda_application_state: LambdaApplicationState,
85    pub cfn_state: CfnState,
86    pub iam_state: IamState,
87    pub service_picker: ServicePickerState,
88    pub service_selected: bool,
89    pub profile: String,
90    pub region: String,
91    pub region_selector_index: usize,
92    pub cw_log_group_visible_column_ids: Vec<ColumnId>,
93    pub cw_log_group_column_ids: Vec<ColumnId>,
94    pub column_selector_index: usize,
95    pub preference_section: Preferences,
96    pub cw_log_stream_visible_column_ids: Vec<ColumnId>,
97    pub cw_log_stream_column_ids: Vec<ColumnId>,
98    pub cw_log_event_visible_column_ids: Vec<ColumnId>,
99    pub cw_log_event_column_ids: Vec<ColumnId>,
100    pub cw_alarm_visible_column_ids: Vec<ColumnId>,
101    pub cw_alarm_column_ids: Vec<ColumnId>,
102    pub s3_bucket_visible_column_ids: Vec<ColumnId>,
103    pub s3_bucket_column_ids: Vec<ColumnId>,
104    pub sqs_visible_column_ids: Vec<ColumnId>,
105    pub sqs_column_ids: Vec<ColumnId>,
106    pub ec2_visible_column_ids: Vec<ColumnId>,
107    pub ec2_column_ids: Vec<ColumnId>,
108    pub ecr_repo_visible_column_ids: Vec<ColumnId>,
109    pub ecr_repo_column_ids: Vec<ColumnId>,
110    pub ecr_image_visible_column_ids: Vec<ColumnId>,
111    pub ecr_image_column_ids: Vec<ColumnId>,
112    pub lambda_application_visible_column_ids: Vec<ColumnId>,
113    pub lambda_application_column_ids: Vec<ColumnId>,
114    pub lambda_deployment_visible_column_ids: Vec<ColumnId>,
115    pub lambda_deployment_column_ids: Vec<ColumnId>,
116    pub lambda_resource_visible_column_ids: Vec<ColumnId>,
117    pub lambda_resource_column_ids: Vec<ColumnId>,
118    pub cfn_visible_column_ids: Vec<ColumnId>,
119    pub cfn_column_ids: Vec<ColumnId>,
120    pub cfn_parameter_visible_column_ids: Vec<ColumnId>,
121    pub cfn_parameter_column_ids: Vec<ColumnId>,
122    pub cfn_output_visible_column_ids: Vec<ColumnId>,
123    pub cfn_output_column_ids: Vec<ColumnId>,
124    pub cfn_resource_visible_column_ids: Vec<ColumnId>,
125    pub cfn_resource_column_ids: Vec<ColumnId>,
126    pub iam_user_visible_column_ids: Vec<ColumnId>,
127    pub iam_user_column_ids: Vec<ColumnId>,
128    pub iam_role_visible_column_ids: Vec<String>,
129    pub iam_role_column_ids: Vec<String>,
130    pub iam_group_visible_column_ids: Vec<String>,
131    pub iam_group_column_ids: Vec<String>,
132    pub iam_policy_visible_column_ids: Vec<String>,
133    pub iam_policy_column_ids: Vec<String>,
134    pub view_mode: ViewMode,
135    pub error_message: Option<String>,
136    pub error_scroll: usize,
137    pub page_input: String,
138    pub calendar_date: Option<time::Date>,
139    pub calendar_selecting: CalendarField,
140    pub cursor_pos: usize,
141    pub current_session: Option<Session>,
142    pub sessions: Vec<Session>,
143    pub session_picker_selected: usize,
144    pub session_filter: String,
145    pub region_filter: String,
146    pub region_picker_selected: usize,
147    pub region_latencies: std::collections::HashMap<String, u64>,
148    pub profile_filter: String,
149    pub profile_picker_selected: usize,
150    pub available_profiles: Vec<AwsProfile>,
151    pub snapshot_requested: bool,
152}
153
154#[derive(Debug, Clone, Copy, PartialEq)]
155pub enum CalendarField {
156    StartDate,
157    EndDate,
158}
159
160pub struct CloudWatchInsightsState {
161    pub insights: InsightsState,
162    pub loading: bool,
163}
164
165pub struct CloudWatchAlarmsState {
166    pub table: TableState<Alarm>,
167    pub alarm_tab: AlarmTab,
168    pub view_as: AlarmViewMode,
169    pub wrap_lines: bool,
170    pub sort_column: String,
171    pub sort_direction: SortDirection,
172    pub input_focus: InputFocus,
173}
174
175impl PageSize {
176    pub fn value(&self) -> usize {
177        match self {
178            PageSize::Ten => 10,
179            PageSize::TwentyFive => 25,
180            PageSize::Fifty => 50,
181            PageSize::OneHundred => 100,
182        }
183    }
184
185    pub fn next(&self) -> Self {
186        match self {
187            PageSize::Ten => PageSize::TwentyFive,
188            PageSize::TwentyFive => PageSize::Fifty,
189            PageSize::Fifty => PageSize::OneHundred,
190            PageSize::OneHundred => PageSize::Ten,
191        }
192    }
193}
194
195pub struct ServicePickerState {
196    pub filter: String,
197    pub selected: usize,
198    pub services: Vec<&'static str>,
199}
200
201#[derive(Debug, Clone, Copy, PartialEq)]
202pub enum ViewMode {
203    List,
204    Detail,
205    Events,
206    InsightsResults,
207    PolicyView,
208}
209
210#[derive(Debug, Clone, Copy, PartialEq)]
211pub enum Service {
212    CloudWatchLogGroups,
213    CloudWatchInsights,
214    CloudWatchAlarms,
215    S3Buckets,
216    SqsQueues,
217    Ec2Instances,
218    EcrRepositories,
219    LambdaFunctions,
220    LambdaApplications,
221    CloudFormationStacks,
222    IamUsers,
223    IamRoles,
224    IamUserGroups,
225}
226
227impl Service {
228    pub fn name(&self) -> &str {
229        match self {
230            Service::CloudWatchLogGroups => "CloudWatch > Log Groups",
231            Service::CloudWatchInsights => "CloudWatch > Logs Insights",
232            Service::CloudWatchAlarms => "CloudWatch > Alarms",
233            Service::S3Buckets => "S3 > Buckets",
234            Service::SqsQueues => "SQS > Queues",
235            Service::Ec2Instances => "EC2 > Instances",
236            Service::EcrRepositories => "ECR > Repositories",
237            Service::LambdaFunctions => "Lambda > Functions",
238            Service::LambdaApplications => "Lambda > Applications",
239            Service::CloudFormationStacks => "CloudFormation > Stacks",
240            Service::IamUsers => "IAM > Users",
241            Service::IamRoles => "IAM > Roles",
242            Service::IamUserGroups => "IAM > User Groups",
243        }
244    }
245}
246
247fn copy_to_clipboard(text: &str) {
248    use std::io::Write;
249    use std::process::{Command, Stdio};
250    if let Ok(mut child) = Command::new("pbcopy").stdin(Stdio::piped()).spawn() {
251        if let Some(mut stdin) = child.stdin.take() {
252            let _ = stdin.write_all(text.as_bytes());
253        }
254        let _ = child.wait();
255    }
256}
257
258fn nav_page_down(selected: &mut usize, max: usize, page_size: usize) {
259    if max > 0 {
260        *selected = (*selected + page_size).min(max - 1);
261    }
262}
263
264impl App {
265    pub fn get_input_focus(&self) -> InputFocus {
266        InputFocus::Filter
267    }
268
269    fn get_active_filter_mut(&mut self) -> Option<&mut String> {
270        if self.current_service == Service::CloudWatchAlarms {
271            Some(&mut self.alarms_state.table.filter)
272        } else if self.current_service == Service::Ec2Instances {
273            Some(&mut self.ec2_state.table.filter)
274        } else if self.current_service == Service::S3Buckets {
275            if self.s3_state.current_bucket.is_some() {
276                Some(&mut self.s3_state.object_filter)
277            } else {
278                Some(&mut self.s3_state.buckets.filter)
279            }
280        } else if self.current_service == Service::EcrRepositories {
281            if self.ecr_state.current_repository.is_some() {
282                Some(&mut self.ecr_state.images.filter)
283            } else {
284                Some(&mut self.ecr_state.repositories.filter)
285            }
286        } else if self.current_service == Service::SqsQueues {
287            if self.sqs_state.current_queue.is_some()
288                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
289            {
290                Some(&mut self.sqs_state.triggers.filter)
291            } else if self.sqs_state.current_queue.is_some()
292                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
293            {
294                Some(&mut self.sqs_state.pipes.filter)
295            } else if self.sqs_state.current_queue.is_some()
296                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
297            {
298                Some(&mut self.sqs_state.tags.filter)
299            } else if self.sqs_state.current_queue.is_some()
300                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
301            {
302                Some(&mut self.sqs_state.subscriptions.filter)
303            } else {
304                Some(&mut self.sqs_state.queues.filter)
305            }
306        } else if self.current_service == Service::LambdaFunctions {
307            if self.lambda_state.current_version.is_some()
308                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
309            {
310                Some(&mut self.lambda_state.alias_table.filter)
311            } else if self.lambda_state.current_function.is_some()
312                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
313            {
314                Some(&mut self.lambda_state.version_table.filter)
315            } else if self.lambda_state.current_function.is_some()
316                && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
317            {
318                Some(&mut self.lambda_state.alias_table.filter)
319            } else {
320                Some(&mut self.lambda_state.table.filter)
321            }
322        } else if self.current_service == Service::LambdaApplications {
323            if self.lambda_application_state.current_application.is_some() {
324                if self.lambda_application_state.detail_tab
325                    == LambdaApplicationDetailTab::Deployments
326                {
327                    Some(&mut self.lambda_application_state.deployments.filter)
328                } else {
329                    Some(&mut self.lambda_application_state.resources.filter)
330                }
331            } else {
332                Some(&mut self.lambda_application_state.table.filter)
333            }
334        } else if self.current_service == Service::CloudFormationStacks {
335            if self.cfn_state.current_stack.is_some()
336                && self.cfn_state.detail_tab == CfnDetailTab::Resources
337            {
338                Some(&mut self.cfn_state.resources.filter)
339            } else {
340                Some(&mut self.cfn_state.table.filter)
341            }
342        } else if self.current_service == Service::IamUsers {
343            if self.iam_state.current_user.is_some() {
344                if self.iam_state.user_tab == UserTab::Tags {
345                    Some(&mut self.iam_state.user_tags.filter)
346                } else {
347                    Some(&mut self.iam_state.policies.filter)
348                }
349            } else {
350                Some(&mut self.iam_state.users.filter)
351            }
352        } else if self.current_service == Service::IamRoles {
353            if self.iam_state.current_role.is_some() {
354                if self.iam_state.role_tab == RoleTab::Tags {
355                    Some(&mut self.iam_state.tags.filter)
356                } else if self.iam_state.role_tab == RoleTab::LastAccessed {
357                    Some(&mut self.iam_state.last_accessed_filter)
358                } else {
359                    Some(&mut self.iam_state.policies.filter)
360                }
361            } else {
362                Some(&mut self.iam_state.roles.filter)
363            }
364        } else if self.current_service == Service::IamUserGroups {
365            if self.iam_state.current_group.is_some() {
366                if self.iam_state.group_tab == GroupTab::Permissions {
367                    Some(&mut self.iam_state.policies.filter)
368                } else if self.iam_state.group_tab == GroupTab::Users {
369                    Some(&mut self.iam_state.group_users.filter)
370                } else {
371                    None
372                }
373            } else {
374                Some(&mut self.iam_state.groups.filter)
375            }
376        } else if self.view_mode == ViewMode::List {
377            Some(&mut self.log_groups_state.log_groups.filter)
378        } else if self.view_mode == ViewMode::Detail
379            && self.log_groups_state.detail_tab == DetailTab::LogStreams
380        {
381            Some(&mut self.log_groups_state.stream_filter)
382        } else {
383            None
384        }
385    }
386
387    pub async fn new(profile: Option<String>, region: Option<String>) -> anyhow::Result<Self> {
388        let profile_name = profile.or_else(|| std::env::var("AWS_PROFILE").ok())
389            .ok_or_else(|| anyhow::anyhow!("No AWS profile specified. Set AWS_PROFILE environment variable or select a profile."))?;
390
391        std::env::set_var("AWS_PROFILE", &profile_name);
392
393        let config = AwsConfig::new(region).await?;
394        let cloudwatch_client = CloudWatchClient::new(config.clone()).await?;
395        let s3_client = S3Client::new(config.clone());
396        let sqs_client = SqsClient::new(config.clone());
397        let alarms_client = AlarmsClient::new(config.clone());
398        let ec2_client = Ec2Client::new(config.clone());
399        let ecr_client = EcrClient::new(config.clone());
400        let iam_client = IamClient::new(config.clone());
401        let lambda_client = LambdaClient::new(config.clone());
402        let cloudformation_client = CloudFormationClient::new(config.clone());
403        let region_name = config.region.clone();
404
405        Ok(Self {
406            running: true,
407            mode: Mode::ServicePicker,
408            config,
409            cloudwatch_client,
410            s3_client,
411            sqs_client,
412            alarms_client,
413            ec2_client,
414            ecr_client,
415            iam_client,
416            lambda_client,
417            cloudformation_client,
418            current_service: Service::CloudWatchLogGroups,
419            tabs: Vec::new(),
420            current_tab: 0,
421            tab_picker_selected: 0,
422            tab_filter: String::new(),
423            pending_key: None,
424            log_groups_state: CloudWatchLogGroupsState::new(),
425            insights_state: CloudWatchInsightsState::new(),
426            alarms_state: CloudWatchAlarmsState::new(),
427            s3_state: S3State::new(),
428            sqs_state: SqsState::new(),
429            ec2_state: Ec2State::default(),
430            ecr_state: EcrState::new(),
431            lambda_state: LambdaState::new(),
432            lambda_application_state: LambdaApplicationState::new(),
433            cfn_state: CfnState::new(),
434            iam_state: IamState::new(),
435            service_picker: ServicePickerState::new(),
436            service_selected: false,
437            profile: profile_name,
438            region: region_name,
439            region_selector_index: 0,
440            cw_log_group_visible_column_ids: LogGroupColumn::default_visible(),
441            cw_log_group_column_ids: LogGroupColumn::ids(),
442            column_selector_index: 0,
443            cw_log_stream_visible_column_ids: StreamColumn::default_visible(),
444            cw_log_stream_column_ids: StreamColumn::ids(),
445            cw_log_event_visible_column_ids: EventColumn::default_visible(),
446            cw_log_event_column_ids: EventColumn::ids(),
447            cw_alarm_visible_column_ids: [
448                AlarmColumn::Name,
449                AlarmColumn::State,
450                AlarmColumn::LastStateUpdate,
451                AlarmColumn::Conditions,
452                AlarmColumn::Actions,
453            ]
454            .iter()
455            .map(|c| c.id())
456            .collect(),
457            cw_alarm_column_ids: AlarmColumn::ids(),
458            s3_bucket_visible_column_ids: S3BucketColumn::ids(),
459            s3_bucket_column_ids: S3BucketColumn::ids(),
460            sqs_visible_column_ids: [
461                SqsColumn::Name,
462                SqsColumn::Type,
463                SqsColumn::Created,
464                SqsColumn::MessagesAvailable,
465                SqsColumn::MessagesInFlight,
466                SqsColumn::Encryption,
467                SqsColumn::ContentBasedDeduplication,
468            ]
469            .iter()
470            .map(|c| c.id())
471            .collect(),
472            sqs_column_ids: SqsColumn::ids(),
473            ec2_visible_column_ids: [
474                Ec2Column::Name,
475                Ec2Column::InstanceId,
476                Ec2Column::InstanceState,
477                Ec2Column::InstanceType,
478                Ec2Column::StatusCheck,
479                Ec2Column::AlarmStatus,
480                Ec2Column::AvailabilityZone,
481                Ec2Column::PublicIpv4Dns,
482                Ec2Column::PublicIpv4Address,
483                Ec2Column::ElasticIp,
484                Ec2Column::Ipv6Ips,
485                Ec2Column::Monitoring,
486                Ec2Column::SecurityGroupName,
487                Ec2Column::KeyName,
488                Ec2Column::LaunchTime,
489                Ec2Column::PlatformDetails,
490            ]
491            .iter()
492            .map(|c| c.id())
493            .collect(),
494            ec2_column_ids: Ec2Column::ids(),
495            ecr_repo_visible_column_ids: EcrColumn::ids(),
496            ecr_repo_column_ids: EcrColumn::ids(),
497            ecr_image_visible_column_ids: EcrImageColumn::ids(),
498            ecr_image_column_ids: EcrImageColumn::ids(),
499            lambda_application_visible_column_ids: LambdaApplicationColumn::visible(),
500            lambda_application_column_ids: LambdaApplicationColumn::ids(),
501            lambda_deployment_visible_column_ids: DeploymentColumn::ids(),
502            lambda_deployment_column_ids: DeploymentColumn::ids(),
503            lambda_resource_visible_column_ids: ResourceColumn::ids(),
504            lambda_resource_column_ids: ResourceColumn::ids(),
505            cfn_visible_column_ids: [
506                CfnColumn::Name,
507                CfnColumn::Status,
508                CfnColumn::CreatedTime,
509                CfnColumn::Description,
510            ]
511            .iter()
512            .map(|c| c.id())
513            .collect(),
514            cfn_column_ids: CfnColumn::ids(),
515            cfn_parameter_visible_column_ids: crate::ui::cfn::parameter_column_ids(),
516            cfn_parameter_column_ids: crate::ui::cfn::parameter_column_ids(),
517            cfn_output_visible_column_ids: crate::ui::cfn::output_column_ids(),
518            cfn_output_column_ids: crate::ui::cfn::output_column_ids(),
519            cfn_resource_visible_column_ids: crate::ui::cfn::resource_column_ids(),
520            cfn_resource_column_ids: crate::ui::cfn::resource_column_ids(),
521            iam_user_visible_column_ids: UserColumn::visible(),
522            iam_user_column_ids: UserColumn::ids(),
523            iam_role_visible_column_ids: vec![
524                "Role name".to_string(),
525                "Trusted entities".to_string(),
526                "Creation time".to_string(),
527            ],
528            iam_role_column_ids: vec![
529                "Role name".to_string(),
530                "Path".to_string(),
531                "Trusted entities".to_string(),
532                "ARN".to_string(),
533                "Creation time".to_string(),
534                "Description".to_string(),
535                "Max session duration".to_string(),
536            ],
537            iam_group_visible_column_ids: vec![
538                "Group name".to_string(),
539                "Users".to_string(),
540                "Permissions".to_string(),
541                "Creation time".to_string(),
542            ],
543            iam_group_column_ids: vec![
544                "Group name".to_string(),
545                "Path".to_string(),
546                "Users".to_string(),
547                "Permissions".to_string(),
548                "Creation time".to_string(),
549            ],
550            iam_policy_visible_column_ids: vec![
551                "Policy name".to_string(),
552                "Type".to_string(),
553                "Attached via".to_string(),
554            ],
555            iam_policy_column_ids: vec![
556                "Policy name".to_string(),
557                "Type".to_string(),
558                "Attached via".to_string(),
559                "Attached entities".to_string(),
560                "Description".to_string(),
561                "Creation time".to_string(),
562                "Edited time".to_string(),
563            ],
564            preference_section: Preferences::Columns,
565            view_mode: ViewMode::List,
566            error_message: None,
567            error_scroll: 0,
568            page_input: String::new(),
569            calendar_date: None,
570            calendar_selecting: CalendarField::StartDate,
571            cursor_pos: 0,
572            current_session: None,
573            sessions: Vec::new(),
574            session_picker_selected: 0,
575            session_filter: String::new(),
576            region_filter: String::new(),
577            region_picker_selected: 0,
578            region_latencies: std::collections::HashMap::new(),
579            profile_filter: String::new(),
580            profile_picker_selected: 0,
581            available_profiles: Vec::new(),
582            snapshot_requested: false,
583        })
584    }
585
586    pub fn new_without_client(profile: String, region: Option<String>) -> Self {
587        let config = AwsConfig::dummy(region.clone());
588        Self {
589            running: true,
590            mode: Mode::ServicePicker,
591            config: config.clone(),
592            cloudwatch_client: CloudWatchClient::dummy(config.clone()),
593            s3_client: S3Client::new(config.clone()),
594            sqs_client: SqsClient::new(config.clone()),
595            alarms_client: AlarmsClient::new(config.clone()),
596            ec2_client: Ec2Client::new(config.clone()),
597            ecr_client: EcrClient::new(config.clone()),
598            iam_client: IamClient::new(config.clone()),
599            lambda_client: LambdaClient::new(config.clone()),
600            cloudformation_client: CloudFormationClient::new(config.clone()),
601            current_service: Service::CloudWatchLogGroups,
602            tabs: Vec::new(),
603            current_tab: 0,
604            tab_picker_selected: 0,
605            tab_filter: String::new(),
606            pending_key: None,
607            log_groups_state: CloudWatchLogGroupsState::new(),
608            insights_state: CloudWatchInsightsState::new(),
609            alarms_state: CloudWatchAlarmsState::new(),
610            s3_state: S3State::new(),
611            sqs_state: SqsState::new(),
612            ec2_state: Ec2State::default(),
613            ecr_state: EcrState::new(),
614            lambda_state: LambdaState::new(),
615            lambda_application_state: LambdaApplicationState::new(),
616            cfn_state: CfnState::new(),
617            iam_state: IamState::new(),
618            service_picker: ServicePickerState::new(),
619            service_selected: false,
620            profile,
621            region: region.unwrap_or_default(),
622            region_selector_index: 0,
623            cw_log_group_visible_column_ids: LogGroupColumn::default_visible(),
624            cw_log_group_column_ids: LogGroupColumn::ids(),
625            column_selector_index: 0,
626            preference_section: Preferences::Columns,
627            cw_log_stream_visible_column_ids: StreamColumn::default_visible(),
628            cw_log_stream_column_ids: StreamColumn::ids(),
629            cw_log_event_visible_column_ids: EventColumn::default_visible(),
630            cw_log_event_column_ids: EventColumn::ids(),
631            cw_alarm_visible_column_ids: [
632                AlarmColumn::Name,
633                AlarmColumn::State,
634                AlarmColumn::LastStateUpdate,
635                AlarmColumn::Conditions,
636                AlarmColumn::Actions,
637            ]
638            .iter()
639            .map(|c| c.id())
640            .collect(),
641            cw_alarm_column_ids: AlarmColumn::ids(),
642            s3_bucket_visible_column_ids: S3BucketColumn::ids(),
643            s3_bucket_column_ids: S3BucketColumn::ids(),
644            sqs_visible_column_ids: [
645                SqsColumn::Name,
646                SqsColumn::Type,
647                SqsColumn::Created,
648                SqsColumn::MessagesAvailable,
649                SqsColumn::MessagesInFlight,
650                SqsColumn::Encryption,
651                SqsColumn::ContentBasedDeduplication,
652            ]
653            .iter()
654            .map(|c| c.id())
655            .collect(),
656            sqs_column_ids: SqsColumn::ids(),
657            ec2_visible_column_ids: [
658                Ec2Column::Name,
659                Ec2Column::InstanceId,
660                Ec2Column::InstanceState,
661                Ec2Column::InstanceType,
662                Ec2Column::StatusCheck,
663                Ec2Column::AlarmStatus,
664                Ec2Column::AvailabilityZone,
665                Ec2Column::PublicIpv4Dns,
666                Ec2Column::PublicIpv4Address,
667                Ec2Column::ElasticIp,
668                Ec2Column::Ipv6Ips,
669                Ec2Column::Monitoring,
670                Ec2Column::SecurityGroupName,
671                Ec2Column::KeyName,
672                Ec2Column::LaunchTime,
673                Ec2Column::PlatformDetails,
674            ]
675            .iter()
676            .map(|c| c.id())
677            .collect(),
678            ec2_column_ids: Ec2Column::ids(),
679            ecr_repo_visible_column_ids: EcrColumn::ids(),
680            ecr_repo_column_ids: EcrColumn::ids(),
681            ecr_image_visible_column_ids: EcrImageColumn::ids(),
682            ecr_image_column_ids: EcrImageColumn::ids(),
683            lambda_application_visible_column_ids: LambdaApplicationColumn::visible(),
684            lambda_application_column_ids: LambdaApplicationColumn::ids(),
685            lambda_deployment_visible_column_ids: DeploymentColumn::ids(),
686            lambda_deployment_column_ids: DeploymentColumn::ids(),
687            lambda_resource_visible_column_ids: ResourceColumn::ids(),
688            lambda_resource_column_ids: ResourceColumn::ids(),
689            cfn_visible_column_ids: [
690                CfnColumn::Name,
691                CfnColumn::Status,
692                CfnColumn::CreatedTime,
693                CfnColumn::Description,
694            ]
695            .iter()
696            .map(|c| c.id())
697            .collect(),
698            cfn_column_ids: CfnColumn::ids(),
699            iam_user_visible_column_ids: UserColumn::visible(),
700            cfn_parameter_visible_column_ids: crate::ui::cfn::parameter_column_ids(),
701            cfn_parameter_column_ids: crate::ui::cfn::parameter_column_ids(),
702            cfn_output_visible_column_ids: crate::ui::cfn::output_column_ids(),
703            cfn_output_column_ids: crate::ui::cfn::output_column_ids(),
704            cfn_resource_visible_column_ids: crate::ui::cfn::resource_column_ids(),
705            cfn_resource_column_ids: crate::ui::cfn::resource_column_ids(),
706            iam_user_column_ids: UserColumn::ids(),
707            iam_role_visible_column_ids: vec![
708                "Role name".to_string(),
709                "Trusted entities".to_string(),
710                "Creation time".to_string(),
711            ],
712            iam_role_column_ids: vec![
713                "Role name".to_string(),
714                "Path".to_string(),
715                "Trusted entities".to_string(),
716                "ARN".to_string(),
717                "Creation time".to_string(),
718                "Description".to_string(),
719                "Max session duration".to_string(),
720            ],
721            iam_group_visible_column_ids: vec![
722                "Group name".to_string(),
723                "Users".to_string(),
724                "Permissions".to_string(),
725                "Creation time".to_string(),
726            ],
727            iam_group_column_ids: vec![
728                "Group name".to_string(),
729                "Path".to_string(),
730                "Users".to_string(),
731                "Permissions".to_string(),
732                "Creation time".to_string(),
733            ],
734            iam_policy_visible_column_ids: vec![
735                "Policy name".to_string(),
736                "Type".to_string(),
737                "Attached via".to_string(),
738            ],
739            iam_policy_column_ids: vec![
740                "Policy name".to_string(),
741                "Type".to_string(),
742                "Attached via".to_string(),
743                "Attached entities".to_string(),
744                "Description".to_string(),
745                "Creation time".to_string(),
746                "Edited time".to_string(),
747            ],
748            view_mode: ViewMode::List,
749            error_message: None,
750            error_scroll: 0,
751            page_input: String::new(),
752            calendar_date: None,
753            calendar_selecting: CalendarField::StartDate,
754            cursor_pos: 0,
755            current_session: None,
756            sessions: Vec::new(),
757            session_picker_selected: 0,
758            session_filter: String::new(),
759            region_filter: String::new(),
760            region_picker_selected: 0,
761            region_latencies: std::collections::HashMap::new(),
762            profile_filter: String::new(),
763            profile_picker_selected: 0,
764            available_profiles: Vec::new(),
765            snapshot_requested: false,
766        }
767    }
768
769    pub fn handle_action(&mut self, action: Action) {
770        match action {
771            Action::Quit => {
772                self.save_current_session();
773                self.running = false;
774            }
775            Action::CloseService => {
776                if !self.tabs.is_empty() {
777                    // Close the current tab
778                    self.tabs.remove(self.current_tab);
779
780                    if self.tabs.is_empty() {
781                        // Last tab closed - show service picker
782                        self.service_selected = false;
783                        self.current_tab = 0;
784                        self.mode = Mode::ServicePicker;
785                    } else {
786                        // Tabs remain - switch to adjacent tab
787                        if self.current_tab >= self.tabs.len() {
788                            self.current_tab = self.tabs.len() - 1;
789                        }
790                        self.current_service = self.tabs[self.current_tab].service;
791                        self.service_selected = true;
792                        self.mode = Mode::Normal;
793                    }
794                } else {
795                    // No tabs - just close service picker if open
796                    self.service_selected = false;
797                    self.mode = Mode::Normal;
798                }
799                self.service_picker.filter.clear();
800                self.service_picker.selected = 0;
801            }
802            Action::NextItem => self.next_item(),
803            Action::PrevItem => self.prev_item(),
804            Action::PageUp => self.page_up(),
805            Action::PageDown => self.page_down(),
806            Action::NextPane => self.next_pane(),
807            Action::PrevPane => self.prev_pane(),
808            Action::Select => self.select_item(),
809            Action::OpenSpaceMenu => {
810                self.mode = Mode::SpaceMenu;
811                self.service_picker.filter.clear();
812                self.service_picker.selected = 0;
813            }
814            Action::CloseMenu => {
815                self.mode = Mode::Normal;
816                self.service_picker.filter.clear();
817                // Reset selection when closing filter to avoid out-of-bounds
818                match self.current_service {
819                    Service::S3Buckets => {
820                        self.s3_state.selected_row = 0;
821                        self.s3_state.selected_object = 0;
822                    }
823                    Service::CloudFormationStacks => {
824                        if self.cfn_state.current_stack.is_some()
825                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
826                        {
827                            self.cfn_state.parameters.reset();
828                        } else if self.cfn_state.current_stack.is_some()
829                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
830                        {
831                            self.cfn_state.outputs.reset();
832                        } else {
833                            self.cfn_state.table.reset();
834                        }
835                    }
836                    Service::LambdaFunctions => {
837                        self.lambda_state.table.reset();
838                    }
839                    Service::SqsQueues => {
840                        self.sqs_state.queues.reset();
841                    }
842                    Service::IamRoles => {
843                        self.iam_state.roles.reset();
844                    }
845                    Service::IamUsers => {
846                        self.iam_state.users.reset();
847                    }
848                    Service::IamUserGroups => {
849                        self.iam_state.groups.reset();
850                    }
851                    Service::CloudWatchAlarms => {
852                        self.alarms_state.table.reset();
853                    }
854                    Service::Ec2Instances => {
855                        self.ec2_state.table.reset();
856                    }
857                    Service::EcrRepositories => {
858                        self.ecr_state.repositories.reset();
859                    }
860                    Service::LambdaApplications => {
861                        self.lambda_application_state.table.reset();
862                    }
863                    _ => {}
864                }
865            }
866            Action::NextTab => {
867                if !self.tabs.is_empty() {
868                    self.current_tab = (self.current_tab + 1) % self.tabs.len();
869                    self.current_service = self.tabs[self.current_tab].service;
870                }
871            }
872            Action::PrevTab => {
873                if !self.tabs.is_empty() {
874                    self.current_tab = if self.current_tab == 0 {
875                        self.tabs.len() - 1
876                    } else {
877                        self.current_tab - 1
878                    };
879                    self.current_service = self.tabs[self.current_tab].service;
880                }
881            }
882            Action::CloseTab => {
883                if !self.tabs.is_empty() {
884                    self.tabs.remove(self.current_tab);
885                    if self.tabs.is_empty() {
886                        // Last tab closed - show service picker
887                        self.service_selected = false;
888                        self.current_tab = 0;
889                        self.service_picker.filter.clear();
890                        self.service_picker.selected = 0;
891                        self.mode = Mode::ServicePicker;
892                    } else {
893                        // If we closed the last tab, move to the left
894                        // Otherwise stay at same index (which is now the next tab to the right)
895                        if self.current_tab >= self.tabs.len() {
896                            self.current_tab = self.tabs.len() - 1;
897                        }
898                        self.current_service = self.tabs[self.current_tab].service;
899                        self.service_selected = true;
900                        self.mode = Mode::Normal;
901                    }
902                }
903            }
904            Action::OpenTabPicker => {
905                if !self.tabs.is_empty() {
906                    self.tab_picker_selected = self.current_tab;
907                    self.mode = Mode::TabPicker;
908                } else {
909                    self.mode = Mode::Normal;
910                }
911            }
912            Action::OpenSessionPicker => {
913                self.save_current_session();
914                self.sessions = Session::list_all().unwrap_or_default();
915                self.session_picker_selected = 0;
916                self.mode = Mode::SessionPicker;
917            }
918            Action::LoadSession => {
919                let filtered_sessions = self.get_filtered_sessions();
920                if let Some(&session) = filtered_sessions.get(self.session_picker_selected) {
921                    let session = session.clone();
922                    // Load the session
923                    self.profile = session.profile.clone();
924                    self.region = session.region.clone();
925                    self.config.account_id = session.account_id.clone();
926                    self.config.role_arn = session.role_arn.clone();
927
928                    // Clear existing tabs and load session tabs
929                    self.tabs.clear();
930                    for session_tab in &session.tabs {
931                        // Parse service from string
932                        let service = match session_tab.service.as_str() {
933                            "CloudWatchLogGroups" => Service::CloudWatchLogGroups,
934                            "CloudWatchInsights" => Service::CloudWatchInsights,
935                            "CloudWatchAlarms" => Service::CloudWatchAlarms,
936                            "S3Buckets" => Service::S3Buckets,
937                            "SqsQueues" => Service::SqsQueues,
938                            "Ec2Instances" => Service::Ec2Instances,
939                            "EcrRepositories" => Service::EcrRepositories,
940                            "LambdaFunctions" => Service::LambdaFunctions,
941                            "LambdaApplications" => Service::LambdaApplications,
942                            "CloudFormationStacks" => Service::CloudFormationStacks,
943                            "IamUsers" => Service::IamUsers,
944                            "IamRoles" => Service::IamRoles,
945                            "IamUserGroups" => Service::IamUserGroups,
946                            _ => continue,
947                        };
948
949                        self.tabs.push(Tab {
950                            service,
951                            title: session_tab.title.clone(),
952                            breadcrumb: session_tab.breadcrumb.clone(),
953                        });
954
955                        // Restore filter if present
956                        if let Some(filter) = &session_tab.filter {
957                            if service == Service::CloudWatchLogGroups {
958                                self.log_groups_state.log_groups.filter = filter.clone();
959                            }
960                        }
961                    }
962
963                    if !self.tabs.is_empty() {
964                        self.current_tab = 0;
965                        self.current_service = self.tabs[0].service;
966                        self.service_selected = true;
967                        self.current_session = Some(session.clone());
968                    }
969                }
970                self.mode = Mode::Normal;
971            }
972            Action::SaveSession => {
973                // TODO: Implement session saving
974            }
975            Action::OpenServicePicker => {
976                if self.mode == Mode::ServicePicker {
977                    self.tabs.push(Tab {
978                        service: Service::S3Buckets,
979                        title: "S3 > Buckets".to_string(),
980                        breadcrumb: "S3 > Buckets".to_string(),
981                    });
982                    self.current_tab = self.tabs.len() - 1;
983                    self.current_service = Service::S3Buckets;
984                    self.view_mode = ViewMode::List;
985                    self.service_selected = true;
986                    self.mode = Mode::Normal;
987                } else {
988                    self.mode = Mode::ServicePicker;
989                    self.service_picker.filter.clear();
990                    self.service_picker.selected = 0;
991                }
992            }
993            Action::OpenCloudWatch => {
994                self.current_service = Service::CloudWatchLogGroups;
995                self.view_mode = ViewMode::List;
996                self.service_selected = true;
997                self.mode = Mode::Normal;
998            }
999            Action::OpenCloudWatchSplit => {
1000                self.current_service = Service::CloudWatchInsights;
1001                self.view_mode = ViewMode::InsightsResults;
1002                self.service_selected = true;
1003                self.mode = Mode::Normal;
1004            }
1005            Action::OpenCloudWatchAlarms => {
1006                self.current_service = Service::CloudWatchAlarms;
1007                self.view_mode = ViewMode::List;
1008                self.service_selected = true;
1009                self.mode = Mode::Normal;
1010            }
1011            Action::FilterInput(c) => {
1012                if self.mode == Mode::TabPicker {
1013                    self.tab_filter.push(c);
1014                    self.tab_picker_selected = 0;
1015                } else if self.mode == Mode::RegionPicker {
1016                    self.region_filter.push(c);
1017                    self.region_picker_selected = 0;
1018                } else if self.mode == Mode::ProfilePicker {
1019                    self.profile_filter.push(c);
1020                    self.profile_picker_selected = 0;
1021                } else if self.mode == Mode::SessionPicker {
1022                    self.session_filter.push(c);
1023                    self.session_picker_selected = 0;
1024                } else if self.mode == Mode::ServicePicker {
1025                    self.service_picker.filter.push(c);
1026                    self.service_picker.selected = 0;
1027                } else if self.mode == Mode::InsightsInput {
1028                    use crate::app::InsightsFocus;
1029                    match self.insights_state.insights.insights_focus {
1030                        InsightsFocus::Query => {
1031                            self.insights_state.insights.query_text.push(c);
1032                        }
1033                        InsightsFocus::LogGroupSearch => {
1034                            self.insights_state.insights.log_group_search.push(c);
1035                            // Update matches
1036                            if !self.insights_state.insights.log_group_search.is_empty() {
1037                                self.insights_state.insights.log_group_matches = self
1038                                    .log_groups_state
1039                                    .log_groups
1040                                    .items
1041                                    .iter()
1042                                    .filter(|g| {
1043                                        g.name.to_lowercase().contains(
1044                                            &self
1045                                                .insights_state
1046                                                .insights
1047                                                .log_group_search
1048                                                .to_lowercase(),
1049                                        )
1050                                    })
1051                                    .take(50)
1052                                    .map(|g| g.name.clone())
1053                                    .collect();
1054                                self.insights_state.insights.show_dropdown = true;
1055                            } else {
1056                                self.insights_state.insights.log_group_matches.clear();
1057                                self.insights_state.insights.show_dropdown = false;
1058                            }
1059                        }
1060                        _ => {}
1061                    }
1062                } else if self.mode == Mode::FilterInput {
1063                    // Check if we should capture digits for page navigation
1064                    let is_pagination_focused = if self.current_service
1065                        == Service::LambdaApplications
1066                    {
1067                        if self.lambda_application_state.current_application.is_some() {
1068                            if self.lambda_application_state.detail_tab
1069                                == LambdaApplicationDetailTab::Deployments
1070                            {
1071                                self.lambda_application_state.deployment_input_focus
1072                                    == InputFocus::Pagination
1073                            } else {
1074                                self.lambda_application_state.resource_input_focus
1075                                    == InputFocus::Pagination
1076                            }
1077                        } else {
1078                            self.lambda_application_state.input_focus == InputFocus::Pagination
1079                        }
1080                    } else if self.current_service == Service::CloudFormationStacks {
1081                        self.cfn_state.input_focus == InputFocus::Pagination
1082                    } else if self.current_service == Service::IamRoles
1083                        && self.iam_state.current_role.is_none()
1084                    {
1085                        self.iam_state.role_input_focus == InputFocus::Pagination
1086                    } else if self.view_mode == ViewMode::PolicyView {
1087                        self.iam_state.policy_input_focus == InputFocus::Pagination
1088                    } else if self.current_service == Service::CloudWatchAlarms {
1089                        self.alarms_state.input_focus == InputFocus::Pagination
1090                    } else if self.current_service == Service::Ec2Instances {
1091                        self.ec2_state.input_focus == InputFocus::Pagination
1092                    } else if self.current_service == Service::CloudWatchLogGroups {
1093                        self.log_groups_state.input_focus == InputFocus::Pagination
1094                    } else if self.current_service == Service::EcrRepositories
1095                        && self.ecr_state.current_repository.is_none()
1096                    {
1097                        self.ecr_state.input_focus == InputFocus::Pagination
1098                    } else if self.current_service == Service::LambdaFunctions {
1099                        if self.lambda_state.current_function.is_some()
1100                            && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1101                        {
1102                            self.lambda_state.version_input_focus == InputFocus::Pagination
1103                        } else if self.lambda_state.current_function.is_none() {
1104                            self.lambda_state.input_focus == InputFocus::Pagination
1105                        } else {
1106                            false
1107                        }
1108                    } else if self.current_service == Service::SqsQueues {
1109                        if self.sqs_state.current_queue.is_some()
1110                            && (self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1111                                || self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1112                                || self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1113                                || self.sqs_state.detail_tab == SqsQueueDetailTab::Encryption
1114                                || self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions)
1115                        {
1116                            self.sqs_state.input_focus == InputFocus::Pagination
1117                        } else {
1118                            false
1119                        }
1120                    } else {
1121                        false
1122                    };
1123
1124                    if is_pagination_focused && c.is_ascii_digit() {
1125                        self.page_input.push(c);
1126                    } else if self.current_service == Service::LambdaApplications {
1127                        let is_input_focused =
1128                            if self.lambda_application_state.current_application.is_some() {
1129                                if self.lambda_application_state.detail_tab
1130                                    == LambdaApplicationDetailTab::Deployments
1131                                {
1132                                    self.lambda_application_state.deployment_input_focus
1133                                        == InputFocus::Filter
1134                                } else {
1135                                    self.lambda_application_state.resource_input_focus
1136                                        == InputFocus::Filter
1137                                }
1138                            } else {
1139                                self.lambda_application_state.input_focus == InputFocus::Filter
1140                            };
1141                        if is_input_focused {
1142                            if let Some(filter) = self.get_active_filter_mut() {
1143                                filter.push(c);
1144                            }
1145                        }
1146                    } else if self.current_service == Service::CloudFormationStacks {
1147                        if self.cfn_state.current_stack.is_some()
1148                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
1149                        {
1150                            if self.cfn_state.parameters_input_focus == InputFocus::Filter {
1151                                self.cfn_state.parameters.filter.push(c);
1152                                self.cfn_state.parameters.selected = 0;
1153                            }
1154                        } else if self.cfn_state.current_stack.is_some()
1155                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
1156                        {
1157                            if self.cfn_state.outputs_input_focus == InputFocus::Filter {
1158                                self.cfn_state.outputs.filter.push(c);
1159                                self.cfn_state.outputs.selected = 0;
1160                            }
1161                        } else if self.cfn_state.current_stack.is_some()
1162                            && self.cfn_state.detail_tab == CfnDetailTab::Resources
1163                        {
1164                            if self.cfn_state.resources_input_focus == InputFocus::Filter {
1165                                self.cfn_state.resources.filter.push(c);
1166                                self.cfn_state.resources.selected = 0;
1167                            }
1168                        } else if self.cfn_state.input_focus == InputFocus::Filter {
1169                            if let Some(filter) = self.get_active_filter_mut() {
1170                                filter.push(c);
1171                            }
1172                        }
1173                    } else if self.current_service == Service::EcrRepositories
1174                        && self.ecr_state.current_repository.is_none()
1175                    {
1176                        if self.ecr_state.input_focus == InputFocus::Filter {
1177                            if let Some(filter) = self.get_active_filter_mut() {
1178                                filter.push(c);
1179                            }
1180                        }
1181                    } else if self.current_service == Service::IamRoles
1182                        && self.iam_state.current_role.is_none()
1183                    {
1184                        if self.iam_state.role_input_focus == InputFocus::Filter {
1185                            if let Some(filter) = self.get_active_filter_mut() {
1186                                filter.push(c);
1187                            }
1188                        }
1189                    } else if self.view_mode == ViewMode::PolicyView {
1190                        if self.iam_state.policy_input_focus == InputFocus::Filter {
1191                            if let Some(filter) = self.get_active_filter_mut() {
1192                                filter.push(c);
1193                            }
1194                        }
1195                    } else if self.current_service == Service::LambdaFunctions
1196                        && self.lambda_state.current_version.is_some()
1197                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
1198                    {
1199                        if self.lambda_state.alias_input_focus == InputFocus::Filter {
1200                            if let Some(filter) = self.get_active_filter_mut() {
1201                                filter.push(c);
1202                            }
1203                        }
1204                    } else if self.current_service == Service::LambdaFunctions
1205                        && self.lambda_state.current_function.is_some()
1206                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1207                    {
1208                        if self.lambda_state.version_input_focus == InputFocus::Filter {
1209                            if let Some(filter) = self.get_active_filter_mut() {
1210                                filter.push(c);
1211                            }
1212                        }
1213                    } else if self.current_service == Service::LambdaFunctions
1214                        && self.lambda_state.current_function.is_some()
1215                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
1216                    {
1217                        if self.lambda_state.alias_input_focus == InputFocus::Filter {
1218                            if let Some(filter) = self.get_active_filter_mut() {
1219                                filter.push(c);
1220                            }
1221                        }
1222                    } else if self.current_service == Service::SqsQueues
1223                        && self.sqs_state.current_queue.is_some()
1224                        && (self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1225                            || self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1226                            || self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1227                            || self.sqs_state.detail_tab == SqsQueueDetailTab::Encryption
1228                            || self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions)
1229                    {
1230                        if self.sqs_state.input_focus == InputFocus::Filter {
1231                            if let Some(filter) = self.get_active_filter_mut() {
1232                                filter.push(c);
1233                            }
1234                        }
1235                    } else if let Some(filter) = self.get_active_filter_mut() {
1236                        filter.push(c);
1237                    }
1238                } else if self.mode == Mode::EventFilterInput {
1239                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1240                        self.log_groups_state.event_filter.push(c);
1241                    } else if c.is_ascii_digit() {
1242                        self.log_groups_state.relative_amount.push(c);
1243                    }
1244                } else if self.mode == Mode::Normal && c.is_ascii_digit() {
1245                    self.page_input.push(c);
1246                }
1247            }
1248            Action::FilterBackspace => {
1249                if self.mode == Mode::ServicePicker {
1250                    self.service_picker.filter.pop();
1251                    self.service_picker.selected = 0;
1252                } else if self.mode == Mode::TabPicker {
1253                    self.tab_filter.pop();
1254                    self.tab_picker_selected = 0;
1255                } else if self.mode == Mode::RegionPicker {
1256                    self.region_filter.pop();
1257                    self.region_picker_selected = 0;
1258                } else if self.mode == Mode::ProfilePicker {
1259                    self.profile_filter.pop();
1260                    self.profile_picker_selected = 0;
1261                } else if self.mode == Mode::SessionPicker {
1262                    self.session_filter.pop();
1263                    self.session_picker_selected = 0;
1264                } else if self.mode == Mode::InsightsInput {
1265                    use crate::app::InsightsFocus;
1266                    match self.insights_state.insights.insights_focus {
1267                        InsightsFocus::Query => {
1268                            self.insights_state.insights.query_text.pop();
1269                        }
1270                        InsightsFocus::LogGroupSearch => {
1271                            self.insights_state.insights.log_group_search.pop();
1272                            // Update matches
1273                            if !self.insights_state.insights.log_group_search.is_empty() {
1274                                self.insights_state.insights.log_group_matches = self
1275                                    .log_groups_state
1276                                    .log_groups
1277                                    .items
1278                                    .iter()
1279                                    .filter(|g| {
1280                                        g.name.to_lowercase().contains(
1281                                            &self
1282                                                .insights_state
1283                                                .insights
1284                                                .log_group_search
1285                                                .to_lowercase(),
1286                                        )
1287                                    })
1288                                    .take(50)
1289                                    .map(|g| g.name.clone())
1290                                    .collect();
1291                                self.insights_state.insights.show_dropdown = true;
1292                            } else {
1293                                self.insights_state.insights.log_group_matches.clear();
1294                                self.insights_state.insights.show_dropdown = false;
1295                            }
1296                        }
1297                        _ => {}
1298                    }
1299                } else if self.mode == Mode::FilterInput {
1300                    // Only allow backspace when focus is on the input field
1301                    if self.current_service == Service::CloudFormationStacks {
1302                        if self.cfn_state.current_stack.is_some()
1303                            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
1304                        {
1305                            if self.cfn_state.parameters_input_focus == InputFocus::Filter {
1306                                self.cfn_state.parameters.filter.pop();
1307                                self.cfn_state.parameters.selected = 0;
1308                            }
1309                        } else if self.cfn_state.current_stack.is_some()
1310                            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
1311                        {
1312                            if self.cfn_state.outputs_input_focus == InputFocus::Filter {
1313                                self.cfn_state.outputs.filter.pop();
1314                                self.cfn_state.outputs.selected = 0;
1315                            }
1316                        } else if self.cfn_state.current_stack.is_some()
1317                            && self.cfn_state.detail_tab == CfnDetailTab::Resources
1318                        {
1319                            if self.cfn_state.resources_input_focus == InputFocus::Filter {
1320                                self.cfn_state.resources.filter.pop();
1321                                self.cfn_state.resources.selected = 0;
1322                            }
1323                        } else if self.cfn_state.input_focus == InputFocus::Filter {
1324                            if let Some(filter) = self.get_active_filter_mut() {
1325                                filter.pop();
1326                            }
1327                        }
1328                    } else if let Some(filter) = self.get_active_filter_mut() {
1329                        filter.pop();
1330                    }
1331                } else if self.mode == Mode::EventFilterInput {
1332                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1333                        self.log_groups_state.event_filter.pop();
1334                    } else {
1335                        self.log_groups_state.relative_amount.pop();
1336                    }
1337                }
1338            }
1339            Action::DeleteWord => {
1340                let text = if self.mode == Mode::ServicePicker {
1341                    &mut self.service_picker.filter
1342                } else if self.mode == Mode::InsightsInput {
1343                    use crate::app::InsightsFocus;
1344                    match self.insights_state.insights.insights_focus {
1345                        InsightsFocus::Query => &mut self.insights_state.insights.query_text,
1346                        InsightsFocus::LogGroupSearch => {
1347                            &mut self.insights_state.insights.log_group_search
1348                        }
1349                        _ => return,
1350                    }
1351                } else if self.mode == Mode::FilterInput {
1352                    if let Some(filter) = self.get_active_filter_mut() {
1353                        filter
1354                    } else {
1355                        return;
1356                    }
1357                } else if self.mode == Mode::EventFilterInput {
1358                    if self.log_groups_state.event_input_focus == EventFilterFocus::Filter {
1359                        &mut self.log_groups_state.event_filter
1360                    } else {
1361                        &mut self.log_groups_state.relative_amount
1362                    }
1363                } else {
1364                    return;
1365                };
1366
1367                if text.is_empty() {
1368                    return;
1369                }
1370
1371                let mut chars: Vec<char> = text.chars().collect();
1372                while !chars.is_empty() && chars.last().is_some_and(|c| c.is_whitespace()) {
1373                    chars.pop();
1374                }
1375                while !chars.is_empty() && !chars.last().is_some_and(|c| c.is_whitespace()) {
1376                    chars.pop();
1377                }
1378                *text = chars.into_iter().collect();
1379            }
1380            Action::WordLeft => {
1381                // Not implemented - would need cursor position tracking
1382            }
1383            Action::WordRight => {
1384                // Not implemented - would need cursor position tracking
1385            }
1386            Action::OpenColumnSelector => {
1387                // Don't allow opening preferences in Template or GitSync tabs
1388                if self.current_service == Service::CloudFormationStacks
1389                    && self.cfn_state.current_stack.is_some()
1390                    && (self.cfn_state.detail_tab == CfnDetailTab::Template
1391                        || self.cfn_state.detail_tab == CfnDetailTab::GitSync)
1392                {
1393                    return;
1394                }
1395
1396                // If we have page input, apply it instead of opening column selector
1397                if !self.page_input.is_empty() {
1398                    if let Ok(page) = self.page_input.parse::<usize>() {
1399                        self.go_to_page(page);
1400                    }
1401                    self.page_input.clear();
1402                } else {
1403                    self.mode = Mode::ColumnSelector;
1404                    self.column_selector_index = 0;
1405                }
1406            }
1407            Action::ToggleColumn => {
1408                if self.current_service == Service::S3Buckets
1409                    && self.s3_state.current_bucket.is_none()
1410                {
1411                    if let Some(col) = self.s3_bucket_column_ids.get(self.column_selector_index) {
1412                        if let Some(pos) = self
1413                            .s3_bucket_visible_column_ids
1414                            .iter()
1415                            .position(|c| c == col)
1416                        {
1417                            self.s3_bucket_visible_column_ids.remove(pos);
1418                        } else {
1419                            self.s3_bucket_visible_column_ids.push(*col);
1420                        }
1421                    }
1422                } else if self.current_service == Service::CloudWatchAlarms {
1423                    // Map flat list index to actual item
1424                    // 0: Columns header, 1-16: columns, 17: empty, 18: ViewAs header, 19-20: view options
1425                    // 21: empty, 22: PageSize header, 23-25: page sizes, 26: empty, 27: WrapLines header, 28: wrap option
1426                    let idx = self.column_selector_index;
1427                    if (1..=16).contains(&idx) {
1428                        // Column toggle
1429                        if let Some(col) = self.cw_alarm_column_ids.get(idx - 1) {
1430                            if let Some(pos) = self
1431                                .cw_alarm_visible_column_ids
1432                                .iter()
1433                                .position(|c| c == col)
1434                            {
1435                                self.cw_alarm_visible_column_ids.remove(pos);
1436                            } else {
1437                                self.cw_alarm_visible_column_ids.push(*col);
1438                            }
1439                        }
1440                    } else if idx == 19 {
1441                        self.alarms_state.view_as = AlarmViewMode::Table;
1442                    } else if idx == 20 {
1443                        self.alarms_state.view_as = AlarmViewMode::Cards;
1444                    } else if idx == 23 {
1445                        self.alarms_state.table.page_size = PageSize::Ten;
1446                    } else if idx == 24 {
1447                        self.alarms_state.table.page_size = PageSize::TwentyFive;
1448                    } else if idx == 25 {
1449                        self.alarms_state.table.page_size = PageSize::Fifty;
1450                    } else if idx == 26 {
1451                        self.alarms_state.table.page_size = PageSize::OneHundred;
1452                    } else if idx == 29 {
1453                        self.alarms_state.wrap_lines = !self.alarms_state.wrap_lines;
1454                    }
1455                } else if self.current_service == Service::EcrRepositories {
1456                    if self.ecr_state.current_repository.is_some() {
1457                        // Images view - columns + page size
1458                        let idx = self.column_selector_index;
1459                        if let Some(col) = self.ecr_image_column_ids.get(idx) {
1460                            if let Some(pos) = self
1461                                .ecr_image_visible_column_ids
1462                                .iter()
1463                                .position(|c| c == col)
1464                            {
1465                                self.ecr_image_visible_column_ids.remove(pos);
1466                            } else {
1467                                self.ecr_image_visible_column_ids.push(*col);
1468                            }
1469                        }
1470                    } else {
1471                        // Repositories view - just columns
1472                        if let Some(col) = self.ecr_repo_column_ids.get(self.column_selector_index)
1473                        {
1474                            if let Some(pos) = self
1475                                .ecr_repo_visible_column_ids
1476                                .iter()
1477                                .position(|c| c == col)
1478                            {
1479                                self.ecr_repo_visible_column_ids.remove(pos);
1480                            } else {
1481                                self.ecr_repo_visible_column_ids.push(*col);
1482                            }
1483                        }
1484                    }
1485                } else if self.current_service == Service::Ec2Instances {
1486                    let idx = self.column_selector_index;
1487                    if idx > 0 && idx <= self.ec2_column_ids.len() {
1488                        if let Some(col) = self.ec2_column_ids.get(idx - 1) {
1489                            if let Some(pos) =
1490                                self.ec2_visible_column_ids.iter().position(|c| c == col)
1491                            {
1492                                self.ec2_visible_column_ids.remove(pos);
1493                            } else {
1494                                self.ec2_visible_column_ids.push(*col);
1495                            }
1496                        }
1497                    } else if idx == self.ec2_column_ids.len() + 3 {
1498                        self.ec2_state.table.page_size = PageSize::Ten;
1499                    } else if idx == self.ec2_column_ids.len() + 4 {
1500                        self.ec2_state.table.page_size = PageSize::TwentyFive;
1501                    } else if idx == self.ec2_column_ids.len() + 5 {
1502                        self.ec2_state.table.page_size = PageSize::Fifty;
1503                    } else if idx == self.ec2_column_ids.len() + 6 {
1504                        self.ec2_state.table.page_size = PageSize::OneHundred;
1505                    }
1506                } else if self.current_service == Service::SqsQueues {
1507                    if self.sqs_state.current_queue.is_some()
1508                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
1509                    {
1510                        // Triggers tab - columns + page size
1511                        let idx = self.column_selector_index;
1512                        if idx > 0 && idx <= self.sqs_state.trigger_column_ids.len() {
1513                            if let Some(col) = self.sqs_state.trigger_column_ids.get(idx - 1) {
1514                                if let Some(pos) = self
1515                                    .sqs_state
1516                                    .trigger_visible_column_ids
1517                                    .iter()
1518                                    .position(|c| c == col)
1519                                {
1520                                    self.sqs_state.trigger_visible_column_ids.remove(pos);
1521                                } else {
1522                                    self.sqs_state.trigger_visible_column_ids.push(col.clone());
1523                                }
1524                            }
1525                        } else if idx == self.sqs_state.trigger_column_ids.len() + 3 {
1526                            self.sqs_state.triggers.page_size = PageSize::Ten;
1527                        } else if idx == self.sqs_state.trigger_column_ids.len() + 4 {
1528                            self.sqs_state.triggers.page_size = PageSize::TwentyFive;
1529                        } else if idx == self.sqs_state.trigger_column_ids.len() + 5 {
1530                            self.sqs_state.triggers.page_size = PageSize::Fifty;
1531                        } else if idx == self.sqs_state.trigger_column_ids.len() + 6 {
1532                            self.sqs_state.triggers.page_size = PageSize::OneHundred;
1533                        }
1534                    } else if self.sqs_state.current_queue.is_some()
1535                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
1536                    {
1537                        // Pipes tab - columns + page size
1538                        let idx = self.column_selector_index;
1539                        if idx > 0 && idx <= self.sqs_state.pipe_column_ids.len() {
1540                            if let Some(col) = self.sqs_state.pipe_column_ids.get(idx - 1) {
1541                                if let Some(pos) = self
1542                                    .sqs_state
1543                                    .pipe_visible_column_ids
1544                                    .iter()
1545                                    .position(|c| c == col)
1546                                {
1547                                    self.sqs_state.pipe_visible_column_ids.remove(pos);
1548                                } else {
1549                                    self.sqs_state.pipe_visible_column_ids.push(col.clone());
1550                                }
1551                            }
1552                        } else if idx == self.sqs_state.pipe_column_ids.len() + 3 {
1553                            self.sqs_state.pipes.page_size = PageSize::Ten;
1554                        } else if idx == self.sqs_state.pipe_column_ids.len() + 4 {
1555                            self.sqs_state.pipes.page_size = PageSize::TwentyFive;
1556                        } else if idx == self.sqs_state.pipe_column_ids.len() + 5 {
1557                            self.sqs_state.pipes.page_size = PageSize::Fifty;
1558                        } else if idx == self.sqs_state.pipe_column_ids.len() + 6 {
1559                            self.sqs_state.pipes.page_size = PageSize::OneHundred;
1560                        }
1561                    } else if self.sqs_state.current_queue.is_some()
1562                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
1563                    {
1564                        // Tags tab - columns + page size
1565                        let idx = self.column_selector_index;
1566                        if idx > 0 && idx <= self.sqs_state.tag_column_ids.len() {
1567                            if let Some(col) = self.sqs_state.tag_column_ids.get(idx - 1) {
1568                                if let Some(pos) = self
1569                                    .sqs_state
1570                                    .tag_visible_column_ids
1571                                    .iter()
1572                                    .position(|c| c == col)
1573                                {
1574                                    self.sqs_state.tag_visible_column_ids.remove(pos);
1575                                } else {
1576                                    self.sqs_state.tag_visible_column_ids.push(col.clone());
1577                                }
1578                            }
1579                        } else if idx == self.sqs_state.tag_column_ids.len() + 3 {
1580                            self.sqs_state.tags.page_size = PageSize::Ten;
1581                        } else if idx == self.sqs_state.tag_column_ids.len() + 4 {
1582                            self.sqs_state.tags.page_size = PageSize::TwentyFive;
1583                        } else if idx == self.sqs_state.tag_column_ids.len() + 5 {
1584                            self.sqs_state.tags.page_size = PageSize::Fifty;
1585                        } else if idx == self.sqs_state.tag_column_ids.len() + 6 {
1586                            self.sqs_state.tags.page_size = PageSize::OneHundred;
1587                        }
1588                    } else if self.sqs_state.current_queue.is_some()
1589                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
1590                    {
1591                        // Subscriptions tab - columns + page size
1592                        let idx = self.column_selector_index;
1593                        if idx > 0 && idx <= self.sqs_state.subscription_column_ids.len() {
1594                            if let Some(col) = self.sqs_state.subscription_column_ids.get(idx - 1) {
1595                                if let Some(pos) = self
1596                                    .sqs_state
1597                                    .subscription_visible_column_ids
1598                                    .iter()
1599                                    .position(|c| c == col)
1600                                {
1601                                    self.sqs_state.subscription_visible_column_ids.remove(pos);
1602                                } else {
1603                                    self.sqs_state
1604                                        .subscription_visible_column_ids
1605                                        .push(col.clone());
1606                                }
1607                            }
1608                        } else if idx == self.sqs_state.subscription_column_ids.len() + 3 {
1609                            self.sqs_state.subscriptions.page_size = PageSize::Ten;
1610                        } else if idx == self.sqs_state.subscription_column_ids.len() + 4 {
1611                            self.sqs_state.subscriptions.page_size = PageSize::TwentyFive;
1612                        } else if idx == self.sqs_state.subscription_column_ids.len() + 5 {
1613                            self.sqs_state.subscriptions.page_size = PageSize::Fifty;
1614                        } else if idx == self.sqs_state.subscription_column_ids.len() + 6 {
1615                            self.sqs_state.subscriptions.page_size = PageSize::OneHundred;
1616                        }
1617                    } else if let Some(col) = self.sqs_column_ids.get(self.column_selector_index) {
1618                        if let Some(pos) = self.sqs_visible_column_ids.iter().position(|c| c == col)
1619                        {
1620                            self.sqs_visible_column_ids.remove(pos);
1621                        } else {
1622                            self.sqs_visible_column_ids.push(*col);
1623                        }
1624                    }
1625                } else if self.current_service == Service::LambdaFunctions {
1626                    let idx = self.column_selector_index;
1627                    // Check if we're in Versions tab
1628                    if self.lambda_state.current_function.is_some()
1629                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
1630                    {
1631                        // Version columns
1632                        if idx > 0 && idx <= self.lambda_state.version_column_ids.len() {
1633                            if let Some(col) = self.lambda_state.version_column_ids.get(idx - 1) {
1634                                if let Some(pos) = self
1635                                    .lambda_state
1636                                    .version_visible_column_ids
1637                                    .iter()
1638                                    .position(|c| *c == *col)
1639                                {
1640                                    self.lambda_state.version_visible_column_ids.remove(pos);
1641                                } else {
1642                                    self.lambda_state
1643                                        .version_visible_column_ids
1644                                        .push(col.clone());
1645                                }
1646                            }
1647                        } else if idx == self.lambda_state.version_column_ids.len() + 3 {
1648                            self.lambda_state.version_table.page_size = PageSize::Ten;
1649                        } else if idx == self.lambda_state.version_column_ids.len() + 4 {
1650                            self.lambda_state.version_table.page_size = PageSize::TwentyFive;
1651                        } else if idx == self.lambda_state.version_column_ids.len() + 5 {
1652                            self.lambda_state.version_table.page_size = PageSize::Fifty;
1653                        } else if idx == self.lambda_state.version_column_ids.len() + 6 {
1654                            self.lambda_state.version_table.page_size = PageSize::OneHundred;
1655                        }
1656                    } else if (self.lambda_state.current_function.is_some()
1657                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases)
1658                        || (self.lambda_state.current_version.is_some()
1659                            && self.lambda_state.detail_tab == LambdaDetailTab::Configuration)
1660                    {
1661                        // Alias columns
1662                        if idx > 0 && idx <= self.lambda_state.alias_column_ids.len() {
1663                            if let Some(col) = self.lambda_state.alias_column_ids.get(idx - 1) {
1664                                if let Some(pos) = self
1665                                    .lambda_state
1666                                    .alias_visible_column_ids
1667                                    .iter()
1668                                    .position(|c| *c == *col)
1669                                {
1670                                    self.lambda_state.alias_visible_column_ids.remove(pos);
1671                                } else {
1672                                    self.lambda_state.alias_visible_column_ids.push(col.clone());
1673                                }
1674                            }
1675                        } else if idx == self.lambda_state.alias_column_ids.len() + 3 {
1676                            self.lambda_state.alias_table.page_size = PageSize::Ten;
1677                        } else if idx == self.lambda_state.alias_column_ids.len() + 4 {
1678                            self.lambda_state.alias_table.page_size = PageSize::TwentyFive;
1679                        } else if idx == self.lambda_state.alias_column_ids.len() + 5 {
1680                            self.lambda_state.alias_table.page_size = PageSize::Fifty;
1681                        } else if idx == self.lambda_state.alias_column_ids.len() + 6 {
1682                            self.lambda_state.alias_table.page_size = PageSize::OneHundred;
1683                        }
1684                    } else {
1685                        // Function columns
1686                        if idx > 0 && idx <= self.lambda_state.function_column_ids.len() {
1687                            if let Some(col) = self.lambda_state.function_column_ids.get(idx - 1) {
1688                                if let Some(pos) = self
1689                                    .lambda_state
1690                                    .function_visible_column_ids
1691                                    .iter()
1692                                    .position(|c| *c == *col)
1693                                {
1694                                    self.lambda_state.function_visible_column_ids.remove(pos);
1695                                } else {
1696                                    self.lambda_state.function_visible_column_ids.push(*col);
1697                                }
1698                            }
1699                        } else if idx == self.lambda_state.function_column_ids.len() + 3 {
1700                            self.lambda_state.table.page_size = PageSize::Ten;
1701                        } else if idx == self.lambda_state.function_column_ids.len() + 4 {
1702                            self.lambda_state.table.page_size = PageSize::TwentyFive;
1703                        } else if idx == self.lambda_state.function_column_ids.len() + 5 {
1704                            self.lambda_state.table.page_size = PageSize::Fifty;
1705                        } else if idx == self.lambda_state.function_column_ids.len() + 6 {
1706                            self.lambda_state.table.page_size = PageSize::OneHundred;
1707                        }
1708                    }
1709                } else if self.current_service == Service::LambdaApplications {
1710                    if self.lambda_application_state.current_application.is_some() {
1711                        // In detail view - handle resource or deployment columns
1712                        if self.lambda_application_state.detail_tab
1713                            == LambdaApplicationDetailTab::Overview
1714                        {
1715                            // Resources columns
1716                            let idx = self.column_selector_index;
1717                            if idx > 0 && idx <= self.lambda_resource_column_ids.len() {
1718                                if let Some(col) = self.lambda_resource_column_ids.get(idx - 1) {
1719                                    if let Some(pos) = self
1720                                        .lambda_resource_visible_column_ids
1721                                        .iter()
1722                                        .position(|c| c == col)
1723                                    {
1724                                        self.lambda_resource_visible_column_ids.remove(pos);
1725                                    } else {
1726                                        self.lambda_resource_visible_column_ids.push(*col);
1727                                    }
1728                                }
1729                            } else if idx == self.lambda_resource_column_ids.len() + 3 {
1730                                self.lambda_application_state.resources.page_size = PageSize::Ten;
1731                            } else if idx == self.lambda_resource_column_ids.len() + 4 {
1732                                self.lambda_application_state.resources.page_size =
1733                                    PageSize::TwentyFive;
1734                            } else if idx == self.lambda_resource_column_ids.len() + 5 {
1735                                self.lambda_application_state.resources.page_size = PageSize::Fifty;
1736                            }
1737                        } else {
1738                            // Deployments columns
1739                            let idx = self.column_selector_index;
1740                            if idx > 0 && idx <= self.lambda_deployment_column_ids.len() {
1741                                if let Some(col) = self.lambda_deployment_column_ids.get(idx - 1) {
1742                                    if let Some(pos) = self
1743                                        .lambda_deployment_visible_column_ids
1744                                        .iter()
1745                                        .position(|c| c == col)
1746                                    {
1747                                        self.lambda_deployment_visible_column_ids.remove(pos);
1748                                    } else {
1749                                        self.lambda_deployment_visible_column_ids.push(*col);
1750                                    }
1751                                }
1752                            } else if idx == self.lambda_deployment_column_ids.len() + 3 {
1753                                self.lambda_application_state.deployments.page_size = PageSize::Ten;
1754                            } else if idx == self.lambda_deployment_column_ids.len() + 4 {
1755                                self.lambda_application_state.deployments.page_size =
1756                                    PageSize::TwentyFive;
1757                            } else if idx == self.lambda_deployment_column_ids.len() + 5 {
1758                                self.lambda_application_state.deployments.page_size =
1759                                    PageSize::Fifty;
1760                            }
1761                        }
1762                    } else {
1763                        // In list view - handle application columns
1764                        let idx = self.column_selector_index;
1765                        if idx > 0 && idx <= self.lambda_application_column_ids.len() {
1766                            if let Some(col) = self.lambda_application_column_ids.get(idx - 1) {
1767                                if let Some(pos) = self
1768                                    .lambda_application_visible_column_ids
1769                                    .iter()
1770                                    .position(|c| *c == *col)
1771                                {
1772                                    self.lambda_application_visible_column_ids.remove(pos);
1773                                } else {
1774                                    self.lambda_application_visible_column_ids.push(*col);
1775                                }
1776                            }
1777                        } else if idx == self.lambda_application_column_ids.len() + 3 {
1778                            self.lambda_application_state.table.page_size = PageSize::Ten;
1779                        } else if idx == self.lambda_application_column_ids.len() + 4 {
1780                            self.lambda_application_state.table.page_size = PageSize::TwentyFive;
1781                        } else if idx == self.lambda_application_column_ids.len() + 5 {
1782                            self.lambda_application_state.table.page_size = PageSize::Fifty;
1783                        }
1784                    }
1785                } else if self.view_mode == ViewMode::Events {
1786                    if let Some(col) = self.cw_log_event_column_ids.get(self.column_selector_index)
1787                    {
1788                        if let Some(pos) = self
1789                            .cw_log_event_visible_column_ids
1790                            .iter()
1791                            .position(|c| c == col)
1792                        {
1793                            self.cw_log_event_visible_column_ids.remove(pos);
1794                        } else {
1795                            self.cw_log_event_visible_column_ids.push(*col);
1796                        }
1797                    }
1798                } else if self.view_mode == ViewMode::Detail {
1799                    if let Some(col) = self
1800                        .cw_log_stream_column_ids
1801                        .get(self.column_selector_index)
1802                    {
1803                        if let Some(pos) = self
1804                            .cw_log_stream_visible_column_ids
1805                            .iter()
1806                            .position(|c| c == col)
1807                        {
1808                            self.cw_log_stream_visible_column_ids.remove(pos);
1809                        } else {
1810                            self.cw_log_stream_visible_column_ids.push(*col);
1811                        }
1812                    }
1813                } else if self.current_service == Service::CloudFormationStacks {
1814                    let idx = self.column_selector_index;
1815                    // Check if we're in StackInfo tab (tags)
1816                    if self.cfn_state.current_stack.is_some()
1817                        && self.cfn_state.detail_tab == CfnDetailTab::StackInfo
1818                    {
1819                        // Tags have 2 columns (Key, Value) - always visible, so only handle page size
1820                        if idx == 4 {
1821                            self.cfn_state.tags.page_size = PageSize::Ten;
1822                        } else if idx == 5 {
1823                            self.cfn_state.tags.page_size = PageSize::TwentyFive;
1824                        } else if idx == 6 {
1825                            self.cfn_state.tags.page_size = PageSize::Fifty;
1826                        } else if idx == 7 {
1827                            self.cfn_state.tags.page_size = PageSize::OneHundred;
1828                        }
1829                    } else if self.cfn_state.current_stack.is_some()
1830                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
1831                    {
1832                        if idx > 0 && idx <= self.cfn_parameter_column_ids.len() {
1833                            if let Some(col) = self.cfn_parameter_column_ids.get(idx - 1) {
1834                                if let Some(pos) = self
1835                                    .cfn_parameter_visible_column_ids
1836                                    .iter()
1837                                    .position(|c| c == col)
1838                                {
1839                                    self.cfn_parameter_visible_column_ids.remove(pos);
1840                                } else {
1841                                    self.cfn_parameter_visible_column_ids.push(col);
1842                                }
1843                            }
1844                        } else if idx == self.cfn_parameter_column_ids.len() + 3 {
1845                            self.cfn_state.parameters.page_size = PageSize::Ten;
1846                        } else if idx == self.cfn_parameter_column_ids.len() + 4 {
1847                            self.cfn_state.parameters.page_size = PageSize::TwentyFive;
1848                        } else if idx == self.cfn_parameter_column_ids.len() + 5 {
1849                            self.cfn_state.parameters.page_size = PageSize::Fifty;
1850                        } else if idx == self.cfn_parameter_column_ids.len() + 6 {
1851                            self.cfn_state.parameters.page_size = PageSize::OneHundred;
1852                        }
1853                    } else if self.cfn_state.current_stack.is_some()
1854                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
1855                    {
1856                        if idx > 0 && idx <= self.cfn_output_column_ids.len() {
1857                            if let Some(col) = self.cfn_output_column_ids.get(idx - 1) {
1858                                if let Some(pos) = self
1859                                    .cfn_output_visible_column_ids
1860                                    .iter()
1861                                    .position(|c| c == col)
1862                                {
1863                                    self.cfn_output_visible_column_ids.remove(pos);
1864                                } else {
1865                                    self.cfn_output_visible_column_ids.push(col);
1866                                }
1867                            }
1868                        } else if idx == self.cfn_output_column_ids.len() + 3 {
1869                            self.cfn_state.outputs.page_size = PageSize::Ten;
1870                        } else if idx == self.cfn_output_column_ids.len() + 4 {
1871                            self.cfn_state.outputs.page_size = PageSize::TwentyFive;
1872                        } else if idx == self.cfn_output_column_ids.len() + 5 {
1873                            self.cfn_state.outputs.page_size = PageSize::Fifty;
1874                        } else if idx == self.cfn_output_column_ids.len() + 6 {
1875                            self.cfn_state.outputs.page_size = PageSize::OneHundred;
1876                        }
1877                    } else if self.cfn_state.current_stack.is_some()
1878                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
1879                    {
1880                        if idx > 0 && idx <= self.cfn_resource_column_ids.len() {
1881                            if let Some(col) = self.cfn_resource_column_ids.get(idx - 1) {
1882                                if let Some(pos) = self
1883                                    .cfn_resource_visible_column_ids
1884                                    .iter()
1885                                    .position(|c| c == col)
1886                                {
1887                                    self.cfn_resource_visible_column_ids.remove(pos);
1888                                } else {
1889                                    self.cfn_resource_visible_column_ids.push(col);
1890                                }
1891                            }
1892                        } else if idx == self.cfn_resource_column_ids.len() + 3 {
1893                            self.cfn_state.resources.page_size = PageSize::Ten;
1894                        } else if idx == self.cfn_resource_column_ids.len() + 4 {
1895                            self.cfn_state.resources.page_size = PageSize::TwentyFive;
1896                        } else if idx == self.cfn_resource_column_ids.len() + 5 {
1897                            self.cfn_state.resources.page_size = PageSize::Fifty;
1898                        } else if idx == self.cfn_resource_column_ids.len() + 6 {
1899                            self.cfn_state.resources.page_size = PageSize::OneHundred;
1900                        }
1901                    } else if self.cfn_state.current_stack.is_none() {
1902                        // Stack list view
1903                        if idx > 0 && idx <= self.cfn_column_ids.len() {
1904                            if let Some(col) = self.cfn_column_ids.get(idx - 1) {
1905                                if let Some(pos) =
1906                                    self.cfn_visible_column_ids.iter().position(|c| c == col)
1907                                {
1908                                    self.cfn_visible_column_ids.remove(pos);
1909                                } else {
1910                                    self.cfn_visible_column_ids.push(*col);
1911                                }
1912                            }
1913                        } else if idx == self.cfn_column_ids.len() + 3 {
1914                            self.cfn_state.table.page_size = PageSize::Ten;
1915                        } else if idx == self.cfn_column_ids.len() + 4 {
1916                            self.cfn_state.table.page_size = PageSize::TwentyFive;
1917                        } else if idx == self.cfn_column_ids.len() + 5 {
1918                            self.cfn_state.table.page_size = PageSize::Fifty;
1919                        } else if idx == self.cfn_column_ids.len() + 6 {
1920                            self.cfn_state.table.page_size = PageSize::OneHundred;
1921                        }
1922                    }
1923                    // Template tab: no column toggle
1924                } else if self.current_service == Service::IamUsers {
1925                    let idx = self.column_selector_index;
1926                    if self.iam_state.current_user.is_some() {
1927                        // Policy columns
1928                        if idx > 0 && idx <= self.iam_policy_column_ids.len() {
1929                            if let Some(col) = self.iam_policy_column_ids.get(idx - 1) {
1930                                if let Some(pos) = self
1931                                    .iam_policy_visible_column_ids
1932                                    .iter()
1933                                    .position(|c| c == col)
1934                                {
1935                                    self.iam_policy_visible_column_ids.remove(pos);
1936                                } else {
1937                                    self.iam_policy_visible_column_ids.push(col.clone());
1938                                }
1939                            }
1940                        } else if idx == self.iam_policy_column_ids.len() + 3 {
1941                            self.iam_state.policies.page_size = PageSize::Ten;
1942                        } else if idx == self.iam_policy_column_ids.len() + 4 {
1943                            self.iam_state.policies.page_size = PageSize::TwentyFive;
1944                        } else if idx == self.iam_policy_column_ids.len() + 5 {
1945                            self.iam_state.policies.page_size = PageSize::Fifty;
1946                        }
1947                    } else {
1948                        // User columns
1949                        if idx > 0 && idx <= self.iam_user_column_ids.len() {
1950                            if let Some(col) = self.iam_user_column_ids.get(idx - 1) {
1951                                if let Some(pos) = self
1952                                    .iam_user_visible_column_ids
1953                                    .iter()
1954                                    .position(|c| c == col)
1955                                {
1956                                    self.iam_user_visible_column_ids.remove(pos);
1957                                } else {
1958                                    self.iam_user_visible_column_ids.push(*col);
1959                                }
1960                            }
1961                        } else if idx == self.iam_user_column_ids.len() + 3 {
1962                            self.iam_state.users.page_size = PageSize::Ten;
1963                        } else if idx == self.iam_user_column_ids.len() + 4 {
1964                            self.iam_state.users.page_size = PageSize::TwentyFive;
1965                        } else if idx == self.iam_user_column_ids.len() + 5 {
1966                            self.iam_state.users.page_size = PageSize::Fifty;
1967                        }
1968                    }
1969                } else if self.current_service == Service::IamRoles {
1970                    let idx = self.column_selector_index;
1971                    if self.iam_state.current_role.is_some() {
1972                        // Policy columns
1973                        if idx > 0 && idx <= self.iam_policy_column_ids.len() {
1974                            if let Some(col) = self.iam_policy_column_ids.get(idx - 1) {
1975                                if let Some(pos) = self
1976                                    .iam_policy_visible_column_ids
1977                                    .iter()
1978                                    .position(|c| c == col)
1979                                {
1980                                    self.iam_policy_visible_column_ids.remove(pos);
1981                                } else {
1982                                    self.iam_policy_visible_column_ids.push(col.clone());
1983                                }
1984                            }
1985                        } else if idx == self.iam_policy_column_ids.len() + 3 {
1986                            self.iam_state.policies.page_size = PageSize::Ten;
1987                        } else if idx == self.iam_policy_column_ids.len() + 4 {
1988                            self.iam_state.policies.page_size = PageSize::TwentyFive;
1989                        } else if idx == self.iam_policy_column_ids.len() + 5 {
1990                            self.iam_state.policies.page_size = PageSize::Fifty;
1991                        }
1992                    } else {
1993                        // Role columns
1994                        if idx > 0 && idx <= self.iam_role_column_ids.len() {
1995                            if let Some(col) = self.iam_role_column_ids.get(idx - 1) {
1996                                if let Some(pos) = self
1997                                    .iam_role_visible_column_ids
1998                                    .iter()
1999                                    .position(|c| c == col)
2000                                {
2001                                    self.iam_role_visible_column_ids.remove(pos);
2002                                } else {
2003                                    self.iam_role_visible_column_ids.push(col.clone());
2004                                }
2005                            }
2006                        } else if idx == self.iam_role_column_ids.len() + 3 {
2007                            self.iam_state.roles.page_size = PageSize::Ten;
2008                        } else if idx == self.iam_role_column_ids.len() + 4 {
2009                            self.iam_state.roles.page_size = PageSize::TwentyFive;
2010                        } else if idx == self.iam_role_column_ids.len() + 5 {
2011                            self.iam_state.roles.page_size = PageSize::Fifty;
2012                        }
2013                    }
2014                } else if self.current_service == Service::IamUserGroups {
2015                    let idx = self.column_selector_index;
2016                    if idx > 0 && idx <= self.iam_group_column_ids.len() {
2017                        if let Some(col) = self.iam_group_column_ids.get(idx - 1) {
2018                            if let Some(pos) = self
2019                                .iam_group_visible_column_ids
2020                                .iter()
2021                                .position(|c| c == col)
2022                            {
2023                                self.iam_group_visible_column_ids.remove(pos);
2024                            } else {
2025                                self.iam_group_visible_column_ids.push(col.clone());
2026                            }
2027                        }
2028                    } else if idx == self.iam_group_column_ids.len() + 3 {
2029                        self.iam_state.groups.page_size = PageSize::Ten;
2030                    } else if idx == self.iam_group_column_ids.len() + 4 {
2031                        self.iam_state.groups.page_size = PageSize::TwentyFive;
2032                    } else if idx == self.iam_group_column_ids.len() + 5 {
2033                        self.iam_state.groups.page_size = PageSize::Fifty;
2034                    }
2035                } else if let Some(col) =
2036                    self.cw_log_group_column_ids.get(self.column_selector_index)
2037                {
2038                    if let Some(pos) = self
2039                        .cw_log_group_visible_column_ids
2040                        .iter()
2041                        .position(|c| c == col)
2042                    {
2043                        self.cw_log_group_visible_column_ids.remove(pos);
2044                    } else {
2045                        self.cw_log_group_visible_column_ids.push(*col);
2046                    }
2047                }
2048            }
2049            Action::NextPreferences => {
2050                if self.current_service == Service::CloudWatchAlarms {
2051                    // Jump to next section: Columns(0), ViewAs(18), PageSize(22), WrapLines(28)
2052                    if self.column_selector_index < 18 {
2053                        self.column_selector_index = 18; // ViewAs header
2054                    } else if self.column_selector_index < 22 {
2055                        self.column_selector_index = 22; // PageSize header
2056                    } else if self.column_selector_index < 28 {
2057                        self.column_selector_index = 28; // WrapLines header
2058                    } else {
2059                        self.column_selector_index = 0; // Back to Columns header
2060                    }
2061                } else if self.current_service == Service::EcrRepositories
2062                    && self.ecr_state.current_repository.is_some()
2063                {
2064                    // Images view: Columns(0), PageSize(columns.len() + 2)
2065                    let page_size_idx = self.ecr_image_column_ids.len() + 2;
2066                    if self.column_selector_index < page_size_idx {
2067                        self.column_selector_index = page_size_idx;
2068                    } else {
2069                        self.column_selector_index = 0;
2070                    }
2071                } else if self.current_service == Service::LambdaFunctions {
2072                    // Lambda: Columns(0), PageSize(columns.len() + 2)
2073                    let page_size_idx = self.lambda_state.function_column_ids.len() + 2;
2074                    if self.column_selector_index < page_size_idx {
2075                        self.column_selector_index = page_size_idx;
2076                    } else {
2077                        self.column_selector_index = 0;
2078                    }
2079                } else if self.current_service == Service::LambdaApplications {
2080                    // Lambda Applications: Columns(0), PageSize(columns.len() + 2)
2081                    let page_size_idx = self.lambda_application_column_ids.len() + 2;
2082                    if self.column_selector_index < page_size_idx {
2083                        self.column_selector_index = page_size_idx;
2084                    } else {
2085                        self.column_selector_index = 0;
2086                    }
2087                } else if self.current_service == Service::CloudFormationStacks {
2088                    // CloudFormation: Columns(0), PageSize(columns.len() + 2)
2089                    let page_size_idx = self.cfn_column_ids.len() + 2;
2090                    if self.column_selector_index < page_size_idx {
2091                        self.column_selector_index = page_size_idx;
2092                    } else {
2093                        self.column_selector_index = 0;
2094                    }
2095                } else if self.current_service == Service::Ec2Instances {
2096                    let page_size_idx = self.ec2_column_ids.len() + 2;
2097                    if self.column_selector_index < page_size_idx {
2098                        self.column_selector_index = page_size_idx;
2099                    } else {
2100                        self.column_selector_index = 0;
2101                    }
2102                } else if self.current_service == Service::IamUsers {
2103                    if self.iam_state.current_user.is_some() {
2104                        // Only Permissions tab has column preferences
2105                        if self.iam_state.user_tab == UserTab::Permissions {
2106                            let page_size_idx = self.iam_policy_column_ids.len() + 2;
2107                            if self.column_selector_index < page_size_idx {
2108                                self.column_selector_index = page_size_idx;
2109                            } else {
2110                                self.column_selector_index = 0;
2111                            }
2112                        }
2113                        // Other tabs (Groups, Tags, Security Credentials, Last Accessed) have no preferences
2114                    } else {
2115                        // User columns: Columns(0), PageSize(columns.len() + 2)
2116                        let page_size_idx = self.iam_user_column_ids.len() + 2;
2117                        if self.column_selector_index < page_size_idx {
2118                            self.column_selector_index = page_size_idx;
2119                        } else {
2120                            self.column_selector_index = 0;
2121                        }
2122                    }
2123                } else if self.current_service == Service::IamRoles {
2124                    if self.iam_state.current_role.is_some() {
2125                        // Policy columns: Columns(0), PageSize(columns.len() + 2)
2126                        let page_size_idx = self.iam_policy_column_ids.len() + 2;
2127                        if self.column_selector_index < page_size_idx {
2128                            self.column_selector_index = page_size_idx;
2129                        } else {
2130                            self.column_selector_index = 0;
2131                        }
2132                    } else {
2133                        // Role columns: Columns(0), PageSize(columns.len() + 2)
2134                        let page_size_idx = self.iam_role_column_ids.len() + 2;
2135                        if self.column_selector_index < page_size_idx {
2136                            self.column_selector_index = page_size_idx;
2137                        } else {
2138                            self.column_selector_index = 0;
2139                        }
2140                    }
2141                } else if self.current_service == Service::IamUserGroups {
2142                    // Group columns: Columns(0), PageSize(columns.len() + 2)
2143                    let page_size_idx = self.iam_group_column_ids.len() + 2;
2144                    if self.column_selector_index < page_size_idx {
2145                        self.column_selector_index = page_size_idx;
2146                    } else {
2147                        self.column_selector_index = 0;
2148                    }
2149                } else if self.current_service == Service::SqsQueues
2150                    && self.sqs_state.current_queue.is_some()
2151                    && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
2152                {
2153                    // Triggers tab: Columns(0), PageSize(columns.len() + 2)
2154                    let page_size_idx = self.sqs_state.trigger_column_ids.len() + 2;
2155                    if self.column_selector_index < page_size_idx {
2156                        self.column_selector_index = page_size_idx;
2157                    } else {
2158                        self.column_selector_index = 0;
2159                    }
2160                } else if self.current_service == Service::SqsQueues
2161                    && self.sqs_state.current_queue.is_some()
2162                    && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
2163                {
2164                    // Pipes tab: Columns(0), PageSize(columns.len() + 2)
2165                    let page_size_idx = self.sqs_state.pipe_column_ids.len() + 2;
2166                    if self.column_selector_index < page_size_idx {
2167                        self.column_selector_index = page_size_idx;
2168                    } else {
2169                        self.column_selector_index = 0;
2170                    }
2171                } else if self.current_service == Service::SqsQueues
2172                    && self.sqs_state.current_queue.is_some()
2173                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
2174                {
2175                    // Tags tab: Columns(0), PageSize(columns.len() + 2)
2176                    let page_size_idx = self.sqs_state.tag_column_ids.len() + 2;
2177                    if self.column_selector_index < page_size_idx {
2178                        self.column_selector_index = page_size_idx;
2179                    } else {
2180                        self.column_selector_index = 0;
2181                    }
2182                } else if self.current_service == Service::SqsQueues
2183                    && self.sqs_state.current_queue.is_some()
2184                    && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
2185                {
2186                    // Subscriptions tab: Columns(0), PageSize(columns.len() + 2)
2187                    let page_size_idx = self.sqs_state.subscription_column_ids.len() + 2;
2188                    if self.column_selector_index < page_size_idx {
2189                        self.column_selector_index = page_size_idx;
2190                    } else {
2191                        self.column_selector_index = 0;
2192                    }
2193                }
2194            }
2195            Action::PrevPreferences => {
2196                if self.current_service == Service::CloudWatchAlarms {
2197                    // Jump to prev section: Columns(0), ViewAs(18), PageSize(22), WrapLines(28)
2198                    if self.column_selector_index >= 28 {
2199                        self.column_selector_index = 22;
2200                    } else if self.column_selector_index >= 22 {
2201                        self.column_selector_index = 18;
2202                    } else if self.column_selector_index >= 18 {
2203                        self.column_selector_index = 0;
2204                    } else {
2205                        self.column_selector_index = 28;
2206                    }
2207                } else if self.current_service == Service::EcrRepositories
2208                    && self.ecr_state.current_repository.is_some()
2209                {
2210                    let page_size_idx = self.ecr_image_column_ids.len() + 2;
2211                    if self.column_selector_index >= page_size_idx {
2212                        self.column_selector_index = 0;
2213                    } else {
2214                        self.column_selector_index = page_size_idx;
2215                    }
2216                } else if self.current_service == Service::LambdaFunctions {
2217                    let page_size_idx = self.lambda_state.function_column_ids.len() + 2;
2218                    if self.column_selector_index >= page_size_idx {
2219                        self.column_selector_index = 0;
2220                    } else {
2221                        self.column_selector_index = page_size_idx;
2222                    }
2223                } else if self.current_service == Service::LambdaApplications {
2224                    let page_size_idx = self.lambda_application_column_ids.len() + 2;
2225                    if self.column_selector_index >= page_size_idx {
2226                        self.column_selector_index = 0;
2227                    } else {
2228                        self.column_selector_index = page_size_idx;
2229                    }
2230                } else if self.current_service == Service::CloudFormationStacks {
2231                    let page_size_idx = self.cfn_column_ids.len() + 2;
2232                    if self.column_selector_index >= page_size_idx {
2233                        self.column_selector_index = 0;
2234                    } else {
2235                        self.column_selector_index = page_size_idx;
2236                    }
2237                } else if self.current_service == Service::Ec2Instances {
2238                    let page_size_idx = self.ec2_column_ids.len() + 2;
2239                    if self.column_selector_index >= page_size_idx {
2240                        self.column_selector_index = 0;
2241                    } else {
2242                        self.column_selector_index = page_size_idx;
2243                    }
2244                } else if self.current_service == Service::IamUsers {
2245                    if self.iam_state.current_user.is_some()
2246                        && self.iam_state.user_tab == UserTab::Permissions
2247                    {
2248                        let page_size_idx = self.iam_policy_column_ids.len() + 2;
2249                        if self.column_selector_index >= page_size_idx {
2250                            self.column_selector_index = 0;
2251                        } else {
2252                            self.column_selector_index = page_size_idx;
2253                        }
2254                    } else if self.iam_state.current_user.is_none() {
2255                        let page_size_idx = self.iam_user_column_ids.len() + 2;
2256                        if self.column_selector_index >= page_size_idx {
2257                            self.column_selector_index = 0;
2258                        } else {
2259                            self.column_selector_index = page_size_idx;
2260                        }
2261                    }
2262                } else if self.current_service == Service::IamRoles {
2263                    let page_size_idx = if self.iam_state.current_role.is_some() {
2264                        self.iam_policy_column_ids.len() + 2
2265                    } else {
2266                        self.iam_role_column_ids.len() + 2
2267                    };
2268                    if self.column_selector_index >= page_size_idx {
2269                        self.column_selector_index = 0;
2270                    } else {
2271                        self.column_selector_index = page_size_idx;
2272                    }
2273                } else if self.current_service == Service::IamUserGroups {
2274                    let page_size_idx = self.iam_group_column_ids.len() + 2;
2275                    if self.column_selector_index >= page_size_idx {
2276                        self.column_selector_index = 0;
2277                    } else {
2278                        self.column_selector_index = page_size_idx;
2279                    }
2280                } else if self.current_service == Service::SqsQueues
2281                    && self.sqs_state.current_queue.is_some()
2282                {
2283                    let page_size_idx = match self.sqs_state.detail_tab {
2284                        SqsQueueDetailTab::LambdaTriggers => {
2285                            self.sqs_state.trigger_column_ids.len() + 2
2286                        }
2287                        SqsQueueDetailTab::EventBridgePipes => {
2288                            self.sqs_state.pipe_column_ids.len() + 2
2289                        }
2290                        SqsQueueDetailTab::Tagging => self.sqs_state.tag_column_ids.len() + 2,
2291                        SqsQueueDetailTab::SnsSubscriptions => {
2292                            self.sqs_state.subscription_column_ids.len() + 2
2293                        }
2294                        _ => 0,
2295                    };
2296                    if page_size_idx > 0 {
2297                        if self.column_selector_index >= page_size_idx {
2298                            self.column_selector_index = 0;
2299                        } else {
2300                            self.column_selector_index = page_size_idx;
2301                        }
2302                    }
2303                }
2304            }
2305            Action::CloseColumnSelector => {
2306                self.mode = Mode::Normal;
2307                self.preference_section = Preferences::Columns;
2308            }
2309            Action::NextDetailTab => {
2310                if self.current_service == Service::SqsQueues
2311                    && self.sqs_state.current_queue.is_some()
2312                {
2313                    self.sqs_state.detail_tab = self.sqs_state.detail_tab.next();
2314                    if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
2315                        self.sqs_state.set_metrics_loading(true);
2316                        self.sqs_state.set_monitoring_scroll(0);
2317                        self.sqs_state.clear_metrics();
2318                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers {
2319                        self.sqs_state.triggers.loading = true;
2320                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes {
2321                        self.sqs_state.pipes.loading = true;
2322                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging {
2323                        self.sqs_state.tags.loading = true;
2324                    } else if self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions {
2325                        self.sqs_state.subscriptions.loading = true;
2326                    }
2327                } else if self.current_service == Service::LambdaApplications
2328                    && self.lambda_application_state.current_application.is_some()
2329                {
2330                    self.lambda_application_state.detail_tab =
2331                        self.lambda_application_state.detail_tab.next();
2332                } else if self.current_service == Service::IamRoles
2333                    && self.iam_state.current_role.is_some()
2334                {
2335                    self.iam_state.role_tab = self.iam_state.role_tab.next();
2336                    if self.iam_state.role_tab == RoleTab::Tags {
2337                        self.iam_state.tags.loading = true;
2338                    }
2339                } else if self.current_service == Service::IamUsers
2340                    && self.iam_state.current_user.is_some()
2341                {
2342                    self.iam_state.user_tab = self.iam_state.user_tab.next();
2343                    if self.iam_state.user_tab == UserTab::Tags {
2344                        self.iam_state.user_tags.loading = true;
2345                    }
2346                } else if self.current_service == Service::IamUserGroups
2347                    && self.iam_state.current_group.is_some()
2348                {
2349                    self.iam_state.group_tab = self.iam_state.group_tab.next();
2350                } else if self.view_mode == ViewMode::Detail {
2351                    self.log_groups_state.detail_tab = self.log_groups_state.detail_tab.next();
2352                } else if self.current_service == Service::S3Buckets {
2353                    if self.s3_state.current_bucket.is_some() {
2354                        self.s3_state.object_tab = self.s3_state.object_tab.next();
2355                    } else {
2356                        self.s3_state.bucket_type = match self.s3_state.bucket_type {
2357                            S3BucketType::GeneralPurpose => S3BucketType::Directory,
2358                            S3BucketType::Directory => S3BucketType::GeneralPurpose,
2359                        };
2360                        self.s3_state.buckets.reset();
2361                    }
2362                } else if self.current_service == Service::CloudWatchAlarms {
2363                    self.alarms_state.alarm_tab = match self.alarms_state.alarm_tab {
2364                        AlarmTab::AllAlarms => AlarmTab::InAlarm,
2365                        AlarmTab::InAlarm => AlarmTab::AllAlarms,
2366                    };
2367                    self.alarms_state.table.reset();
2368                } else if self.current_service == Service::EcrRepositories
2369                    && self.ecr_state.current_repository.is_none()
2370                {
2371                    self.ecr_state.tab = self.ecr_state.tab.next();
2372                    self.ecr_state.repositories.reset();
2373                    self.ecr_state.repositories.loading = true;
2374                } else if self.current_service == Service::LambdaFunctions
2375                    && self.lambda_state.current_function.is_some()
2376                {
2377                    if self.lambda_state.current_version.is_some() {
2378                        // Version view: use VersionDetailTab enum
2379                        self.lambda_state.version_detail_tab =
2380                            self.lambda_state.version_detail_tab.next();
2381                        self.lambda_state.detail_tab =
2382                            self.lambda_state.version_detail_tab.to_detail_tab();
2383                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2384                            self.lambda_state.set_metrics_loading(true);
2385                            self.lambda_state.set_monitoring_scroll(0);
2386                            self.lambda_state.clear_metrics();
2387                        }
2388                    } else {
2389                        self.lambda_state.detail_tab = self.lambda_state.detail_tab.next();
2390                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2391                            self.lambda_state.set_metrics_loading(true);
2392                            self.lambda_state.set_monitoring_scroll(0);
2393                            self.lambda_state.clear_metrics();
2394                        }
2395                    }
2396                } else if self.current_service == Service::CloudFormationStacks
2397                    && self.cfn_state.current_stack.is_some()
2398                {
2399                    self.cfn_state.detail_tab = self.cfn_state.detail_tab.next();
2400                }
2401            }
2402            Action::PrevDetailTab => {
2403                if self.current_service == Service::SqsQueues
2404                    && self.sqs_state.current_queue.is_some()
2405                {
2406                    self.sqs_state.detail_tab = self.sqs_state.detail_tab.prev();
2407                    if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
2408                        self.sqs_state.set_metrics_loading(true);
2409                        self.sqs_state.set_monitoring_scroll(0);
2410                        self.sqs_state.clear_metrics();
2411                    }
2412                } else if self.current_service == Service::LambdaApplications
2413                    && self.lambda_application_state.current_application.is_some()
2414                {
2415                    self.lambda_application_state.detail_tab =
2416                        self.lambda_application_state.detail_tab.prev();
2417                } else if self.current_service == Service::IamRoles
2418                    && self.iam_state.current_role.is_some()
2419                {
2420                    self.iam_state.role_tab = self.iam_state.role_tab.prev();
2421                } else if self.current_service == Service::IamUsers
2422                    && self.iam_state.current_user.is_some()
2423                {
2424                    self.iam_state.user_tab = self.iam_state.user_tab.prev();
2425                } else if self.current_service == Service::IamUserGroups
2426                    && self.iam_state.current_group.is_some()
2427                {
2428                    self.iam_state.group_tab = self.iam_state.group_tab.prev();
2429                } else if self.view_mode == ViewMode::Detail {
2430                    self.log_groups_state.detail_tab = self.log_groups_state.detail_tab.prev();
2431                } else if self.current_service == Service::S3Buckets {
2432                    if self.s3_state.current_bucket.is_some() {
2433                        self.s3_state.object_tab = self.s3_state.object_tab.prev();
2434                    }
2435                } else if self.current_service == Service::CloudWatchAlarms {
2436                    self.alarms_state.alarm_tab = match self.alarms_state.alarm_tab {
2437                        AlarmTab::AllAlarms => AlarmTab::InAlarm,
2438                        AlarmTab::InAlarm => AlarmTab::AllAlarms,
2439                    };
2440                } else if self.current_service == Service::EcrRepositories
2441                    && self.ecr_state.current_repository.is_none()
2442                {
2443                    self.ecr_state.tab = self.ecr_state.tab.prev();
2444                    self.ecr_state.repositories.reset();
2445                    self.ecr_state.repositories.loading = true;
2446                } else if self.current_service == Service::LambdaFunctions
2447                    && self.lambda_state.current_function.is_some()
2448                {
2449                    if self.lambda_state.current_version.is_some() {
2450                        // Version view: use VersionDetailTab enum
2451                        self.lambda_state.version_detail_tab =
2452                            self.lambda_state.version_detail_tab.prev();
2453                        self.lambda_state.detail_tab =
2454                            self.lambda_state.version_detail_tab.to_detail_tab();
2455                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2456                            self.lambda_state.set_metrics_loading(true);
2457                            self.lambda_state.set_monitoring_scroll(0);
2458                            self.lambda_state.clear_metrics();
2459                        }
2460                    } else {
2461                        self.lambda_state.detail_tab = self.lambda_state.detail_tab.prev();
2462                        if self.lambda_state.detail_tab == LambdaDetailTab::Monitor {
2463                            self.lambda_state.set_metrics_loading(true);
2464                            self.lambda_state.set_monitoring_scroll(0);
2465                            self.lambda_state.clear_metrics();
2466                        }
2467                    }
2468                } else if self.current_service == Service::CloudFormationStacks
2469                    && self.cfn_state.current_stack.is_some()
2470                {
2471                    self.cfn_state.detail_tab = self.cfn_state.detail_tab.prev();
2472                }
2473            }
2474            Action::StartFilter => {
2475                // Don't allow filter mode when no service is selected and no tabs open
2476                if !self.service_selected && self.tabs.is_empty() {
2477                    return;
2478                }
2479
2480                if self.current_service == Service::CloudWatchInsights {
2481                    self.mode = Mode::InsightsInput;
2482                } else if self.current_service == Service::CloudWatchAlarms {
2483                    self.mode = Mode::FilterInput;
2484                } else if self.current_service == Service::S3Buckets {
2485                    self.mode = Mode::FilterInput;
2486                    self.log_groups_state.filter_mode = true;
2487                } else if self.current_service == Service::EcrRepositories
2488                    || self.current_service == Service::IamUsers
2489                    || self.current_service == Service::IamUserGroups
2490                {
2491                    self.mode = Mode::FilterInput;
2492                    if self.current_service == Service::EcrRepositories
2493                        && self.ecr_state.current_repository.is_none()
2494                    {
2495                        self.ecr_state.input_focus = InputFocus::Filter;
2496                    }
2497                } else if self.current_service == Service::LambdaFunctions {
2498                    self.mode = Mode::FilterInput;
2499                    if self.lambda_state.current_version.is_some()
2500                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
2501                    {
2502                        self.lambda_state.alias_input_focus = InputFocus::Filter;
2503                    } else if self.lambda_state.current_function.is_some()
2504                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
2505                    {
2506                        self.lambda_state.version_input_focus = InputFocus::Filter;
2507                    } else if self.lambda_state.current_function.is_none() {
2508                        self.lambda_state.input_focus = InputFocus::Filter;
2509                    }
2510                } else if self.current_service == Service::LambdaApplications {
2511                    self.mode = Mode::FilterInput;
2512                    if self.lambda_application_state.current_application.is_some() {
2513                        // In detail view - check which tab
2514                        if self.lambda_application_state.detail_tab
2515                            == LambdaApplicationDetailTab::Overview
2516                        {
2517                            self.lambda_application_state.resource_input_focus = InputFocus::Filter;
2518                        } else {
2519                            self.lambda_application_state.deployment_input_focus =
2520                                InputFocus::Filter;
2521                        }
2522                    } else {
2523                        self.lambda_application_state.input_focus = InputFocus::Filter;
2524                    }
2525                } else if self.current_service == Service::IamRoles {
2526                    self.mode = Mode::FilterInput;
2527                } else if self.current_service == Service::CloudFormationStacks {
2528                    self.mode = Mode::FilterInput;
2529                    if self.cfn_state.current_stack.is_some()
2530                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
2531                    {
2532                        self.cfn_state.parameters_input_focus = InputFocus::Filter;
2533                    } else if self.cfn_state.current_stack.is_some()
2534                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
2535                    {
2536                        self.cfn_state.outputs_input_focus = InputFocus::Filter;
2537                    } else {
2538                        self.cfn_state.input_focus = InputFocus::Filter;
2539                    }
2540                } else if self.current_service == Service::SqsQueues {
2541                    self.mode = Mode::FilterInput;
2542                    self.sqs_state.input_focus = InputFocus::Filter;
2543                } else if self.view_mode == ViewMode::List
2544                    || (self.view_mode == ViewMode::Detail
2545                        && self.log_groups_state.detail_tab == DetailTab::LogStreams)
2546                {
2547                    self.mode = Mode::FilterInput;
2548                    self.log_groups_state.filter_mode = true;
2549                    self.log_groups_state.input_focus = InputFocus::Filter;
2550                }
2551            }
2552            Action::StartEventFilter => {
2553                if self.current_service == Service::CloudWatchInsights {
2554                    self.mode = Mode::InsightsInput;
2555                } else if self.view_mode == ViewMode::List {
2556                    self.mode = Mode::FilterInput;
2557                    self.log_groups_state.filter_mode = true;
2558                    self.log_groups_state.input_focus = InputFocus::Filter;
2559                } else if self.view_mode == ViewMode::Events {
2560                    self.mode = Mode::EventFilterInput;
2561                } else if self.view_mode == ViewMode::Detail
2562                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
2563                {
2564                    self.mode = Mode::FilterInput;
2565                    self.log_groups_state.filter_mode = true;
2566                    self.log_groups_state.input_focus = InputFocus::Filter;
2567                }
2568            }
2569            Action::NextFilterFocus => {
2570                if self.mode == Mode::FilterInput && self.current_service == Service::Ec2Instances {
2571                    self.ec2_state.input_focus =
2572                        self.ec2_state.input_focus.next(&ec2::FILTER_CONTROLS);
2573                } else if self.mode == Mode::FilterInput
2574                    && self.current_service == Service::LambdaApplications
2575                {
2576                    use crate::ui::lambda::FILTER_CONTROLS;
2577                    if self.lambda_application_state.current_application.is_some() {
2578                        if self.lambda_application_state.detail_tab
2579                            == LambdaApplicationDetailTab::Deployments
2580                        {
2581                            self.lambda_application_state.deployment_input_focus = self
2582                                .lambda_application_state
2583                                .deployment_input_focus
2584                                .next(&FILTER_CONTROLS);
2585                        } else {
2586                            self.lambda_application_state.resource_input_focus = self
2587                                .lambda_application_state
2588                                .resource_input_focus
2589                                .next(&FILTER_CONTROLS);
2590                        }
2591                    } else {
2592                        self.lambda_application_state.input_focus = self
2593                            .lambda_application_state
2594                            .input_focus
2595                            .next(&FILTER_CONTROLS);
2596                    }
2597                } else if self.mode == Mode::FilterInput
2598                    && self.current_service == Service::IamRoles
2599                    && self.iam_state.current_role.is_some()
2600                {
2601                    use crate::ui::iam::POLICY_FILTER_CONTROLS;
2602                    self.iam_state.policy_input_focus = self
2603                        .iam_state
2604                        .policy_input_focus
2605                        .next(&POLICY_FILTER_CONTROLS);
2606                } else if self.mode == Mode::FilterInput
2607                    && self.current_service == Service::IamRoles
2608                    && self.iam_state.current_role.is_none()
2609                {
2610                    use crate::ui::iam::ROLE_FILTER_CONTROLS;
2611                    self.iam_state.role_input_focus =
2612                        self.iam_state.role_input_focus.next(&ROLE_FILTER_CONTROLS);
2613                } else if self.mode == Mode::InsightsInput {
2614                    use crate::app::InsightsFocus;
2615                    self.insights_state.insights.insights_focus =
2616                        match self.insights_state.insights.insights_focus {
2617                            InsightsFocus::QueryLanguage => InsightsFocus::DatePicker,
2618                            InsightsFocus::DatePicker => InsightsFocus::LogGroupSearch,
2619                            InsightsFocus::LogGroupSearch => InsightsFocus::Query,
2620                            InsightsFocus::Query => InsightsFocus::QueryLanguage,
2621                        };
2622                } else if self.mode == Mode::FilterInput
2623                    && self.current_service == Service::CloudFormationStacks
2624                {
2625                    if self.cfn_state.current_stack.is_some()
2626                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
2627                    {
2628                        self.cfn_state.parameters_input_focus = self
2629                            .cfn_state
2630                            .parameters_input_focus
2631                            .next(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
2632                    } else if self.cfn_state.current_stack.is_some()
2633                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
2634                    {
2635                        self.cfn_state.outputs_input_focus = self
2636                            .cfn_state
2637                            .outputs_input_focus
2638                            .next(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
2639                    } else if self.cfn_state.current_stack.is_some()
2640                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
2641                    {
2642                        self.cfn_state.resources_input_focus = self
2643                            .cfn_state
2644                            .resources_input_focus
2645                            .next(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
2646                    } else {
2647                        self.cfn_state.input_focus = self
2648                            .cfn_state
2649                            .input_focus
2650                            .next(&CfnStateConstants::FILTER_CONTROLS);
2651                    }
2652                } else if self.mode == Mode::FilterInput
2653                    && self.current_service == Service::SqsQueues
2654                {
2655                    if self.sqs_state.current_queue.is_some()
2656                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
2657                    {
2658                        use crate::ui::sqs::SUBSCRIPTION_FILTER_CONTROLS;
2659                        self.sqs_state.input_focus = self
2660                            .sqs_state
2661                            .input_focus
2662                            .next(SUBSCRIPTION_FILTER_CONTROLS);
2663                    } else {
2664                        use crate::ui::sqs::FILTER_CONTROLS;
2665                        self.sqs_state.input_focus =
2666                            self.sqs_state.input_focus.next(FILTER_CONTROLS);
2667                    }
2668                } else if self.mode == Mode::FilterInput
2669                    && self.current_service == Service::CloudWatchLogGroups
2670                {
2671                    use crate::ui::cw::logs::FILTER_CONTROLS;
2672                    self.log_groups_state.input_focus =
2673                        self.log_groups_state.input_focus.next(&FILTER_CONTROLS);
2674                } else if self.mode == Mode::EventFilterInput {
2675                    self.log_groups_state.event_input_focus =
2676                        self.log_groups_state.event_input_focus.next();
2677                } else if self.mode == Mode::FilterInput
2678                    && self.current_service == Service::CloudWatchAlarms
2679                {
2680                    use crate::ui::cw::alarms::FILTER_CONTROLS;
2681                    self.alarms_state.input_focus =
2682                        self.alarms_state.input_focus.next(&FILTER_CONTROLS);
2683                } else if self.mode == Mode::FilterInput
2684                    && self.current_service == Service::EcrRepositories
2685                    && self.ecr_state.current_repository.is_none()
2686                {
2687                    use crate::ui::ecr::FILTER_CONTROLS;
2688                    self.ecr_state.input_focus = self.ecr_state.input_focus.next(&FILTER_CONTROLS);
2689                } else if self.mode == Mode::FilterInput
2690                    && self.current_service == Service::LambdaFunctions
2691                {
2692                    use crate::ui::lambda::FILTER_CONTROLS;
2693                    if self.lambda_state.current_version.is_some()
2694                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
2695                    {
2696                        self.lambda_state.alias_input_focus =
2697                            self.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
2698                    } else if self.lambda_state.current_function.is_some()
2699                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
2700                    {
2701                        self.lambda_state.version_input_focus =
2702                            self.lambda_state.version_input_focus.next(&FILTER_CONTROLS);
2703                    } else if self.lambda_state.current_function.is_some()
2704                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
2705                    {
2706                        self.lambda_state.alias_input_focus =
2707                            self.lambda_state.alias_input_focus.next(&FILTER_CONTROLS);
2708                    } else if self.lambda_state.current_function.is_none() {
2709                        self.lambda_state.input_focus =
2710                            self.lambda_state.input_focus.next(&FILTER_CONTROLS);
2711                    }
2712                }
2713            }
2714            Action::PrevFilterFocus => {
2715                if self.mode == Mode::FilterInput && self.current_service == Service::Ec2Instances {
2716                    self.ec2_state.input_focus =
2717                        self.ec2_state.input_focus.prev(&ec2::FILTER_CONTROLS);
2718                } else if self.mode == Mode::FilterInput
2719                    && self.current_service == Service::LambdaApplications
2720                {
2721                    use crate::ui::lambda::FILTER_CONTROLS;
2722                    if self.lambda_application_state.current_application.is_some() {
2723                        if self.lambda_application_state.detail_tab
2724                            == LambdaApplicationDetailTab::Deployments
2725                        {
2726                            self.lambda_application_state.deployment_input_focus = self
2727                                .lambda_application_state
2728                                .deployment_input_focus
2729                                .prev(&FILTER_CONTROLS);
2730                        } else {
2731                            self.lambda_application_state.resource_input_focus = self
2732                                .lambda_application_state
2733                                .resource_input_focus
2734                                .prev(&FILTER_CONTROLS);
2735                        }
2736                    } else {
2737                        self.lambda_application_state.input_focus = self
2738                            .lambda_application_state
2739                            .input_focus
2740                            .prev(&FILTER_CONTROLS);
2741                    }
2742                } else if self.mode == Mode::FilterInput
2743                    && self.current_service == Service::CloudFormationStacks
2744                {
2745                    if self.cfn_state.current_stack.is_some()
2746                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
2747                    {
2748                        self.cfn_state.parameters_input_focus = self
2749                            .cfn_state
2750                            .parameters_input_focus
2751                            .prev(&CfnStateConstants::PARAMETERS_FILTER_CONTROLS);
2752                    } else if self.cfn_state.current_stack.is_some()
2753                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
2754                    {
2755                        self.cfn_state.outputs_input_focus = self
2756                            .cfn_state
2757                            .outputs_input_focus
2758                            .prev(&CfnStateConstants::OUTPUTS_FILTER_CONTROLS);
2759                    } else if self.cfn_state.current_stack.is_some()
2760                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
2761                    {
2762                        self.cfn_state.resources_input_focus = self
2763                            .cfn_state
2764                            .resources_input_focus
2765                            .prev(&CfnStateConstants::RESOURCES_FILTER_CONTROLS);
2766                    } else {
2767                        self.cfn_state.input_focus = self
2768                            .cfn_state
2769                            .input_focus
2770                            .prev(&CfnStateConstants::FILTER_CONTROLS);
2771                    }
2772                } else if self.mode == Mode::FilterInput
2773                    && self.current_service == Service::SqsQueues
2774                {
2775                    if self.sqs_state.current_queue.is_some()
2776                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
2777                    {
2778                        use crate::ui::sqs::SUBSCRIPTION_FILTER_CONTROLS;
2779                        self.sqs_state.input_focus = self
2780                            .sqs_state
2781                            .input_focus
2782                            .prev(SUBSCRIPTION_FILTER_CONTROLS);
2783                    } else {
2784                        use crate::ui::sqs::FILTER_CONTROLS;
2785                        self.sqs_state.input_focus =
2786                            self.sqs_state.input_focus.prev(FILTER_CONTROLS);
2787                    }
2788                } else if self.mode == Mode::FilterInput
2789                    && self.current_service == Service::IamRoles
2790                    && self.iam_state.current_role.is_none()
2791                {
2792                    use crate::ui::iam::ROLE_FILTER_CONTROLS;
2793                    self.iam_state.role_input_focus =
2794                        self.iam_state.role_input_focus.prev(&ROLE_FILTER_CONTROLS);
2795                } else if self.mode == Mode::FilterInput
2796                    && self.current_service == Service::CloudWatchLogGroups
2797                {
2798                    use crate::ui::cw::logs::FILTER_CONTROLS;
2799                    self.log_groups_state.input_focus =
2800                        self.log_groups_state.input_focus.prev(&FILTER_CONTROLS);
2801                } else if self.mode == Mode::EventFilterInput {
2802                    self.log_groups_state.event_input_focus =
2803                        self.log_groups_state.event_input_focus.prev();
2804                } else if self.mode == Mode::FilterInput
2805                    && self.current_service == Service::IamRoles
2806                    && self.iam_state.current_role.is_some()
2807                {
2808                    use crate::ui::iam::POLICY_FILTER_CONTROLS;
2809                    self.iam_state.policy_input_focus = self
2810                        .iam_state
2811                        .policy_input_focus
2812                        .prev(&POLICY_FILTER_CONTROLS);
2813                } else if self.mode == Mode::FilterInput
2814                    && self.current_service == Service::CloudWatchAlarms
2815                {
2816                    use crate::ui::cw::alarms::FILTER_CONTROLS;
2817                    self.alarms_state.input_focus =
2818                        self.alarms_state.input_focus.prev(&FILTER_CONTROLS);
2819                } else if self.mode == Mode::FilterInput
2820                    && self.current_service == Service::EcrRepositories
2821                    && self.ecr_state.current_repository.is_none()
2822                {
2823                    use crate::ui::ecr::FILTER_CONTROLS;
2824                    self.ecr_state.input_focus = self.ecr_state.input_focus.prev(&FILTER_CONTROLS);
2825                } else if self.mode == Mode::FilterInput
2826                    && self.current_service == Service::LambdaFunctions
2827                {
2828                    use crate::ui::lambda::FILTER_CONTROLS;
2829                    if self.lambda_state.current_version.is_some()
2830                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration
2831                    {
2832                        self.lambda_state.alias_input_focus =
2833                            self.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
2834                    } else if self.lambda_state.current_function.is_some()
2835                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
2836                    {
2837                        self.lambda_state.version_input_focus =
2838                            self.lambda_state.version_input_focus.prev(&FILTER_CONTROLS);
2839                    } else if self.lambda_state.current_function.is_some()
2840                        && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
2841                    {
2842                        self.lambda_state.alias_input_focus =
2843                            self.lambda_state.alias_input_focus.prev(&FILTER_CONTROLS);
2844                    } else if self.lambda_state.current_function.is_none() {
2845                        self.lambda_state.input_focus =
2846                            self.lambda_state.input_focus.prev(&FILTER_CONTROLS);
2847                    }
2848                }
2849            }
2850            Action::ToggleFilterCheckbox => {
2851                if self.mode == Mode::FilterInput && self.current_service == Service::Ec2Instances {
2852                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
2853                        self.ec2_state.state_filter = self.ec2_state.state_filter.next();
2854                        self.ec2_state.table.reset();
2855                    }
2856                } else if self.mode == Mode::InsightsInput {
2857                    use crate::app::InsightsFocus;
2858                    if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
2859                        && self.insights_state.insights.show_dropdown
2860                        && !self.insights_state.insights.log_group_matches.is_empty()
2861                    {
2862                        let selected_idx = self.insights_state.insights.dropdown_selected;
2863                        if let Some(group_name) = self
2864                            .insights_state
2865                            .insights
2866                            .log_group_matches
2867                            .get(selected_idx)
2868                        {
2869                            let group_name = group_name.clone();
2870                            if let Some(pos) = self
2871                                .insights_state
2872                                .insights
2873                                .selected_log_groups
2874                                .iter()
2875                                .position(|g| g == &group_name)
2876                            {
2877                                self.insights_state.insights.selected_log_groups.remove(pos);
2878                            } else if self.insights_state.insights.selected_log_groups.len() < 50 {
2879                                self.insights_state
2880                                    .insights
2881                                    .selected_log_groups
2882                                    .push(group_name);
2883                            }
2884                        }
2885                    }
2886                } else if self.mode == Mode::FilterInput
2887                    && self.current_service == Service::CloudFormationStacks
2888                {
2889                    use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
2890                    match self.cfn_state.input_focus {
2891                        STATUS_FILTER => {
2892                            self.cfn_state.status_filter = self.cfn_state.status_filter.next();
2893                            self.cfn_state.table.reset();
2894                        }
2895                        VIEW_NESTED => {
2896                            self.cfn_state.view_nested = !self.cfn_state.view_nested;
2897                            self.cfn_state.table.reset();
2898                        }
2899                        _ => {}
2900                    }
2901                } else if self.mode == Mode::FilterInput
2902                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
2903                {
2904                    match self.log_groups_state.input_focus {
2905                        InputFocus::Checkbox("ExactMatch") => {
2906                            self.log_groups_state.exact_match = !self.log_groups_state.exact_match
2907                        }
2908                        InputFocus::Checkbox("ShowExpired") => {
2909                            self.log_groups_state.show_expired = !self.log_groups_state.show_expired
2910                        }
2911                        _ => {}
2912                    }
2913                } else if self.mode == Mode::EventFilterInput
2914                    && self.log_groups_state.event_input_focus == EventFilterFocus::DateRange
2915                {
2916                    self.log_groups_state.relative_unit =
2917                        self.log_groups_state.relative_unit.next();
2918                }
2919            }
2920            Action::CycleSortColumn => {
2921                if self.view_mode == ViewMode::Detail
2922                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
2923                {
2924                    self.log_groups_state.stream_sort = match self.log_groups_state.stream_sort {
2925                        StreamSort::Name => StreamSort::CreationTime,
2926                        StreamSort::CreationTime => StreamSort::LastEventTime,
2927                        StreamSort::LastEventTime => StreamSort::Name,
2928                    };
2929                }
2930            }
2931            Action::ToggleSortDirection => {
2932                if self.view_mode == ViewMode::Detail
2933                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
2934                {
2935                    self.log_groups_state.stream_sort_desc =
2936                        !self.log_groups_state.stream_sort_desc;
2937                }
2938            }
2939            Action::ScrollUp => {
2940                if self.mode == Mode::ErrorModal {
2941                    self.error_scroll = self.error_scroll.saturating_sub(1);
2942                } else if self.current_service == Service::LambdaFunctions
2943                    && self.lambda_state.current_function.is_some()
2944                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
2945                    && !self.lambda_state.is_metrics_loading()
2946                {
2947                    self.lambda_state.set_monitoring_scroll(
2948                        self.lambda_state.monitoring_scroll().saturating_sub(1),
2949                    );
2950                } else if self.current_service == Service::SqsQueues
2951                    && self.sqs_state.current_queue.is_some()
2952                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
2953                    && !self.sqs_state.is_metrics_loading()
2954                {
2955                    self.sqs_state.set_monitoring_scroll(
2956                        self.sqs_state.monitoring_scroll().saturating_sub(1),
2957                    );
2958                } else if self.view_mode == ViewMode::PolicyView {
2959                    self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(10);
2960                } else if self.current_service == Service::IamRoles
2961                    && self.iam_state.current_role.is_some()
2962                    && self.iam_state.role_tab == RoleTab::TrustRelationships
2963                {
2964                    self.iam_state.trust_policy_scroll =
2965                        self.iam_state.trust_policy_scroll.saturating_sub(10);
2966                } else if self.view_mode == ViewMode::Events {
2967                    if self.log_groups_state.event_scroll_offset == 0
2968                        && self.log_groups_state.has_older_events
2969                    {
2970                        self.log_groups_state.loading = true;
2971                    } else {
2972                        self.log_groups_state.event_scroll_offset =
2973                            self.log_groups_state.event_scroll_offset.saturating_sub(1);
2974                    }
2975                } else if self.view_mode == ViewMode::InsightsResults {
2976                    self.insights_state.insights.results_selected = self
2977                        .insights_state
2978                        .insights
2979                        .results_selected
2980                        .saturating_sub(1);
2981                } else if self.view_mode == ViewMode::Detail {
2982                    self.log_groups_state.selected_stream =
2983                        self.log_groups_state.selected_stream.saturating_sub(1);
2984                    self.log_groups_state.expanded_stream = None;
2985                } else if self.view_mode == ViewMode::List
2986                    && self.current_service == Service::CloudWatchLogGroups
2987                {
2988                    self.log_groups_state.log_groups.selected =
2989                        self.log_groups_state.log_groups.selected.saturating_sub(1);
2990                    self.log_groups_state.log_groups.snap_to_page();
2991                } else if self.current_service == Service::EcrRepositories {
2992                    if self.ecr_state.current_repository.is_some() {
2993                        self.ecr_state.images.page_up();
2994                    } else {
2995                        self.ecr_state.repositories.page_up();
2996                    }
2997                }
2998            }
2999            Action::ScrollDown => {
3000                if self.mode == Mode::ErrorModal {
3001                    if let Some(error_msg) = &self.error_message {
3002                        let lines = error_msg.lines().count();
3003                        let max_scroll = lines.saturating_sub(1);
3004                        self.error_scroll = (self.error_scroll + 1).min(max_scroll);
3005                    }
3006                } else if self.current_service == Service::SqsQueues
3007                    && self.sqs_state.current_queue.is_some()
3008                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
3009                {
3010                    self.sqs_state
3011                        .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(1));
3012                } else if self.view_mode == ViewMode::PolicyView {
3013                    let lines = self.iam_state.policy_document.lines().count();
3014                    let max_scroll = lines.saturating_sub(1);
3015                    self.iam_state.policy_scroll =
3016                        (self.iam_state.policy_scroll + 10).min(max_scroll);
3017                } else if self.current_service == Service::IamRoles
3018                    && self.iam_state.current_role.is_some()
3019                    && self.iam_state.role_tab == RoleTab::TrustRelationships
3020                {
3021                    let lines = self.iam_state.trust_policy_document.lines().count();
3022                    let max_scroll = lines.saturating_sub(1);
3023                    self.iam_state.trust_policy_scroll =
3024                        (self.iam_state.trust_policy_scroll + 10).min(max_scroll);
3025                } else if self.view_mode == ViewMode::Events {
3026                    let max_scroll = self.log_groups_state.log_events.len().saturating_sub(1);
3027                    if self.log_groups_state.event_scroll_offset >= max_scroll {
3028                        // At the end, do nothing
3029                    } else {
3030                        self.log_groups_state.event_scroll_offset =
3031                            (self.log_groups_state.event_scroll_offset + 1).min(max_scroll);
3032                    }
3033                } else if self.view_mode == ViewMode::InsightsResults {
3034                    let max = self
3035                        .insights_state
3036                        .insights
3037                        .query_results
3038                        .len()
3039                        .saturating_sub(1);
3040                    self.insights_state.insights.results_selected =
3041                        (self.insights_state.insights.results_selected + 1).min(max);
3042                } else if self.view_mode == ViewMode::Detail {
3043                    let filtered_streams = self.filtered_log_streams();
3044                    let max = filtered_streams.len().saturating_sub(1);
3045                    self.log_groups_state.selected_stream =
3046                        (self.log_groups_state.selected_stream + 1).min(max);
3047                } else if self.view_mode == ViewMode::List
3048                    && self.current_service == Service::CloudWatchLogGroups
3049                {
3050                    let filtered_groups = self.filtered_log_groups();
3051                    self.log_groups_state
3052                        .log_groups
3053                        .next_item(filtered_groups.len());
3054                } else if self.current_service == Service::EcrRepositories {
3055                    if self.ecr_state.current_repository.is_some() {
3056                        let filtered_images = self.filtered_ecr_images();
3057                        self.ecr_state.images.page_down(filtered_images.len());
3058                    } else {
3059                        let filtered_repos = self.filtered_ecr_repositories();
3060                        self.ecr_state.repositories.page_down(filtered_repos.len());
3061                    }
3062                }
3063            }
3064
3065            Action::Refresh => {
3066                if self.mode == Mode::ProfilePicker {
3067                    self.log_groups_state.loading = true;
3068                    self.log_groups_state.loading_message = "Refreshing...".to_string();
3069                } else if self.mode == Mode::RegionPicker {
3070                    self.measure_region_latencies();
3071                } else if self.mode == Mode::SessionPicker {
3072                    self.sessions = Session::list_all().unwrap_or_default();
3073                } else if self.current_service == Service::CloudWatchInsights
3074                    && !self.insights_state.insights.selected_log_groups.is_empty()
3075                {
3076                    self.log_groups_state.loading = true;
3077                    self.insights_state.insights.query_completed = true;
3078                } else if self.current_service == Service::LambdaFunctions {
3079                    self.lambda_state.table.loading = true;
3080                } else if self.current_service == Service::LambdaApplications {
3081                    self.lambda_application_state.table.loading = true;
3082                } else if matches!(
3083                    self.view_mode,
3084                    ViewMode::Events | ViewMode::Detail | ViewMode::List
3085                ) {
3086                    self.log_groups_state.loading = true;
3087                }
3088            }
3089            Action::Yank => {
3090                if self.mode == Mode::ErrorModal {
3091                    // Copy error message
3092                    if let Some(error) = &self.error_message {
3093                        copy_to_clipboard(error);
3094                    }
3095                } else if self.view_mode == ViewMode::Events {
3096                    if let Some(event) = self
3097                        .log_groups_state
3098                        .log_events
3099                        .get(self.log_groups_state.event_scroll_offset)
3100                    {
3101                        copy_to_clipboard(&event.message);
3102                    }
3103                } else if self.current_service == Service::EcrRepositories {
3104                    if self.ecr_state.current_repository.is_some() {
3105                        let filtered_images = self.filtered_ecr_images();
3106                        if let Some(image) = self.ecr_state.images.get_selected(&filtered_images) {
3107                            copy_to_clipboard(&image.uri);
3108                        }
3109                    } else {
3110                        let filtered_repos = self.filtered_ecr_repositories();
3111                        if let Some(repo) =
3112                            self.ecr_state.repositories.get_selected(&filtered_repos)
3113                        {
3114                            copy_to_clipboard(&repo.uri);
3115                        }
3116                    }
3117                } else if self.current_service == Service::LambdaFunctions {
3118                    let filtered_functions = crate::ui::lambda::filtered_lambda_functions(self);
3119                    if let Some(func) = self.lambda_state.table.get_selected(&filtered_functions) {
3120                        copy_to_clipboard(&func.arn);
3121                    }
3122                } else if self.current_service == Service::CloudFormationStacks {
3123                    if let Some(stack_name) = &self.cfn_state.current_stack {
3124                        // In detail view - copy current stack ARN
3125                        if let Some(stack) = self
3126                            .cfn_state
3127                            .table
3128                            .items
3129                            .iter()
3130                            .find(|s| &s.name == stack_name)
3131                        {
3132                            copy_to_clipboard(&stack.stack_id);
3133                        }
3134                    } else {
3135                        // In list view - copy selected stack ARN
3136                        let filtered_stacks = self.filtered_cloudformation_stacks();
3137                        if let Some(stack) = self.cfn_state.table.get_selected(&filtered_stacks) {
3138                            copy_to_clipboard(&stack.stack_id);
3139                        }
3140                    }
3141                } else if self.current_service == Service::IamUsers {
3142                    if self.iam_state.current_user.is_some() {
3143                        if let Some(user_name) = &self.iam_state.current_user {
3144                            if let Some(user) = self
3145                                .iam_state
3146                                .users
3147                                .items
3148                                .iter()
3149                                .find(|u| u.user_name == *user_name)
3150                            {
3151                                copy_to_clipboard(&user.arn);
3152                            }
3153                        }
3154                    } else {
3155                        let filtered_users = crate::ui::iam::filtered_iam_users(self);
3156                        if let Some(user) = self.iam_state.users.get_selected(&filtered_users) {
3157                            copy_to_clipboard(&user.arn);
3158                        }
3159                    }
3160                } else if self.current_service == Service::IamRoles {
3161                    if self.iam_state.current_role.is_some() {
3162                        if let Some(role_name) = &self.iam_state.current_role {
3163                            if let Some(role) = self
3164                                .iam_state
3165                                .roles
3166                                .items
3167                                .iter()
3168                                .find(|r| r.role_name == *role_name)
3169                            {
3170                                copy_to_clipboard(&role.arn);
3171                            }
3172                        }
3173                    } else {
3174                        let filtered_roles = crate::ui::iam::filtered_iam_roles(self);
3175                        if let Some(role) = self.iam_state.roles.get_selected(&filtered_roles) {
3176                            copy_to_clipboard(&role.arn);
3177                        }
3178                    }
3179                } else if self.current_service == Service::IamUserGroups {
3180                    if self.iam_state.current_group.is_some() {
3181                        if let Some(group_name) = &self.iam_state.current_group {
3182                            let arn = iam::format_arn(&self.config.account_id, "group", group_name);
3183                            copy_to_clipboard(&arn);
3184                        }
3185                    } else {
3186                        let filtered_groups: Vec<_> = self
3187                            .iam_state
3188                            .groups
3189                            .items
3190                            .iter()
3191                            .filter(|g| {
3192                                if self.iam_state.groups.filter.is_empty() {
3193                                    true
3194                                } else {
3195                                    g.group_name
3196                                        .to_lowercase()
3197                                        .contains(&self.iam_state.groups.filter.to_lowercase())
3198                                }
3199                            })
3200                            .collect();
3201                        if let Some(group) = self.iam_state.groups.get_selected(&filtered_groups) {
3202                            let arn = iam::format_arn(
3203                                &self.config.account_id,
3204                                "group",
3205                                &group.group_name,
3206                            );
3207                            copy_to_clipboard(&arn);
3208                        }
3209                    }
3210                } else if self.current_service == Service::SqsQueues {
3211                    if self.sqs_state.current_queue.is_some() {
3212                        // In queue detail view - copy queue ARN
3213                        if let Some(queue) = self
3214                            .sqs_state
3215                            .queues
3216                            .items
3217                            .iter()
3218                            .find(|q| Some(&q.url) == self.sqs_state.current_queue.as_ref())
3219                        {
3220                            let arn = format!(
3221                                "arn:aws:sqs:{}:{}:{}",
3222                                crate::ui::sqs::extract_region(&queue.url),
3223                                crate::ui::sqs::extract_account_id(&queue.url),
3224                                queue.name
3225                            );
3226                            copy_to_clipboard(&arn);
3227                        }
3228                    } else {
3229                        // In list view - copy selected queue ARN
3230                        let filtered_queues = crate::ui::sqs::filtered_queues(
3231                            &self.sqs_state.queues.items,
3232                            &self.sqs_state.queues.filter,
3233                        );
3234                        if let Some(queue) = self.sqs_state.queues.get_selected(&filtered_queues) {
3235                            let arn = format!(
3236                                "arn:aws:sqs:{}:{}:{}",
3237                                crate::ui::sqs::extract_region(&queue.url),
3238                                crate::ui::sqs::extract_account_id(&queue.url),
3239                                queue.name
3240                            );
3241                            copy_to_clipboard(&arn);
3242                        }
3243                    }
3244                }
3245            }
3246            Action::CopyToClipboard => {
3247                // Request snapshot - will be captured after next render
3248                self.snapshot_requested = true;
3249            }
3250            Action::RetryLoad => {
3251                self.error_message = None;
3252                self.mode = Mode::Normal;
3253                self.log_groups_state.loading = true;
3254            }
3255            Action::ApplyFilter => {
3256                if self.mode == Mode::FilterInput
3257                    && self.current_service == Service::SqsQueues
3258                    && self.sqs_state.input_focus
3259                        == crate::common::InputFocus::Dropdown("SubscriptionRegion")
3260                {
3261                    let regions = crate::aws::Region::all();
3262                    if let Some(region) = regions.get(self.sqs_state.subscription_region_selected) {
3263                        self.sqs_state.subscription_region_filter = region.code.to_string();
3264                    }
3265                    self.mode = Mode::Normal;
3266                } else if self.mode == Mode::InsightsInput {
3267                    use crate::app::InsightsFocus;
3268                    if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
3269                        && self.insights_state.insights.show_dropdown
3270                    {
3271                        // Close dropdown, exit input mode, and execute query
3272                        self.insights_state.insights.show_dropdown = false;
3273                        self.mode = Mode::Normal;
3274                        if !self.insights_state.insights.selected_log_groups.is_empty() {
3275                            self.log_groups_state.loading = true;
3276                            self.insights_state.insights.query_completed = true;
3277                        }
3278                    }
3279                } else if self.mode == Mode::Normal && !self.page_input.is_empty() {
3280                    if let Ok(page) = self.page_input.parse::<usize>() {
3281                        self.go_to_page(page);
3282                    }
3283                    self.page_input.clear();
3284                } else {
3285                    self.mode = Mode::Normal;
3286                    self.log_groups_state.filter_mode = false;
3287                }
3288            }
3289            Action::ToggleExactMatch => {
3290                if self.view_mode == ViewMode::Detail
3291                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3292                {
3293                    self.log_groups_state.exact_match = !self.log_groups_state.exact_match;
3294                }
3295            }
3296            Action::ToggleShowExpired => {
3297                if self.view_mode == ViewMode::Detail
3298                    && self.log_groups_state.detail_tab == DetailTab::LogStreams
3299                {
3300                    self.log_groups_state.show_expired = !self.log_groups_state.show_expired;
3301                }
3302            }
3303            Action::GoBack => {
3304                // ServicePicker: close if we have tabs
3305                if self.mode == Mode::ServicePicker && !self.tabs.is_empty() {
3306                    self.mode = Mode::Normal;
3307                    self.service_picker.filter.clear();
3308                }
3309                // S3: pop navigation stack first, then exit bucket
3310                else if self.current_service == Service::S3Buckets
3311                    && self.s3_state.current_bucket.is_some()
3312                {
3313                    if !self.s3_state.prefix_stack.is_empty() {
3314                        self.s3_state.prefix_stack.pop();
3315                        self.s3_state.buckets.loading = true;
3316                    } else {
3317                        self.s3_state.current_bucket = None;
3318                        self.s3_state.objects.clear();
3319                    }
3320                }
3321                // ECR: go back from images to repositories
3322                else if self.current_service == Service::EcrRepositories
3323                    && self.ecr_state.current_repository.is_some()
3324                {
3325                    if self.ecr_state.images.has_expanded_item() {
3326                        self.ecr_state.images.collapse();
3327                    } else {
3328                        self.ecr_state.current_repository = None;
3329                        self.ecr_state.current_repository_uri = None;
3330                        self.ecr_state.images.items.clear();
3331                        self.ecr_state.images.reset();
3332                    }
3333                }
3334                // SQS: go back from queue detail to list
3335                else if self.current_service == Service::SqsQueues
3336                    && self.sqs_state.current_queue.is_some()
3337                {
3338                    self.sqs_state.current_queue = None;
3339                }
3340                // IAM: go back from user detail to list
3341                else if self.current_service == Service::IamUsers
3342                    && self.iam_state.current_user.is_some()
3343                {
3344                    self.iam_state.current_user = None;
3345                    self.iam_state.policies.items.clear();
3346                    self.iam_state.policies.reset();
3347                    self.update_current_tab_breadcrumb();
3348                }
3349                // IAM: go back from group detail to list
3350                else if self.current_service == Service::IamUserGroups
3351                    && self.iam_state.current_group.is_some()
3352                {
3353                    self.iam_state.current_group = None;
3354                    self.update_current_tab_breadcrumb();
3355                }
3356                // IAM: go back from role detail to list
3357                else if self.current_service == Service::IamRoles {
3358                    if self.view_mode == ViewMode::PolicyView {
3359                        // Go back from policy view to role detail
3360                        self.view_mode = ViewMode::Detail;
3361                        self.iam_state.current_policy = None;
3362                        self.iam_state.policy_document.clear();
3363                        self.iam_state.policy_scroll = 0;
3364                        self.update_current_tab_breadcrumb();
3365                    } else if self.iam_state.current_role.is_some() {
3366                        self.iam_state.current_role = None;
3367                        self.iam_state.policies.items.clear();
3368                        self.iam_state.policies.reset();
3369                        self.update_current_tab_breadcrumb();
3370                    }
3371                }
3372                // Lambda: go back from version detail to function detail
3373                else if self.current_service == Service::LambdaFunctions
3374                    && self.lambda_state.current_version.is_some()
3375                {
3376                    self.lambda_state.current_version = None;
3377                    self.lambda_state.detail_tab = LambdaDetailTab::Versions;
3378                }
3379                // Lambda: go back from alias detail to function detail
3380                else if self.current_service == Service::LambdaFunctions
3381                    && self.lambda_state.current_alias.is_some()
3382                {
3383                    self.lambda_state.current_alias = None;
3384                    self.lambda_state.detail_tab = LambdaDetailTab::Aliases;
3385                }
3386                // Lambda: go back from function detail to list
3387                else if self.current_service == Service::LambdaFunctions
3388                    && self.lambda_state.current_function.is_some()
3389                {
3390                    self.lambda_state.current_function = None;
3391                    self.update_current_tab_breadcrumb();
3392                }
3393                // Lambda Applications: go back from application detail to list
3394                else if self.current_service == Service::LambdaApplications
3395                    && self.lambda_application_state.current_application.is_some()
3396                {
3397                    self.lambda_application_state.current_application = None;
3398                    self.update_current_tab_breadcrumb();
3399                }
3400                // CloudFormation: go back from stack detail to list
3401                else if self.current_service == Service::CloudFormationStacks
3402                    && self.cfn_state.current_stack.is_some()
3403                {
3404                    self.cfn_state.current_stack = None;
3405                    self.update_current_tab_breadcrumb();
3406                }
3407                // From insights results -> collapse if expanded, otherwise back to sidebar
3408                else if self.view_mode == ViewMode::InsightsResults {
3409                    if self.insights_state.insights.expanded_result.is_some() {
3410                        self.insights_state.insights.expanded_result = None;
3411                    }
3412                }
3413                // From alarms view -> collapse if expanded
3414                else if self.current_service == Service::CloudWatchAlarms {
3415                    if self.alarms_state.table.has_expanded_item() {
3416                        self.alarms_state.table.collapse();
3417                    }
3418                }
3419                // From EC2 instances view -> collapse if expanded
3420                else if self.current_service == Service::Ec2Instances {
3421                    if self.ec2_state.table.has_expanded_item() {
3422                        self.ec2_state.table.collapse();
3423                    }
3424                }
3425                // From events view -> collapse if expanded, otherwise back to detail view
3426                else if self.view_mode == ViewMode::Events {
3427                    if self.log_groups_state.expanded_event.is_some() {
3428                        self.log_groups_state.expanded_event = None;
3429                    } else {
3430                        self.view_mode = ViewMode::Detail;
3431                        self.log_groups_state.event_filter.clear();
3432                    }
3433                }
3434                // From detail view -> back to list view
3435                else if self.view_mode == ViewMode::Detail {
3436                    self.view_mode = ViewMode::List;
3437                    self.log_groups_state.stream_filter.clear();
3438                    self.log_groups_state.exact_match = false;
3439                    self.log_groups_state.show_expired = false;
3440                }
3441            }
3442            Action::OpenInConsole | Action::OpenInBrowser => {
3443                let url = self.get_console_url();
3444                let _ = webbrowser::open(&url);
3445            }
3446            Action::ShowHelp => {
3447                self.mode = Mode::HelpModal;
3448            }
3449            Action::OpenRegionPicker => {
3450                self.region_filter.clear();
3451                self.region_picker_selected = 0;
3452                self.measure_region_latencies();
3453                self.mode = Mode::RegionPicker;
3454            }
3455            Action::OpenProfilePicker => {
3456                self.profile_filter.clear();
3457                self.profile_picker_selected = 0;
3458                self.available_profiles = Self::load_aws_profiles();
3459                self.mode = Mode::ProfilePicker;
3460            }
3461            Action::OpenCalendar => {
3462                self.calendar_date = Some(time::OffsetDateTime::now_utc().date());
3463                self.calendar_selecting = CalendarField::StartDate;
3464                self.mode = Mode::CalendarPicker;
3465            }
3466            Action::CloseCalendar => {
3467                self.mode = Mode::Normal;
3468                self.calendar_date = None;
3469            }
3470            Action::CalendarPrevDay => {
3471                if let Some(date) = self.calendar_date {
3472                    self.calendar_date = date.checked_sub(time::Duration::days(1));
3473                }
3474            }
3475            Action::CalendarNextDay => {
3476                if let Some(date) = self.calendar_date {
3477                    self.calendar_date = date.checked_add(time::Duration::days(1));
3478                }
3479            }
3480            Action::CalendarPrevWeek => {
3481                if let Some(date) = self.calendar_date {
3482                    self.calendar_date = date.checked_sub(time::Duration::weeks(1));
3483                }
3484            }
3485            Action::CalendarNextWeek => {
3486                if let Some(date) = self.calendar_date {
3487                    self.calendar_date = date.checked_add(time::Duration::weeks(1));
3488                }
3489            }
3490            Action::CalendarPrevMonth => {
3491                if let Some(date) = self.calendar_date {
3492                    self.calendar_date = Some(if date.month() == time::Month::January {
3493                        date.replace_month(time::Month::December)
3494                            .unwrap()
3495                            .replace_year(date.year() - 1)
3496                            .unwrap()
3497                    } else {
3498                        date.replace_month(date.month().previous()).unwrap()
3499                    });
3500                }
3501            }
3502            Action::CalendarNextMonth => {
3503                if let Some(date) = self.calendar_date {
3504                    self.calendar_date = Some(if date.month() == time::Month::December {
3505                        date.replace_month(time::Month::January)
3506                            .unwrap()
3507                            .replace_year(date.year() + 1)
3508                            .unwrap()
3509                    } else {
3510                        date.replace_month(date.month().next()).unwrap()
3511                    });
3512                }
3513            }
3514            Action::CalendarSelect => {
3515                if let Some(date) = self.calendar_date {
3516                    let timestamp = time::OffsetDateTime::new_utc(date, time::Time::MIDNIGHT)
3517                        .unix_timestamp()
3518                        * 1000;
3519                    match self.calendar_selecting {
3520                        CalendarField::StartDate => {
3521                            self.log_groups_state.start_time = Some(timestamp);
3522                            self.calendar_selecting = CalendarField::EndDate;
3523                        }
3524                        CalendarField::EndDate => {
3525                            self.log_groups_state.end_time = Some(timestamp);
3526                            self.mode = Mode::Normal;
3527                            self.calendar_date = None;
3528                        }
3529                    }
3530                }
3531            }
3532        }
3533    }
3534
3535    pub fn filtered_services(&self) -> Vec<&'static str> {
3536        let mut services = if self.service_picker.filter.is_empty() {
3537            self.service_picker.services.clone()
3538        } else {
3539            self.service_picker
3540                .services
3541                .iter()
3542                .filter(|s| {
3543                    s.to_lowercase()
3544                        .contains(&self.service_picker.filter.to_lowercase())
3545                })
3546                .copied()
3547                .collect()
3548        };
3549        services.sort();
3550        services
3551    }
3552
3553    pub fn selected_log_group(&self) -> Option<&LogGroup> {
3554        crate::ui::cw::logs::selected_log_group(self)
3555    }
3556
3557    pub fn filtered_log_streams(&self) -> Vec<&LogStream> {
3558        crate::ui::cw::logs::filtered_log_streams(self)
3559    }
3560
3561    pub fn filtered_log_events(&self) -> Vec<&LogEvent> {
3562        crate::ui::cw::logs::filtered_log_events(self)
3563    }
3564
3565    pub fn filtered_log_groups(&self) -> Vec<&LogGroup> {
3566        crate::ui::cw::logs::filtered_log_groups(self)
3567    }
3568
3569    pub fn filtered_ecr_repositories(&self) -> Vec<&EcrRepository> {
3570        crate::ui::ecr::filtered_ecr_repositories(self)
3571    }
3572
3573    pub fn filtered_ecr_images(&self) -> Vec<&EcrImage> {
3574        crate::ui::ecr::filtered_ecr_images(self)
3575    }
3576
3577    pub fn filtered_cloudformation_stacks(&self) -> Vec<&CfnStack> {
3578        crate::ui::cfn::filtered_cloudformation_stacks(self)
3579    }
3580
3581    pub fn breadcrumbs(&self) -> String {
3582        if !self.service_selected {
3583            return String::new();
3584        }
3585
3586        let mut parts = vec![];
3587
3588        match self.current_service {
3589            Service::CloudWatchLogGroups => {
3590                parts.push("CloudWatch".to_string());
3591                parts.push("Log groups".to_string());
3592
3593                if self.view_mode != ViewMode::List {
3594                    if let Some(group) = self.selected_log_group() {
3595                        parts.push(group.name.clone());
3596                    }
3597                }
3598
3599                if self.view_mode == ViewMode::Events {
3600                    if let Some(stream) = self
3601                        .log_groups_state
3602                        .log_streams
3603                        .get(self.log_groups_state.selected_stream)
3604                    {
3605                        parts.push(stream.name.clone());
3606                    }
3607                }
3608            }
3609            Service::CloudWatchInsights => {
3610                parts.push("CloudWatch".to_string());
3611                parts.push("Insights".to_string());
3612            }
3613            Service::CloudWatchAlarms => {
3614                parts.push("CloudWatch".to_string());
3615                parts.push("Alarms".to_string());
3616            }
3617            Service::S3Buckets => {
3618                parts.push("S3".to_string());
3619                if let Some(bucket) = &self.s3_state.current_bucket {
3620                    parts.push(bucket.clone());
3621                    if let Some(prefix) = self.s3_state.prefix_stack.last() {
3622                        parts.push(prefix.trim_end_matches('/').to_string());
3623                    }
3624                } else {
3625                    parts.push("Buckets".to_string());
3626                }
3627            }
3628            Service::SqsQueues => {
3629                parts.push("SQS".to_string());
3630                parts.push("Queues".to_string());
3631            }
3632            Service::EcrRepositories => {
3633                parts.push("ECR".to_string());
3634                if let Some(repo) = &self.ecr_state.current_repository {
3635                    parts.push(repo.clone());
3636                } else {
3637                    parts.push("Repositories".to_string());
3638                }
3639            }
3640            Service::LambdaFunctions => {
3641                parts.push("Lambda".to_string());
3642                if let Some(func) = &self.lambda_state.current_function {
3643                    parts.push(func.clone());
3644                } else {
3645                    parts.push("Functions".to_string());
3646                }
3647            }
3648            Service::LambdaApplications => {
3649                parts.push("Lambda".to_string());
3650                parts.push("Applications".to_string());
3651            }
3652            Service::CloudFormationStacks => {
3653                parts.push("CloudFormation".to_string());
3654                if let Some(stack_name) = &self.cfn_state.current_stack {
3655                    parts.push(stack_name.clone());
3656                } else {
3657                    parts.push("Stacks".to_string());
3658                }
3659            }
3660            Service::IamUsers => {
3661                parts.push("IAM".to_string());
3662                parts.push("Users".to_string());
3663            }
3664            Service::IamRoles => {
3665                parts.push("IAM".to_string());
3666                parts.push("Roles".to_string());
3667                if let Some(role_name) = &self.iam_state.current_role {
3668                    parts.push(role_name.clone());
3669                    if let Some(policy_name) = &self.iam_state.current_policy {
3670                        parts.push(policy_name.clone());
3671                    }
3672                }
3673            }
3674            Service::IamUserGroups => {
3675                parts.push("IAM".to_string());
3676                parts.push("User Groups".to_string());
3677                if let Some(group_name) = &self.iam_state.current_group {
3678                    parts.push(group_name.clone());
3679                }
3680            }
3681            Service::Ec2Instances => {
3682                parts.push("EC2".to_string());
3683                parts.push("Instances".to_string());
3684            }
3685        }
3686
3687        parts.join(" > ")
3688    }
3689
3690    pub fn update_current_tab_breadcrumb(&mut self) {
3691        if !self.tabs.is_empty() {
3692            self.tabs[self.current_tab].breadcrumb = self.breadcrumbs();
3693        }
3694    }
3695
3696    pub fn get_console_url(&self) -> String {
3697        use crate::{cfn, cw, ecr, iam, lambda, s3};
3698
3699        match self.current_service {
3700            Service::CloudWatchLogGroups => {
3701                if self.view_mode == ViewMode::Events {
3702                    if let Some(group) = self.selected_log_group() {
3703                        if let Some(stream) = self
3704                            .log_groups_state
3705                            .log_streams
3706                            .get(self.log_groups_state.selected_stream)
3707                        {
3708                            return cw::logs::console_url_stream(
3709                                &self.config.region,
3710                                &group.name,
3711                                &stream.name,
3712                            );
3713                        }
3714                    }
3715                } else if self.view_mode == ViewMode::Detail {
3716                    if let Some(group) = self.selected_log_group() {
3717                        return cw::logs::console_url_detail(&self.config.region, &group.name);
3718                    }
3719                }
3720                cw::logs::console_url_list(&self.config.region)
3721            }
3722            Service::CloudWatchInsights => cw::insights::console_url(
3723                &self.config.region,
3724                &self.config.account_id,
3725                &self.insights_state.insights.query_text,
3726                &self.insights_state.insights.selected_log_groups,
3727            ),
3728            Service::CloudWatchAlarms => {
3729                let view_type = match self.alarms_state.view_as {
3730                    AlarmViewMode::Table | AlarmViewMode::Detail => "table",
3731                    AlarmViewMode::Cards => "card",
3732                };
3733                cw::alarms::console_url(
3734                    &self.config.region,
3735                    view_type,
3736                    self.alarms_state.table.page_size.value(),
3737                    &self.alarms_state.sort_column,
3738                    self.alarms_state.sort_direction.as_str(),
3739                )
3740            }
3741            Service::S3Buckets => {
3742                if let Some(bucket_name) = &self.s3_state.current_bucket {
3743                    let prefix = self.s3_state.prefix_stack.join("");
3744                    s3::console_url_bucket(&self.config.region, bucket_name, &prefix)
3745                } else {
3746                    s3::console_url_buckets(&self.config.region)
3747                }
3748            }
3749            Service::SqsQueues => {
3750                if let Some(queue_url) = &self.sqs_state.current_queue {
3751                    crate::sqs::console_url_queue_detail(&self.config.region, queue_url)
3752                } else {
3753                    crate::sqs::console_url_queues(&self.config.region)
3754                }
3755            }
3756            Service::EcrRepositories => {
3757                if let Some(repo_name) = &self.ecr_state.current_repository {
3758                    ecr::console_url_private_repository(
3759                        &self.config.region,
3760                        &self.config.account_id,
3761                        repo_name,
3762                    )
3763                } else {
3764                    ecr::console_url_repositories(&self.config.region)
3765                }
3766            }
3767            Service::LambdaFunctions => {
3768                if let Some(func_name) = &self.lambda_state.current_function {
3769                    if let Some(version) = &self.lambda_state.current_version {
3770                        lambda::console_url_function_version(
3771                            &self.config.region,
3772                            func_name,
3773                            version,
3774                            &self.lambda_state.detail_tab,
3775                        )
3776                    } else {
3777                        lambda::console_url_function_detail(&self.config.region, func_name)
3778                    }
3779                } else {
3780                    lambda::console_url_functions(&self.config.region)
3781                }
3782            }
3783            Service::LambdaApplications => {
3784                if let Some(app_name) = &self.lambda_application_state.current_application {
3785                    lambda::console_url_application_detail(
3786                        &self.config.region,
3787                        app_name,
3788                        &self.lambda_application_state.detail_tab,
3789                    )
3790                } else {
3791                    lambda::console_url_applications(&self.config.region)
3792                }
3793            }
3794            Service::CloudFormationStacks => {
3795                if let Some(stack_name) = &self.cfn_state.current_stack {
3796                    if let Some(stack) = self
3797                        .cfn_state
3798                        .table
3799                        .items
3800                        .iter()
3801                        .find(|s| &s.name == stack_name)
3802                    {
3803                        return cfn::console_url_stack_detail_with_tab(
3804                            &self.config.region,
3805                            &stack.stack_id,
3806                            &self.cfn_state.detail_tab,
3807                        );
3808                    }
3809                }
3810                cfn::console_url_stacks(&self.config.region)
3811            }
3812            Service::IamUsers => {
3813                if let Some(user_name) = &self.iam_state.current_user {
3814                    let section = match self.iam_state.user_tab {
3815                        UserTab::Permissions => "permissions",
3816                        UserTab::Groups => "groups",
3817                        UserTab::Tags => "tags",
3818                        UserTab::SecurityCredentials => "security_credentials",
3819                        UserTab::LastAccessed => "access_advisor",
3820                    };
3821                    iam::console_url_user_detail(&self.config.region, user_name, section)
3822                } else {
3823                    iam::console_url_users(&self.config.region)
3824                }
3825            }
3826            Service::IamRoles => {
3827                if let Some(policy_name) = &self.iam_state.current_policy {
3828                    if let Some(role_name) = &self.iam_state.current_role {
3829                        return iam::console_url_role_policy(
3830                            &self.config.region,
3831                            role_name,
3832                            policy_name,
3833                        );
3834                    }
3835                }
3836                if let Some(role_name) = &self.iam_state.current_role {
3837                    let section = match self.iam_state.role_tab {
3838                        RoleTab::Permissions => "permissions",
3839                        RoleTab::TrustRelationships => "trust_relationships",
3840                        RoleTab::Tags => "tags",
3841                        RoleTab::LastAccessed => "access_advisor",
3842                        RoleTab::RevokeSessions => "revoke_sessions",
3843                    };
3844                    iam::console_url_role_detail(&self.config.region, role_name, section)
3845                } else {
3846                    iam::console_url_roles(&self.config.region)
3847                }
3848            }
3849            Service::IamUserGroups => iam::console_url_groups(&self.config.region),
3850            Service::Ec2Instances => {
3851                format!(
3852                    "https://{}.console.aws.amazon.com/ec2/home?region={}#Instances:",
3853                    self.config.region, self.config.region
3854                )
3855            }
3856        }
3857    }
3858
3859    fn calculate_total_bucket_rows(&self) -> usize {
3860        crate::ui::s3::calculate_total_bucket_rows(self)
3861    }
3862
3863    fn calculate_total_object_rows(&self) -> usize {
3864        crate::ui::s3::calculate_total_object_rows(self)
3865    }
3866
3867    fn get_column_selector_max(&self) -> usize {
3868        if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none() {
3869            self.s3_bucket_column_ids.len() - 1
3870        } else if self.view_mode == ViewMode::Events {
3871            self.cw_log_event_column_ids.len() - 1
3872        } else if self.view_mode == ViewMode::Detail {
3873            self.cw_log_stream_column_ids.len() - 1
3874        } else if self.current_service == Service::CloudWatchAlarms {
3875            29
3876        } else if self.current_service == Service::Ec2Instances {
3877            self.ec2_column_ids.len() + 6
3878        } else if self.current_service == Service::EcrRepositories {
3879            if self.ecr_state.current_repository.is_some() {
3880                self.ecr_image_column_ids.len() + 6
3881            } else {
3882                self.ecr_repo_column_ids.len() - 1
3883            }
3884        } else if self.current_service == Service::SqsQueues {
3885            self.sqs_column_ids.len() - 1
3886        } else if self.current_service == Service::LambdaFunctions {
3887            self.lambda_state.function_column_ids.len() + 6
3888        } else if self.current_service == Service::LambdaApplications {
3889            self.lambda_application_column_ids.len() + 5
3890        } else if self.current_service == Service::CloudFormationStacks {
3891            self.cfn_column_ids.len() + 6
3892        } else if self.current_service == Service::IamUsers {
3893            if self.iam_state.current_user.is_some() {
3894                self.iam_policy_column_ids.len() + 5
3895            } else {
3896                self.iam_user_column_ids.len() + 5
3897            }
3898        } else if self.current_service == Service::IamRoles {
3899            if self.iam_state.current_role.is_some() {
3900                self.iam_policy_column_ids.len() + 5
3901            } else {
3902                self.iam_role_column_ids.len() + 5
3903            }
3904        } else {
3905            self.cw_log_group_column_ids.len() - 1
3906        }
3907    }
3908
3909    fn next_item(&mut self) {
3910        match self.mode {
3911            Mode::FilterInput => {
3912                if self.current_service == Service::CloudFormationStacks {
3913                    use crate::ui::cfn::STATUS_FILTER;
3914                    if self.cfn_state.input_focus == STATUS_FILTER {
3915                        self.cfn_state.status_filter = self.cfn_state.status_filter.next();
3916                        self.cfn_state.table.reset();
3917                    }
3918                } else if self.current_service == Service::Ec2Instances {
3919                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
3920                        self.ec2_state.state_filter = self.ec2_state.state_filter.next();
3921                        self.ec2_state.table.reset();
3922                    }
3923                } else if self.current_service == Service::SqsQueues {
3924                    use crate::ui::sqs::SUBSCRIPTION_REGION;
3925                    if self.sqs_state.input_focus == SUBSCRIPTION_REGION {
3926                        let regions = crate::aws::Region::all();
3927                        self.sqs_state.subscription_region_selected =
3928                            (self.sqs_state.subscription_region_selected + 1)
3929                                .min(regions.len() - 1);
3930                        self.sqs_state.subscriptions.reset();
3931                    }
3932                }
3933            }
3934            Mode::RegionPicker => {
3935                let filtered = self.get_filtered_regions();
3936                if !filtered.is_empty() {
3937                    self.region_picker_selected =
3938                        (self.region_picker_selected + 1).min(filtered.len() - 1);
3939                }
3940            }
3941            Mode::ProfilePicker => {
3942                let filtered = self.get_filtered_profiles();
3943                if !filtered.is_empty() {
3944                    self.profile_picker_selected =
3945                        (self.profile_picker_selected + 1).min(filtered.len() - 1);
3946                }
3947            }
3948            Mode::SessionPicker => {
3949                let filtered = self.get_filtered_sessions();
3950                if !filtered.is_empty() {
3951                    self.session_picker_selected =
3952                        (self.session_picker_selected + 1).min(filtered.len() - 1);
3953                }
3954            }
3955            Mode::InsightsInput => {
3956                use crate::app::InsightsFocus;
3957                if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
3958                    && self.insights_state.insights.show_dropdown
3959                    && !self.insights_state.insights.log_group_matches.is_empty()
3960                {
3961                    let max = self.insights_state.insights.log_group_matches.len() - 1;
3962                    self.insights_state.insights.dropdown_selected =
3963                        (self.insights_state.insights.dropdown_selected + 1).min(max);
3964                }
3965            }
3966            Mode::ColumnSelector => {
3967                let max = self.get_column_selector_max();
3968                self.column_selector_index = (self.column_selector_index + 1).min(max);
3969            }
3970            Mode::ServicePicker => {
3971                let filtered = self.filtered_services();
3972                if !filtered.is_empty() {
3973                    self.service_picker.selected =
3974                        (self.service_picker.selected + 1).min(filtered.len() - 1);
3975                }
3976            }
3977            Mode::TabPicker => {
3978                let filtered = self.get_filtered_tabs();
3979                if !filtered.is_empty() {
3980                    self.tab_picker_selected =
3981                        (self.tab_picker_selected + 1).min(filtered.len() - 1);
3982                }
3983            }
3984            Mode::Normal => {
3985                if !self.service_selected {
3986                    let filtered = self.filtered_services();
3987                    if !filtered.is_empty() {
3988                        self.service_picker.selected =
3989                            (self.service_picker.selected + 1).min(filtered.len() - 1);
3990                    }
3991                } else if self.current_service == Service::S3Buckets {
3992                    if self.s3_state.current_bucket.is_some() {
3993                        if self.s3_state.object_tab == S3ObjectTab::Properties {
3994                            // Scroll properties view
3995                            self.s3_state.properties_scroll =
3996                                self.s3_state.properties_scroll.saturating_add(1);
3997                        } else {
3998                            // Calculate total rows including all nested preview items
3999                            let total_rows = self.calculate_total_object_rows();
4000                            let max = total_rows.saturating_sub(1);
4001                            self.s3_state.selected_object =
4002                                (self.s3_state.selected_object + 1).min(max);
4003
4004                            // Adjust scroll offset if selection goes below viewport
4005                            let visible_rows = self.s3_state.object_visible_rows.get();
4006                            if self.s3_state.selected_object
4007                                >= self.s3_state.object_scroll_offset + visible_rows
4008                            {
4009                                self.s3_state.object_scroll_offset =
4010                                    self.s3_state.selected_object - visible_rows + 1;
4011                            }
4012                        }
4013                    } else {
4014                        // Navigate rows in bucket list
4015                        let total_rows = self.calculate_total_bucket_rows();
4016                        if total_rows > 0 {
4017                            self.s3_state.selected_row =
4018                                (self.s3_state.selected_row + 1).min(total_rows - 1);
4019
4020                            // Adjust scroll offset if selection goes below viewport
4021                            let visible_rows = self.s3_state.bucket_visible_rows.get();
4022                            if self.s3_state.selected_row
4023                                >= self.s3_state.bucket_scroll_offset + visible_rows
4024                            {
4025                                self.s3_state.bucket_scroll_offset =
4026                                    self.s3_state.selected_row - visible_rows + 1;
4027                            }
4028                        }
4029                    }
4030                } else if self.view_mode == ViewMode::InsightsResults {
4031                    let max = self
4032                        .insights_state
4033                        .insights
4034                        .query_results
4035                        .len()
4036                        .saturating_sub(1);
4037                    if self.insights_state.insights.results_selected < max {
4038                        self.insights_state.insights.results_selected += 1;
4039                    }
4040                } else if self.view_mode == ViewMode::PolicyView {
4041                    let lines = self.iam_state.policy_document.lines().count();
4042                    let max_scroll = lines.saturating_sub(1);
4043                    self.iam_state.policy_scroll =
4044                        (self.iam_state.policy_scroll + 1).min(max_scroll);
4045                } else if self.current_service == Service::CloudFormationStacks
4046                    && self.cfn_state.current_stack.is_some()
4047                    && self.cfn_state.detail_tab == CfnDetailTab::Template
4048                {
4049                    let lines = self.cfn_state.template_body.lines().count();
4050                    let max_scroll = lines.saturating_sub(1);
4051                    self.cfn_state.template_scroll =
4052                        (self.cfn_state.template_scroll + 1).min(max_scroll);
4053                } else if self.current_service == Service::SqsQueues
4054                    && self.sqs_state.current_queue.is_some()
4055                    && self.sqs_state.detail_tab == SqsQueueDetailTab::QueuePolicies
4056                {
4057                    let lines = self.sqs_state.policy_document.lines().count();
4058                    let max_scroll = lines.saturating_sub(1);
4059                    self.sqs_state.policy_scroll =
4060                        (self.sqs_state.policy_scroll + 1).min(max_scroll);
4061                } else if self.current_service == Service::LambdaFunctions
4062                    && self.lambda_state.current_function.is_some()
4063                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
4064                    && !self.lambda_state.is_metrics_loading()
4065                {
4066                    self.lambda_state
4067                        .set_monitoring_scroll((self.lambda_state.monitoring_scroll() + 1).min(9));
4068                } else if self.current_service == Service::SqsQueues
4069                    && self.sqs_state.current_queue.is_some()
4070                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
4071                    && !self.sqs_state.is_metrics_loading()
4072                {
4073                    self.sqs_state
4074                        .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(8));
4075                } else if self.view_mode == ViewMode::Events {
4076                    let max_scroll = self.log_groups_state.log_events.len().saturating_sub(1);
4077                    if self.log_groups_state.event_scroll_offset >= max_scroll {
4078                        // At the end, do nothing
4079                    } else {
4080                        self.log_groups_state.event_scroll_offset =
4081                            (self.log_groups_state.event_scroll_offset + 1).min(max_scroll);
4082                    }
4083                } else if self.current_service == Service::CloudWatchLogGroups {
4084                    if self.view_mode == ViewMode::List {
4085                        let filtered_groups = self.filtered_log_groups();
4086                        self.log_groups_state
4087                            .log_groups
4088                            .next_item(filtered_groups.len());
4089                    } else if self.view_mode == ViewMode::Detail {
4090                        let filtered_streams = self.filtered_log_streams();
4091                        if !filtered_streams.is_empty() {
4092                            let max = filtered_streams.len() - 1;
4093                            if self.log_groups_state.selected_stream >= max {
4094                                // At the end, do nothing
4095                            } else {
4096                                self.log_groups_state.selected_stream =
4097                                    (self.log_groups_state.selected_stream + 1).min(max);
4098                            }
4099                        }
4100                    }
4101                } else if self.current_service == Service::CloudWatchAlarms {
4102                    let filtered_alarms = match self.alarms_state.alarm_tab {
4103                        AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
4104                        AlarmTab::InAlarm => self
4105                            .alarms_state
4106                            .table
4107                            .items
4108                            .iter()
4109                            .filter(|a| a.state.to_uppercase() == "ALARM")
4110                            .count(),
4111                    };
4112                    if filtered_alarms > 0 {
4113                        self.alarms_state.table.next_item(filtered_alarms);
4114                    }
4115                } else if self.current_service == Service::Ec2Instances {
4116                    let filtered: Vec<_> = self
4117                        .ec2_state
4118                        .table
4119                        .items
4120                        .iter()
4121                        .filter(|i| self.ec2_state.state_filter.matches(&i.state))
4122                        .filter(|i| {
4123                            if self.ec2_state.table.filter.is_empty() {
4124                                return true;
4125                            }
4126                            i.name.contains(&self.ec2_state.table.filter)
4127                                || i.instance_id.contains(&self.ec2_state.table.filter)
4128                                || i.state.contains(&self.ec2_state.table.filter)
4129                                || i.instance_type.contains(&self.ec2_state.table.filter)
4130                                || i.availability_zone.contains(&self.ec2_state.table.filter)
4131                                || i.security_groups.contains(&self.ec2_state.table.filter)
4132                                || i.key_name.contains(&self.ec2_state.table.filter)
4133                        })
4134                        .collect();
4135                    if !filtered.is_empty() {
4136                        self.ec2_state.table.next_item(filtered.len());
4137                    }
4138                } else if self.current_service == Service::EcrRepositories {
4139                    if self.ecr_state.current_repository.is_some() {
4140                        let filtered_images = self.filtered_ecr_images();
4141                        if !filtered_images.is_empty() {
4142                            self.ecr_state.images.next_item(filtered_images.len());
4143                        }
4144                    } else {
4145                        let filtered_repos = self.filtered_ecr_repositories();
4146                        if !filtered_repos.is_empty() {
4147                            self.ecr_state.repositories.selected =
4148                                (self.ecr_state.repositories.selected + 1)
4149                                    .min(filtered_repos.len() - 1);
4150                            self.ecr_state.repositories.snap_to_page();
4151                        }
4152                    }
4153                } else if self.current_service == Service::SqsQueues {
4154                    if self.sqs_state.current_queue.is_some()
4155                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
4156                    {
4157                        let filtered = crate::ui::sqs::filtered_lambda_triggers(self);
4158                        if !filtered.is_empty() {
4159                            self.sqs_state.triggers.next_item(filtered.len());
4160                        }
4161                    } else if self.sqs_state.current_queue.is_some()
4162                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
4163                    {
4164                        let filtered = crate::ui::sqs::filtered_eventbridge_pipes(self);
4165                        if !filtered.is_empty() {
4166                            self.sqs_state.pipes.next_item(filtered.len());
4167                        }
4168                    } else if self.sqs_state.current_queue.is_some()
4169                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
4170                    {
4171                        let filtered = crate::ui::sqs::filtered_tags(self);
4172                        if !filtered.is_empty() {
4173                            self.sqs_state.tags.next_item(filtered.len());
4174                        }
4175                    } else if self.sqs_state.current_queue.is_some()
4176                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
4177                    {
4178                        let filtered = crate::ui::sqs::filtered_subscriptions(self);
4179                        if !filtered.is_empty() {
4180                            self.sqs_state.subscriptions.next_item(filtered.len());
4181                        }
4182                    } else {
4183                        let filtered_queues = crate::ui::sqs::filtered_queues(
4184                            &self.sqs_state.queues.items,
4185                            &self.sqs_state.queues.filter,
4186                        );
4187                        if !filtered_queues.is_empty() {
4188                            self.sqs_state.queues.next_item(filtered_queues.len());
4189                        }
4190                    }
4191                } else if self.current_service == Service::LambdaFunctions {
4192                    if self.lambda_state.current_function.is_some()
4193                        && self.lambda_state.detail_tab == LambdaDetailTab::Code
4194                    {
4195                        // Layer table navigation in Code tab
4196                        if let Some(func_name) = &self.lambda_state.current_function {
4197                            if let Some(func) = self
4198                                .lambda_state
4199                                .table
4200                                .items
4201                                .iter()
4202                                .find(|f| f.name == *func_name)
4203                            {
4204                                let max = func.layers.len().saturating_sub(1);
4205                                if !func.layers.is_empty() {
4206                                    self.lambda_state.layer_selected =
4207                                        (self.lambda_state.layer_selected + 1).min(max);
4208                                }
4209                            }
4210                        }
4211                    } else if self.lambda_state.current_function.is_some()
4212                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
4213                    {
4214                        // Version table navigation
4215                        let filtered: Vec<_> = self
4216                            .lambda_state
4217                            .version_table
4218                            .items
4219                            .iter()
4220                            .filter(|v| {
4221                                self.lambda_state.version_table.filter.is_empty()
4222                                    || v.version.to_lowercase().contains(
4223                                        &self.lambda_state.version_table.filter.to_lowercase(),
4224                                    )
4225                                    || v.aliases.to_lowercase().contains(
4226                                        &self.lambda_state.version_table.filter.to_lowercase(),
4227                                    )
4228                                    || v.description.to_lowercase().contains(
4229                                        &self.lambda_state.version_table.filter.to_lowercase(),
4230                                    )
4231                            })
4232                            .collect();
4233                        if !filtered.is_empty() {
4234                            self.lambda_state.version_table.selected =
4235                                (self.lambda_state.version_table.selected + 1)
4236                                    .min(filtered.len() - 1);
4237                            self.lambda_state.version_table.snap_to_page();
4238                        }
4239                    } else if self.lambda_state.current_function.is_some()
4240                        && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
4241                            || (self.lambda_state.current_version.is_some()
4242                                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
4243                    {
4244                        // Alias table navigation (both in Aliases tab and Version Configuration)
4245                        let version_filter = self.lambda_state.current_version.clone();
4246                        let filtered: Vec<_> = self
4247                            .lambda_state
4248                            .alias_table
4249                            .items
4250                            .iter()
4251                            .filter(|a| {
4252                                (version_filter.is_none()
4253                                    || a.versions.contains(version_filter.as_ref().unwrap()))
4254                                    && (self.lambda_state.alias_table.filter.is_empty()
4255                                        || a.name.to_lowercase().contains(
4256                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4257                                        )
4258                                        || a.versions.to_lowercase().contains(
4259                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4260                                        )
4261                                        || a.description.to_lowercase().contains(
4262                                            &self.lambda_state.alias_table.filter.to_lowercase(),
4263                                        ))
4264                            })
4265                            .collect();
4266                        if !filtered.is_empty() {
4267                            self.lambda_state.alias_table.selected =
4268                                (self.lambda_state.alias_table.selected + 1)
4269                                    .min(filtered.len() - 1);
4270                            self.lambda_state.alias_table.snap_to_page();
4271                        }
4272                    } else if self.lambda_state.current_function.is_none() {
4273                        let filtered = crate::ui::lambda::filtered_lambda_functions(self);
4274                        if !filtered.is_empty() {
4275                            self.lambda_state.table.next_item(filtered.len());
4276                            self.lambda_state.table.snap_to_page();
4277                        }
4278                    }
4279                } else if self.current_service == Service::LambdaApplications {
4280                    if self.lambda_application_state.current_application.is_some() {
4281                        if self.lambda_application_state.detail_tab
4282                            == LambdaApplicationDetailTab::Overview
4283                        {
4284                            let len = self.lambda_application_state.resources.items.len();
4285                            if len > 0 {
4286                                self.lambda_application_state.resources.next_item(len);
4287                            }
4288                        } else {
4289                            let len = self.lambda_application_state.deployments.items.len();
4290                            if len > 0 {
4291                                self.lambda_application_state.deployments.next_item(len);
4292                            }
4293                        }
4294                    } else {
4295                        let filtered = crate::ui::lambda::filtered_lambda_applications(self);
4296                        if !filtered.is_empty() {
4297                            self.lambda_application_state.table.selected =
4298                                (self.lambda_application_state.table.selected + 1)
4299                                    .min(filtered.len() - 1);
4300                            self.lambda_application_state.table.snap_to_page();
4301                        }
4302                    }
4303                } else if self.current_service == Service::CloudFormationStacks {
4304                    if self.cfn_state.current_stack.is_some()
4305                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
4306                    {
4307                        let filtered = filtered_parameters(self);
4308                        self.cfn_state.parameters.next_item(filtered.len());
4309                    } else if self.cfn_state.current_stack.is_some()
4310                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
4311                    {
4312                        let filtered = filtered_outputs(self);
4313                        self.cfn_state.outputs.next_item(filtered.len());
4314                    } else if self.cfn_state.current_stack.is_some()
4315                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
4316                    {
4317                        let filtered = filtered_resources(self);
4318                        self.cfn_state.resources.next_item(filtered.len());
4319                    } else {
4320                        let filtered = self.filtered_cloudformation_stacks();
4321                        self.cfn_state.table.next_item(filtered.len());
4322                    }
4323                } else if self.current_service == Service::IamUsers {
4324                    if self.iam_state.current_user.is_some() {
4325                        if self.iam_state.user_tab == UserTab::Tags {
4326                            let filtered = crate::ui::iam::filtered_user_tags(self);
4327                            if !filtered.is_empty() {
4328                                self.iam_state.user_tags.next_item(filtered.len());
4329                            }
4330                        } else {
4331                            let filtered = crate::ui::iam::filtered_iam_policies(self);
4332                            if !filtered.is_empty() {
4333                                self.iam_state.policies.next_item(filtered.len());
4334                            }
4335                        }
4336                    } else {
4337                        let filtered = crate::ui::iam::filtered_iam_users(self);
4338                        if !filtered.is_empty() {
4339                            self.iam_state.users.next_item(filtered.len());
4340                        }
4341                    }
4342                } else if self.current_service == Service::IamRoles {
4343                    if self.iam_state.current_role.is_some() {
4344                        if self.iam_state.role_tab == RoleTab::TrustRelationships {
4345                            let lines = self.iam_state.trust_policy_document.lines().count();
4346                            let max_scroll = lines.saturating_sub(1);
4347                            self.iam_state.trust_policy_scroll =
4348                                (self.iam_state.trust_policy_scroll + 1).min(max_scroll);
4349                        } else if self.iam_state.role_tab == RoleTab::RevokeSessions {
4350                            self.iam_state.revoke_sessions_scroll =
4351                                (self.iam_state.revoke_sessions_scroll + 1).min(19);
4352                        } else if self.iam_state.role_tab == RoleTab::Tags {
4353                            let filtered = crate::ui::iam::filtered_tags(self);
4354                            if !filtered.is_empty() {
4355                                self.iam_state.tags.next_item(filtered.len());
4356                            }
4357                        } else if self.iam_state.role_tab == RoleTab::LastAccessed {
4358                            let filtered = crate::ui::iam::filtered_last_accessed(self);
4359                            if !filtered.is_empty() {
4360                                self.iam_state
4361                                    .last_accessed_services
4362                                    .next_item(filtered.len());
4363                            }
4364                        } else {
4365                            let filtered = crate::ui::iam::filtered_iam_policies(self);
4366                            if !filtered.is_empty() {
4367                                self.iam_state.policies.next_item(filtered.len());
4368                            }
4369                        }
4370                    } else {
4371                        let filtered = crate::ui::iam::filtered_iam_roles(self);
4372                        if !filtered.is_empty() {
4373                            self.iam_state.roles.next_item(filtered.len());
4374                        }
4375                    }
4376                } else if self.current_service == Service::IamUserGroups {
4377                    if self.iam_state.current_group.is_some() {
4378                        if self.iam_state.group_tab == GroupTab::Users {
4379                            let filtered: Vec<_> = self
4380                                .iam_state
4381                                .group_users
4382                                .items
4383                                .iter()
4384                                .filter(|u| {
4385                                    if self.iam_state.group_users.filter.is_empty() {
4386                                        true
4387                                    } else {
4388                                        u.user_name.to_lowercase().contains(
4389                                            &self.iam_state.group_users.filter.to_lowercase(),
4390                                        )
4391                                    }
4392                                })
4393                                .collect();
4394                            if !filtered.is_empty() {
4395                                self.iam_state.group_users.next_item(filtered.len());
4396                            }
4397                        } else if self.iam_state.group_tab == GroupTab::Permissions {
4398                            let filtered = crate::ui::iam::filtered_iam_policies(self);
4399                            if !filtered.is_empty() {
4400                                self.iam_state.policies.next_item(filtered.len());
4401                            }
4402                        } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
4403                            let filtered = crate::ui::iam::filtered_last_accessed(self);
4404                            if !filtered.is_empty() {
4405                                self.iam_state
4406                                    .last_accessed_services
4407                                    .next_item(filtered.len());
4408                            }
4409                        }
4410                    } else {
4411                        let filtered: Vec<_> = self
4412                            .iam_state
4413                            .groups
4414                            .items
4415                            .iter()
4416                            .filter(|g| {
4417                                if self.iam_state.groups.filter.is_empty() {
4418                                    true
4419                                } else {
4420                                    g.group_name
4421                                        .to_lowercase()
4422                                        .contains(&self.iam_state.groups.filter.to_lowercase())
4423                                }
4424                            })
4425                            .collect();
4426                        if !filtered.is_empty() {
4427                            self.iam_state.groups.next_item(filtered.len());
4428                        }
4429                    }
4430                }
4431            }
4432            _ => {}
4433        }
4434    }
4435
4436    fn prev_item(&mut self) {
4437        match self.mode {
4438            Mode::FilterInput => {
4439                if self.current_service == Service::CloudFormationStacks {
4440                    use crate::ui::cfn::STATUS_FILTER;
4441                    if self.cfn_state.input_focus == STATUS_FILTER {
4442                        self.cfn_state.status_filter = self.cfn_state.status_filter.prev();
4443                        self.cfn_state.table.reset();
4444                    }
4445                } else if self.current_service == Service::Ec2Instances {
4446                    if self.ec2_state.input_focus == EC2_STATE_FILTER {
4447                        self.ec2_state.state_filter = self.ec2_state.state_filter.prev();
4448                        self.ec2_state.table.reset();
4449                    }
4450                } else if self.current_service == Service::SqsQueues {
4451                    use crate::ui::sqs::SUBSCRIPTION_REGION;
4452                    if self.sqs_state.input_focus == SUBSCRIPTION_REGION {
4453                        self.sqs_state.subscription_region_selected = self
4454                            .sqs_state
4455                            .subscription_region_selected
4456                            .saturating_sub(1);
4457                        self.sqs_state.subscriptions.reset();
4458                    }
4459                }
4460            }
4461            Mode::RegionPicker => {
4462                self.region_picker_selected = self.region_picker_selected.saturating_sub(1);
4463            }
4464            Mode::ProfilePicker => {
4465                self.profile_picker_selected = self.profile_picker_selected.saturating_sub(1);
4466            }
4467            Mode::SessionPicker => {
4468                self.session_picker_selected = self.session_picker_selected.saturating_sub(1);
4469            }
4470            Mode::InsightsInput => {
4471                use crate::app::InsightsFocus;
4472                if self.insights_state.insights.insights_focus == InsightsFocus::LogGroupSearch
4473                    && self.insights_state.insights.show_dropdown
4474                    && !self.insights_state.insights.log_group_matches.is_empty()
4475                {
4476                    self.insights_state.insights.dropdown_selected = self
4477                        .insights_state
4478                        .insights
4479                        .dropdown_selected
4480                        .saturating_sub(1);
4481                }
4482            }
4483            Mode::ColumnSelector => {
4484                self.column_selector_index = self.column_selector_index.saturating_sub(1);
4485            }
4486            Mode::ServicePicker => {
4487                self.service_picker.selected = self.service_picker.selected.saturating_sub(1);
4488            }
4489            Mode::TabPicker => {
4490                self.tab_picker_selected = self.tab_picker_selected.saturating_sub(1);
4491            }
4492            Mode::Normal => {
4493                if !self.service_selected {
4494                    self.service_picker.selected = self.service_picker.selected.saturating_sub(1);
4495                } else if self.current_service == Service::S3Buckets {
4496                    if self.s3_state.current_bucket.is_some() {
4497                        if self.s3_state.object_tab == S3ObjectTab::Properties {
4498                            self.s3_state.properties_scroll =
4499                                self.s3_state.properties_scroll.saturating_sub(1);
4500                        } else {
4501                            self.s3_state.selected_object =
4502                                self.s3_state.selected_object.saturating_sub(1);
4503
4504                            // Adjust scroll offset if selection goes above viewport
4505                            if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
4506                                self.s3_state.object_scroll_offset = self.s3_state.selected_object;
4507                            }
4508                        }
4509                    } else {
4510                        self.s3_state.selected_row = self.s3_state.selected_row.saturating_sub(1);
4511
4512                        // Adjust scroll offset if selection goes above viewport
4513                        if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
4514                            self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
4515                        }
4516                    }
4517                } else if self.view_mode == ViewMode::InsightsResults {
4518                    if self.insights_state.insights.results_selected > 0 {
4519                        self.insights_state.insights.results_selected -= 1;
4520                    }
4521                } else if self.view_mode == ViewMode::PolicyView {
4522                    self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(1);
4523                } else if self.current_service == Service::CloudFormationStacks
4524                    && self.cfn_state.current_stack.is_some()
4525                    && self.cfn_state.detail_tab == CfnDetailTab::Template
4526                {
4527                    self.cfn_state.template_scroll =
4528                        self.cfn_state.template_scroll.saturating_sub(1);
4529                } else if self.current_service == Service::SqsQueues
4530                    && self.sqs_state.current_queue.is_some()
4531                    && self.sqs_state.detail_tab == SqsQueueDetailTab::QueuePolicies
4532                {
4533                    self.sqs_state.policy_scroll = self.sqs_state.policy_scroll.saturating_sub(1);
4534                } else if self.current_service == Service::LambdaFunctions
4535                    && self.lambda_state.current_function.is_some()
4536                    && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
4537                    && !self.lambda_state.is_metrics_loading()
4538                {
4539                    self.lambda_state.set_monitoring_scroll(
4540                        self.lambda_state.monitoring_scroll().saturating_sub(1),
4541                    );
4542                } else if self.current_service == Service::SqsQueues
4543                    && self.sqs_state.current_queue.is_some()
4544                    && self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring
4545                    && !self.sqs_state.is_metrics_loading()
4546                {
4547                    self.sqs_state.set_monitoring_scroll(
4548                        self.sqs_state.monitoring_scroll().saturating_sub(1),
4549                    );
4550                } else if self.view_mode == ViewMode::Events {
4551                    if self.log_groups_state.event_scroll_offset == 0 {
4552                        if self.log_groups_state.has_older_events {
4553                            self.log_groups_state.loading = true;
4554                        }
4555                        // Don't move if at position 0
4556                    } else {
4557                        self.log_groups_state.event_scroll_offset =
4558                            self.log_groups_state.event_scroll_offset.saturating_sub(1);
4559                    }
4560                } else if self.current_service == Service::CloudWatchLogGroups {
4561                    if self.view_mode == ViewMode::List {
4562                        self.log_groups_state.log_groups.prev_item();
4563                    } else if self.view_mode == ViewMode::Detail
4564                        && self.log_groups_state.selected_stream > 0
4565                    {
4566                        self.log_groups_state.selected_stream =
4567                            self.log_groups_state.selected_stream.saturating_sub(1);
4568                        self.log_groups_state.expanded_stream = None;
4569                    }
4570                } else if self.current_service == Service::CloudWatchAlarms {
4571                    self.alarms_state.table.prev_item();
4572                } else if self.current_service == Service::Ec2Instances {
4573                    self.ec2_state.table.prev_item();
4574                } else if self.current_service == Service::EcrRepositories {
4575                    if self.ecr_state.current_repository.is_some() {
4576                        self.ecr_state.images.prev_item();
4577                    } else {
4578                        self.ecr_state.repositories.prev_item();
4579                    }
4580                } else if self.current_service == Service::SqsQueues {
4581                    if self.sqs_state.current_queue.is_some()
4582                        && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
4583                    {
4584                        self.sqs_state.triggers.prev_item();
4585                    } else if self.sqs_state.current_queue.is_some()
4586                        && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
4587                    {
4588                        self.sqs_state.pipes.prev_item();
4589                    } else if self.sqs_state.current_queue.is_some()
4590                        && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
4591                    {
4592                        self.sqs_state.tags.prev_item();
4593                    } else if self.sqs_state.current_queue.is_some()
4594                        && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
4595                    {
4596                        self.sqs_state.subscriptions.prev_item();
4597                    } else {
4598                        self.sqs_state.queues.prev_item();
4599                    }
4600                } else if self.current_service == Service::LambdaFunctions {
4601                    if self.lambda_state.current_function.is_some()
4602                        && self.lambda_state.detail_tab == LambdaDetailTab::Code
4603                    {
4604                        // Layer table navigation in Code tab
4605                        self.lambda_state.layer_selected =
4606                            self.lambda_state.layer_selected.saturating_sub(1);
4607                    } else if self.lambda_state.current_function.is_some()
4608                        && self.lambda_state.detail_tab == LambdaDetailTab::Versions
4609                    {
4610                        self.lambda_state.version_table.prev_item();
4611                    } else if self.lambda_state.current_function.is_some()
4612                        && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
4613                            || (self.lambda_state.current_version.is_some()
4614                                && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
4615                    {
4616                        self.lambda_state.alias_table.prev_item();
4617                    } else if self.lambda_state.current_function.is_none() {
4618                        self.lambda_state.table.prev_item();
4619                    }
4620                } else if self.current_service == Service::LambdaApplications {
4621                    if self.lambda_application_state.current_application.is_some()
4622                        && self.lambda_application_state.detail_tab
4623                            == LambdaApplicationDetailTab::Overview
4624                    {
4625                        self.lambda_application_state.resources.selected = self
4626                            .lambda_application_state
4627                            .resources
4628                            .selected
4629                            .saturating_sub(1);
4630                    } else if self.lambda_application_state.current_application.is_some()
4631                        && self.lambda_application_state.detail_tab
4632                            == LambdaApplicationDetailTab::Deployments
4633                    {
4634                        self.lambda_application_state.deployments.selected = self
4635                            .lambda_application_state
4636                            .deployments
4637                            .selected
4638                            .saturating_sub(1);
4639                    } else {
4640                        self.lambda_application_state.table.selected = self
4641                            .lambda_application_state
4642                            .table
4643                            .selected
4644                            .saturating_sub(1);
4645                        self.lambda_application_state.table.snap_to_page();
4646                    }
4647                } else if self.current_service == Service::CloudFormationStacks {
4648                    if self.cfn_state.current_stack.is_some()
4649                        && self.cfn_state.detail_tab == CfnDetailTab::Parameters
4650                    {
4651                        self.cfn_state.parameters.prev_item();
4652                    } else if self.cfn_state.current_stack.is_some()
4653                        && self.cfn_state.detail_tab == CfnDetailTab::Outputs
4654                    {
4655                        self.cfn_state.outputs.prev_item();
4656                    } else if self.cfn_state.current_stack.is_some()
4657                        && self.cfn_state.detail_tab == CfnDetailTab::Resources
4658                    {
4659                        self.cfn_state.resources.prev_item();
4660                    } else {
4661                        self.cfn_state.table.prev_item();
4662                    }
4663                } else if self.current_service == Service::IamUsers {
4664                    self.iam_state.users.prev_item();
4665                } else if self.current_service == Service::IamRoles {
4666                    if self.iam_state.current_role.is_some() {
4667                        if self.iam_state.role_tab == RoleTab::TrustRelationships {
4668                            self.iam_state.trust_policy_scroll =
4669                                self.iam_state.trust_policy_scroll.saturating_sub(1);
4670                        } else if self.iam_state.role_tab == RoleTab::RevokeSessions {
4671                            self.iam_state.revoke_sessions_scroll =
4672                                self.iam_state.revoke_sessions_scroll.saturating_sub(1);
4673                        } else if self.iam_state.role_tab == RoleTab::Tags {
4674                            self.iam_state.tags.prev_item();
4675                        } else if self.iam_state.role_tab == RoleTab::LastAccessed {
4676                            self.iam_state.last_accessed_services.prev_item();
4677                        } else {
4678                            self.iam_state.policies.prev_item();
4679                        }
4680                    } else {
4681                        self.iam_state.roles.prev_item();
4682                    }
4683                } else if self.current_service == Service::IamUserGroups {
4684                    if self.iam_state.current_group.is_some() {
4685                        if self.iam_state.group_tab == GroupTab::Users {
4686                            self.iam_state.group_users.prev_item();
4687                        } else if self.iam_state.group_tab == GroupTab::Permissions {
4688                            self.iam_state.policies.prev_item();
4689                        } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
4690                            self.iam_state.last_accessed_services.prev_item();
4691                        }
4692                    } else {
4693                        self.iam_state.groups.prev_item();
4694                    }
4695                }
4696            }
4697            _ => {}
4698        }
4699    }
4700
4701    fn page_down(&mut self) {
4702        if self.mode == Mode::ColumnSelector {
4703            let max = self.get_column_selector_max();
4704            self.column_selector_index = (self.column_selector_index + 10).min(max);
4705        } else if self.mode == Mode::FilterInput
4706            && self.current_service == Service::CloudFormationStacks
4707        {
4708            if self.cfn_state.current_stack.is_some()
4709                && self.cfn_state.detail_tab == CfnDetailTab::Parameters
4710            {
4711                let page_size = self.cfn_state.parameters.page_size.value();
4712                let filtered_count = filtered_parameters(self).len();
4713                self.cfn_state.parameters_input_focus.handle_page_down(
4714                    &mut self.cfn_state.parameters.selected,
4715                    &mut self.cfn_state.parameters.scroll_offset,
4716                    page_size,
4717                    filtered_count,
4718                );
4719            } else if self.cfn_state.current_stack.is_some()
4720                && self.cfn_state.detail_tab == CfnDetailTab::Outputs
4721            {
4722                let page_size = self.cfn_state.outputs.page_size.value();
4723                let filtered_count = filtered_outputs(self).len();
4724                self.cfn_state.outputs_input_focus.handle_page_down(
4725                    &mut self.cfn_state.outputs.selected,
4726                    &mut self.cfn_state.outputs.scroll_offset,
4727                    page_size,
4728                    filtered_count,
4729                );
4730            } else {
4731                use crate::ui::cfn::filtered_cloudformation_stacks;
4732                let page_size = self.cfn_state.table.page_size.value();
4733                let filtered_count = filtered_cloudformation_stacks(self).len();
4734                self.cfn_state.input_focus.handle_page_down(
4735                    &mut self.cfn_state.table.selected,
4736                    &mut self.cfn_state.table.scroll_offset,
4737                    page_size,
4738                    filtered_count,
4739                );
4740            }
4741        } else if self.mode == Mode::FilterInput
4742            && self.current_service == Service::IamRoles
4743            && self.iam_state.current_role.is_none()
4744        {
4745            let page_size = self.iam_state.roles.page_size.value();
4746            let filtered_count = crate::ui::iam::filtered_iam_roles(self).len();
4747            self.iam_state.role_input_focus.handle_page_down(
4748                &mut self.iam_state.roles.selected,
4749                &mut self.iam_state.roles.scroll_offset,
4750                page_size,
4751                filtered_count,
4752            );
4753        } else if self.mode == Mode::FilterInput
4754            && self.current_service == Service::CloudWatchAlarms
4755        {
4756            let page_size = self.alarms_state.table.page_size.value();
4757            let filtered_count = self.alarms_state.table.items.len();
4758            self.alarms_state.input_focus.handle_page_down(
4759                &mut self.alarms_state.table.selected,
4760                &mut self.alarms_state.table.scroll_offset,
4761                page_size,
4762                filtered_count,
4763            );
4764        } else if self.mode == Mode::FilterInput
4765            && self.current_service == Service::CloudWatchLogGroups
4766        {
4767            if self.view_mode == ViewMode::List {
4768                // Log groups list pagination
4769                let filtered = self.filtered_log_groups();
4770                let page_size = self.log_groups_state.log_groups.page_size.value();
4771                let filtered_count = filtered.len();
4772                self.log_groups_state.input_focus.handle_page_down(
4773                    &mut self.log_groups_state.log_groups.selected,
4774                    &mut self.log_groups_state.log_groups.scroll_offset,
4775                    page_size,
4776                    filtered_count,
4777                );
4778            } else {
4779                // Log streams pagination
4780                let filtered = self.filtered_log_streams();
4781                let page_size = 20;
4782                let filtered_count = filtered.len();
4783                self.log_groups_state.input_focus.handle_page_down(
4784                    &mut self.log_groups_state.selected_stream,
4785                    &mut self.log_groups_state.stream_page,
4786                    page_size,
4787                    filtered_count,
4788                );
4789                self.log_groups_state.expanded_stream = None;
4790            }
4791        } else if self.mode == Mode::FilterInput && self.current_service == Service::LambdaFunctions
4792        {
4793            if self.lambda_state.current_function.is_some()
4794                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
4795                && self.lambda_state.version_input_focus == InputFocus::Pagination
4796            {
4797                let page_size = self.lambda_state.version_table.page_size.value();
4798                let filtered_count: usize = self
4799                    .lambda_state
4800                    .version_table
4801                    .items
4802                    .iter()
4803                    .filter(|v| {
4804                        self.lambda_state.version_table.filter.is_empty()
4805                            || v.version
4806                                .to_lowercase()
4807                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
4808                            || v.aliases
4809                                .to_lowercase()
4810                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
4811                            || v.description
4812                                .to_lowercase()
4813                                .contains(&self.lambda_state.version_table.filter.to_lowercase())
4814                    })
4815                    .count();
4816                let target = self.lambda_state.version_table.selected + page_size;
4817                self.lambda_state.version_table.selected =
4818                    target.min(filtered_count.saturating_sub(1));
4819            } else if self.lambda_state.current_function.is_some()
4820                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
4821                    || (self.lambda_state.current_version.is_some()
4822                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
4823                && self.lambda_state.alias_input_focus == InputFocus::Pagination
4824            {
4825                let page_size = self.lambda_state.alias_table.page_size.value();
4826                let version_filter = self.lambda_state.current_version.clone();
4827                let filtered_count = self
4828                    .lambda_state
4829                    .alias_table
4830                    .items
4831                    .iter()
4832                    .filter(|a| {
4833                        (version_filter.is_none()
4834                            || a.versions.contains(version_filter.as_ref().unwrap()))
4835                            && (self.lambda_state.alias_table.filter.is_empty()
4836                                || a.name
4837                                    .to_lowercase()
4838                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
4839                                || a.versions
4840                                    .to_lowercase()
4841                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
4842                                || a.description
4843                                    .to_lowercase()
4844                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase()))
4845                    })
4846                    .count();
4847                let target = self.lambda_state.alias_table.selected + page_size;
4848                self.lambda_state.alias_table.selected =
4849                    target.min(filtered_count.saturating_sub(1));
4850            } else if self.lambda_state.current_function.is_none() {
4851                let page_size = self.lambda_state.table.page_size.value();
4852                let filtered_count = crate::ui::lambda::filtered_lambda_functions(self).len();
4853                self.lambda_state.input_focus.handle_page_down(
4854                    &mut self.lambda_state.table.selected,
4855                    &mut self.lambda_state.table.scroll_offset,
4856                    page_size,
4857                    filtered_count,
4858                );
4859            }
4860        } else if self.mode == Mode::FilterInput
4861            && self.current_service == Service::EcrRepositories
4862            && self.ecr_state.current_repository.is_none()
4863            && self.ecr_state.input_focus == InputFocus::Filter
4864        {
4865            // When input is focused, allow table scrolling
4866            let filtered = self.filtered_ecr_repositories();
4867            self.ecr_state.repositories.page_down(filtered.len());
4868        } else if self.mode == Mode::FilterInput
4869            && self.current_service == Service::EcrRepositories
4870            && self.ecr_state.current_repository.is_none()
4871        {
4872            let page_size = self.ecr_state.repositories.page_size.value();
4873            let filtered_count = self.filtered_ecr_repositories().len();
4874            self.ecr_state.input_focus.handle_page_down(
4875                &mut self.ecr_state.repositories.selected,
4876                &mut self.ecr_state.repositories.scroll_offset,
4877                page_size,
4878                filtered_count,
4879            );
4880        } else if self.mode == Mode::FilterInput && self.view_mode == ViewMode::PolicyView {
4881            let page_size = self.iam_state.policies.page_size.value();
4882            let filtered_count = crate::ui::iam::filtered_iam_policies(self).len();
4883            self.iam_state.policy_input_focus.handle_page_down(
4884                &mut self.iam_state.policies.selected,
4885                &mut self.iam_state.policies.scroll_offset,
4886                page_size,
4887                filtered_count,
4888            );
4889        } else if self.view_mode == ViewMode::PolicyView {
4890            let lines = self.iam_state.policy_document.lines().count();
4891            let max_scroll = lines.saturating_sub(1);
4892            self.iam_state.policy_scroll = (self.iam_state.policy_scroll + 10).min(max_scroll);
4893        } else if self.current_service == Service::CloudFormationStacks
4894            && self.cfn_state.current_stack.is_some()
4895            && self.cfn_state.detail_tab == CfnDetailTab::Template
4896        {
4897            let lines = self.cfn_state.template_body.lines().count();
4898            let max_scroll = lines.saturating_sub(1);
4899            self.cfn_state.template_scroll = (self.cfn_state.template_scroll + 10).min(max_scroll);
4900        } else if self.current_service == Service::LambdaFunctions
4901            && self.lambda_state.current_function.is_some()
4902            && self.lambda_state.detail_tab == LambdaDetailTab::Monitor
4903            && !self.lambda_state.is_metrics_loading()
4904        {
4905            self.lambda_state
4906                .set_monitoring_scroll((self.lambda_state.monitoring_scroll() + 1).min(9));
4907        } else if self.current_service == Service::SqsQueues
4908            && self.sqs_state.current_queue.is_some()
4909        {
4910            if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
4911                self.sqs_state
4912                    .set_monitoring_scroll((self.sqs_state.monitoring_scroll() + 1).min(8));
4913            } else {
4914                let lines = self.sqs_state.policy_document.lines().count();
4915                let max_scroll = lines.saturating_sub(1);
4916                self.sqs_state.policy_scroll = (self.sqs_state.policy_scroll + 10).min(max_scroll);
4917            }
4918        } else if self.current_service == Service::IamRoles
4919            && self.iam_state.current_role.is_some()
4920            && self.iam_state.role_tab == RoleTab::TrustRelationships
4921        {
4922            let lines = self.iam_state.trust_policy_document.lines().count();
4923            let max_scroll = lines.saturating_sub(1);
4924            self.iam_state.trust_policy_scroll =
4925                (self.iam_state.trust_policy_scroll + 10).min(max_scroll);
4926        } else if self.current_service == Service::IamRoles
4927            && self.iam_state.current_role.is_some()
4928            && self.iam_state.role_tab == RoleTab::RevokeSessions
4929        {
4930            self.iam_state.revoke_sessions_scroll =
4931                (self.iam_state.revoke_sessions_scroll + 10).min(19);
4932        } else if self.mode == Mode::Normal {
4933            if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none()
4934            {
4935                let total_rows = self.calculate_total_bucket_rows();
4936                self.s3_state.selected_row = self
4937                    .s3_state
4938                    .selected_row
4939                    .saturating_add(10)
4940                    .min(total_rows.saturating_sub(1));
4941
4942                // Adjust scroll offset if selection goes below viewport
4943                let visible_rows = self.s3_state.bucket_visible_rows.get();
4944                if self.s3_state.selected_row >= self.s3_state.bucket_scroll_offset + visible_rows {
4945                    self.s3_state.bucket_scroll_offset =
4946                        self.s3_state.selected_row - visible_rows + 1;
4947                }
4948            } else if self.current_service == Service::S3Buckets
4949                && self.s3_state.current_bucket.is_some()
4950            {
4951                let total_rows = self.calculate_total_object_rows();
4952                self.s3_state.selected_object = self
4953                    .s3_state
4954                    .selected_object
4955                    .saturating_add(10)
4956                    .min(total_rows.saturating_sub(1));
4957
4958                // Adjust scroll offset if selection goes below viewport
4959                let visible_rows = self.s3_state.object_visible_rows.get();
4960                if self.s3_state.selected_object
4961                    >= self.s3_state.object_scroll_offset + visible_rows
4962                {
4963                    self.s3_state.object_scroll_offset =
4964                        self.s3_state.selected_object - visible_rows + 1;
4965                }
4966            } else if self.current_service == Service::CloudWatchLogGroups
4967                && self.view_mode == ViewMode::List
4968            {
4969                let filtered = self.filtered_log_groups();
4970                self.log_groups_state.log_groups.page_down(filtered.len());
4971            } else if self.current_service == Service::CloudWatchLogGroups
4972                && self.view_mode == ViewMode::Detail
4973            {
4974                let len = self.filtered_log_streams().len();
4975                nav_page_down(&mut self.log_groups_state.selected_stream, len, 10);
4976            } else if self.view_mode == ViewMode::Events {
4977                let max = self.log_groups_state.log_events.len();
4978                nav_page_down(&mut self.log_groups_state.event_scroll_offset, max, 10);
4979            } else if self.view_mode == ViewMode::InsightsResults {
4980                let max = self.insights_state.insights.query_results.len();
4981                nav_page_down(&mut self.insights_state.insights.results_selected, max, 10);
4982            } else if self.current_service == Service::CloudWatchAlarms {
4983                let filtered = match self.alarms_state.alarm_tab {
4984                    AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
4985                    AlarmTab::InAlarm => self
4986                        .alarms_state
4987                        .table
4988                        .items
4989                        .iter()
4990                        .filter(|a| a.state.to_uppercase() == "ALARM")
4991                        .count(),
4992                };
4993                if filtered > 0 {
4994                    self.alarms_state.table.page_down(filtered);
4995                }
4996            } else if self.current_service == Service::Ec2Instances {
4997                let filtered: Vec<_> = self
4998                    .ec2_state
4999                    .table
5000                    .items
5001                    .iter()
5002                    .filter(|i| self.ec2_state.state_filter.matches(&i.state))
5003                    .filter(|i| {
5004                        if self.ec2_state.table.filter.is_empty() {
5005                            return true;
5006                        }
5007                        i.name.contains(&self.ec2_state.table.filter)
5008                            || i.instance_id.contains(&self.ec2_state.table.filter)
5009                            || i.state.contains(&self.ec2_state.table.filter)
5010                            || i.instance_type.contains(&self.ec2_state.table.filter)
5011                            || i.availability_zone.contains(&self.ec2_state.table.filter)
5012                            || i.security_groups.contains(&self.ec2_state.table.filter)
5013                            || i.key_name.contains(&self.ec2_state.table.filter)
5014                    })
5015                    .collect();
5016                if !filtered.is_empty() {
5017                    self.ec2_state.table.page_down(filtered.len());
5018                }
5019            } else if self.current_service == Service::EcrRepositories {
5020                if self.ecr_state.current_repository.is_some() {
5021                    let filtered = self.filtered_ecr_images();
5022                    self.ecr_state.images.page_down(filtered.len());
5023                } else {
5024                    let filtered = self.filtered_ecr_repositories();
5025                    self.ecr_state.repositories.page_down(filtered.len());
5026                }
5027            } else if self.current_service == Service::SqsQueues {
5028                let filtered = crate::ui::sqs::filtered_queues(
5029                    &self.sqs_state.queues.items,
5030                    &self.sqs_state.queues.filter,
5031                );
5032                self.sqs_state.queues.page_down(filtered.len());
5033            } else if self.current_service == Service::LambdaFunctions {
5034                let len = crate::ui::lambda::filtered_lambda_functions(self).len();
5035                self.lambda_state.table.page_down(len);
5036            } else if self.current_service == Service::LambdaApplications {
5037                let len = crate::ui::lambda::filtered_lambda_applications(self).len();
5038                self.lambda_application_state.table.page_down(len);
5039            } else if self.current_service == Service::CloudFormationStacks {
5040                if self.cfn_state.current_stack.is_some()
5041                    && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5042                {
5043                    let filtered = filtered_parameters(self);
5044                    self.cfn_state.parameters.page_down(filtered.len());
5045                } else if self.cfn_state.current_stack.is_some()
5046                    && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5047                {
5048                    let filtered = filtered_outputs(self);
5049                    self.cfn_state.outputs.page_down(filtered.len());
5050                } else {
5051                    let filtered = self.filtered_cloudformation_stacks();
5052                    self.cfn_state.table.page_down(filtered.len());
5053                }
5054            } else if self.current_service == Service::IamUsers {
5055                let len = crate::ui::iam::filtered_iam_users(self).len();
5056                nav_page_down(&mut self.iam_state.users.selected, len, 10);
5057            } else if self.current_service == Service::IamRoles {
5058                if self.iam_state.current_role.is_some() {
5059                    let filtered = crate::ui::iam::filtered_iam_policies(self);
5060                    if !filtered.is_empty() {
5061                        self.iam_state.policies.page_down(filtered.len());
5062                    }
5063                } else {
5064                    let filtered = crate::ui::iam::filtered_iam_roles(self);
5065                    self.iam_state.roles.page_down(filtered.len());
5066                }
5067            } else if self.current_service == Service::IamUserGroups {
5068                if self.iam_state.current_group.is_some() {
5069                    if self.iam_state.group_tab == GroupTab::Users {
5070                        let filtered: Vec<_> = self
5071                            .iam_state
5072                            .group_users
5073                            .items
5074                            .iter()
5075                            .filter(|u| {
5076                                if self.iam_state.group_users.filter.is_empty() {
5077                                    true
5078                                } else {
5079                                    u.user_name
5080                                        .to_lowercase()
5081                                        .contains(&self.iam_state.group_users.filter.to_lowercase())
5082                                }
5083                            })
5084                            .collect();
5085                        if !filtered.is_empty() {
5086                            self.iam_state.group_users.page_down(filtered.len());
5087                        }
5088                    } else if self.iam_state.group_tab == GroupTab::Permissions {
5089                        let filtered = crate::ui::iam::filtered_iam_policies(self);
5090                        if !filtered.is_empty() {
5091                            self.iam_state.policies.page_down(filtered.len());
5092                        }
5093                    } else if self.iam_state.group_tab == GroupTab::AccessAdvisor {
5094                        let filtered = crate::ui::iam::filtered_last_accessed(self);
5095                        if !filtered.is_empty() {
5096                            self.iam_state
5097                                .last_accessed_services
5098                                .page_down(filtered.len());
5099                        }
5100                    }
5101                } else {
5102                    let filtered: Vec<_> = self
5103                        .iam_state
5104                        .groups
5105                        .items
5106                        .iter()
5107                        .filter(|g| {
5108                            if self.iam_state.groups.filter.is_empty() {
5109                                true
5110                            } else {
5111                                g.group_name
5112                                    .to_lowercase()
5113                                    .contains(&self.iam_state.groups.filter.to_lowercase())
5114                            }
5115                        })
5116                        .collect();
5117                    if !filtered.is_empty() {
5118                        self.iam_state.groups.page_down(filtered.len());
5119                    }
5120                }
5121            }
5122        }
5123    }
5124
5125    fn page_up(&mut self) {
5126        if self.mode == Mode::ColumnSelector {
5127            self.column_selector_index = self.column_selector_index.saturating_sub(10);
5128        } else if self.mode == Mode::FilterInput
5129            && self.current_service == Service::CloudFormationStacks
5130        {
5131            if self.cfn_state.current_stack.is_some()
5132                && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5133            {
5134                let page_size = self.cfn_state.parameters.page_size.value();
5135                self.cfn_state.parameters_input_focus.handle_page_up(
5136                    &mut self.cfn_state.parameters.selected,
5137                    &mut self.cfn_state.parameters.scroll_offset,
5138                    page_size,
5139                );
5140            } else if self.cfn_state.current_stack.is_some()
5141                && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5142            {
5143                let page_size = self.cfn_state.outputs.page_size.value();
5144                self.cfn_state.outputs_input_focus.handle_page_up(
5145                    &mut self.cfn_state.outputs.selected,
5146                    &mut self.cfn_state.outputs.scroll_offset,
5147                    page_size,
5148                );
5149            } else {
5150                let page_size = self.cfn_state.table.page_size.value();
5151                self.cfn_state.input_focus.handle_page_up(
5152                    &mut self.cfn_state.table.selected,
5153                    &mut self.cfn_state.table.scroll_offset,
5154                    page_size,
5155                );
5156            }
5157        } else if self.mode == Mode::FilterInput
5158            && self.current_service == Service::IamRoles
5159            && self.iam_state.current_role.is_none()
5160        {
5161            let page_size = self.iam_state.roles.page_size.value();
5162            self.iam_state.role_input_focus.handle_page_up(
5163                &mut self.iam_state.roles.selected,
5164                &mut self.iam_state.roles.scroll_offset,
5165                page_size,
5166            );
5167        } else if self.mode == Mode::FilterInput
5168            && self.current_service == Service::CloudWatchAlarms
5169        {
5170            let page_size = self.alarms_state.table.page_size.value();
5171            self.alarms_state.input_focus.handle_page_up(
5172                &mut self.alarms_state.table.selected,
5173                &mut self.alarms_state.table.scroll_offset,
5174                page_size,
5175            );
5176        } else if self.mode == Mode::FilterInput
5177            && self.current_service == Service::CloudWatchLogGroups
5178        {
5179            if self.view_mode == ViewMode::List {
5180                // Log groups list pagination
5181                let page_size = self.log_groups_state.log_groups.page_size.value();
5182                self.log_groups_state.input_focus.handle_page_up(
5183                    &mut self.log_groups_state.log_groups.selected,
5184                    &mut self.log_groups_state.log_groups.scroll_offset,
5185                    page_size,
5186                );
5187            } else {
5188                // Log streams pagination
5189                let page_size = 20;
5190                self.log_groups_state.input_focus.handle_page_up(
5191                    &mut self.log_groups_state.selected_stream,
5192                    &mut self.log_groups_state.stream_page,
5193                    page_size,
5194                );
5195                self.log_groups_state.expanded_stream = None;
5196            }
5197        } else if self.mode == Mode::FilterInput && self.current_service == Service::LambdaFunctions
5198        {
5199            if self.lambda_state.current_function.is_some()
5200                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5201                && self.lambda_state.version_input_focus == InputFocus::Pagination
5202            {
5203                let page_size = self.lambda_state.version_table.page_size.value();
5204                self.lambda_state.version_table.selected = self
5205                    .lambda_state
5206                    .version_table
5207                    .selected
5208                    .saturating_sub(page_size);
5209            } else if self.lambda_state.current_function.is_some()
5210                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
5211                    || (self.lambda_state.current_version.is_some()
5212                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
5213                && self.lambda_state.alias_input_focus == InputFocus::Pagination
5214            {
5215                let page_size = self.lambda_state.alias_table.page_size.value();
5216                self.lambda_state.alias_table.selected = self
5217                    .lambda_state
5218                    .alias_table
5219                    .selected
5220                    .saturating_sub(page_size);
5221            } else if self.lambda_state.current_function.is_none() {
5222                let page_size = self.lambda_state.table.page_size.value();
5223                self.lambda_state.input_focus.handle_page_up(
5224                    &mut self.lambda_state.table.selected,
5225                    &mut self.lambda_state.table.scroll_offset,
5226                    page_size,
5227                );
5228            }
5229        } else if self.mode == Mode::FilterInput
5230            && self.current_service == Service::EcrRepositories
5231            && self.ecr_state.current_repository.is_none()
5232            && self.ecr_state.input_focus == InputFocus::Filter
5233        {
5234            // When input is focused, allow table scrolling
5235            self.ecr_state.repositories.page_up();
5236        } else if self.mode == Mode::FilterInput
5237            && self.current_service == Service::EcrRepositories
5238            && self.ecr_state.current_repository.is_none()
5239        {
5240            let page_size = self.ecr_state.repositories.page_size.value();
5241            self.ecr_state.input_focus.handle_page_up(
5242                &mut self.ecr_state.repositories.selected,
5243                &mut self.ecr_state.repositories.scroll_offset,
5244                page_size,
5245            );
5246        } else if self.mode == Mode::FilterInput && self.view_mode == ViewMode::PolicyView {
5247            let page_size = self.iam_state.policies.page_size.value();
5248            self.iam_state.policy_input_focus.handle_page_up(
5249                &mut self.iam_state.policies.selected,
5250                &mut self.iam_state.policies.scroll_offset,
5251                page_size,
5252            );
5253        } else if self.view_mode == ViewMode::PolicyView {
5254            self.iam_state.policy_scroll = self.iam_state.policy_scroll.saturating_sub(10);
5255        } else if self.current_service == Service::CloudFormationStacks
5256            && self.cfn_state.current_stack.is_some()
5257            && self.cfn_state.detail_tab == CfnDetailTab::Template
5258        {
5259            self.cfn_state.template_scroll = self.cfn_state.template_scroll.saturating_sub(10);
5260        } else if self.current_service == Service::SqsQueues
5261            && self.sqs_state.current_queue.is_some()
5262        {
5263            if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
5264                self.sqs_state
5265                    .set_monitoring_scroll(self.sqs_state.monitoring_scroll().saturating_sub(1));
5266            } else {
5267                self.sqs_state.policy_scroll = self.sqs_state.policy_scroll.saturating_sub(10);
5268            }
5269        } else if self.current_service == Service::IamRoles
5270            && self.iam_state.current_role.is_some()
5271            && self.iam_state.role_tab == RoleTab::TrustRelationships
5272        {
5273            self.iam_state.trust_policy_scroll =
5274                self.iam_state.trust_policy_scroll.saturating_sub(10);
5275        } else if self.current_service == Service::IamRoles
5276            && self.iam_state.current_role.is_some()
5277            && self.iam_state.role_tab == RoleTab::RevokeSessions
5278        {
5279            self.iam_state.revoke_sessions_scroll =
5280                self.iam_state.revoke_sessions_scroll.saturating_sub(10);
5281        } else if self.mode == Mode::Normal {
5282            if self.current_service == Service::S3Buckets && self.s3_state.current_bucket.is_none()
5283            {
5284                self.s3_state.selected_row = self.s3_state.selected_row.saturating_sub(10);
5285
5286                // Adjust scroll offset if selection goes above viewport
5287                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
5288                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
5289                }
5290            } else if self.current_service == Service::S3Buckets
5291                && self.s3_state.current_bucket.is_some()
5292            {
5293                self.s3_state.selected_object = self.s3_state.selected_object.saturating_sub(10);
5294
5295                // Adjust scroll offset if selection goes above viewport
5296                if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
5297                    self.s3_state.object_scroll_offset = self.s3_state.selected_object;
5298                }
5299            } else if self.current_service == Service::CloudWatchLogGroups
5300                && self.view_mode == ViewMode::List
5301            {
5302                self.log_groups_state.log_groups.page_up();
5303            } else if self.current_service == Service::CloudWatchLogGroups
5304                && self.view_mode == ViewMode::Detail
5305            {
5306                self.log_groups_state.selected_stream =
5307                    self.log_groups_state.selected_stream.saturating_sub(10);
5308            } else if self.view_mode == ViewMode::Events {
5309                if self.log_groups_state.event_scroll_offset < 10
5310                    && self.log_groups_state.has_older_events
5311                {
5312                    self.log_groups_state.loading = true;
5313                }
5314                self.log_groups_state.event_scroll_offset =
5315                    self.log_groups_state.event_scroll_offset.saturating_sub(10);
5316            } else if self.view_mode == ViewMode::InsightsResults {
5317                self.insights_state.insights.results_selected = self
5318                    .insights_state
5319                    .insights
5320                    .results_selected
5321                    .saturating_sub(10);
5322            } else if self.current_service == Service::CloudWatchAlarms {
5323                self.alarms_state.table.page_up();
5324            } else if self.current_service == Service::Ec2Instances {
5325                self.ec2_state.table.page_up();
5326            } else if self.current_service == Service::EcrRepositories {
5327                if self.ecr_state.current_repository.is_some() {
5328                    self.ecr_state.images.page_up();
5329                } else {
5330                    self.ecr_state.repositories.page_up();
5331                }
5332            } else if self.current_service == Service::SqsQueues {
5333                self.sqs_state.queues.page_up();
5334            } else if self.current_service == Service::LambdaFunctions {
5335                self.lambda_state.table.page_up();
5336            } else if self.current_service == Service::LambdaApplications {
5337                self.lambda_application_state.table.page_up();
5338            } else if self.current_service == Service::CloudFormationStacks {
5339                if self.cfn_state.current_stack.is_some()
5340                    && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5341                {
5342                    self.cfn_state.parameters.page_up();
5343                } else if self.cfn_state.current_stack.is_some()
5344                    && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5345                {
5346                    self.cfn_state.outputs.page_up();
5347                } else {
5348                    self.cfn_state.table.page_up();
5349                }
5350            } else if self.current_service == Service::IamUsers {
5351                self.iam_state.users.page_up();
5352            } else if self.current_service == Service::IamRoles {
5353                if self.iam_state.current_role.is_some() {
5354                    self.iam_state.policies.page_up();
5355                } else {
5356                    self.iam_state.roles.page_up();
5357                }
5358            }
5359        }
5360    }
5361
5362    fn next_pane(&mut self) {
5363        if self.current_service == Service::S3Buckets {
5364            if self.s3_state.current_bucket.is_some() {
5365                // In objects view - expand prefix and trigger preview load
5366                // Map visual index to actual object (including nested items)
5367                let mut visual_idx = 0;
5368                let mut found_obj: Option<S3Object> = None;
5369
5370                // Helper to recursively check nested items
5371                fn check_nested(
5372                    obj: &S3Object,
5373                    visual_idx: &mut usize,
5374                    target_idx: usize,
5375                    expanded_prefixes: &std::collections::HashSet<String>,
5376                    prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
5377                    found_obj: &mut Option<S3Object>,
5378                ) {
5379                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
5380                        if let Some(preview) = prefix_preview.get(&obj.key) {
5381                            for nested_obj in preview {
5382                                if *visual_idx == target_idx {
5383                                    *found_obj = Some(nested_obj.clone());
5384                                    return;
5385                                }
5386                                *visual_idx += 1;
5387
5388                                // Recursively check deeper levels
5389                                check_nested(
5390                                    nested_obj,
5391                                    visual_idx,
5392                                    target_idx,
5393                                    expanded_prefixes,
5394                                    prefix_preview,
5395                                    found_obj,
5396                                );
5397                                if found_obj.is_some() {
5398                                    return;
5399                                }
5400                            }
5401                        } else {
5402                            // Loading row
5403                            *visual_idx += 1;
5404                        }
5405                    }
5406                }
5407
5408                for obj in &self.s3_state.objects {
5409                    if visual_idx == self.s3_state.selected_object {
5410                        found_obj = Some(obj.clone());
5411                        break;
5412                    }
5413                    visual_idx += 1;
5414
5415                    // Check nested items recursively
5416                    check_nested(
5417                        obj,
5418                        &mut visual_idx,
5419                        self.s3_state.selected_object,
5420                        &self.s3_state.expanded_prefixes,
5421                        &self.s3_state.prefix_preview,
5422                        &mut found_obj,
5423                    );
5424                    if found_obj.is_some() {
5425                        break;
5426                    }
5427                }
5428
5429                if let Some(obj) = found_obj {
5430                    if obj.is_prefix {
5431                        if !self.s3_state.expanded_prefixes.contains(&obj.key) {
5432                            self.s3_state.expanded_prefixes.insert(obj.key.clone());
5433                            // Trigger preview load if not already cached
5434                            if !self.s3_state.prefix_preview.contains_key(&obj.key) {
5435                                self.s3_state.buckets.loading = true;
5436                            }
5437                        }
5438                        // Move to first child if expanded and has children
5439                        if self.s3_state.expanded_prefixes.contains(&obj.key) {
5440                            if let Some(preview) = self.s3_state.prefix_preview.get(&obj.key) {
5441                                if !preview.is_empty() {
5442                                    self.s3_state.selected_object += 1;
5443                                }
5444                            }
5445                        }
5446                    }
5447                }
5448            } else {
5449                // In bucket list - find which bucket/prefix the selected row corresponds to
5450                let mut row_idx = 0;
5451                let mut found = false;
5452                for bucket in &self.s3_state.buckets.items {
5453                    if row_idx == self.s3_state.selected_row {
5454                        // Selected row is a bucket - expand and move to first child
5455                        if !self.s3_state.expanded_prefixes.contains(&bucket.name) {
5456                            self.s3_state.expanded_prefixes.insert(bucket.name.clone());
5457                            if !self.s3_state.bucket_preview.contains_key(&bucket.name)
5458                                && !self.s3_state.bucket_errors.contains_key(&bucket.name)
5459                            {
5460                                self.s3_state.buckets.loading = true;
5461                            }
5462                        }
5463                        // Move to first child if expanded and has children
5464                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
5465                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
5466                                if !preview.is_empty() {
5467                                    self.s3_state.selected_row = row_idx + 1;
5468                                }
5469                            }
5470                        }
5471                        break;
5472                    }
5473                    row_idx += 1;
5474
5475                    // Skip error rows - they're not selectable
5476                    if self.s3_state.bucket_errors.contains_key(&bucket.name)
5477                        && self.s3_state.expanded_prefixes.contains(&bucket.name)
5478                    {
5479                        continue;
5480                    }
5481
5482                    if self.s3_state.expanded_prefixes.contains(&bucket.name) {
5483                        if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
5484                            // Recursive function to check nested items at any depth
5485                            #[allow(clippy::too_many_arguments)]
5486                            fn check_nested_expansion(
5487                                objects: &[crate::s3::Object],
5488                                row_idx: &mut usize,
5489                                target_row: usize,
5490                                expanded_prefixes: &mut std::collections::HashSet<String>,
5491                                prefix_preview: &std::collections::HashMap<
5492                                    String,
5493                                    Vec<crate::s3::Object>,
5494                                >,
5495                                found: &mut bool,
5496                                loading: &mut bool,
5497                                selected_row: &mut usize,
5498                            ) {
5499                                for obj in objects {
5500                                    if *row_idx == target_row {
5501                                        // Selected this item - expand and move to first child
5502                                        if obj.is_prefix {
5503                                            if !expanded_prefixes.contains(&obj.key) {
5504                                                expanded_prefixes.insert(obj.key.clone());
5505                                                if !prefix_preview.contains_key(&obj.key) {
5506                                                    *loading = true;
5507                                                }
5508                                            }
5509                                            // Move to first child if expanded and has children
5510                                            if expanded_prefixes.contains(&obj.key) {
5511                                                if let Some(preview) = prefix_preview.get(&obj.key)
5512                                                {
5513                                                    if !preview.is_empty() {
5514                                                        *selected_row = *row_idx + 1;
5515                                                    }
5516                                                }
5517                                            }
5518                                        }
5519                                        *found = true;
5520                                        return;
5521                                    }
5522                                    *row_idx += 1;
5523
5524                                    // Recursively check nested items if expanded
5525                                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
5526                                        if let Some(nested) = prefix_preview.get(&obj.key) {
5527                                            check_nested_expansion(
5528                                                nested,
5529                                                row_idx,
5530                                                target_row,
5531                                                expanded_prefixes,
5532                                                prefix_preview,
5533                                                found,
5534                                                loading,
5535                                                selected_row,
5536                                            );
5537                                            if *found {
5538                                                return;
5539                                            }
5540                                        } else {
5541                                            *row_idx += 1; // Loading row
5542                                        }
5543                                    }
5544                                }
5545                            }
5546
5547                            check_nested_expansion(
5548                                preview,
5549                                &mut row_idx,
5550                                self.s3_state.selected_row,
5551                                &mut self.s3_state.expanded_prefixes,
5552                                &self.s3_state.prefix_preview,
5553                                &mut found,
5554                                &mut self.s3_state.buckets.loading,
5555                                &mut self.s3_state.selected_row,
5556                            );
5557                            if found || row_idx > self.s3_state.selected_row {
5558                                break;
5559                            }
5560                        } else {
5561                            row_idx += 1;
5562                            if row_idx > self.s3_state.selected_row {
5563                                break;
5564                            }
5565                        }
5566                    }
5567                    if found {
5568                        break;
5569                    }
5570                }
5571            }
5572        } else if self.view_mode == ViewMode::InsightsResults {
5573            // Right arrow scrolls horizontally by 1 column
5574            let max_cols = self
5575                .insights_state
5576                .insights
5577                .query_results
5578                .first()
5579                .map(|r| r.len())
5580                .unwrap_or(0);
5581            if self.insights_state.insights.results_horizontal_scroll < max_cols.saturating_sub(1) {
5582                self.insights_state.insights.results_horizontal_scroll += 1;
5583            }
5584        } else if self.current_service == Service::CloudWatchLogGroups
5585            && self.view_mode == ViewMode::List
5586        {
5587            // Expand selected log group
5588            if self.log_groups_state.log_groups.expanded_item
5589                != Some(self.log_groups_state.log_groups.selected)
5590            {
5591                self.log_groups_state.log_groups.expanded_item =
5592                    Some(self.log_groups_state.log_groups.selected);
5593            }
5594        } else if self.current_service == Service::CloudWatchLogGroups
5595            && self.view_mode == ViewMode::Detail
5596        {
5597            // Expand selected log stream
5598            if self.log_groups_state.expanded_stream != Some(self.log_groups_state.selected_stream)
5599            {
5600                self.log_groups_state.expanded_stream = Some(self.log_groups_state.selected_stream);
5601            }
5602        } else if self.view_mode == ViewMode::Events {
5603            // Only scroll if there are hidden columns
5604            // Expand selected event
5605            if self.log_groups_state.expanded_event
5606                != Some(self.log_groups_state.event_scroll_offset)
5607            {
5608                self.log_groups_state.expanded_event =
5609                    Some(self.log_groups_state.event_scroll_offset);
5610            }
5611        } else if self.current_service == Service::CloudWatchAlarms {
5612            // Expand selected alarm
5613            if !self.alarms_state.table.is_expanded() {
5614                self.alarms_state.table.toggle_expand();
5615            }
5616        } else if self.current_service == Service::Ec2Instances {
5617            if !self.ec2_state.table.is_expanded() {
5618                self.ec2_state.table.toggle_expand();
5619            }
5620        } else if self.current_service == Service::EcrRepositories {
5621            if self.ecr_state.current_repository.is_some() {
5622                // In images view - expand selected image
5623                self.ecr_state.images.toggle_expand();
5624            } else {
5625                // In repositories view - expand selected repository
5626                self.ecr_state.repositories.toggle_expand();
5627            }
5628        } else if self.current_service == Service::SqsQueues {
5629            if self.sqs_state.current_queue.is_some()
5630                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
5631            {
5632                self.sqs_state.triggers.toggle_expand();
5633            } else if self.sqs_state.current_queue.is_some()
5634                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
5635            {
5636                self.sqs_state.pipes.toggle_expand();
5637            } else if self.sqs_state.current_queue.is_some()
5638                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
5639            {
5640                self.sqs_state.tags.toggle_expand();
5641            } else if self.sqs_state.current_queue.is_some()
5642                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
5643            {
5644                self.sqs_state.subscriptions.toggle_expand();
5645            } else {
5646                self.sqs_state.queues.expand();
5647            }
5648        } else if self.current_service == Service::LambdaFunctions {
5649            if self.lambda_state.current_function.is_some()
5650                && self.lambda_state.detail_tab == LambdaDetailTab::Code
5651            {
5652                // Expand selected layer
5653                if self.lambda_state.layer_expanded != Some(self.lambda_state.layer_selected) {
5654                    self.lambda_state.layer_expanded = Some(self.lambda_state.layer_selected);
5655                } else {
5656                    self.lambda_state.layer_expanded = None;
5657                }
5658            } else if self.lambda_state.current_function.is_some()
5659                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5660            {
5661                // Expand selected version
5662                self.lambda_state.version_table.toggle_expand();
5663            } else if self.lambda_state.current_function.is_some()
5664                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
5665                    || (self.lambda_state.current_version.is_some()
5666                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
5667            {
5668                // Expand selected alias
5669                self.lambda_state.alias_table.toggle_expand();
5670            } else if self.lambda_state.current_function.is_none() {
5671                // Expand selected function
5672                self.lambda_state.table.toggle_expand();
5673            }
5674        } else if self.current_service == Service::LambdaApplications {
5675            if self.lambda_application_state.current_application.is_some() {
5676                // In detail view - expand resource or deployment
5677                if self.lambda_application_state.detail_tab == LambdaApplicationDetailTab::Overview
5678                {
5679                    self.lambda_application_state.resources.toggle_expand();
5680                } else {
5681                    self.lambda_application_state.deployments.toggle_expand();
5682                }
5683            } else {
5684                // Expand selected application in list
5685                if self.lambda_application_state.table.expanded_item
5686                    != Some(self.lambda_application_state.table.selected)
5687                {
5688                    self.lambda_application_state.table.expanded_item =
5689                        Some(self.lambda_application_state.table.selected);
5690                }
5691            }
5692        } else if self.current_service == Service::CloudFormationStacks
5693            && self.cfn_state.current_stack.is_none()
5694        {
5695            self.cfn_state.table.toggle_expand();
5696        } else if self.current_service == Service::CloudFormationStacks
5697            && self.cfn_state.current_stack.is_some()
5698            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
5699        {
5700            self.cfn_state.parameters.toggle_expand();
5701        } else if self.current_service == Service::CloudFormationStacks
5702            && self.cfn_state.current_stack.is_some()
5703            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
5704        {
5705            self.cfn_state.outputs.toggle_expand();
5706        } else if self.current_service == Service::CloudFormationStacks
5707            && self.cfn_state.current_stack.is_some()
5708            && self.cfn_state.detail_tab == CfnDetailTab::Resources
5709        {
5710            self.cfn_state.resources.toggle_expand();
5711        } else if self.current_service == Service::IamUsers {
5712            if self.iam_state.current_user.is_some() {
5713                if self.iam_state.user_tab == UserTab::Tags {
5714                    if self.iam_state.user_tags.expanded_item
5715                        != Some(self.iam_state.user_tags.selected)
5716                    {
5717                        self.iam_state.user_tags.expanded_item =
5718                            Some(self.iam_state.user_tags.selected);
5719                    }
5720                } else if self.iam_state.policies.expanded_item
5721                    != Some(self.iam_state.policies.selected)
5722                {
5723                    self.iam_state.policies.toggle_expand();
5724                }
5725            } else if !self.iam_state.users.is_expanded() {
5726                self.iam_state.users.toggle_expand();
5727            }
5728        } else if self.current_service == Service::IamRoles {
5729            if self.iam_state.current_role.is_some() {
5730                // Handle expansion based on current tab
5731                if self.iam_state.role_tab == RoleTab::Tags {
5732                    if !self.iam_state.tags.is_expanded() {
5733                        self.iam_state.tags.expand();
5734                    }
5735                } else if self.iam_state.role_tab == RoleTab::LastAccessed {
5736                    if !self.iam_state.last_accessed_services.is_expanded() {
5737                        self.iam_state.last_accessed_services.expand();
5738                    }
5739                } else if !self.iam_state.policies.is_expanded() {
5740                    self.iam_state.policies.expand();
5741                }
5742            } else if !self.iam_state.roles.is_expanded() {
5743                self.iam_state.roles.expand();
5744            }
5745        } else if self.current_service == Service::IamUserGroups {
5746            if self.iam_state.current_group.is_some() {
5747                if self.iam_state.group_tab == GroupTab::Users {
5748                    if !self.iam_state.group_users.is_expanded() {
5749                        self.iam_state.group_users.expand();
5750                    }
5751                } else if self.iam_state.group_tab == GroupTab::Permissions {
5752                    if !self.iam_state.policies.is_expanded() {
5753                        self.iam_state.policies.expand();
5754                    }
5755                } else if self.iam_state.group_tab == GroupTab::AccessAdvisor
5756                    && !self.iam_state.last_accessed_services.is_expanded()
5757                {
5758                    self.iam_state.last_accessed_services.expand();
5759                }
5760            } else if !self.iam_state.groups.is_expanded() {
5761                self.iam_state.groups.expand();
5762            }
5763        }
5764    }
5765
5766    fn go_to_page(&mut self, page: usize) {
5767        if page == 0 {
5768            return;
5769        }
5770
5771        match self.current_service {
5772            Service::CloudWatchAlarms => {
5773                let alarm_page_size = self.alarms_state.table.page_size.value();
5774                let target = (page - 1) * alarm_page_size;
5775                let filtered_count = match self.alarms_state.alarm_tab {
5776                    AlarmTab::AllAlarms => self.alarms_state.table.items.len(),
5777                    AlarmTab::InAlarm => self
5778                        .alarms_state
5779                        .table
5780                        .items
5781                        .iter()
5782                        .filter(|a| a.state.to_uppercase() == "ALARM")
5783                        .count(),
5784                };
5785                let max_offset = filtered_count.saturating_sub(alarm_page_size);
5786                self.alarms_state.table.scroll_offset = target.min(max_offset);
5787                self.alarms_state.table.selected = self
5788                    .alarms_state
5789                    .table
5790                    .scroll_offset
5791                    .min(filtered_count.saturating_sub(1));
5792            }
5793            Service::CloudWatchLogGroups => match self.view_mode {
5794                ViewMode::Events => {
5795                    let page_size = 20;
5796                    let target = (page - 1) * page_size;
5797                    let max = self.log_groups_state.log_events.len().saturating_sub(1);
5798                    self.log_groups_state.event_scroll_offset = target.min(max);
5799                }
5800                ViewMode::Detail => {
5801                    let page_size = 20;
5802                    let target = (page - 1) * page_size;
5803                    let max = self.log_groups_state.log_streams.len().saturating_sub(1);
5804                    self.log_groups_state.selected_stream = target.min(max);
5805                }
5806                ViewMode::List => {
5807                    let total = self.log_groups_state.log_groups.items.len();
5808                    self.log_groups_state.log_groups.goto_page(page, total);
5809                }
5810                _ => {}
5811            },
5812            Service::EcrRepositories => {
5813                if self.ecr_state.current_repository.is_some() {
5814                    let filtered_count = self
5815                        .ecr_state
5816                        .images
5817                        .filtered(|img| {
5818                            self.ecr_state.images.filter.is_empty()
5819                                || img
5820                                    .tag
5821                                    .to_lowercase()
5822                                    .contains(&self.ecr_state.images.filter.to_lowercase())
5823                                || img
5824                                    .digest
5825                                    .to_lowercase()
5826                                    .contains(&self.ecr_state.images.filter.to_lowercase())
5827                        })
5828                        .len();
5829                    self.ecr_state.images.goto_page(page, filtered_count);
5830                } else {
5831                    let filtered_count = self
5832                        .ecr_state
5833                        .repositories
5834                        .filtered(|r| {
5835                            self.ecr_state.repositories.filter.is_empty()
5836                                || r.name
5837                                    .to_lowercase()
5838                                    .contains(&self.ecr_state.repositories.filter.to_lowercase())
5839                        })
5840                        .len();
5841                    self.ecr_state.repositories.goto_page(page, filtered_count);
5842                }
5843            }
5844            Service::SqsQueues => {
5845                let filtered_count = crate::ui::sqs::filtered_queues(
5846                    &self.sqs_state.queues.items,
5847                    &self.sqs_state.queues.filter,
5848                )
5849                .len();
5850                self.sqs_state.queues.goto_page(page, filtered_count);
5851            }
5852            Service::S3Buckets => {
5853                if self.s3_state.current_bucket.is_some() {
5854                    let page_size = 50; // S3 objects use fixed page size
5855                    let target = (page - 1) * page_size;
5856                    let total_rows = self.calculate_total_object_rows();
5857                    let max = total_rows.saturating_sub(1);
5858                    self.s3_state.selected_object = target.min(max);
5859                } else {
5860                    let page_size = 50; // S3 buckets use fixed page size
5861                    let target = (page - 1) * page_size;
5862                    let total_rows = self.calculate_total_bucket_rows();
5863                    let max = total_rows.saturating_sub(1);
5864                    self.s3_state.selected_row = target.min(max);
5865                }
5866            }
5867            Service::LambdaFunctions => {
5868                if self.lambda_state.current_function.is_some()
5869                    && self.lambda_state.detail_tab == LambdaDetailTab::Versions
5870                {
5871                    let filtered_count = self
5872                        .lambda_state
5873                        .version_table
5874                        .filtered(|v| {
5875                            self.lambda_state.version_table.filter.is_empty()
5876                                || v.version.to_lowercase().contains(
5877                                    &self.lambda_state.version_table.filter.to_lowercase(),
5878                                )
5879                                || v.aliases.to_lowercase().contains(
5880                                    &self.lambda_state.version_table.filter.to_lowercase(),
5881                                )
5882                                || v.description.to_lowercase().contains(
5883                                    &self.lambda_state.version_table.filter.to_lowercase(),
5884                                )
5885                        })
5886                        .len();
5887                    self.lambda_state
5888                        .version_table
5889                        .goto_page(page, filtered_count);
5890                } else {
5891                    let filtered_count = crate::ui::lambda::filtered_lambda_functions(self).len();
5892                    self.lambda_state.table.goto_page(page, filtered_count);
5893                }
5894            }
5895            Service::LambdaApplications => {
5896                let filtered_count = crate::ui::lambda::filtered_lambda_applications(self).len();
5897                self.lambda_application_state
5898                    .table
5899                    .goto_page(page, filtered_count);
5900            }
5901            Service::CloudFormationStacks => {
5902                let filtered_count = self.filtered_cloudformation_stacks().len();
5903                self.cfn_state.table.goto_page(page, filtered_count);
5904            }
5905            Service::IamUsers => {
5906                let filtered_count = crate::ui::iam::filtered_iam_users(self).len();
5907                self.iam_state.users.goto_page(page, filtered_count);
5908            }
5909            Service::IamRoles => {
5910                let filtered_count = crate::ui::iam::filtered_iam_roles(self).len();
5911                self.iam_state.roles.goto_page(page, filtered_count);
5912            }
5913            _ => {}
5914        }
5915    }
5916
5917    fn prev_pane(&mut self) {
5918        if self.current_service == Service::S3Buckets {
5919            if self.s3_state.current_bucket.is_some() {
5920                // In objects view - collapse prefix or jump to parent
5921                // Map visual index to actual object (including nested items)
5922                let mut visual_idx = 0;
5923                let mut found_obj: Option<S3Object> = None;
5924                let mut parent_idx: Option<usize> = None;
5925
5926                // Helper to recursively find object and its parent
5927                #[allow(clippy::too_many_arguments)]
5928                fn find_with_parent(
5929                    objects: &[S3Object],
5930                    visual_idx: &mut usize,
5931                    target_idx: usize,
5932                    expanded_prefixes: &std::collections::HashSet<String>,
5933                    prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
5934                    found_obj: &mut Option<S3Object>,
5935                    parent_idx: &mut Option<usize>,
5936                    current_parent: Option<usize>,
5937                ) {
5938                    for obj in objects {
5939                        if *visual_idx == target_idx {
5940                            *found_obj = Some(obj.clone());
5941                            *parent_idx = current_parent;
5942                            return;
5943                        }
5944                        let obj_idx = *visual_idx;
5945                        *visual_idx += 1;
5946
5947                        // Check nested items if expanded
5948                        if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
5949                            if let Some(preview) = prefix_preview.get(&obj.key) {
5950                                find_with_parent(
5951                                    preview,
5952                                    visual_idx,
5953                                    target_idx,
5954                                    expanded_prefixes,
5955                                    prefix_preview,
5956                                    found_obj,
5957                                    parent_idx,
5958                                    Some(obj_idx),
5959                                );
5960                                if found_obj.is_some() {
5961                                    return;
5962                                }
5963                            }
5964                        }
5965                    }
5966                }
5967
5968                find_with_parent(
5969                    &self.s3_state.objects,
5970                    &mut visual_idx,
5971                    self.s3_state.selected_object,
5972                    &self.s3_state.expanded_prefixes,
5973                    &self.s3_state.prefix_preview,
5974                    &mut found_obj,
5975                    &mut parent_idx,
5976                    None,
5977                );
5978
5979                if let Some(obj) = found_obj {
5980                    if obj.is_prefix && self.s3_state.expanded_prefixes.contains(&obj.key) {
5981                        // Expanded: collapse it
5982                        self.s3_state.expanded_prefixes.remove(&obj.key);
5983                    } else if let Some(parent) = parent_idx {
5984                        // Already collapsed or not a prefix: jump to parent
5985                        self.s3_state.selected_object = parent;
5986                    }
5987                }
5988
5989                // Adjust scroll offset to keep selection visible
5990                let visible_rows = self.s3_state.object_visible_rows.get();
5991                if self.s3_state.selected_object < self.s3_state.object_scroll_offset {
5992                    self.s3_state.object_scroll_offset = self.s3_state.selected_object;
5993                } else if self.s3_state.selected_object
5994                    >= self.s3_state.object_scroll_offset + visible_rows
5995                {
5996                    self.s3_state.object_scroll_offset = self
5997                        .s3_state
5998                        .selected_object
5999                        .saturating_sub(visible_rows - 1);
6000                }
6001            } else {
6002                // In bucket list - find which bucket/prefix the selected row corresponds to
6003                let mut row_idx = 0;
6004                for bucket in &self.s3_state.buckets.items {
6005                    if row_idx == self.s3_state.selected_row {
6006                        // Selected row is a bucket - collapse it
6007                        self.s3_state.expanded_prefixes.remove(&bucket.name);
6008                        break;
6009                    }
6010                    row_idx += 1;
6011                    if self.s3_state.expanded_prefixes.contains(&bucket.name) {
6012                        if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
6013                            // Recursive function to check nested items at any depth
6014                            #[allow(clippy::too_many_arguments)]
6015                            fn check_nested_collapse(
6016                                objects: &[crate::s3::Object],
6017                                row_idx: &mut usize,
6018                                target_row: usize,
6019                                expanded_prefixes: &mut std::collections::HashSet<String>,
6020                                prefix_preview: &std::collections::HashMap<
6021                                    String,
6022                                    Vec<crate::s3::Object>,
6023                                >,
6024                                found: &mut bool,
6025                                selected_row: &mut usize,
6026                                parent_row: usize,
6027                            ) {
6028                                for obj in objects {
6029                                    let current_row = *row_idx;
6030                                    if *row_idx == target_row {
6031                                        // Selected this item - collapse or jump to parent
6032                                        if obj.is_prefix {
6033                                            if expanded_prefixes.contains(&obj.key) {
6034                                                // Expanded: collapse it
6035                                                expanded_prefixes.remove(&obj.key);
6036                                            } else {
6037                                                // Already collapsed: jump to parent
6038                                                *selected_row = parent_row;
6039                                            }
6040                                        } else {
6041                                            // Not a prefix: jump to parent
6042                                            *selected_row = parent_row;
6043                                        }
6044                                        *found = true;
6045                                        return;
6046                                    }
6047                                    *row_idx += 1;
6048
6049                                    // Recursively check nested items if expanded
6050                                    if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6051                                        if let Some(nested) = prefix_preview.get(&obj.key) {
6052                                            check_nested_collapse(
6053                                                nested,
6054                                                row_idx,
6055                                                target_row,
6056                                                expanded_prefixes,
6057                                                prefix_preview,
6058                                                found,
6059                                                selected_row,
6060                                                current_row,
6061                                            );
6062                                            if *found {
6063                                                return;
6064                                            }
6065                                        } else {
6066                                            *row_idx += 1; // Loading row
6067                                        }
6068                                    }
6069                                }
6070                            }
6071
6072                            let mut found = false;
6073                            let parent_row = row_idx - 1; // Parent is the bucket
6074                            check_nested_collapse(
6075                                preview,
6076                                &mut row_idx,
6077                                self.s3_state.selected_row,
6078                                &mut self.s3_state.expanded_prefixes,
6079                                &self.s3_state.prefix_preview,
6080                                &mut found,
6081                                &mut self.s3_state.selected_row,
6082                                parent_row,
6083                            );
6084                            if found {
6085                                // Adjust scroll offset to keep selection visible
6086                                let visible_rows = self.s3_state.bucket_visible_rows.get();
6087                                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
6088                                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
6089                                } else if self.s3_state.selected_row
6090                                    >= self.s3_state.bucket_scroll_offset + visible_rows
6091                                {
6092                                    self.s3_state.bucket_scroll_offset =
6093                                        self.s3_state.selected_row.saturating_sub(visible_rows - 1);
6094                                }
6095                                return;
6096                            }
6097                        } else {
6098                            row_idx += 1;
6099                        }
6100                    }
6101                }
6102
6103                // Adjust scroll offset to keep selection visible after collapse
6104                let visible_rows = self.s3_state.bucket_visible_rows.get();
6105                if self.s3_state.selected_row < self.s3_state.bucket_scroll_offset {
6106                    self.s3_state.bucket_scroll_offset = self.s3_state.selected_row;
6107                } else if self.s3_state.selected_row
6108                    >= self.s3_state.bucket_scroll_offset + visible_rows
6109                {
6110                    self.s3_state.bucket_scroll_offset =
6111                        self.s3_state.selected_row.saturating_sub(visible_rows - 1);
6112                }
6113            }
6114        } else if self.view_mode == ViewMode::InsightsResults {
6115            // Left arrow scrolls horizontally by 1 column
6116            self.insights_state.insights.results_horizontal_scroll = self
6117                .insights_state
6118                .insights
6119                .results_horizontal_scroll
6120                .saturating_sub(1);
6121        } else if self.current_service == Service::CloudWatchLogGroups
6122            && self.view_mode == ViewMode::List
6123        {
6124            // Collapse expanded log group
6125            if self.log_groups_state.log_groups.has_expanded_item() {
6126                self.log_groups_state.log_groups.collapse();
6127            }
6128        } else if self.current_service == Service::CloudWatchLogGroups
6129            && self.view_mode == ViewMode::Detail
6130        {
6131            // Collapse expanded log stream
6132            if self.log_groups_state.expanded_stream.is_some() {
6133                self.log_groups_state.expanded_stream = None;
6134            }
6135        } else if self.view_mode == ViewMode::Events {
6136            // Collapse expanded event
6137            if self.log_groups_state.expanded_event.is_some() {
6138                self.log_groups_state.expanded_event = None;
6139            }
6140        } else if self.current_service == Service::CloudWatchAlarms {
6141            // Collapse expanded alarm
6142            self.alarms_state.table.collapse();
6143        } else if self.current_service == Service::Ec2Instances {
6144            self.ec2_state.table.collapse();
6145        } else if self.current_service == Service::EcrRepositories {
6146            if self.ecr_state.current_repository.is_some() {
6147                // In images view - collapse expanded image
6148                self.ecr_state.images.collapse();
6149            } else {
6150                // In repositories view - collapse expanded repository
6151                self.ecr_state.repositories.collapse();
6152            }
6153        } else if self.current_service == Service::SqsQueues {
6154            if self.sqs_state.current_queue.is_some()
6155                && self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers
6156            {
6157                self.sqs_state.triggers.collapse();
6158            } else if self.sqs_state.current_queue.is_some()
6159                && self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes
6160            {
6161                self.sqs_state.pipes.collapse();
6162            } else if self.sqs_state.current_queue.is_some()
6163                && self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging
6164            {
6165                self.sqs_state.tags.collapse();
6166            } else if self.sqs_state.current_queue.is_some()
6167                && self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions
6168            {
6169                self.sqs_state.subscriptions.collapse();
6170            } else {
6171                self.sqs_state.queues.collapse();
6172            }
6173        } else if self.current_service == Service::LambdaFunctions {
6174            if self.lambda_state.current_function.is_some()
6175                && self.lambda_state.detail_tab == LambdaDetailTab::Code
6176            {
6177                // Collapse selected layer
6178                self.lambda_state.layer_expanded = None;
6179            } else if self.lambda_state.current_function.is_some()
6180                && self.lambda_state.detail_tab == LambdaDetailTab::Versions
6181            {
6182                // Collapse selected version
6183                self.lambda_state.version_table.collapse();
6184            } else if self.lambda_state.current_function.is_some()
6185                && (self.lambda_state.detail_tab == LambdaDetailTab::Aliases
6186                    || (self.lambda_state.current_version.is_some()
6187                        && self.lambda_state.detail_tab == LambdaDetailTab::Configuration))
6188            {
6189                // Collapse selected alias
6190                self.lambda_state.alias_table.collapse();
6191            } else if self.lambda_state.current_function.is_none() {
6192                // Collapse expanded function
6193                self.lambda_state.table.collapse();
6194            }
6195        } else if self.current_service == Service::LambdaApplications {
6196            if self.lambda_application_state.current_application.is_some() {
6197                // In detail view - collapse resource or deployment
6198                if self.lambda_application_state.detail_tab == LambdaApplicationDetailTab::Overview
6199                {
6200                    self.lambda_application_state.resources.collapse();
6201                } else {
6202                    self.lambda_application_state.deployments.collapse();
6203                }
6204            } else {
6205                // Collapse expanded application in list
6206                if self.lambda_application_state.table.has_expanded_item() {
6207                    self.lambda_application_state.table.collapse();
6208                }
6209            }
6210        } else if self.current_service == Service::CloudFormationStacks
6211            && self.cfn_state.current_stack.is_none()
6212        {
6213            self.cfn_state.table.collapse();
6214        } else if self.current_service == Service::CloudFormationStacks
6215            && self.cfn_state.current_stack.is_some()
6216            && self.cfn_state.detail_tab == CfnDetailTab::Parameters
6217        {
6218            self.cfn_state.parameters.collapse();
6219        } else if self.current_service == Service::CloudFormationStacks
6220            && self.cfn_state.current_stack.is_some()
6221            && self.cfn_state.detail_tab == CfnDetailTab::Outputs
6222        {
6223            self.cfn_state.outputs.collapse();
6224        } else if self.current_service == Service::CloudFormationStacks
6225            && self.cfn_state.current_stack.is_some()
6226            && self.cfn_state.detail_tab == CfnDetailTab::Resources
6227        {
6228            self.cfn_state.resources.collapse();
6229        } else if self.current_service == Service::IamUsers {
6230            if self.iam_state.users.has_expanded_item() {
6231                self.iam_state.users.collapse();
6232            }
6233        } else if self.current_service == Service::IamRoles {
6234            if self.view_mode == ViewMode::PolicyView {
6235                // Go back from policy view to role detail
6236                self.view_mode = ViewMode::Detail;
6237                self.iam_state.current_policy = None;
6238                self.iam_state.policy_document.clear();
6239                self.iam_state.policy_scroll = 0;
6240            } else if self.iam_state.current_role.is_some() {
6241                if self.iam_state.role_tab == RoleTab::Tags
6242                    && self.iam_state.tags.has_expanded_item()
6243                {
6244                    self.iam_state.tags.collapse();
6245                } else if self.iam_state.role_tab == RoleTab::LastAccessed
6246                    && self
6247                        .iam_state
6248                        .last_accessed_services
6249                        .expanded_item
6250                        .is_some()
6251                {
6252                    self.iam_state.last_accessed_services.collapse();
6253                } else if self.iam_state.policies.has_expanded_item() {
6254                    self.iam_state.policies.collapse();
6255                }
6256            } else if self.iam_state.roles.has_expanded_item() {
6257                self.iam_state.roles.collapse();
6258            }
6259        } else if self.current_service == Service::IamUserGroups {
6260            if self.iam_state.current_group.is_some() {
6261                if self.iam_state.group_tab == GroupTab::Users
6262                    && self.iam_state.group_users.has_expanded_item()
6263                {
6264                    self.iam_state.group_users.collapse();
6265                } else if self.iam_state.group_tab == GroupTab::Permissions
6266                    && self.iam_state.policies.has_expanded_item()
6267                {
6268                    self.iam_state.policies.collapse();
6269                } else if self.iam_state.group_tab == GroupTab::AccessAdvisor
6270                    && self
6271                        .iam_state
6272                        .last_accessed_services
6273                        .expanded_item
6274                        .is_some()
6275                {
6276                    self.iam_state.last_accessed_services.collapse();
6277                }
6278            } else if self.iam_state.groups.has_expanded_item() {
6279                self.iam_state.groups.collapse();
6280            }
6281        }
6282    }
6283
6284    fn select_item(&mut self) {
6285        if self.mode == Mode::RegionPicker {
6286            let filtered = self.get_filtered_regions();
6287            if let Some(region) = filtered.get(self.region_picker_selected) {
6288                // Save current session before changing region
6289                if !self.tabs.is_empty() {
6290                    let mut session = Session::new(
6291                        self.profile.clone(),
6292                        self.region.clone(),
6293                        self.config.account_id.clone(),
6294                        self.config.role_arn.clone(),
6295                    );
6296
6297                    for tab in &self.tabs {
6298                        session.tabs.push(SessionTab {
6299                            service: format!("{:?}", tab.service),
6300                            title: tab.title.clone(),
6301                            breadcrumb: tab.breadcrumb.clone(),
6302                            filter: None,
6303                            selected_item: None,
6304                        });
6305                    }
6306
6307                    let _ = session.save();
6308                }
6309
6310                self.region = region.code.to_string();
6311                self.config.region = region.code.to_string();
6312
6313                // Close all tabs - region change invalidates all data
6314                self.tabs.clear();
6315                self.current_tab = 0;
6316                self.service_selected = false;
6317
6318                self.mode = Mode::Normal;
6319            }
6320        } else if self.mode == Mode::ProfilePicker {
6321            let filtered = self.get_filtered_profiles();
6322            if let Some(profile) = filtered.get(self.profile_picker_selected) {
6323                let profile_name = profile.name.clone();
6324                let profile_region = profile.region.clone();
6325
6326                self.profile = profile_name.clone();
6327                std::env::set_var("AWS_PROFILE", &profile_name);
6328
6329                // Use profile's region if available
6330                if let Some(region) = profile_region {
6331                    self.region = region;
6332                }
6333
6334                self.mode = Mode::Normal;
6335                // Note: Changing profile requires reconnecting to AWS
6336            }
6337        } else if self.mode == Mode::ServicePicker {
6338            let filtered = self.filtered_services();
6339            if let Some(&service) = filtered.get(self.service_picker.selected) {
6340                let new_service = match service {
6341                    "CloudWatch > Log Groups" => Service::CloudWatchLogGroups,
6342                    "CloudWatch > Logs Insights" => Service::CloudWatchInsights,
6343                    "CloudWatch > Alarms" => Service::CloudWatchAlarms,
6344                    "CloudFormation > Stacks" => Service::CloudFormationStacks,
6345                    "EC2 > Instances" => Service::Ec2Instances,
6346                    "ECR > Repositories" => Service::EcrRepositories,
6347                    "IAM > Users" => Service::IamUsers,
6348                    "IAM > Roles" => Service::IamRoles,
6349                    "IAM > User Groups" => Service::IamUserGroups,
6350                    "Lambda > Functions" => Service::LambdaFunctions,
6351                    "Lambda > Applications" => Service::LambdaApplications,
6352                    "S3 > Buckets" => Service::S3Buckets,
6353                    "SQS > Queues" => Service::SqsQueues,
6354                    _ => return,
6355                };
6356
6357                // Create new tab
6358                self.tabs.push(Tab {
6359                    service: new_service,
6360                    title: service.to_string(),
6361                    breadcrumb: service.to_string(),
6362                });
6363                self.current_tab = self.tabs.len() - 1;
6364                self.current_service = new_service;
6365                self.view_mode = ViewMode::List;
6366                self.service_selected = true;
6367                self.mode = Mode::Normal;
6368            }
6369        } else if self.mode == Mode::TabPicker {
6370            let filtered = self.get_filtered_tabs();
6371            if let Some(&(idx, _)) = filtered.get(self.tab_picker_selected) {
6372                self.current_tab = idx;
6373                self.current_service = self.tabs[idx].service;
6374                self.mode = Mode::Normal;
6375                self.tab_filter.clear();
6376            }
6377        } else if self.mode == Mode::SessionPicker {
6378            let filtered = self.get_filtered_sessions();
6379            if let Some(&session) = filtered.get(self.session_picker_selected) {
6380                let session = session.clone();
6381
6382                // Load the selected session
6383                self.current_session = Some(session.clone());
6384                self.profile = session.profile.clone();
6385                self.region = session.region.clone();
6386                self.config.region = session.region.clone();
6387                self.config.account_id = session.account_id.clone();
6388                self.config.role_arn = session.role_arn.clone();
6389
6390                // Restore tabs
6391                self.tabs = session
6392                    .tabs
6393                    .iter()
6394                    .map(|st| Tab {
6395                        service: match st.service.as_str() {
6396                            "CloudWatchLogGroups" => Service::CloudWatchLogGroups,
6397                            "CloudWatchInsights" => Service::CloudWatchInsights,
6398                            "CloudWatchAlarms" => Service::CloudWatchAlarms,
6399                            "S3Buckets" => Service::S3Buckets,
6400                            "SqsQueues" => Service::SqsQueues,
6401                            _ => Service::CloudWatchLogGroups,
6402                        },
6403                        title: st.title.clone(),
6404                        breadcrumb: st.breadcrumb.clone(),
6405                    })
6406                    .collect();
6407
6408                if !self.tabs.is_empty() {
6409                    self.current_tab = 0;
6410                    self.current_service = self.tabs[0].service;
6411                    self.service_selected = true;
6412                }
6413
6414                self.mode = Mode::Normal;
6415            }
6416        } else if self.mode == Mode::InsightsInput {
6417            // In InsightsInput mode, behavior depends on focus
6418            use crate::app::InsightsFocus;
6419            match self.insights_state.insights.insights_focus {
6420                InsightsFocus::Query => {
6421                    // Add newline to query
6422                    self.insights_state.insights.query_text.push('\n');
6423                    self.insights_state.insights.query_cursor_line += 1;
6424                    self.insights_state.insights.query_cursor_col = 0;
6425                }
6426                InsightsFocus::LogGroupSearch => {
6427                    // Toggle dropdown
6428                    self.insights_state.insights.show_dropdown =
6429                        !self.insights_state.insights.show_dropdown;
6430                }
6431                _ => {}
6432            }
6433        } else if self.mode == Mode::Normal {
6434            // If no service selected, select from service picker
6435            if !self.service_selected {
6436                let filtered = self.filtered_services();
6437                if let Some(&service) = filtered.get(self.service_picker.selected) {
6438                    match service {
6439                        "CloudWatch > Log Groups" => {
6440                            self.current_service = Service::CloudWatchLogGroups;
6441                            self.view_mode = ViewMode::List;
6442                            self.service_selected = true;
6443                        }
6444                        "CloudWatch > Logs Insights" => {
6445                            self.current_service = Service::CloudWatchInsights;
6446                            self.view_mode = ViewMode::InsightsResults;
6447                            self.service_selected = true;
6448                        }
6449                        "CloudWatch > Alarms" => {
6450                            self.current_service = Service::CloudWatchAlarms;
6451                            self.view_mode = ViewMode::List;
6452                            self.service_selected = true;
6453                        }
6454                        "S3 > Buckets" => {
6455                            self.current_service = Service::S3Buckets;
6456                            self.view_mode = ViewMode::List;
6457                            self.service_selected = true;
6458                        }
6459                        "EC2 > Instances" => {
6460                            self.current_service = Service::Ec2Instances;
6461                            self.view_mode = ViewMode::List;
6462                            self.service_selected = true;
6463                        }
6464                        "ECR > Repositories" => {
6465                            self.current_service = Service::EcrRepositories;
6466                            self.view_mode = ViewMode::List;
6467                            self.service_selected = true;
6468                        }
6469                        "Lambda > Functions" => {
6470                            self.current_service = Service::LambdaFunctions;
6471                            self.view_mode = ViewMode::List;
6472                            self.service_selected = true;
6473                        }
6474                        "Lambda > Applications" => {
6475                            self.current_service = Service::LambdaApplications;
6476                            self.view_mode = ViewMode::List;
6477                            self.service_selected = true;
6478                        }
6479                        _ => {}
6480                    }
6481                }
6482                return;
6483            }
6484
6485            // Select in content area
6486            if self.view_mode == ViewMode::InsightsResults {
6487                // Toggle expand for selected result
6488                if self.insights_state.insights.expanded_result
6489                    == Some(self.insights_state.insights.results_selected)
6490                {
6491                    self.insights_state.insights.expanded_result = None;
6492                } else {
6493                    self.insights_state.insights.expanded_result =
6494                        Some(self.insights_state.insights.results_selected);
6495                }
6496            } else if self.current_service == Service::S3Buckets {
6497                if self.s3_state.current_bucket.is_none() {
6498                    // Find which bucket/prefix the selected row corresponds to
6499                    let mut row_idx = 0;
6500                    for bucket in &self.s3_state.buckets.items {
6501                        if row_idx == self.s3_state.selected_row {
6502                            // Selected a bucket - drill into it
6503                            self.s3_state.current_bucket = Some(bucket.name.clone());
6504                            self.s3_state.prefix_stack.clear();
6505                            self.s3_state.buckets.loading = true;
6506                            return;
6507                        }
6508                        row_idx += 1;
6509
6510                        // Skip error rows - they're not selectable
6511                        if self.s3_state.bucket_errors.contains_key(&bucket.name)
6512                            && self.s3_state.expanded_prefixes.contains(&bucket.name)
6513                        {
6514                            continue;
6515                        }
6516
6517                        if self.s3_state.expanded_prefixes.contains(&bucket.name) {
6518                            if let Some(preview) = self.s3_state.bucket_preview.get(&bucket.name) {
6519                                for obj in preview {
6520                                    if row_idx == self.s3_state.selected_row {
6521                                        // Selected a prefix - drill into bucket with this prefix
6522                                        if obj.is_prefix {
6523                                            self.s3_state.current_bucket =
6524                                                Some(bucket.name.clone());
6525                                            self.s3_state.prefix_stack = vec![obj.key.clone()];
6526                                            self.s3_state.buckets.loading = true;
6527                                        }
6528                                        return;
6529                                    }
6530                                    row_idx += 1;
6531
6532                                    // Check nested preview rows
6533                                    if obj.is_prefix
6534                                        && self.s3_state.expanded_prefixes.contains(&obj.key)
6535                                    {
6536                                        if let Some(nested) =
6537                                            self.s3_state.prefix_preview.get(&obj.key)
6538                                        {
6539                                            for nested_obj in nested {
6540                                                if row_idx == self.s3_state.selected_row {
6541                                                    // Selected a nested prefix - drill into bucket with this prefix
6542                                                    if nested_obj.is_prefix {
6543                                                        self.s3_state.current_bucket =
6544                                                            Some(bucket.name.clone());
6545                                                        // Build proper prefix stack: parent, then child
6546                                                        self.s3_state.prefix_stack = vec![
6547                                                            obj.key.clone(),
6548                                                            nested_obj.key.clone(),
6549                                                        ];
6550                                                        self.s3_state.buckets.loading = true;
6551                                                    }
6552                                                    return;
6553                                                }
6554                                                row_idx += 1;
6555                                            }
6556                                        } else {
6557                                            row_idx += 1;
6558                                        }
6559                                    }
6560                                }
6561                            } else {
6562                                row_idx += 1;
6563                            }
6564                        }
6565                    }
6566                } else {
6567                    // In objects view - map visual index to actual object (including nested items)
6568                    let mut visual_idx = 0;
6569                    let mut found_obj: Option<S3Object> = None;
6570
6571                    // Helper to recursively check nested items
6572                    fn check_nested_select(
6573                        obj: &S3Object,
6574                        visual_idx: &mut usize,
6575                        target_idx: usize,
6576                        expanded_prefixes: &std::collections::HashSet<String>,
6577                        prefix_preview: &std::collections::HashMap<String, Vec<S3Object>>,
6578                        found_obj: &mut Option<S3Object>,
6579                    ) {
6580                        if obj.is_prefix && expanded_prefixes.contains(&obj.key) {
6581                            if let Some(preview) = prefix_preview.get(&obj.key) {
6582                                for nested_obj in preview {
6583                                    if *visual_idx == target_idx {
6584                                        *found_obj = Some(nested_obj.clone());
6585                                        return;
6586                                    }
6587                                    *visual_idx += 1;
6588
6589                                    // Recursively check deeper levels
6590                                    check_nested_select(
6591                                        nested_obj,
6592                                        visual_idx,
6593                                        target_idx,
6594                                        expanded_prefixes,
6595                                        prefix_preview,
6596                                        found_obj,
6597                                    );
6598                                    if found_obj.is_some() {
6599                                        return;
6600                                    }
6601                                }
6602                            } else {
6603                                // Loading row
6604                                *visual_idx += 1;
6605                            }
6606                        }
6607                    }
6608
6609                    for obj in &self.s3_state.objects {
6610                        if visual_idx == self.s3_state.selected_object {
6611                            found_obj = Some(obj.clone());
6612                            break;
6613                        }
6614                        visual_idx += 1;
6615
6616                        // Check nested items recursively
6617                        check_nested_select(
6618                            obj,
6619                            &mut visual_idx,
6620                            self.s3_state.selected_object,
6621                            &self.s3_state.expanded_prefixes,
6622                            &self.s3_state.prefix_preview,
6623                            &mut found_obj,
6624                        );
6625                        if found_obj.is_some() {
6626                            break;
6627                        }
6628                    }
6629
6630                    if let Some(obj) = found_obj {
6631                        if obj.is_prefix {
6632                            // Drill into prefix
6633                            self.s3_state.prefix_stack.push(obj.key.clone());
6634                            self.s3_state.buckets.loading = true;
6635                        }
6636                    }
6637                }
6638            } else if self.current_service == Service::CloudFormationStacks {
6639                if self.cfn_state.current_stack.is_none() {
6640                    // Drill into stack detail view
6641                    let filtered_stacks = self.filtered_cloudformation_stacks();
6642                    if let Some(stack) = self.cfn_state.table.get_selected(&filtered_stacks) {
6643                        let stack_name = stack.name.clone();
6644                        let mut tags = stack.tags.clone();
6645                        tags.sort_by(|a, b| a.0.cmp(&b.0));
6646
6647                        self.cfn_state.current_stack = Some(stack_name);
6648                        self.cfn_state.tags.items = tags;
6649                        self.cfn_state.tags.reset();
6650                        self.cfn_state.table.loading = true;
6651                        self.update_current_tab_breadcrumb();
6652                    }
6653                }
6654            } else if self.current_service == Service::EcrRepositories {
6655                if self.ecr_state.current_repository.is_none() {
6656                    // In repositories view - drill into selected repository
6657                    let filtered_repos = self.filtered_ecr_repositories();
6658                    if let Some(repo) = self.ecr_state.repositories.get_selected(&filtered_repos) {
6659                        let repo_name = repo.name.clone();
6660                        let repo_uri = repo.uri.clone();
6661                        self.ecr_state.current_repository = Some(repo_name);
6662                        self.ecr_state.current_repository_uri = Some(repo_uri);
6663                        self.ecr_state.images.reset();
6664                        self.ecr_state.repositories.loading = true;
6665                    }
6666                }
6667            } else if self.current_service == Service::SqsQueues {
6668                if self.sqs_state.current_queue.is_none() {
6669                    let filtered_queues = crate::ui::sqs::filtered_queues(
6670                        &self.sqs_state.queues.items,
6671                        &self.sqs_state.queues.filter,
6672                    );
6673                    if let Some(queue) = self.sqs_state.queues.get_selected(&filtered_queues) {
6674                        self.sqs_state.current_queue = Some(queue.url.clone());
6675
6676                        if self.sqs_state.detail_tab == SqsQueueDetailTab::Monitoring {
6677                            self.sqs_state.metrics_loading = true;
6678                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::LambdaTriggers {
6679                            self.sqs_state.triggers.loading = true;
6680                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::EventBridgePipes {
6681                            self.sqs_state.pipes.loading = true;
6682                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::Tagging {
6683                            self.sqs_state.tags.loading = true;
6684                        } else if self.sqs_state.detail_tab == SqsQueueDetailTab::SnsSubscriptions {
6685                            self.sqs_state.subscriptions.loading = true;
6686                        }
6687                    }
6688                }
6689            } else if self.current_service == Service::IamUsers {
6690                if self.iam_state.current_user.is_some() {
6691                    // Open policy view (but not on Tags tab)
6692                    if self.iam_state.user_tab != UserTab::Tags {
6693                        let filtered = crate::ui::iam::filtered_iam_policies(self);
6694                        if let Some(policy) = self.iam_state.policies.get_selected(&filtered) {
6695                            self.iam_state.current_policy = Some(policy.policy_name.clone());
6696                            self.iam_state.policy_scroll = 0;
6697                            self.view_mode = ViewMode::PolicyView;
6698                            self.iam_state.policies.loading = true;
6699                            self.update_current_tab_breadcrumb();
6700                        }
6701                    }
6702                } else if self.iam_state.current_user.is_none() {
6703                    let filtered_users = crate::ui::iam::filtered_iam_users(self);
6704                    if let Some(user) = self.iam_state.users.get_selected(&filtered_users) {
6705                        self.iam_state.current_user = Some(user.user_name.clone());
6706                        self.iam_state.user_tab = UserTab::Permissions;
6707                        self.iam_state.policies.reset();
6708                        self.update_current_tab_breadcrumb();
6709                    }
6710                }
6711            } else if self.current_service == Service::IamRoles {
6712                if self.iam_state.current_role.is_some() {
6713                    // Open policy view (but not on Tags tab)
6714                    if self.iam_state.role_tab != RoleTab::Tags {
6715                        let filtered = crate::ui::iam::filtered_iam_policies(self);
6716                        if let Some(policy) = self.iam_state.policies.get_selected(&filtered) {
6717                            self.iam_state.current_policy = Some(policy.policy_name.clone());
6718                            self.iam_state.policy_scroll = 0;
6719                            self.view_mode = ViewMode::PolicyView;
6720                            self.iam_state.policies.loading = true;
6721                            self.update_current_tab_breadcrumb();
6722                        }
6723                    }
6724                } else if self.iam_state.current_role.is_none() {
6725                    let filtered_roles = crate::ui::iam::filtered_iam_roles(self);
6726                    if let Some(role) = self.iam_state.roles.get_selected(&filtered_roles) {
6727                        self.iam_state.current_role = Some(role.role_name.clone());
6728                        self.iam_state.role_tab = RoleTab::Permissions;
6729                        self.iam_state.policies.reset();
6730                        self.update_current_tab_breadcrumb();
6731                    }
6732                }
6733            } else if self.current_service == Service::IamUserGroups {
6734                if self.iam_state.current_group.is_none() {
6735                    let filtered_groups: Vec<_> = self
6736                        .iam_state
6737                        .groups
6738                        .items
6739                        .iter()
6740                        .filter(|g| {
6741                            if self.iam_state.groups.filter.is_empty() {
6742                                true
6743                            } else {
6744                                g.group_name
6745                                    .to_lowercase()
6746                                    .contains(&self.iam_state.groups.filter.to_lowercase())
6747                            }
6748                        })
6749                        .collect();
6750                    if let Some(group) = self.iam_state.groups.get_selected(&filtered_groups) {
6751                        self.iam_state.current_group = Some(group.group_name.clone());
6752                        self.update_current_tab_breadcrumb();
6753                    }
6754                }
6755            } else if self.current_service == Service::LambdaFunctions {
6756                if self.lambda_state.current_function.is_some()
6757                    && self.lambda_state.detail_tab == LambdaDetailTab::Versions
6758                {
6759                    // In Normal mode, select version to open detail view
6760                    // In other modes (FilterInput), toggle expansion
6761                    if self.mode == Mode::Normal {
6762                        let page_size = self.lambda_state.version_table.page_size.value();
6763                        let filtered: Vec<_> = self
6764                            .lambda_state
6765                            .version_table
6766                            .items
6767                            .iter()
6768                            .filter(|v| {
6769                                self.lambda_state.version_table.filter.is_empty()
6770                                    || v.version.to_lowercase().contains(
6771                                        &self.lambda_state.version_table.filter.to_lowercase(),
6772                                    )
6773                                    || v.aliases.to_lowercase().contains(
6774                                        &self.lambda_state.version_table.filter.to_lowercase(),
6775                                    )
6776                            })
6777                            .collect();
6778                        let current_page = self.lambda_state.version_table.selected / page_size;
6779                        let start_idx = current_page * page_size;
6780                        let end_idx = (start_idx + page_size).min(filtered.len());
6781                        let paginated: Vec<_> = filtered[start_idx..end_idx].to_vec();
6782                        let page_index = self.lambda_state.version_table.selected % page_size;
6783                        if let Some(version) = paginated.get(page_index) {
6784                            self.lambda_state.current_version = Some(version.version.clone());
6785                            self.lambda_state.detail_tab = LambdaDetailTab::Code;
6786                        }
6787                    } else {
6788                        // Toggle expansion
6789                        if self.lambda_state.version_table.expanded_item
6790                            == Some(self.lambda_state.version_table.selected)
6791                        {
6792                            self.lambda_state.version_table.collapse();
6793                        } else {
6794                            self.lambda_state.version_table.expanded_item =
6795                                Some(self.lambda_state.version_table.selected);
6796                        }
6797                    }
6798                } else if self.lambda_state.current_function.is_some()
6799                    && self.lambda_state.detail_tab == LambdaDetailTab::Aliases
6800                {
6801                    // Select alias to view detail (no tab change - alias view has no tabs)
6802                    let filtered: Vec<_> = self
6803                        .lambda_state
6804                        .alias_table
6805                        .items
6806                        .iter()
6807                        .filter(|a| {
6808                            self.lambda_state.alias_table.filter.is_empty()
6809                                || a.name
6810                                    .to_lowercase()
6811                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
6812                                || a.versions
6813                                    .to_lowercase()
6814                                    .contains(&self.lambda_state.alias_table.filter.to_lowercase())
6815                        })
6816                        .collect();
6817                    if let Some(alias) = self.lambda_state.alias_table.get_selected(&filtered) {
6818                        self.lambda_state.current_alias = Some(alias.name.clone());
6819                    }
6820                } else if self.lambda_state.current_function.is_none() {
6821                    let filtered_functions = crate::ui::lambda::filtered_lambda_functions(self);
6822                    if let Some(func) = self.lambda_state.table.get_selected(&filtered_functions) {
6823                        self.lambda_state.current_function = Some(func.name.clone());
6824                        self.lambda_state.detail_tab = LambdaDetailTab::Code;
6825                        self.update_current_tab_breadcrumb();
6826                    }
6827                }
6828            } else if self.current_service == Service::LambdaApplications {
6829                let filtered = crate::ui::lambda::filtered_lambda_applications(self);
6830                if let Some(app) = self.lambda_application_state.table.get_selected(&filtered) {
6831                    let app_name = app.name.clone();
6832                    self.lambda_application_state.current_application = Some(app_name.clone());
6833                    self.lambda_application_state.detail_tab = LambdaApplicationDetailTab::Overview;
6834
6835                    // Load mock resources
6836                    use crate::lambda::Resource;
6837                    self.lambda_application_state.resources.items = vec![
6838                        Resource {
6839                            logical_id: "ApiGatewayRestApi".to_string(),
6840                            physical_id: "abc123xyz".to_string(),
6841                            resource_type: "AWS::ApiGateway::RestApi".to_string(),
6842                            last_modified: "2025-01-10 14:30:00 (UTC)".to_string(),
6843                        },
6844                        Resource {
6845                            logical_id: "LambdaFunction".to_string(),
6846                            physical_id: format!("{}-function", app_name),
6847                            resource_type: "AWS::Lambda::Function".to_string(),
6848                            last_modified: "2025-01-10 14:25:00 (UTC)".to_string(),
6849                        },
6850                        Resource {
6851                            logical_id: "DynamoDBTable".to_string(),
6852                            physical_id: format!("{}-table", app_name),
6853                            resource_type: "AWS::DynamoDB::Table".to_string(),
6854                            last_modified: "2025-01-09 10:15:00 (UTC)".to_string(),
6855                        },
6856                    ];
6857
6858                    // Load mock deployments
6859                    use crate::lambda::Deployment;
6860                    self.lambda_application_state.deployments.items = vec![
6861                        Deployment {
6862                            deployment_id: "d-ABC123XYZ".to_string(),
6863                            resource_type: "AWS::Serverless::Application".to_string(),
6864                            last_updated: "2025-01-10 14:30:00 (UTC)".to_string(),
6865                            status: "Succeeded".to_string(),
6866                        },
6867                        Deployment {
6868                            deployment_id: "d-DEF456UVW".to_string(),
6869                            resource_type: "AWS::Serverless::Application".to_string(),
6870                            last_updated: "2025-01-09 10:15:00 (UTC)".to_string(),
6871                            status: "Succeeded".to_string(),
6872                        },
6873                    ];
6874
6875                    self.update_current_tab_breadcrumb();
6876                }
6877            } else if self.current_service == Service::CloudWatchLogGroups {
6878                if self.view_mode == ViewMode::List {
6879                    // Map filtered selection to actual group index
6880                    let filtered_groups = self.filtered_log_groups();
6881                    if let Some(selected_group) =
6882                        filtered_groups.get(self.log_groups_state.log_groups.selected)
6883                    {
6884                        if let Some(actual_idx) = self
6885                            .log_groups_state
6886                            .log_groups
6887                            .items
6888                            .iter()
6889                            .position(|g| g.name == selected_group.name)
6890                        {
6891                            self.log_groups_state.log_groups.selected = actual_idx;
6892                        }
6893                    }
6894                    self.view_mode = ViewMode::Detail;
6895                    self.log_groups_state.log_streams.clear();
6896                    self.log_groups_state.selected_stream = 0;
6897                    self.log_groups_state.loading = true;
6898                    self.update_current_tab_breadcrumb();
6899                } else if self.view_mode == ViewMode::Detail {
6900                    // Map filtered stream selection to actual stream index
6901                    let filtered_streams = self.filtered_log_streams();
6902                    if let Some(selected_stream) =
6903                        filtered_streams.get(self.log_groups_state.selected_stream)
6904                    {
6905                        if let Some(actual_idx) = self
6906                            .log_groups_state
6907                            .log_streams
6908                            .iter()
6909                            .position(|s| s.name == selected_stream.name)
6910                        {
6911                            self.log_groups_state.selected_stream = actual_idx;
6912                        }
6913                    }
6914                    self.view_mode = ViewMode::Events;
6915                    self.update_current_tab_breadcrumb();
6916                    self.log_groups_state.log_events.clear();
6917                    self.log_groups_state.event_scroll_offset = 0;
6918                    self.log_groups_state.next_backward_token = None;
6919                    self.log_groups_state.loading = true;
6920                } else if self.view_mode == ViewMode::Events {
6921                    // Toggle expand for selected event
6922                    if self.log_groups_state.expanded_event
6923                        == Some(self.log_groups_state.event_scroll_offset)
6924                    {
6925                        self.log_groups_state.expanded_event = None;
6926                    } else {
6927                        self.log_groups_state.expanded_event =
6928                            Some(self.log_groups_state.event_scroll_offset);
6929                    }
6930                }
6931            } else if self.current_service == Service::CloudWatchAlarms {
6932                // Toggle expand for selected alarm
6933                self.alarms_state.table.toggle_expand();
6934            } else if self.current_service == Service::CloudWatchInsights {
6935                // In Normal mode, Enter always executes query
6936                if !self.insights_state.insights.selected_log_groups.is_empty() {
6937                    self.log_groups_state.loading = true;
6938                    self.insights_state.insights.query_completed = true;
6939                }
6940            }
6941        }
6942    }
6943
6944    pub async fn load_log_groups(&mut self) -> anyhow::Result<()> {
6945        self.log_groups_state.log_groups.items = self.cloudwatch_client.list_log_groups().await?;
6946        Ok(())
6947    }
6948
6949    pub async fn load_alarms(&mut self) -> anyhow::Result<()> {
6950        let alarms = self.alarms_client.list_alarms().await?;
6951        self.alarms_state.table.items = alarms
6952            .into_iter()
6953            .map(
6954                |(
6955                    name,
6956                    state,
6957                    state_updated,
6958                    description,
6959                    metric_name,
6960                    namespace,
6961                    statistic,
6962                    period,
6963                    comparison,
6964                    threshold,
6965                    actions_enabled,
6966                    state_reason,
6967                    resource,
6968                    dimensions,
6969                    expression,
6970                    alarm_type,
6971                    cross_account,
6972                )| Alarm {
6973                    name,
6974                    state,
6975                    state_updated_timestamp: state_updated,
6976                    description,
6977                    metric_name,
6978                    namespace,
6979                    statistic,
6980                    period,
6981                    comparison_operator: comparison,
6982                    threshold,
6983                    actions_enabled,
6984                    state_reason,
6985                    resource,
6986                    dimensions,
6987                    expression,
6988                    alarm_type,
6989                    cross_account,
6990                },
6991            )
6992            .collect();
6993        Ok(())
6994    }
6995
6996    pub async fn load_s3_objects(&mut self) -> anyhow::Result<()> {
6997        if let Some(bucket_name) = &self.s3_state.current_bucket {
6998            // Get or fetch bucket region
6999            let bucket_region = if let Some(bucket) = self
7000                .s3_state
7001                .buckets
7002                .items
7003                .iter_mut()
7004                .find(|b| &b.name == bucket_name)
7005            {
7006                if bucket.region.is_empty() {
7007                    // Fetch the actual region
7008                    let region = self.s3_client.get_bucket_location(bucket_name).await?;
7009                    bucket.region = region.clone();
7010                    region
7011                } else {
7012                    bucket.region.clone()
7013                }
7014            } else {
7015                self.config.region.clone()
7016            };
7017
7018            let prefix = self
7019                .s3_state
7020                .prefix_stack
7021                .last()
7022                .cloned()
7023                .unwrap_or_default();
7024            let objects = self
7025                .s3_client
7026                .list_objects(bucket_name, &bucket_region, &prefix)
7027                .await?;
7028            self.s3_state.objects = objects
7029                .into_iter()
7030                .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
7031                    key,
7032                    size,
7033                    last_modified: modified,
7034                    is_prefix,
7035                    storage_class,
7036                })
7037                .collect();
7038            self.s3_state.selected_object = 0;
7039        }
7040        Ok(())
7041    }
7042
7043    pub async fn load_bucket_preview(&mut self, bucket_name: String) -> anyhow::Result<()> {
7044        let bucket_region = self
7045            .s3_state
7046            .buckets
7047            .items
7048            .iter()
7049            .find(|b| b.name == bucket_name)
7050            .and_then(|b| {
7051                if b.region.is_empty() {
7052                    None
7053                } else {
7054                    Some(b.region.as_str())
7055                }
7056            })
7057            .unwrap_or(self.config.region.as_str());
7058        let objects = self
7059            .s3_client
7060            .list_objects(&bucket_name, bucket_region, "")
7061            .await?;
7062        let preview: Vec<S3Object> = objects
7063            .into_iter()
7064            .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
7065                key,
7066                size,
7067                last_modified: modified,
7068                is_prefix,
7069                storage_class,
7070            })
7071            .collect();
7072        self.s3_state.bucket_preview.insert(bucket_name, preview);
7073        Ok(())
7074    }
7075
7076    pub async fn load_prefix_preview(
7077        &mut self,
7078        bucket_name: String,
7079        prefix: String,
7080    ) -> anyhow::Result<()> {
7081        let bucket_region = self
7082            .s3_state
7083            .buckets
7084            .items
7085            .iter()
7086            .find(|b| b.name == bucket_name)
7087            .and_then(|b| {
7088                if b.region.is_empty() {
7089                    None
7090                } else {
7091                    Some(b.region.as_str())
7092                }
7093            })
7094            .unwrap_or(self.config.region.as_str());
7095        let objects = self
7096            .s3_client
7097            .list_objects(&bucket_name, bucket_region, &prefix)
7098            .await?;
7099        let preview: Vec<S3Object> = objects
7100            .into_iter()
7101            .map(|(key, size, modified, is_prefix, storage_class)| S3Object {
7102                key,
7103                size,
7104                last_modified: modified,
7105                is_prefix,
7106                storage_class,
7107            })
7108            .collect();
7109        self.s3_state.prefix_preview.insert(prefix, preview);
7110        Ok(())
7111    }
7112
7113    pub async fn load_ecr_repositories(&mut self) -> anyhow::Result<()> {
7114        let repos = match self.ecr_state.tab {
7115            EcrTab::Private => self.ecr_client.list_private_repositories().await?,
7116            EcrTab::Public => self.ecr_client.list_public_repositories().await?,
7117        };
7118
7119        self.ecr_state.repositories.items = repos
7120            .into_iter()
7121            .map(|r| EcrRepository {
7122                name: r.name,
7123                uri: r.uri,
7124                created_at: r.created_at,
7125                tag_immutability: r.tag_immutability,
7126                encryption_type: r.encryption_type,
7127            })
7128            .collect();
7129
7130        self.ecr_state
7131            .repositories
7132            .items
7133            .sort_by(|a, b| a.name.cmp(&b.name));
7134        Ok(())
7135    }
7136
7137    pub async fn load_ec2_instances(&mut self) -> anyhow::Result<()> {
7138        let instances = self.ec2_client.list_instances().await?;
7139
7140        self.ec2_state.table.items = instances
7141            .into_iter()
7142            .map(|i| Ec2Instance {
7143                instance_id: i.instance_id,
7144                name: i.name,
7145                state: i.state,
7146                instance_type: i.instance_type,
7147                availability_zone: i.availability_zone,
7148                public_ipv4_dns: i.public_ipv4_dns,
7149                public_ipv4_address: i.public_ipv4_address,
7150                elastic_ip: i.elastic_ip,
7151                ipv6_ips: i.ipv6_ips,
7152                monitoring: i.monitoring,
7153                security_groups: i.security_groups,
7154                key_name: i.key_name,
7155                launch_time: i.launch_time,
7156                platform_details: i.platform_details,
7157                status_checks: i.status_checks,
7158                alarm_status: i.alarm_status,
7159                private_dns_name: String::new(),
7160                private_ip_address: String::new(),
7161                security_group_ids: String::new(),
7162                owner_id: String::new(),
7163                volume_id: String::new(),
7164                root_device_name: String::new(),
7165                root_device_type: String::new(),
7166                ebs_optimized: String::new(),
7167                image_id: String::new(),
7168                kernel_id: String::new(),
7169                ramdisk_id: String::new(),
7170                ami_launch_index: String::new(),
7171                reservation_id: String::new(),
7172                vpc_id: String::new(),
7173                subnet_ids: String::new(),
7174                instance_lifecycle: String::new(),
7175                architecture: String::new(),
7176                virtualization_type: String::new(),
7177                platform: String::new(),
7178                iam_instance_profile_arn: String::new(),
7179                tenancy: String::new(),
7180                affinity: String::new(),
7181                host_id: String::new(),
7182                placement_group: String::new(),
7183                partition_number: String::new(),
7184                capacity_reservation_id: String::new(),
7185                state_transition_reason_code: String::new(),
7186                state_transition_reason_message: String::new(),
7187                stop_hibernation_behavior: String::new(),
7188                outpost_arn: String::new(),
7189                product_codes: String::new(),
7190                availability_zone_id: String::new(),
7191                imdsv2: String::new(),
7192                usage_operation: String::new(),
7193                managed: String::new(),
7194                operator: String::new(),
7195            })
7196            .collect();
7197
7198        // Sort by launch time descending by default
7199        self.ec2_state
7200            .table
7201            .items
7202            .sort_by(|a, b| b.launch_time.cmp(&a.launch_time));
7203        Ok(())
7204    }
7205
7206    pub async fn load_ecr_images(&mut self) -> anyhow::Result<()> {
7207        if let Some(repo_name) = &self.ecr_state.current_repository {
7208            if let Some(repo_uri) = &self.ecr_state.current_repository_uri {
7209                let images = self.ecr_client.list_images(repo_name, repo_uri).await?;
7210
7211                self.ecr_state.images.items = images
7212                    .into_iter()
7213                    .map(|i| EcrImage {
7214                        tag: i.tag,
7215                        artifact_type: i.artifact_type,
7216                        pushed_at: i.pushed_at,
7217                        size_bytes: i.size_bytes,
7218                        uri: i.uri,
7219                        digest: i.digest,
7220                        last_pull_time: i.last_pull_time,
7221                    })
7222                    .collect();
7223
7224                self.ecr_state
7225                    .images
7226                    .items
7227                    .sort_by(|a, b| b.pushed_at.cmp(&a.pushed_at));
7228            }
7229        }
7230        Ok(())
7231    }
7232
7233    pub async fn load_cloudformation_stacks(&mut self) -> anyhow::Result<()> {
7234        let stacks = self
7235            .cloudformation_client
7236            .list_stacks(self.cfn_state.view_nested)
7237            .await?;
7238
7239        let mut stacks: Vec<CfnStack> = stacks
7240            .into_iter()
7241            .map(|s| CfnStack {
7242                name: s.name,
7243                stack_id: s.stack_id,
7244                status: s.status,
7245                created_time: s.created_time,
7246                updated_time: s.updated_time,
7247                deleted_time: s.deleted_time,
7248                drift_status: s.drift_status,
7249                last_drift_check_time: s.last_drift_check_time,
7250                status_reason: s.status_reason,
7251                description: s.description,
7252                detailed_status: String::new(),
7253                root_stack: String::new(),
7254                parent_stack: String::new(),
7255                termination_protection: false,
7256                iam_role: String::new(),
7257                tags: Vec::new(),
7258                stack_policy: String::new(),
7259                rollback_monitoring_time: String::new(),
7260                rollback_alarms: Vec::new(),
7261                notification_arns: Vec::new(),
7262            })
7263            .collect();
7264
7265        // Sort by created_time DESC
7266        stacks.sort_by(|a, b| b.created_time.cmp(&a.created_time));
7267
7268        self.cfn_state.table.items = stacks;
7269
7270        Ok(())
7271    }
7272
7273    pub async fn load_cfn_template(&mut self, stack_name: &str) -> anyhow::Result<()> {
7274        let template = self.cloudformation_client.get_template(stack_name).await?;
7275        self.cfn_state.template_body = template;
7276        self.cfn_state.template_scroll = 0;
7277        Ok(())
7278    }
7279
7280    pub async fn load_cfn_parameters(&mut self, stack_name: &str) -> anyhow::Result<()> {
7281        let mut parameters = self
7282            .cloudformation_client
7283            .get_stack_parameters(stack_name)
7284            .await?;
7285        parameters.sort_by(|a, b| a.key.cmp(&b.key));
7286        self.cfn_state.parameters.items = parameters;
7287        self.cfn_state.parameters.reset();
7288        Ok(())
7289    }
7290
7291    pub async fn load_cfn_outputs(&mut self, stack_name: &str) -> anyhow::Result<()> {
7292        let outputs = self
7293            .cloudformation_client
7294            .get_stack_outputs(stack_name)
7295            .await?;
7296        self.cfn_state.outputs.items = outputs;
7297        self.cfn_state.outputs.reset();
7298        Ok(())
7299    }
7300
7301    pub async fn load_cfn_resources(&mut self, stack_name: &str) -> anyhow::Result<()> {
7302        let resources = self
7303            .cloudformation_client
7304            .get_stack_resources(stack_name)
7305            .await?;
7306        self.cfn_state.resources.items = resources;
7307        self.cfn_state.resources.reset();
7308        Ok(())
7309    }
7310
7311    pub async fn load_role_policies(&mut self, role_name: &str) -> anyhow::Result<()> {
7312        // Load attached (managed) policies
7313        let attached_policies = self
7314            .iam_client
7315            .list_attached_role_policies(role_name)
7316            .await
7317            .map_err(|e| anyhow::anyhow!(e))?;
7318
7319        let mut policies: Vec<crate::iam::Policy> = attached_policies
7320            .into_iter()
7321            .map(|p| crate::iam::Policy {
7322                policy_name: p.policy_name().unwrap_or("").to_string(),
7323                policy_type: "Managed".to_string(),
7324                attached_via: "Direct".to_string(),
7325                attached_entities: "-".to_string(),
7326                description: "-".to_string(),
7327                creation_time: "-".to_string(),
7328                edited_time: "-".to_string(),
7329                policy_arn: p.policy_arn().map(|s| s.to_string()),
7330            })
7331            .collect();
7332
7333        // Load inline policies
7334        let inline_policy_names = self
7335            .iam_client
7336            .list_role_policies(role_name)
7337            .await
7338            .map_err(|e| anyhow::anyhow!(e))?;
7339
7340        for policy_name in inline_policy_names {
7341            policies.push(crate::iam::Policy {
7342                policy_name,
7343                policy_type: "Inline".to_string(),
7344                attached_via: "Direct".to_string(),
7345                attached_entities: "-".to_string(),
7346                description: "-".to_string(),
7347                creation_time: "-".to_string(),
7348                edited_time: "-".to_string(),
7349                policy_arn: None,
7350            });
7351        }
7352
7353        self.iam_state.policies.items = policies;
7354
7355        Ok(())
7356    }
7357
7358    pub async fn load_group_policies(&mut self, group_name: &str) -> anyhow::Result<()> {
7359        let attached_policies = self
7360            .iam_client
7361            .list_attached_group_policies(group_name)
7362            .await
7363            .map_err(|e| anyhow::anyhow!(e))?;
7364
7365        let mut policies: Vec<crate::iam::Policy> = attached_policies
7366            .into_iter()
7367            .map(|p| crate::iam::Policy {
7368                policy_name: p.policy_name().unwrap_or("").to_string(),
7369                policy_type: "AWS managed".to_string(),
7370                attached_via: "Direct".to_string(),
7371                attached_entities: "-".to_string(),
7372                description: "-".to_string(),
7373                creation_time: "-".to_string(),
7374                edited_time: "-".to_string(),
7375                policy_arn: p.policy_arn().map(|s| s.to_string()),
7376            })
7377            .collect();
7378
7379        let inline_policy_names = self
7380            .iam_client
7381            .list_group_policies(group_name)
7382            .await
7383            .map_err(|e| anyhow::anyhow!(e))?;
7384
7385        for policy_name in inline_policy_names {
7386            policies.push(crate::iam::Policy {
7387                policy_name,
7388                policy_type: "Inline".to_string(),
7389                attached_via: "Direct".to_string(),
7390                attached_entities: "-".to_string(),
7391                description: "-".to_string(),
7392                creation_time: "-".to_string(),
7393                edited_time: "-".to_string(),
7394                policy_arn: None,
7395            });
7396        }
7397
7398        self.iam_state.policies.items = policies;
7399
7400        Ok(())
7401    }
7402
7403    pub async fn load_group_users(&mut self, group_name: &str) -> anyhow::Result<()> {
7404        let users = self
7405            .iam_client
7406            .get_group_users(group_name)
7407            .await
7408            .map_err(|e| anyhow::anyhow!(e))?;
7409
7410        let group_users: Vec<crate::iam::GroupUser> = users
7411            .into_iter()
7412            .map(|u| {
7413                let creation_time = {
7414                    let dt = u.create_date();
7415                    let timestamp = dt.secs();
7416                    let datetime =
7417                        chrono::DateTime::from_timestamp(timestamp, 0).unwrap_or_default();
7418                    datetime.format("%Y-%m-%d %H:%M:%S (UTC)").to_string()
7419                };
7420
7421                crate::iam::GroupUser {
7422                    user_name: u.user_name().to_string(),
7423                    groups: String::new(),
7424                    last_activity: String::new(),
7425                    creation_time,
7426                }
7427            })
7428            .collect();
7429
7430        self.iam_state.group_users.items = group_users;
7431
7432        Ok(())
7433    }
7434
7435    pub async fn load_policy_document(
7436        &mut self,
7437        role_name: &str,
7438        policy_name: &str,
7439    ) -> anyhow::Result<()> {
7440        // Find the policy to get its ARN and type
7441        let policy = self
7442            .iam_state
7443            .policies
7444            .items
7445            .iter()
7446            .find(|p| p.policy_name == policy_name)
7447            .ok_or_else(|| anyhow::anyhow!("Policy not found"))?;
7448
7449        let document = if let Some(policy_arn) = &policy.policy_arn {
7450            // Managed policy - use get_policy_version
7451            self.iam_client
7452                .get_policy_version(policy_arn)
7453                .await
7454                .map_err(|e| anyhow::anyhow!(e))?
7455        } else {
7456            // Inline policy - use get_role_policy
7457            self.iam_client
7458                .get_role_policy(role_name, policy_name)
7459                .await
7460                .map_err(|e| anyhow::anyhow!(e))?
7461        };
7462
7463        self.iam_state.policy_document = document;
7464
7465        Ok(())
7466    }
7467
7468    pub async fn load_trust_policy(&mut self, role_name: &str) -> anyhow::Result<()> {
7469        let document = self
7470            .iam_client
7471            .get_role(role_name)
7472            .await
7473            .map_err(|e| anyhow::anyhow!(e))?;
7474
7475        self.iam_state.trust_policy_document = document;
7476
7477        Ok(())
7478    }
7479
7480    pub async fn load_last_accessed_services(&mut self, _role_name: &str) -> anyhow::Result<()> {
7481        // TODO: Implement real AWS API call to get service last accessed details
7482        self.iam_state.last_accessed_services.items = vec![];
7483        self.iam_state.last_accessed_services.selected = 0;
7484
7485        Ok(())
7486    }
7487
7488    pub async fn load_role_tags(&mut self, role_name: &str) -> anyhow::Result<()> {
7489        let tags = self
7490            .iam_client
7491            .list_role_tags(role_name)
7492            .await
7493            .map_err(|e| anyhow::anyhow!(e))?;
7494        self.iam_state.tags.items = tags
7495            .into_iter()
7496            .map(|(k, v)| crate::iam::RoleTag { key: k, value: v })
7497            .collect();
7498        self.iam_state.tags.reset();
7499        Ok(())
7500    }
7501
7502    pub async fn load_user_tags(&mut self, user_name: &str) -> anyhow::Result<()> {
7503        let tags = self
7504            .iam_client
7505            .list_user_tags(user_name)
7506            .await
7507            .map_err(|e| anyhow::anyhow!(e))?;
7508        self.iam_state.user_tags.items = tags
7509            .into_iter()
7510            .map(|(k, v)| crate::iam::UserTag { key: k, value: v })
7511            .collect();
7512        self.iam_state.user_tags.reset();
7513        Ok(())
7514    }
7515
7516    pub async fn load_log_streams(&mut self) -> anyhow::Result<()> {
7517        if let Some(group) = self
7518            .log_groups_state
7519            .log_groups
7520            .items
7521            .get(self.log_groups_state.log_groups.selected)
7522        {
7523            self.log_groups_state.log_streams =
7524                self.cloudwatch_client.list_log_streams(&group.name).await?;
7525            self.log_groups_state.selected_stream = 0;
7526        }
7527        Ok(())
7528    }
7529
7530    pub async fn load_log_events(&mut self) -> anyhow::Result<()> {
7531        if let Some(group) = self
7532            .log_groups_state
7533            .log_groups
7534            .items
7535            .get(self.log_groups_state.log_groups.selected)
7536        {
7537            if let Some(stream) = self
7538                .log_groups_state
7539                .log_streams
7540                .get(self.log_groups_state.selected_stream)
7541            {
7542                // Calculate time range from relative date picker
7543                let (start_time, end_time) =
7544                    if let Ok(amount) = self.log_groups_state.relative_amount.parse::<i64>() {
7545                        let now = chrono::Utc::now().timestamp_millis();
7546                        let duration_ms = match self.log_groups_state.relative_unit {
7547                            crate::app::TimeUnit::Minutes => amount * 60 * 1000,
7548                            crate::app::TimeUnit::Hours => amount * 60 * 60 * 1000,
7549                            crate::app::TimeUnit::Days => amount * 24 * 60 * 60 * 1000,
7550                            crate::app::TimeUnit::Weeks => amount * 7 * 24 * 60 * 60 * 1000,
7551                        };
7552                        (Some(now - duration_ms), Some(now))
7553                    } else {
7554                        (None, None)
7555                    };
7556
7557                let (mut events, has_more, token) = self
7558                    .cloudwatch_client
7559                    .get_log_events(
7560                        &group.name,
7561                        &stream.name,
7562                        self.log_groups_state.next_backward_token.clone(),
7563                        start_time,
7564                        end_time,
7565                    )
7566                    .await?;
7567
7568                if self.log_groups_state.next_backward_token.is_some() {
7569                    // Prepend older events - keep selection at top
7570                    events.append(&mut self.log_groups_state.log_events);
7571                    self.log_groups_state.event_scroll_offset = 0;
7572                } else {
7573                    // Initial load - start at first event
7574                    self.log_groups_state.event_scroll_offset = 0;
7575                }
7576
7577                self.log_groups_state.log_events = events;
7578                self.log_groups_state.has_older_events =
7579                    has_more && self.log_groups_state.log_events.len() >= 25;
7580                self.log_groups_state.next_backward_token = token;
7581                self.log_groups_state.selected_event = 0;
7582            }
7583        }
7584        Ok(())
7585    }
7586
7587    pub async fn execute_insights_query(&mut self) -> anyhow::Result<()> {
7588        if self.insights_state.insights.selected_log_groups.is_empty() {
7589            return Err(anyhow::anyhow!(
7590                "No log groups selected. Please select at least one log group."
7591            ));
7592        }
7593
7594        let now = chrono::Utc::now().timestamp_millis();
7595        let amount = self
7596            .insights_state
7597            .insights
7598            .insights_relative_amount
7599            .parse::<i64>()
7600            .unwrap_or(1);
7601        let duration_ms = match self.insights_state.insights.insights_relative_unit {
7602            TimeUnit::Minutes => amount * 60 * 1000,
7603            TimeUnit::Hours => amount * 60 * 60 * 1000,
7604            TimeUnit::Days => amount * 24 * 60 * 60 * 1000,
7605            TimeUnit::Weeks => amount * 7 * 24 * 60 * 60 * 1000,
7606        };
7607        let start_time = now - duration_ms;
7608
7609        let query_id = self
7610            .cloudwatch_client
7611            .start_query(
7612                self.insights_state.insights.selected_log_groups.clone(),
7613                self.insights_state.insights.query_text.trim().to_string(),
7614                start_time,
7615                now,
7616            )
7617            .await?;
7618
7619        // Poll for results
7620        for _ in 0..60 {
7621            tokio::time::sleep(tokio::time::Duration::from_millis(500)).await;
7622            let (status, results) = self.cloudwatch_client.get_query_results(&query_id).await?;
7623
7624            if status == "Complete" {
7625                self.insights_state.insights.query_results = results;
7626                self.insights_state.insights.query_completed = true;
7627                self.insights_state.insights.results_selected = 0;
7628                self.insights_state.insights.expanded_result = None;
7629                self.view_mode = ViewMode::InsightsResults;
7630                return Ok(());
7631            } else if status == "Failed" || status == "Cancelled" {
7632                return Err(anyhow::anyhow!("Query {}", status.to_lowercase()));
7633            }
7634        }
7635
7636        Err(anyhow::anyhow!("Query timeout"))
7637    }
7638}
7639
7640impl CloudWatchInsightsState {
7641    fn new() -> Self {
7642        Self {
7643            insights: InsightsState::default(),
7644            loading: false,
7645        }
7646    }
7647}
7648
7649impl CloudWatchAlarmsState {
7650    fn new() -> Self {
7651        Self {
7652            table: TableState::new(),
7653            alarm_tab: AlarmTab::AllAlarms,
7654            view_as: AlarmViewMode::Table,
7655            wrap_lines: false,
7656            sort_column: "Last state update".to_string(),
7657            sort_direction: SortDirection::Asc,
7658            input_focus: InputFocus::Filter,
7659        }
7660    }
7661}
7662
7663impl ServicePickerState {
7664    fn new() -> Self {
7665        Self {
7666            filter: String::new(),
7667            selected: 0,
7668            services: vec![
7669                "CloudWatch > Log Groups",
7670                "CloudWatch > Logs Insights",
7671                "CloudWatch > Alarms",
7672                "CloudFormation > Stacks",
7673                "EC2 > Instances",
7674                "ECR > Repositories",
7675                "IAM > Users",
7676                "IAM > Roles",
7677                "IAM > User Groups",
7678                "Lambda > Functions",
7679                "Lambda > Applications",
7680                "S3 > Buckets",
7681                "SQS > Queues",
7682            ],
7683        }
7684    }
7685}
7686
7687#[cfg(test)]
7688mod test_helpers {
7689    use super::*;
7690
7691    // Test helper functions to reduce boilerplate
7692    #[allow(dead_code)]
7693    pub fn test_app() -> App {
7694        App::new_without_client("test".to_string(), Some("us-east-1".to_string()))
7695    }
7696
7697    #[allow(dead_code)]
7698    pub fn test_app_no_region() -> App {
7699        App::new_without_client("test".to_string(), None)
7700    }
7701
7702    #[allow(dead_code)]
7703    pub fn test_tab(service: Service) -> Tab {
7704        Tab {
7705            service,
7706            title: service.name().to_string(),
7707            breadcrumb: service.name().to_string(),
7708        }
7709    }
7710
7711    #[allow(dead_code)]
7712    pub fn test_iam_role(name: &str) -> crate::iam::IamRole {
7713        crate::iam::IamRole {
7714            role_name: name.to_string(),
7715            path: "/".to_string(),
7716            description: format!("Test role {}", name),
7717            trusted_entities: "AWS Service: ec2.amazonaws.com".to_string(),
7718            creation_time: "2024-01-01 00:00:00".to_string(),
7719            arn: format!("arn:aws:iam::123456789012:role/{}", name),
7720            max_session_duration: Some(3600),
7721            last_activity: "-".to_string(),
7722        }
7723    }
7724}
7725
7726#[cfg(test)]
7727mod tests {
7728    use super::*;
7729    use crate::keymap::Action;
7730    use test_helpers::*;
7731
7732    #[test]
7733    fn test_next_tab_cycles_forward() {
7734        let mut app = test_app();
7735        app.tabs = vec![
7736            Tab {
7737                service: Service::CloudWatchLogGroups,
7738                title: "CloudWatch > Log Groups".to_string(),
7739                breadcrumb: "CloudWatch > Log Groups".to_string(),
7740            },
7741            Tab {
7742                service: Service::CloudWatchInsights,
7743                title: "CloudWatch > Logs Insights".to_string(),
7744                breadcrumb: "CloudWatch > Logs Insights".to_string(),
7745            },
7746            Tab {
7747                service: Service::CloudWatchAlarms,
7748                title: "CloudWatch > Alarms".to_string(),
7749                breadcrumb: "CloudWatch > Alarms".to_string(),
7750            },
7751        ];
7752        app.current_tab = 0;
7753
7754        app.handle_action(Action::NextTab);
7755        assert_eq!(app.current_tab, 1);
7756        assert_eq!(app.current_service, Service::CloudWatchInsights);
7757
7758        app.handle_action(Action::NextTab);
7759        assert_eq!(app.current_tab, 2);
7760        assert_eq!(app.current_service, Service::CloudWatchAlarms);
7761
7762        // Should wrap around
7763        app.handle_action(Action::NextTab);
7764        assert_eq!(app.current_tab, 0);
7765        assert_eq!(app.current_service, Service::CloudWatchLogGroups);
7766    }
7767
7768    #[test]
7769    fn test_prev_tab_cycles_backward() {
7770        let mut app = test_app();
7771        app.tabs = vec![
7772            Tab {
7773                service: Service::CloudWatchLogGroups,
7774                title: "CloudWatch > Log Groups".to_string(),
7775                breadcrumb: "CloudWatch > Log Groups".to_string(),
7776            },
7777            Tab {
7778                service: Service::CloudWatchInsights,
7779                title: "CloudWatch > Logs Insights".to_string(),
7780                breadcrumb: "CloudWatch > Logs Insights".to_string(),
7781            },
7782            Tab {
7783                service: Service::CloudWatchAlarms,
7784                title: "CloudWatch > Alarms".to_string(),
7785                breadcrumb: "CloudWatch > Alarms".to_string(),
7786            },
7787        ];
7788        app.current_tab = 2;
7789
7790        app.handle_action(Action::PrevTab);
7791        assert_eq!(app.current_tab, 1);
7792        assert_eq!(app.current_service, Service::CloudWatchInsights);
7793
7794        app.handle_action(Action::PrevTab);
7795        assert_eq!(app.current_tab, 0);
7796        assert_eq!(app.current_service, Service::CloudWatchLogGroups);
7797
7798        // Should wrap around
7799        app.handle_action(Action::PrevTab);
7800        assert_eq!(app.current_tab, 2);
7801        assert_eq!(app.current_service, Service::CloudWatchAlarms);
7802    }
7803
7804    #[test]
7805    fn test_close_tab_removes_current() {
7806        let mut app = test_app();
7807        app.tabs = vec![
7808            Tab {
7809                service: Service::CloudWatchLogGroups,
7810                title: "CloudWatch > Log Groups".to_string(),
7811                breadcrumb: "CloudWatch > Log Groups".to_string(),
7812            },
7813            Tab {
7814                service: Service::CloudWatchInsights,
7815                title: "CloudWatch > Logs Insights".to_string(),
7816                breadcrumb: "CloudWatch > Logs Insights".to_string(),
7817            },
7818            Tab {
7819                service: Service::CloudWatchAlarms,
7820                title: "CloudWatch > Alarms".to_string(),
7821                breadcrumb: "CloudWatch > Alarms".to_string(),
7822            },
7823        ];
7824        app.current_tab = 1;
7825        app.service_selected = true;
7826
7827        app.handle_action(Action::CloseTab);
7828        assert_eq!(app.tabs.len(), 2);
7829        assert_eq!(app.current_tab, 1);
7830        assert_eq!(app.current_service, Service::CloudWatchAlarms);
7831    }
7832
7833    #[test]
7834    fn test_close_last_tab_exits_service() {
7835        let mut app = test_app();
7836        app.tabs = vec![Tab {
7837            service: Service::CloudWatchLogGroups,
7838            title: "CloudWatch > Log Groups".to_string(),
7839            breadcrumb: "CloudWatch > Log Groups".to_string(),
7840        }];
7841        app.current_tab = 0;
7842        app.service_selected = true;
7843
7844        app.handle_action(Action::CloseTab);
7845        assert_eq!(app.tabs.len(), 0);
7846        assert!(!app.service_selected);
7847        assert_eq!(app.current_tab, 0);
7848    }
7849
7850    #[test]
7851    fn test_close_service_removes_current_tab() {
7852        let mut app = test_app();
7853        app.tabs = vec![
7854            Tab {
7855                service: Service::CloudWatchLogGroups,
7856                title: "CloudWatch > Log Groups".to_string(),
7857                breadcrumb: "CloudWatch > Log Groups".to_string(),
7858            },
7859            Tab {
7860                service: Service::CloudWatchInsights,
7861                title: "CloudWatch > Logs Insights".to_string(),
7862                breadcrumb: "CloudWatch > Logs Insights".to_string(),
7863            },
7864            Tab {
7865                service: Service::CloudWatchAlarms,
7866                title: "CloudWatch > Alarms".to_string(),
7867                breadcrumb: "CloudWatch > Alarms".to_string(),
7868            },
7869        ];
7870        app.current_tab = 1;
7871        app.service_selected = true;
7872
7873        app.handle_action(Action::CloseService);
7874
7875        // Tab should be removed
7876        assert_eq!(app.tabs.len(), 2);
7877        // Should switch to next tab (Alarms at index 1)
7878        assert_eq!(app.current_tab, 1);
7879        assert_eq!(app.current_service, Service::CloudWatchAlarms);
7880        // Should stay in service mode, NOT show service picker
7881        assert!(app.service_selected);
7882        assert_eq!(app.mode, Mode::Normal);
7883    }
7884
7885    #[test]
7886    fn test_close_service_last_tab_shows_picker() {
7887        let mut app = test_app();
7888        app.tabs = vec![Tab {
7889            service: Service::CloudWatchLogGroups,
7890            title: "CloudWatch > Log Groups".to_string(),
7891            breadcrumb: "CloudWatch > Log Groups".to_string(),
7892        }];
7893        app.current_tab = 0;
7894        app.service_selected = true;
7895
7896        app.handle_action(Action::CloseService);
7897
7898        // Tab should be removed
7899        assert_eq!(app.tabs.len(), 0);
7900        // Should show service picker
7901        assert!(!app.service_selected);
7902        assert_eq!(app.mode, Mode::ServicePicker);
7903    }
7904
7905    #[test]
7906    fn test_open_tab_picker_with_tabs() {
7907        let mut app = test_app();
7908        app.tabs = vec![
7909            Tab {
7910                service: Service::CloudWatchLogGroups,
7911                title: "CloudWatch > Log Groups".to_string(),
7912                breadcrumb: "CloudWatch > Log Groups".to_string(),
7913            },
7914            Tab {
7915                service: Service::CloudWatchInsights,
7916                title: "CloudWatch > Logs Insights".to_string(),
7917                breadcrumb: "CloudWatch > Logs Insights".to_string(),
7918            },
7919        ];
7920        app.current_tab = 1;
7921
7922        app.handle_action(Action::OpenTabPicker);
7923        assert_eq!(app.mode, Mode::TabPicker);
7924        assert_eq!(app.tab_picker_selected, 1);
7925    }
7926
7927    #[test]
7928    fn test_open_tab_picker_without_tabs() {
7929        let mut app = test_app();
7930        app.tabs = vec![];
7931
7932        app.handle_action(Action::OpenTabPicker);
7933        assert_eq!(app.mode, Mode::Normal);
7934    }
7935
7936    #[test]
7937    fn test_pending_key_state() {
7938        let mut app = test_app();
7939        assert_eq!(app.pending_key, None);
7940
7941        app.pending_key = Some('g');
7942        assert_eq!(app.pending_key, Some('g'));
7943    }
7944
7945    #[test]
7946    fn test_tab_breadcrumb_updates() {
7947        let mut app = test_app();
7948        app.tabs = vec![Tab {
7949            service: Service::CloudWatchLogGroups,
7950            title: "CloudWatch > Log Groups".to_string(),
7951            breadcrumb: "CloudWatch > Log groups".to_string(),
7952        }];
7953        app.current_tab = 0;
7954        app.service_selected = true;
7955        app.current_service = Service::CloudWatchLogGroups;
7956
7957        // Initial breadcrumb
7958        assert_eq!(app.tabs[0].breadcrumb, "CloudWatch > Log groups");
7959
7960        // Add a log group and update breadcrumb
7961        app.log_groups_state
7962            .log_groups
7963            .items
7964            .push(rusticity_core::LogGroup {
7965                name: "/aws/lambda/test".to_string(),
7966                creation_time: None,
7967                stored_bytes: Some(1024),
7968                retention_days: None,
7969                log_class: None,
7970                arn: None,
7971            });
7972        app.log_groups_state.log_groups.reset();
7973        app.view_mode = ViewMode::Detail;
7974        app.update_current_tab_breadcrumb();
7975
7976        // Breadcrumb should now include log group
7977        assert_eq!(
7978            app.tabs[0].breadcrumb,
7979            "CloudWatch > Log groups > /aws/lambda/test"
7980        );
7981    }
7982
7983    #[test]
7984    fn test_s3_bucket_column_selector_navigation() {
7985        let mut app = test_app();
7986        app.current_service = Service::S3Buckets;
7987        app.mode = Mode::ColumnSelector;
7988        app.column_selector_index = 0;
7989
7990        // Should navigate through 3 S3 bucket columns (0, 1, 2)
7991        app.handle_action(Action::NextItem);
7992        assert_eq!(app.column_selector_index, 1);
7993
7994        app.handle_action(Action::NextItem);
7995        assert_eq!(app.column_selector_index, 2);
7996
7997        // Should not go beyond max (2)
7998        app.handle_action(Action::NextItem);
7999        assert_eq!(app.column_selector_index, 2);
8000
8001        // Navigate back
8002        app.handle_action(Action::PrevItem);
8003        assert_eq!(app.column_selector_index, 1);
8004
8005        app.handle_action(Action::PrevItem);
8006        assert_eq!(app.column_selector_index, 0);
8007
8008        // Should not go below 0
8009        app.handle_action(Action::PrevItem);
8010        assert_eq!(app.column_selector_index, 0);
8011    }
8012
8013    #[test]
8014    fn test_cloudwatch_alarms_state_initialized() {
8015        let app = test_app();
8016
8017        // Alarms state should be initialized
8018        assert_eq!(app.alarms_state.table.items.len(), 0);
8019        assert_eq!(app.alarms_state.table.selected, 0);
8020        assert_eq!(app.alarms_state.alarm_tab, AlarmTab::AllAlarms);
8021        assert!(!app.alarms_state.table.loading);
8022        assert_eq!(app.alarms_state.view_as, AlarmViewMode::Table);
8023        assert_eq!(app.alarms_state.table.page_size, PageSize::Fifty);
8024    }
8025
8026    #[test]
8027    fn test_cloudwatch_alarms_service_selection() {
8028        let mut app = test_app();
8029
8030        // Switch to alarms service
8031        app.current_service = Service::CloudWatchAlarms;
8032        app.service_selected = true;
8033
8034        assert_eq!(app.current_service, Service::CloudWatchAlarms);
8035        assert!(app.service_selected);
8036    }
8037
8038    #[test]
8039    fn test_cloudwatch_alarms_column_preferences() {
8040        let app = test_app();
8041
8042        // Should have alarm columns defined
8043        assert!(!app.cw_alarm_column_ids.is_empty());
8044        assert!(!app.cw_alarm_visible_column_ids.is_empty());
8045
8046        // Default visible columns
8047        assert!(app
8048            .cw_alarm_visible_column_ids
8049            .contains(&AlarmColumn::Name.id()));
8050        assert!(app
8051            .cw_alarm_visible_column_ids
8052            .contains(&AlarmColumn::State.id()));
8053    }
8054
8055    #[test]
8056    fn test_s3_bucket_navigation_without_expansion() {
8057        let mut app = test_app();
8058        app.current_service = Service::S3Buckets;
8059        app.service_selected = true;
8060        app.mode = Mode::Normal;
8061
8062        // Add 3 buckets
8063        app.s3_state.buckets.items = vec![
8064            S3Bucket {
8065                name: "bucket1".to_string(),
8066                region: "us-east-1".to_string(),
8067                creation_date: "2024-01-01T00:00:00Z".to_string(),
8068            },
8069            S3Bucket {
8070                name: "bucket2".to_string(),
8071                region: "us-east-1".to_string(),
8072                creation_date: "2024-01-02T00:00:00Z".to_string(),
8073            },
8074            S3Bucket {
8075                name: "bucket3".to_string(),
8076                region: "us-east-1".to_string(),
8077                creation_date: "2024-01-03T00:00:00Z".to_string(),
8078            },
8079        ];
8080        app.s3_state.selected_row = 0;
8081
8082        // Navigate down
8083        app.handle_action(Action::NextItem);
8084        assert_eq!(app.s3_state.selected_row, 1);
8085
8086        app.handle_action(Action::NextItem);
8087        assert_eq!(app.s3_state.selected_row, 2);
8088
8089        // Should not go beyond last bucket
8090        app.handle_action(Action::NextItem);
8091        assert_eq!(app.s3_state.selected_row, 2);
8092
8093        // Navigate up
8094        app.handle_action(Action::PrevItem);
8095        assert_eq!(app.s3_state.selected_row, 1);
8096
8097        app.handle_action(Action::PrevItem);
8098        assert_eq!(app.s3_state.selected_row, 0);
8099
8100        // Should not go below 0
8101        app.handle_action(Action::PrevItem);
8102        assert_eq!(app.s3_state.selected_row, 0);
8103    }
8104
8105    #[test]
8106    fn test_s3_bucket_navigation_with_expansion() {
8107        let mut app = test_app();
8108        app.current_service = Service::S3Buckets;
8109        app.service_selected = true;
8110        app.mode = Mode::Normal;
8111
8112        // Add 2 buckets
8113        app.s3_state.buckets.items = vec![
8114            S3Bucket {
8115                name: "bucket1".to_string(),
8116                region: "us-east-1".to_string(),
8117                creation_date: "2024-01-01T00:00:00Z".to_string(),
8118            },
8119            S3Bucket {
8120                name: "bucket2".to_string(),
8121                region: "us-east-1".to_string(),
8122                creation_date: "2024-01-02T00:00:00Z".to_string(),
8123            },
8124        ];
8125
8126        // Expand bucket1 with 2 objects
8127        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
8128        app.s3_state.bucket_preview.insert(
8129            "bucket1".to_string(),
8130            vec![
8131                S3Object {
8132                    key: "file1.txt".to_string(),
8133                    size: 100,
8134                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8135                    is_prefix: false,
8136                    storage_class: "STANDARD".to_string(),
8137                },
8138                S3Object {
8139                    key: "folder/".to_string(),
8140                    size: 0,
8141                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8142                    is_prefix: true,
8143                    storage_class: String::new(),
8144                },
8145            ],
8146        );
8147
8148        app.s3_state.selected_row = 0;
8149
8150        // Total rows: bucket1 (row 0) + file1.txt (row 1) + folder/ (row 2) + bucket2 (row 3) = 4 rows
8151        // Navigate through all rows
8152        app.handle_action(Action::NextItem);
8153        assert_eq!(app.s3_state.selected_row, 1); // file1.txt
8154
8155        app.handle_action(Action::NextItem);
8156        assert_eq!(app.s3_state.selected_row, 2); // folder/
8157
8158        app.handle_action(Action::NextItem);
8159        assert_eq!(app.s3_state.selected_row, 3); // bucket2
8160
8161        // Should not go beyond last row
8162        app.handle_action(Action::NextItem);
8163        assert_eq!(app.s3_state.selected_row, 3);
8164    }
8165
8166    #[test]
8167    fn test_s3_bucket_navigation_with_nested_expansion() {
8168        let mut app = test_app();
8169        app.current_service = Service::S3Buckets;
8170        app.service_selected = true;
8171        app.mode = Mode::Normal;
8172
8173        // Add 1 bucket
8174        app.s3_state.buckets.items = vec![S3Bucket {
8175            name: "bucket1".to_string(),
8176            region: "us-east-1".to_string(),
8177            creation_date: "2024-01-01T00:00:00Z".to_string(),
8178        }];
8179
8180        // Expand bucket1 with a folder
8181        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
8182        app.s3_state.bucket_preview.insert(
8183            "bucket1".to_string(),
8184            vec![S3Object {
8185                key: "folder/".to_string(),
8186                size: 0,
8187                last_modified: "2024-01-01T00:00:00Z".to_string(),
8188                is_prefix: true,
8189                storage_class: String::new(),
8190            }],
8191        );
8192
8193        // Expand the folder with 2 nested objects
8194        app.s3_state.expanded_prefixes.insert("folder/".to_string());
8195        app.s3_state.prefix_preview.insert(
8196            "folder/".to_string(),
8197            vec![
8198                S3Object {
8199                    key: "folder/file1.txt".to_string(),
8200                    size: 100,
8201                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8202                    is_prefix: false,
8203                    storage_class: "STANDARD".to_string(),
8204                },
8205                S3Object {
8206                    key: "folder/file2.txt".to_string(),
8207                    size: 200,
8208                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8209                    is_prefix: false,
8210                    storage_class: "STANDARD".to_string(),
8211                },
8212            ],
8213        );
8214
8215        app.s3_state.selected_row = 0;
8216
8217        // Total rows: bucket1 (0) + folder/ (1) + file1.txt (2) + file2.txt (3) = 4 rows
8218        app.handle_action(Action::NextItem);
8219        assert_eq!(app.s3_state.selected_row, 1); // folder/
8220
8221        app.handle_action(Action::NextItem);
8222        assert_eq!(app.s3_state.selected_row, 2); // folder/file1.txt
8223
8224        app.handle_action(Action::NextItem);
8225        assert_eq!(app.s3_state.selected_row, 3); // folder/file2.txt
8226
8227        // Should not go beyond last row
8228        app.handle_action(Action::NextItem);
8229        assert_eq!(app.s3_state.selected_row, 3);
8230    }
8231
8232    #[test]
8233    fn test_calculate_total_bucket_rows() {
8234        let mut app = test_app();
8235
8236        // No buckets
8237        assert_eq!(app.calculate_total_bucket_rows(), 0);
8238
8239        // 2 buckets, no expansion
8240        app.s3_state.buckets.items = vec![
8241            S3Bucket {
8242                name: "bucket1".to_string(),
8243                region: "us-east-1".to_string(),
8244                creation_date: "2024-01-01T00:00:00Z".to_string(),
8245            },
8246            S3Bucket {
8247                name: "bucket2".to_string(),
8248                region: "us-east-1".to_string(),
8249                creation_date: "2024-01-02T00:00:00Z".to_string(),
8250            },
8251        ];
8252        assert_eq!(app.calculate_total_bucket_rows(), 2);
8253
8254        // Expand bucket1 with 3 objects
8255        app.s3_state.expanded_prefixes.insert("bucket1".to_string());
8256        app.s3_state.bucket_preview.insert(
8257            "bucket1".to_string(),
8258            vec![
8259                S3Object {
8260                    key: "file1.txt".to_string(),
8261                    size: 100,
8262                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8263                    is_prefix: false,
8264                    storage_class: "STANDARD".to_string(),
8265                },
8266                S3Object {
8267                    key: "file2.txt".to_string(),
8268                    size: 200,
8269                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8270                    is_prefix: false,
8271                    storage_class: "STANDARD".to_string(),
8272                },
8273                S3Object {
8274                    key: "folder/".to_string(),
8275                    size: 0,
8276                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8277                    is_prefix: true,
8278                    storage_class: String::new(),
8279                },
8280            ],
8281        );
8282        assert_eq!(app.calculate_total_bucket_rows(), 5); // 2 buckets + 3 objects
8283
8284        // Expand folder/ with 2 nested objects
8285        app.s3_state.expanded_prefixes.insert("folder/".to_string());
8286        app.s3_state.prefix_preview.insert(
8287            "folder/".to_string(),
8288            vec![
8289                S3Object {
8290                    key: "folder/nested1.txt".to_string(),
8291                    size: 50,
8292                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8293                    is_prefix: false,
8294                    storage_class: "STANDARD".to_string(),
8295                },
8296                S3Object {
8297                    key: "folder/nested2.txt".to_string(),
8298                    size: 75,
8299                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8300                    is_prefix: false,
8301                    storage_class: "STANDARD".to_string(),
8302                },
8303            ],
8304        );
8305        assert_eq!(app.calculate_total_bucket_rows(), 7); // 2 buckets + 3 objects + 2 nested
8306    }
8307
8308    #[test]
8309    fn test_calculate_total_object_rows() {
8310        let mut app = test_app();
8311        app.s3_state.current_bucket = Some("test-bucket".to_string());
8312
8313        // No objects
8314        assert_eq!(app.calculate_total_object_rows(), 0);
8315
8316        // 2 objects, no expansion
8317        app.s3_state.objects = vec![
8318            S3Object {
8319                key: "file1.txt".to_string(),
8320                size: 100,
8321                last_modified: "2024-01-01T00:00:00Z".to_string(),
8322                is_prefix: false,
8323                storage_class: "STANDARD".to_string(),
8324            },
8325            S3Object {
8326                key: "folder/".to_string(),
8327                size: 0,
8328                last_modified: "2024-01-01T00:00:00Z".to_string(),
8329                is_prefix: true,
8330                storage_class: String::new(),
8331            },
8332        ];
8333        assert_eq!(app.calculate_total_object_rows(), 2);
8334
8335        // Expand folder/ with 2 items
8336        app.s3_state.expanded_prefixes.insert("folder/".to_string());
8337        app.s3_state.prefix_preview.insert(
8338            "folder/".to_string(),
8339            vec![
8340                S3Object {
8341                    key: "folder/file2.txt".to_string(),
8342                    size: 200,
8343                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8344                    is_prefix: false,
8345                    storage_class: "STANDARD".to_string(),
8346                },
8347                S3Object {
8348                    key: "folder/subfolder/".to_string(),
8349                    size: 0,
8350                    last_modified: "2024-01-01T00:00:00Z".to_string(),
8351                    is_prefix: true,
8352                    storage_class: String::new(),
8353                },
8354            ],
8355        );
8356        assert_eq!(app.calculate_total_object_rows(), 4); // 2 + 2 nested
8357
8358        // Expand subfolder/ with 1 item (3rd level)
8359        app.s3_state
8360            .expanded_prefixes
8361            .insert("folder/subfolder/".to_string());
8362        app.s3_state.prefix_preview.insert(
8363            "folder/subfolder/".to_string(),
8364            vec![S3Object {
8365                key: "folder/subfolder/deep.txt".to_string(),
8366                size: 50,
8367                last_modified: "2024-01-01T00:00:00Z".to_string(),
8368                is_prefix: false,
8369                storage_class: "STANDARD".to_string(),
8370            }],
8371        );
8372        assert_eq!(app.calculate_total_object_rows(), 5); // 2 + 2 nested + 1 deep
8373    }
8374
8375    #[test]
8376    fn test_s3_object_navigation_with_deep_nesting() {
8377        let mut app = test_app();
8378        app.current_service = Service::S3Buckets;
8379        app.service_selected = true;
8380        app.mode = Mode::Normal;
8381        app.s3_state.current_bucket = Some("test-bucket".to_string());
8382
8383        // Add folder structure: folder1/ -> folder2/ -> file.txt
8384        app.s3_state.objects = vec![S3Object {
8385            key: "folder1/".to_string(),
8386            size: 0,
8387            last_modified: "2024-01-01T00:00:00Z".to_string(),
8388            is_prefix: true,
8389            storage_class: String::new(),
8390        }];
8391
8392        // Expand folder1/
8393        app.s3_state
8394            .expanded_prefixes
8395            .insert("folder1/".to_string());
8396        app.s3_state.prefix_preview.insert(
8397            "folder1/".to_string(),
8398            vec![S3Object {
8399                key: "folder1/folder2/".to_string(),
8400                size: 0,
8401                last_modified: "2024-01-01T00:00:00Z".to_string(),
8402                is_prefix: true,
8403                storage_class: String::new(),
8404            }],
8405        );
8406
8407        // Expand folder2/
8408        app.s3_state
8409            .expanded_prefixes
8410            .insert("folder1/folder2/".to_string());
8411        app.s3_state.prefix_preview.insert(
8412            "folder1/folder2/".to_string(),
8413            vec![S3Object {
8414                key: "folder1/folder2/file.txt".to_string(),
8415                size: 100,
8416                last_modified: "2024-01-01T00:00:00Z".to_string(),
8417                is_prefix: false,
8418                storage_class: "STANDARD".to_string(),
8419            }],
8420        );
8421
8422        app.s3_state.selected_object = 0;
8423
8424        // Total: folder1/ (0) + folder2/ (1) + file.txt (2) = 3 rows
8425        app.handle_action(Action::NextItem);
8426        assert_eq!(app.s3_state.selected_object, 1); // folder2/
8427
8428        app.handle_action(Action::NextItem);
8429        assert_eq!(app.s3_state.selected_object, 2); // file.txt
8430
8431        // Should not go beyond
8432        app.handle_action(Action::NextItem);
8433        assert_eq!(app.s3_state.selected_object, 2);
8434    }
8435
8436    #[test]
8437    fn test_s3_expand_nested_folder_in_objects_view() {
8438        let mut app = test_app();
8439        app.current_service = Service::S3Buckets;
8440        app.service_selected = true;
8441        app.mode = Mode::Normal;
8442        app.s3_state.current_bucket = Some("test-bucket".to_string());
8443
8444        // Add parent folder
8445        app.s3_state.objects = vec![S3Object {
8446            key: "parent/".to_string(),
8447            size: 0,
8448            last_modified: "2024-01-01T00:00:00Z".to_string(),
8449            is_prefix: true,
8450            storage_class: String::new(),
8451        }];
8452
8453        // Expand parent
8454        app.s3_state.expanded_prefixes.insert("parent/".to_string());
8455        app.s3_state.prefix_preview.insert(
8456            "parent/".to_string(),
8457            vec![S3Object {
8458                key: "parent/child/".to_string(),
8459                size: 0,
8460                last_modified: "2024-01-01T00:00:00Z".to_string(),
8461                is_prefix: true,
8462                storage_class: String::new(),
8463            }],
8464        );
8465
8466        // Select the nested folder (index 1)
8467        app.s3_state.selected_object = 1;
8468
8469        // Expand it (simulate pressing Enter/Right)
8470        app.handle_action(Action::NextPane);
8471
8472        // Should be expanded now
8473        assert!(app.s3_state.expanded_prefixes.contains("parent/child/"));
8474        assert!(app.s3_state.buckets.loading); // Should trigger load
8475    }
8476
8477    #[test]
8478    fn test_s3_drill_into_nested_folder() {
8479        let mut app = test_app();
8480        app.current_service = Service::S3Buckets;
8481        app.service_selected = true;
8482        app.mode = Mode::Normal;
8483        app.s3_state.current_bucket = Some("test-bucket".to_string());
8484
8485        // Add parent folder
8486        app.s3_state.objects = vec![S3Object {
8487            key: "parent/".to_string(),
8488            size: 0,
8489            last_modified: "2024-01-01T00:00:00Z".to_string(),
8490            is_prefix: true,
8491            storage_class: String::new(),
8492        }];
8493
8494        // Expand parent
8495        app.s3_state.expanded_prefixes.insert("parent/".to_string());
8496        app.s3_state.prefix_preview.insert(
8497            "parent/".to_string(),
8498            vec![S3Object {
8499                key: "parent/child/".to_string(),
8500                size: 0,
8501                last_modified: "2024-01-01T00:00:00Z".to_string(),
8502                is_prefix: true,
8503                storage_class: String::new(),
8504            }],
8505        );
8506
8507        // Select the nested folder (index 1)
8508        app.s3_state.selected_object = 1;
8509
8510        // Drill into it (simulate pressing Enter)
8511        app.handle_action(Action::Select);
8512
8513        // Should navigate into the folder
8514        assert_eq!(app.s3_state.prefix_stack, vec!["parent/child/".to_string()]);
8515        assert!(app.s3_state.buckets.loading); // Should trigger load
8516    }
8517
8518    #[test]
8519    fn test_s3_esc_pops_navigation_stack() {
8520        let mut app = test_app();
8521        app.current_service = Service::S3Buckets;
8522        app.s3_state.current_bucket = Some("test-bucket".to_string());
8523        app.s3_state.prefix_stack = vec!["level1/".to_string(), "level1/level2/".to_string()];
8524
8525        // Press Esc - should pop from stack
8526        app.handle_action(Action::GoBack);
8527        assert_eq!(app.s3_state.prefix_stack, vec!["level1/".to_string()]);
8528        assert!(app.s3_state.buckets.loading);
8529
8530        // Press Esc again - should pop to bucket root
8531        app.s3_state.buckets.loading = false;
8532        app.handle_action(Action::GoBack);
8533        assert_eq!(app.s3_state.prefix_stack, Vec::<String>::new());
8534        assert!(app.s3_state.buckets.loading);
8535
8536        // Press Esc again - should exit bucket
8537        app.s3_state.buckets.loading = false;
8538        app.handle_action(Action::GoBack);
8539        assert_eq!(app.s3_state.current_bucket, None);
8540    }
8541
8542    #[test]
8543    fn test_s3_esc_from_bucket_root_exits() {
8544        let mut app = test_app();
8545        app.current_service = Service::S3Buckets;
8546        app.s3_state.current_bucket = Some("test-bucket".to_string());
8547        app.s3_state.prefix_stack = vec![];
8548
8549        // Press Esc from bucket root - should exit bucket
8550        app.handle_action(Action::GoBack);
8551        assert_eq!(app.s3_state.current_bucket, None);
8552        assert_eq!(app.s3_state.objects.len(), 0);
8553    }
8554
8555    #[test]
8556    fn test_s3_drill_into_nested_prefix_from_bucket_list() {
8557        let mut app = test_app();
8558        app.current_service = Service::S3Buckets;
8559        app.service_selected = true;
8560        app.mode = Mode::Normal;
8561
8562        // Setup bucket with nested preview
8563        app.s3_state.buckets.items = vec![S3Bucket {
8564            name: "test-bucket".to_string(),
8565            region: "us-east-1".to_string(),
8566            creation_date: "2024-01-01".to_string(),
8567        }];
8568
8569        // Expand bucket to show first-level prefix
8570        app.s3_state
8571            .expanded_prefixes
8572            .insert("test-bucket".to_string());
8573        app.s3_state.bucket_preview.insert(
8574            "test-bucket".to_string(),
8575            vec![S3Object {
8576                key: "parent/".to_string(),
8577                size: 0,
8578                last_modified: "2024-01-01".to_string(),
8579                is_prefix: true,
8580                storage_class: String::new(),
8581            }],
8582        );
8583
8584        // Expand parent to show nested prefix
8585        app.s3_state.expanded_prefixes.insert("parent/".to_string());
8586        app.s3_state.prefix_preview.insert(
8587            "parent/".to_string(),
8588            vec![S3Object {
8589                key: "parent/child/".to_string(),
8590                size: 0,
8591                last_modified: "2024-01-01".to_string(),
8592                is_prefix: true,
8593                storage_class: String::new(),
8594            }],
8595        );
8596
8597        // Select nested prefix (row 2: bucket, parent, nested)
8598        app.s3_state.selected_row = 2;
8599
8600        // Drill into nested prefix
8601        app.handle_action(Action::Select);
8602
8603        // Should have both parent and child in stack
8604        assert_eq!(
8605            app.s3_state.prefix_stack,
8606            vec!["parent/".to_string(), "parent/child/".to_string()]
8607        );
8608        assert_eq!(app.s3_state.current_bucket, Some("test-bucket".to_string()));
8609        assert!(app.s3_state.buckets.loading);
8610
8611        // Now press Esc - should go back to parent
8612        app.s3_state.buckets.loading = false;
8613        app.handle_action(Action::GoBack);
8614        assert_eq!(app.s3_state.prefix_stack, vec!["parent/".to_string()]);
8615        assert!(app.s3_state.buckets.loading);
8616
8617        // Press Esc again - should go to bucket root
8618        app.s3_state.buckets.loading = false;
8619        app.handle_action(Action::GoBack);
8620        assert_eq!(app.s3_state.prefix_stack, Vec::<String>::new());
8621        assert!(app.s3_state.buckets.loading);
8622
8623        // Press Esc again - should exit bucket
8624        app.s3_state.buckets.loading = false;
8625        app.handle_action(Action::GoBack);
8626        assert_eq!(app.s3_state.current_bucket, None);
8627    }
8628
8629    #[test]
8630    fn test_region_picker_fuzzy_filter() {
8631        let mut app = test_app();
8632        app.region_latencies.insert("us-east-1".to_string(), 10);
8633        app.region_filter = "vir".to_string();
8634        let filtered = app.get_filtered_regions();
8635        assert!(filtered.iter().any(|r| r.code == "us-east-1"));
8636    }
8637
8638    #[test]
8639    fn test_profile_picker_loads_profiles() {
8640        let profiles = App::load_aws_profiles();
8641        // Should at least have default profile or be empty if no config
8642        assert!(profiles.is_empty() || profiles.iter().any(|p| p.name == "default"));
8643    }
8644
8645    #[test]
8646    fn test_profile_with_region_uses_it() {
8647        let mut app = test_app_no_region();
8648        app.available_profiles = vec![AwsProfile {
8649            name: "test-profile".to_string(),
8650            region: Some("eu-west-1".to_string()),
8651            account: Some("123456789".to_string()),
8652            role_arn: None,
8653            source_profile: None,
8654        }];
8655        app.profile_picker_selected = 0;
8656        app.mode = Mode::ProfilePicker;
8657
8658        // Simulate selecting the profile
8659        let filtered = app.get_filtered_profiles();
8660        if let Some(profile) = filtered.first() {
8661            let profile_name = profile.name.clone();
8662            let profile_region = profile.region.clone();
8663
8664            app.profile = profile_name;
8665            if let Some(region) = profile_region {
8666                app.region = region;
8667            }
8668        }
8669
8670        assert_eq!(app.profile, "test-profile");
8671        assert_eq!(app.region, "eu-west-1");
8672    }
8673
8674    #[test]
8675    fn test_profile_without_region_keeps_unknown() {
8676        let mut app = test_app_no_region();
8677        let initial_region = app.region.clone();
8678
8679        app.available_profiles = vec![AwsProfile {
8680            name: "test-profile".to_string(),
8681            region: None,
8682            account: None,
8683            role_arn: None,
8684            source_profile: None,
8685        }];
8686        app.profile_picker_selected = 0;
8687        app.mode = Mode::ProfilePicker;
8688
8689        let filtered = app.get_filtered_profiles();
8690        if let Some(profile) = filtered.first() {
8691            let profile_name = profile.name.clone();
8692            let profile_region = profile.region.clone();
8693
8694            app.profile = profile_name;
8695            if let Some(region) = profile_region {
8696                app.region = region;
8697            }
8698        }
8699
8700        assert_eq!(app.profile, "test-profile");
8701        assert_eq!(app.region, initial_region); // Should keep initial region
8702    }
8703
8704    #[test]
8705    fn test_region_selection_closes_all_tabs() {
8706        let mut app = test_app();
8707
8708        // Add some tabs
8709        app.tabs.push(Tab {
8710            service: Service::CloudWatchLogGroups,
8711            title: "CloudWatch".to_string(),
8712            breadcrumb: "CloudWatch".to_string(),
8713        });
8714        app.tabs.push(Tab {
8715            service: Service::S3Buckets,
8716            title: "S3".to_string(),
8717            breadcrumb: "S3".to_string(),
8718        });
8719        app.service_selected = true;
8720        app.current_tab = 1;
8721
8722        // Add latency for region
8723        app.region_latencies.insert("eu-west-1".to_string(), 50);
8724
8725        // Simulate selecting a different region
8726        app.mode = Mode::RegionPicker;
8727        app.region_picker_selected = 0;
8728
8729        let filtered = app.get_filtered_regions();
8730        if let Some(region) = filtered.first() {
8731            app.region = region.code.to_string();
8732            app.tabs.clear();
8733            app.current_tab = 0;
8734            app.service_selected = false;
8735            app.mode = Mode::Normal;
8736        }
8737
8738        assert_eq!(app.tabs.len(), 0);
8739        assert_eq!(app.current_tab, 0);
8740        assert!(!app.service_selected);
8741        assert_eq!(app.region, "eu-west-1");
8742    }
8743
8744    #[test]
8745    fn test_region_picker_can_be_closed_without_selection() {
8746        let mut app = test_app();
8747        let initial_region = app.region.clone();
8748
8749        app.mode = Mode::RegionPicker;
8750
8751        // Close without selecting (Esc)
8752        app.mode = Mode::Normal;
8753
8754        // Region should not change
8755        assert_eq!(app.region, initial_region);
8756    }
8757
8758    #[test]
8759    fn test_session_filter_works() {
8760        let mut app = test_app();
8761
8762        app.sessions = vec![
8763            Session {
8764                id: "1".to_string(),
8765                timestamp: "2024-01-01".to_string(),
8766                profile: "prod-profile".to_string(),
8767                region: "us-east-1".to_string(),
8768                account_id: "123456789".to_string(),
8769                role_arn: "arn:aws:iam::123456789:role/admin".to_string(),
8770                tabs: vec![],
8771            },
8772            Session {
8773                id: "2".to_string(),
8774                timestamp: "2024-01-02".to_string(),
8775                profile: "dev-profile".to_string(),
8776                region: "eu-west-1".to_string(),
8777                account_id: "987654321".to_string(),
8778                role_arn: "arn:aws:iam::987654321:role/dev".to_string(),
8779                tabs: vec![],
8780            },
8781        ];
8782
8783        // Filter by profile
8784        app.session_filter = "prod".to_string();
8785        let filtered = app.get_filtered_sessions();
8786        assert_eq!(filtered.len(), 1);
8787        assert_eq!(filtered[0].profile, "prod-profile");
8788
8789        // Filter by region
8790        app.session_filter = "eu".to_string();
8791        let filtered = app.get_filtered_sessions();
8792        assert_eq!(filtered.len(), 1);
8793        assert_eq!(filtered[0].region, "eu-west-1");
8794
8795        // No filter
8796        app.session_filter.clear();
8797        let filtered = app.get_filtered_sessions();
8798        assert_eq!(filtered.len(), 2);
8799    }
8800
8801    #[test]
8802    fn test_profile_picker_shows_account() {
8803        let mut app = test_app_no_region();
8804        app.available_profiles = vec![AwsProfile {
8805            name: "test-profile".to_string(),
8806            region: Some("us-east-1".to_string()),
8807            account: Some("123456789".to_string()),
8808            role_arn: None,
8809            source_profile: None,
8810        }];
8811
8812        let filtered = app.get_filtered_profiles();
8813        assert_eq!(filtered.len(), 1);
8814        assert_eq!(filtered[0].account, Some("123456789".to_string()));
8815    }
8816
8817    #[test]
8818    fn test_profile_without_account() {
8819        let mut app = test_app_no_region();
8820        app.available_profiles = vec![AwsProfile {
8821            name: "test-profile".to_string(),
8822            region: Some("us-east-1".to_string()),
8823            account: None,
8824            role_arn: None,
8825            source_profile: None,
8826        }];
8827
8828        let filtered = app.get_filtered_profiles();
8829        assert_eq!(filtered.len(), 1);
8830        assert_eq!(filtered[0].account, None);
8831    }
8832
8833    #[test]
8834    fn test_profile_with_all_fields() {
8835        let mut app = test_app_no_region();
8836        app.available_profiles = vec![AwsProfile {
8837            name: "prod-profile".to_string(),
8838            region: Some("us-west-2".to_string()),
8839            account: Some("123456789".to_string()),
8840            role_arn: Some("arn:aws:iam::123456789:role/AdminRole".to_string()),
8841            source_profile: Some("base-profile".to_string()),
8842        }];
8843
8844        let filtered = app.get_filtered_profiles();
8845        assert_eq!(filtered.len(), 1);
8846        assert_eq!(filtered[0].name, "prod-profile");
8847        assert_eq!(filtered[0].region, Some("us-west-2".to_string()));
8848        assert_eq!(filtered[0].account, Some("123456789".to_string()));
8849        assert_eq!(
8850            filtered[0].role_arn,
8851            Some("arn:aws:iam::123456789:role/AdminRole".to_string())
8852        );
8853        assert_eq!(filtered[0].source_profile, Some("base-profile".to_string()));
8854    }
8855
8856    #[test]
8857    fn test_profile_filter_by_source_profile() {
8858        let mut app = test_app_no_region();
8859        app.available_profiles = vec![
8860            AwsProfile {
8861                name: "profile1".to_string(),
8862                region: None,
8863                account: None,
8864                role_arn: None,
8865                source_profile: Some("base".to_string()),
8866            },
8867            AwsProfile {
8868                name: "profile2".to_string(),
8869                region: None,
8870                account: None,
8871                role_arn: None,
8872                source_profile: Some("other".to_string()),
8873            },
8874        ];
8875
8876        app.profile_filter = "base".to_string();
8877        let filtered = app.get_filtered_profiles();
8878        assert_eq!(filtered.len(), 1);
8879        assert_eq!(filtered[0].name, "profile1");
8880    }
8881
8882    #[test]
8883    fn test_profile_filter_by_role() {
8884        let mut app = test_app_no_region();
8885        app.available_profiles = vec![
8886            AwsProfile {
8887                name: "admin-profile".to_string(),
8888                region: None,
8889                account: None,
8890                role_arn: Some("arn:aws:iam::123:role/AdminRole".to_string()),
8891                source_profile: None,
8892            },
8893            AwsProfile {
8894                name: "dev-profile".to_string(),
8895                region: None,
8896                account: None,
8897                role_arn: Some("arn:aws:iam::123:role/DevRole".to_string()),
8898                source_profile: None,
8899            },
8900        ];
8901
8902        app.profile_filter = "Admin".to_string();
8903        let filtered = app.get_filtered_profiles();
8904        assert_eq!(filtered.len(), 1);
8905        assert_eq!(filtered[0].name, "admin-profile");
8906    }
8907
8908    #[test]
8909    fn test_profiles_sorted_by_name() {
8910        let mut app = test_app_no_region();
8911        app.available_profiles = vec![
8912            AwsProfile {
8913                name: "zebra-profile".to_string(),
8914                region: None,
8915                account: None,
8916                role_arn: None,
8917                source_profile: None,
8918            },
8919            AwsProfile {
8920                name: "alpha-profile".to_string(),
8921                region: None,
8922                account: None,
8923                role_arn: None,
8924                source_profile: None,
8925            },
8926            AwsProfile {
8927                name: "beta-profile".to_string(),
8928                region: None,
8929                account: None,
8930                role_arn: None,
8931                source_profile: None,
8932            },
8933        ];
8934
8935        let filtered = app.get_filtered_profiles();
8936        assert_eq!(filtered.len(), 3);
8937        assert_eq!(filtered[0].name, "alpha-profile");
8938        assert_eq!(filtered[1].name, "beta-profile");
8939        assert_eq!(filtered[2].name, "zebra-profile");
8940    }
8941
8942    #[test]
8943    fn test_profile_with_role_arn() {
8944        let mut app = test_app_no_region();
8945        app.available_profiles = vec![AwsProfile {
8946            name: "role-profile".to_string(),
8947            region: Some("us-east-1".to_string()),
8948            account: Some("123456789".to_string()),
8949            role_arn: Some("arn:aws:iam::123456789:role/AdminRole".to_string()),
8950            source_profile: None,
8951        }];
8952
8953        let filtered = app.get_filtered_profiles();
8954        assert_eq!(filtered.len(), 1);
8955        assert!(filtered[0].role_arn.as_ref().unwrap().contains(":role/"));
8956    }
8957
8958    #[test]
8959    fn test_profile_with_user_arn() {
8960        let mut app = test_app_no_region();
8961        app.available_profiles = vec![AwsProfile {
8962            name: "user-profile".to_string(),
8963            region: Some("us-east-1".to_string()),
8964            account: Some("123456789".to_string()),
8965            role_arn: Some("arn:aws:iam::123456789:user/john-doe".to_string()),
8966            source_profile: None,
8967        }];
8968
8969        let filtered = app.get_filtered_profiles();
8970        assert_eq!(filtered.len(), 1);
8971        assert!(filtered[0].role_arn.as_ref().unwrap().contains(":user/"));
8972    }
8973
8974    #[test]
8975    fn test_filtered_profiles_also_sorted() {
8976        let mut app = test_app_no_region();
8977        app.available_profiles = vec![
8978            AwsProfile {
8979                name: "prod-zebra".to_string(),
8980                region: Some("us-east-1".to_string()),
8981                account: None,
8982                role_arn: None,
8983                source_profile: None,
8984            },
8985            AwsProfile {
8986                name: "prod-alpha".to_string(),
8987                region: Some("us-east-1".to_string()),
8988                account: None,
8989                role_arn: None,
8990                source_profile: None,
8991            },
8992            AwsProfile {
8993                name: "dev-profile".to_string(),
8994                region: Some("us-west-2".to_string()),
8995                account: None,
8996                role_arn: None,
8997                source_profile: None,
8998            },
8999        ];
9000
9001        app.profile_filter = "prod".to_string();
9002        let filtered = app.get_filtered_profiles();
9003        assert_eq!(filtered.len(), 2);
9004        assert_eq!(filtered[0].name, "prod-alpha");
9005        assert_eq!(filtered[1].name, "prod-zebra");
9006    }
9007
9008    #[test]
9009    fn test_profile_picker_has_all_columns() {
9010        let mut app = test_app_no_region();
9011        app.available_profiles = vec![AwsProfile {
9012            name: "test".to_string(),
9013            region: Some("us-east-1".to_string()),
9014            account: Some("123456789".to_string()),
9015            role_arn: Some("arn:aws:iam::123456789:role/Admin".to_string()),
9016            source_profile: Some("base".to_string()),
9017        }];
9018
9019        let filtered = app.get_filtered_profiles();
9020        assert_eq!(filtered.len(), 1);
9021        assert!(filtered[0].name == "test");
9022        assert!(filtered[0].region.is_some());
9023        assert!(filtered[0].account.is_some());
9024        assert!(filtered[0].role_arn.is_some());
9025        assert!(filtered[0].source_profile.is_some());
9026    }
9027
9028    #[test]
9029    fn test_session_picker_shows_tab_count() {
9030        let mut app = test_app_no_region();
9031        app.sessions = vec![Session {
9032            id: "1".to_string(),
9033            timestamp: "2024-01-01".to_string(),
9034            profile: "test".to_string(),
9035            region: "us-east-1".to_string(),
9036            account_id: "123".to_string(),
9037            role_arn: String::new(),
9038            tabs: vec![
9039                SessionTab {
9040                    service: "CloudWatch".to_string(),
9041                    title: "Logs".to_string(),
9042                    breadcrumb: String::new(),
9043                    filter: None,
9044                    selected_item: None,
9045                },
9046                SessionTab {
9047                    service: "S3".to_string(),
9048                    title: "Buckets".to_string(),
9049                    breadcrumb: String::new(),
9050                    filter: None,
9051                    selected_item: None,
9052                },
9053            ],
9054        }];
9055
9056        let filtered = app.get_filtered_sessions();
9057        assert_eq!(filtered.len(), 1);
9058        assert_eq!(filtered[0].tabs.len(), 2);
9059    }
9060
9061    #[test]
9062    fn test_start_background_data_fetch_loads_profiles() {
9063        let mut app = test_app_no_region();
9064        assert!(app.available_profiles.is_empty());
9065
9066        // Load profiles synchronously
9067        app.available_profiles = App::load_aws_profiles();
9068
9069        // Profiles should be loaded
9070        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
9071    }
9072
9073    #[test]
9074    fn test_refresh_in_profile_picker() {
9075        let mut app = test_app_no_region();
9076        app.mode = Mode::ProfilePicker;
9077        app.available_profiles = vec![AwsProfile {
9078            name: "test".to_string(),
9079            region: None,
9080            account: None,
9081            role_arn: None,
9082            source_profile: None,
9083        }];
9084
9085        app.handle_action(Action::Refresh);
9086
9087        // Should set loading state
9088        assert!(app.log_groups_state.loading);
9089        assert_eq!(app.log_groups_state.loading_message, "Refreshing...");
9090    }
9091
9092    #[test]
9093    fn test_refresh_sets_loading_for_profile_picker() {
9094        let mut app = test_app_no_region();
9095        app.mode = Mode::ProfilePicker;
9096
9097        assert!(!app.log_groups_state.loading);
9098
9099        app.handle_action(Action::Refresh);
9100
9101        assert!(app.log_groups_state.loading);
9102    }
9103
9104    #[test]
9105    fn test_profiles_loaded_on_demand() {
9106        let mut app = test_app_no_region();
9107
9108        // Profiles not loaded by default
9109        assert!(app.available_profiles.is_empty());
9110
9111        // Load on demand
9112        app.available_profiles = App::load_aws_profiles();
9113
9114        // Now loaded
9115        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
9116    }
9117
9118    #[test]
9119    fn test_profile_accounts_not_fetched_automatically() {
9120        let mut app = test_app_no_region();
9121        app.available_profiles = App::load_aws_profiles();
9122
9123        // Accounts should not be populated automatically
9124        for profile in &app.available_profiles {
9125            // Account may or may not be set depending on what's in config
9126            // But we're not fetching them automatically
9127            assert!(profile.account.is_none() || profile.account.is_some());
9128        }
9129    }
9130
9131    #[test]
9132    fn test_ctrl_r_triggers_account_fetch() {
9133        let mut app = test_app_no_region();
9134        app.mode = Mode::ProfilePicker;
9135        app.available_profiles = vec![AwsProfile {
9136            name: "test".to_string(),
9137            region: Some("us-east-1".to_string()),
9138            account: None,
9139            role_arn: None,
9140            source_profile: None,
9141        }];
9142
9143        // Before refresh, account is None
9144        assert!(app.available_profiles[0].account.is_none());
9145
9146        // Trigger refresh
9147        app.handle_action(Action::Refresh);
9148
9149        // Should set loading state (actual fetch happens in main.rs event loop)
9150        assert!(app.log_groups_state.loading);
9151    }
9152
9153    #[test]
9154    fn test_refresh_in_region_picker() {
9155        let mut app = test_app_no_region();
9156        app.mode = Mode::RegionPicker;
9157
9158        let initial_latencies = app.region_latencies.len();
9159        app.handle_action(Action::Refresh);
9160
9161        // Latencies should be cleared and remeasured
9162        assert!(app.region_latencies.is_empty() || app.region_latencies.len() >= initial_latencies);
9163    }
9164
9165    #[test]
9166    fn test_refresh_in_session_picker() {
9167        let mut app = test_app_no_region();
9168        app.mode = Mode::SessionPicker;
9169        app.sessions = vec![];
9170
9171        app.handle_action(Action::Refresh);
9172
9173        // Sessions should be reloaded (may be empty if no saved sessions)
9174        assert!(app.sessions.is_empty() || !app.sessions.is_empty());
9175    }
9176
9177    #[test]
9178    fn test_session_picker_selection() {
9179        let mut app = test_app();
9180
9181        app.sessions = vec![Session {
9182            id: "1".to_string(),
9183            timestamp: "2024-01-01".to_string(),
9184            profile: "prod-profile".to_string(),
9185            region: "us-west-2".to_string(),
9186            account_id: "123456789".to_string(),
9187            role_arn: "arn:aws:iam::123456789:role/admin".to_string(),
9188            tabs: vec![SessionTab {
9189                service: "CloudWatchLogGroups".to_string(),
9190                title: "Log Groups".to_string(),
9191                breadcrumb: "CloudWatch > Log Groups".to_string(),
9192                filter: Some("test".to_string()),
9193                selected_item: None,
9194            }],
9195        }];
9196
9197        app.mode = Mode::SessionPicker;
9198        app.session_picker_selected = 0;
9199
9200        // Simulate selecting the session
9201        app.handle_action(Action::Select);
9202
9203        assert_eq!(app.mode, Mode::Normal);
9204        assert_eq!(app.profile, "prod-profile");
9205        assert_eq!(app.region, "us-west-2");
9206        assert_eq!(app.config.account_id, "123456789");
9207        assert_eq!(app.tabs.len(), 1);
9208        assert_eq!(app.tabs[0].title, "Log Groups");
9209    }
9210
9211    #[test]
9212    fn test_save_session_creates_session() {
9213        let mut app =
9214            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
9215        app.config.account_id = "123456789".to_string();
9216        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
9217
9218        app.tabs.push(Tab {
9219            service: Service::CloudWatchLogGroups,
9220            title: "Log Groups".to_string(),
9221            breadcrumb: "CloudWatch > Log Groups".to_string(),
9222        });
9223
9224        app.save_current_session();
9225
9226        assert!(app.current_session.is_some());
9227        let session = app.current_session.clone().unwrap();
9228        assert_eq!(session.profile, "test-profile");
9229        assert_eq!(session.region, "us-east-1");
9230        assert_eq!(session.account_id, "123456789");
9231        assert_eq!(session.tabs.len(), 1);
9232
9233        // Cleanup
9234        let _ = session.delete();
9235    }
9236
9237    #[test]
9238    fn test_save_session_updates_existing() {
9239        let mut app =
9240            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
9241        app.config.account_id = "123456789".to_string();
9242        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
9243
9244        app.current_session = Some(Session {
9245            id: "existing".to_string(),
9246            timestamp: "2024-01-01".to_string(),
9247            profile: "test-profile".to_string(),
9248            region: "us-east-1".to_string(),
9249            account_id: "123456789".to_string(),
9250            role_arn: "arn:aws:iam::123456789:role/test".to_string(),
9251            tabs: vec![],
9252        });
9253
9254        app.tabs.push(Tab {
9255            service: Service::CloudWatchLogGroups,
9256            title: "Log Groups".to_string(),
9257            breadcrumb: "CloudWatch > Log Groups".to_string(),
9258        });
9259
9260        app.save_current_session();
9261
9262        let session = app.current_session.clone().unwrap();
9263        assert_eq!(session.id, "existing");
9264        assert_eq!(session.tabs.len(), 1);
9265
9266        // Cleanup
9267        let _ = session.delete();
9268    }
9269
9270    #[test]
9271    fn test_save_session_skips_empty_tabs() {
9272        let mut app =
9273            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
9274        app.config.account_id = "123456789".to_string();
9275
9276        app.save_current_session();
9277
9278        assert!(app.current_session.is_none());
9279    }
9280
9281    #[test]
9282    fn test_save_session_deletes_when_tabs_closed() {
9283        let mut app =
9284            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
9285        app.config.account_id = "123456789".to_string();
9286        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
9287
9288        // Create a session with tabs
9289        app.current_session = Some(Session {
9290            id: "test_delete".to_string(),
9291            timestamp: "2024-01-01 10:00:00 UTC".to_string(),
9292            profile: "test-profile".to_string(),
9293            region: "us-east-1".to_string(),
9294            account_id: "123456789".to_string(),
9295            role_arn: "arn:aws:iam::123456789:role/test".to_string(),
9296            tabs: vec![],
9297        });
9298
9299        // Save with no tabs should delete session
9300        app.save_current_session();
9301
9302        assert!(app.current_session.is_none());
9303    }
9304
9305    #[test]
9306    fn test_closing_all_tabs_deletes_session() {
9307        let mut app =
9308            App::new_without_client("test-profile".to_string(), Some("us-east-1".to_string()));
9309        app.config.account_id = "123456789".to_string();
9310        app.config.role_arn = "arn:aws:iam::123456789:role/test".to_string();
9311
9312        // Add a tab
9313        app.tabs.push(Tab {
9314            service: Service::CloudWatchLogGroups,
9315            title: "Log Groups".to_string(),
9316            breadcrumb: "CloudWatch > Log Groups".to_string(),
9317        });
9318
9319        // Create session
9320        app.save_current_session();
9321        assert!(app.current_session.is_some());
9322        let session_id = app.current_session.as_ref().unwrap().id.clone();
9323
9324        // Close all tabs
9325        app.tabs.clear();
9326
9327        // Save should delete session
9328        app.save_current_session();
9329        assert!(app.current_session.is_none());
9330
9331        // Cleanup - ensure session file is deleted
9332        let _ = Session::load(&session_id).map(|s| s.delete());
9333    }
9334
9335    #[test]
9336    fn test_credential_error_opens_profile_picker() {
9337        // Simulate what main.rs does on credential error
9338        let mut app = App::new_without_client("default".to_string(), None);
9339        let error_str = "Unable to load credentials from any source";
9340
9341        if error_str.contains("credentials") {
9342            app.available_profiles = App::load_aws_profiles();
9343            app.mode = Mode::ProfilePicker;
9344        }
9345
9346        assert_eq!(app.mode, Mode::ProfilePicker);
9347        // Should have loaded profiles
9348        assert!(!app.available_profiles.is_empty() || app.available_profiles.is_empty());
9349    }
9350
9351    #[test]
9352    fn test_non_credential_error_shows_error_modal() {
9353        let mut app = App::new_without_client("default".to_string(), None);
9354        let error_str = "Network timeout";
9355
9356        if !error_str.contains("credentials") {
9357            app.error_message = Some(error_str.to_string());
9358            app.mode = Mode::ErrorModal;
9359        }
9360
9361        assert_eq!(app.mode, Mode::ErrorModal);
9362        assert!(app.error_message.is_some());
9363    }
9364
9365    #[tokio::test]
9366    async fn test_profile_selection_loads_credentials() {
9367        // Set a valid AWS profile if available
9368        std::env::set_var("AWS_PROFILE", "default");
9369
9370        // Try to create app with profile
9371        let result = App::new(Some("default".to_string()), Some("us-east-1".to_string())).await;
9372
9373        if let Ok(app) = result {
9374            // If credentials are available, verify they're loaded
9375            assert!(!app.config.account_id.is_empty());
9376            assert!(!app.config.role_arn.is_empty());
9377            assert_eq!(app.profile, "default");
9378            assert_eq!(app.config.region, "us-east-1");
9379        }
9380        // If no credentials, test passes (can't test without real AWS creds)
9381    }
9382
9383    #[test]
9384    fn test_new_app_shows_service_picker_with_no_tabs() {
9385        let app = App::new_without_client("default".to_string(), Some("us-east-1".to_string()));
9386
9387        // Should start with no service selected
9388        assert!(!app.service_selected);
9389        // Should be in ServicePicker mode (service picker)
9390        assert_eq!(app.mode, Mode::ServicePicker);
9391        // Should have no tabs
9392        assert!(app.tabs.is_empty());
9393    }
9394
9395    #[tokio::test]
9396    async fn test_aws_profile_env_var_read_before_config_load() {
9397        // This test verifies the bug: AWS_PROFILE should be read and used
9398        std::env::set_var("AWS_PROFILE", "test-profile");
9399
9400        // Simulate what happens in App::new
9401        let profile_name = None
9402            .or_else(|| std::env::var("AWS_PROFILE").ok())
9403            .unwrap_or_else(|| "default".to_string());
9404
9405        // Should have read test-profile from env
9406        assert_eq!(profile_name, "test-profile");
9407
9408        // Now set it (redundant but that's what the code does)
9409        std::env::set_var("AWS_PROFILE", &profile_name);
9410
9411        // Verify it's still set
9412        assert_eq!(std::env::var("AWS_PROFILE").unwrap(), "test-profile");
9413
9414        std::env::remove_var("AWS_PROFILE");
9415    }
9416
9417    #[test]
9418    fn test_next_preferences_cloudformation() {
9419        let mut app = test_app();
9420        app.current_service = Service::CloudFormationStacks;
9421        app.mode = Mode::ColumnSelector;
9422        app.column_selector_index = 0;
9423
9424        // Should jump to PageSize section
9425        let page_size_idx = app.cfn_column_ids.len() + 2;
9426        app.handle_action(Action::NextPreferences);
9427        assert_eq!(app.column_selector_index, page_size_idx);
9428
9429        // Should wrap back to Columns
9430        app.handle_action(Action::NextPreferences);
9431        assert_eq!(app.column_selector_index, 0);
9432    }
9433
9434    #[test]
9435    fn test_next_preferences_lambda_functions() {
9436        let mut app = test_app();
9437        app.current_service = Service::LambdaFunctions;
9438        app.mode = Mode::ColumnSelector;
9439        app.column_selector_index = 0;
9440
9441        let page_size_idx = app.lambda_state.function_column_ids.len() + 2;
9442        app.handle_action(Action::NextPreferences);
9443        assert_eq!(app.column_selector_index, page_size_idx);
9444
9445        app.handle_action(Action::NextPreferences);
9446        assert_eq!(app.column_selector_index, 0);
9447    }
9448
9449    #[test]
9450    fn test_next_preferences_lambda_applications() {
9451        let mut app = test_app();
9452        app.current_service = Service::LambdaApplications;
9453        app.mode = Mode::ColumnSelector;
9454        app.column_selector_index = 0;
9455
9456        let page_size_idx = app.lambda_application_column_ids.len() + 2;
9457        app.handle_action(Action::NextPreferences);
9458        assert_eq!(app.column_selector_index, page_size_idx);
9459
9460        app.handle_action(Action::NextPreferences);
9461        assert_eq!(app.column_selector_index, 0);
9462    }
9463
9464    #[test]
9465    fn test_next_preferences_ecr_images() {
9466        let mut app = test_app();
9467        app.current_service = Service::EcrRepositories;
9468        app.ecr_state.current_repository = Some("test-repo".to_string());
9469        app.mode = Mode::ColumnSelector;
9470        app.column_selector_index = 0;
9471
9472        let page_size_idx = app.ecr_image_column_ids.len() + 2;
9473        app.handle_action(Action::NextPreferences);
9474        assert_eq!(app.column_selector_index, page_size_idx);
9475
9476        app.handle_action(Action::NextPreferences);
9477        assert_eq!(app.column_selector_index, 0);
9478    }
9479
9480    #[test]
9481    fn test_cloudformation_next_item() {
9482        let mut app = test_app();
9483        app.current_service = Service::CloudFormationStacks;
9484        app.service_selected = true;
9485        app.mode = Mode::Normal;
9486        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9487        app.cfn_state.table.items = vec![
9488            CfnStack {
9489                name: "stack1".to_string(),
9490                stack_id: "id1".to_string(),
9491                status: "CREATE_COMPLETE".to_string(),
9492                created_time: "2024-01-01".to_string(),
9493                updated_time: String::new(),
9494                deleted_time: String::new(),
9495                drift_status: String::new(),
9496                last_drift_check_time: String::new(),
9497                status_reason: String::new(),
9498                description: String::new(),
9499                detailed_status: String::new(),
9500                root_stack: String::new(),
9501                parent_stack: String::new(),
9502                termination_protection: false,
9503                iam_role: String::new(),
9504                tags: Vec::new(),
9505                stack_policy: String::new(),
9506                rollback_monitoring_time: String::new(),
9507                rollback_alarms: Vec::new(),
9508                notification_arns: Vec::new(),
9509            },
9510            CfnStack {
9511                name: "stack2".to_string(),
9512                stack_id: "id2".to_string(),
9513                status: "UPDATE_COMPLETE".to_string(),
9514                created_time: "2024-01-02".to_string(),
9515                updated_time: String::new(),
9516                deleted_time: String::new(),
9517                drift_status: String::new(),
9518                last_drift_check_time: String::new(),
9519                status_reason: String::new(),
9520                description: String::new(),
9521                detailed_status: String::new(),
9522                root_stack: String::new(),
9523                parent_stack: String::new(),
9524                termination_protection: false,
9525                iam_role: String::new(),
9526                tags: Vec::new(),
9527                stack_policy: String::new(),
9528                rollback_monitoring_time: String::new(),
9529                rollback_alarms: Vec::new(),
9530                notification_arns: Vec::new(),
9531            },
9532        ];
9533        app.cfn_state.table.reset();
9534
9535        app.handle_action(Action::NextItem);
9536        assert_eq!(app.cfn_state.table.selected, 1);
9537
9538        app.handle_action(Action::NextItem);
9539        assert_eq!(app.cfn_state.table.selected, 1); // At max
9540    }
9541
9542    #[test]
9543    fn test_cloudformation_prev_item() {
9544        let mut app = test_app();
9545        app.current_service = Service::CloudFormationStacks;
9546        app.service_selected = true;
9547        app.mode = Mode::Normal;
9548        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9549        app.cfn_state.table.items = vec![
9550            CfnStack {
9551                name: "stack1".to_string(),
9552                stack_id: "id1".to_string(),
9553                status: "CREATE_COMPLETE".to_string(),
9554                created_time: "2024-01-01".to_string(),
9555                updated_time: String::new(),
9556                deleted_time: String::new(),
9557                drift_status: String::new(),
9558                last_drift_check_time: String::new(),
9559                status_reason: String::new(),
9560                description: String::new(),
9561                detailed_status: String::new(),
9562                root_stack: String::new(),
9563                parent_stack: String::new(),
9564                termination_protection: false,
9565                iam_role: String::new(),
9566                tags: Vec::new(),
9567                stack_policy: String::new(),
9568                rollback_monitoring_time: String::new(),
9569                rollback_alarms: Vec::new(),
9570                notification_arns: Vec::new(),
9571            },
9572            CfnStack {
9573                name: "stack2".to_string(),
9574                stack_id: "id2".to_string(),
9575                status: "UPDATE_COMPLETE".to_string(),
9576                created_time: "2024-01-02".to_string(),
9577                updated_time: String::new(),
9578                deleted_time: String::new(),
9579                drift_status: String::new(),
9580                last_drift_check_time: String::new(),
9581                status_reason: String::new(),
9582                description: String::new(),
9583                detailed_status: String::new(),
9584                root_stack: String::new(),
9585                parent_stack: String::new(),
9586                termination_protection: false,
9587                iam_role: String::new(),
9588                tags: Vec::new(),
9589                stack_policy: String::new(),
9590                rollback_monitoring_time: String::new(),
9591                rollback_alarms: Vec::new(),
9592                notification_arns: Vec::new(),
9593            },
9594        ];
9595        app.cfn_state.table.selected = 1;
9596
9597        app.handle_action(Action::PrevItem);
9598        assert_eq!(app.cfn_state.table.selected, 0);
9599
9600        app.handle_action(Action::PrevItem);
9601        assert_eq!(app.cfn_state.table.selected, 0); // At min
9602    }
9603
9604    #[test]
9605    fn test_cloudformation_page_down() {
9606        let mut app = test_app();
9607        app.current_service = Service::CloudFormationStacks;
9608        app.service_selected = true;
9609        app.mode = Mode::Normal;
9610        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9611
9612        // Create 20 stacks
9613        for i in 0..20 {
9614            app.cfn_state.table.items.push(CfnStack {
9615                name: format!("stack{}", i),
9616                stack_id: format!("id{}", i),
9617                status: "CREATE_COMPLETE".to_string(),
9618                created_time: format!("2024-01-{:02}", i + 1),
9619                updated_time: String::new(),
9620                deleted_time: String::new(),
9621                drift_status: String::new(),
9622                last_drift_check_time: String::new(),
9623                status_reason: String::new(),
9624                description: String::new(),
9625                detailed_status: String::new(),
9626                root_stack: String::new(),
9627                parent_stack: String::new(),
9628                termination_protection: false,
9629                iam_role: String::new(),
9630                tags: Vec::new(),
9631                stack_policy: String::new(),
9632                rollback_monitoring_time: String::new(),
9633                rollback_alarms: Vec::new(),
9634                notification_arns: Vec::new(),
9635            });
9636        }
9637        app.cfn_state.table.reset();
9638
9639        app.handle_action(Action::PageDown);
9640        assert_eq!(app.cfn_state.table.selected, 10);
9641
9642        app.handle_action(Action::PageDown);
9643        assert_eq!(app.cfn_state.table.selected, 19); // Clamped to max
9644    }
9645
9646    #[test]
9647    fn test_cloudformation_page_up() {
9648        let mut app = test_app();
9649        app.current_service = Service::CloudFormationStacks;
9650        app.service_selected = true;
9651        app.mode = Mode::Normal;
9652        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9653
9654        // Create 20 stacks
9655        for i in 0..20 {
9656            app.cfn_state.table.items.push(CfnStack {
9657                name: format!("stack{}", i),
9658                stack_id: format!("id{}", i),
9659                status: "CREATE_COMPLETE".to_string(),
9660                created_time: format!("2024-01-{:02}", i + 1),
9661                updated_time: String::new(),
9662                deleted_time: String::new(),
9663                drift_status: String::new(),
9664                last_drift_check_time: String::new(),
9665                status_reason: String::new(),
9666                description: String::new(),
9667                detailed_status: String::new(),
9668                root_stack: String::new(),
9669                parent_stack: String::new(),
9670                termination_protection: false,
9671                iam_role: String::new(),
9672                tags: Vec::new(),
9673                stack_policy: String::new(),
9674                rollback_monitoring_time: String::new(),
9675                rollback_alarms: Vec::new(),
9676                notification_arns: Vec::new(),
9677            });
9678        }
9679        app.cfn_state.table.selected = 15;
9680
9681        app.handle_action(Action::PageUp);
9682        assert_eq!(app.cfn_state.table.selected, 5);
9683
9684        app.handle_action(Action::PageUp);
9685        assert_eq!(app.cfn_state.table.selected, 0); // Clamped to min
9686    }
9687
9688    #[test]
9689    fn test_cloudformation_filter_input() {
9690        let mut app = test_app();
9691        app.current_service = Service::CloudFormationStacks;
9692        app.service_selected = true;
9693        app.mode = Mode::Normal;
9694
9695        app.handle_action(Action::StartFilter);
9696        assert_eq!(app.mode, Mode::FilterInput);
9697
9698        // Directly set filter (character input is handled in event loop, not actions)
9699        app.cfn_state.table.filter = "test".to_string();
9700        assert_eq!(app.cfn_state.table.filter, "test");
9701    }
9702
9703    #[test]
9704    fn test_cloudformation_filter_applies() {
9705        let mut app = test_app();
9706        app.current_service = Service::CloudFormationStacks;
9707        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9708        app.cfn_state.table.items = vec![
9709            CfnStack {
9710                name: "prod-stack".to_string(),
9711                stack_id: "id1".to_string(),
9712                status: "CREATE_COMPLETE".to_string(),
9713                created_time: "2024-01-01".to_string(),
9714                updated_time: String::new(),
9715                deleted_time: String::new(),
9716                drift_status: String::new(),
9717                last_drift_check_time: String::new(),
9718                status_reason: String::new(),
9719                description: "Production stack".to_string(),
9720                detailed_status: String::new(),
9721                root_stack: String::new(),
9722                parent_stack: String::new(),
9723                termination_protection: false,
9724                iam_role: String::new(),
9725                tags: Vec::new(),
9726                stack_policy: String::new(),
9727                rollback_monitoring_time: String::new(),
9728                rollback_alarms: Vec::new(),
9729                notification_arns: Vec::new(),
9730            },
9731            CfnStack {
9732                name: "dev-stack".to_string(),
9733                stack_id: "id2".to_string(),
9734                status: "UPDATE_COMPLETE".to_string(),
9735                created_time: "2024-01-02".to_string(),
9736                updated_time: String::new(),
9737                deleted_time: String::new(),
9738                drift_status: String::new(),
9739                last_drift_check_time: String::new(),
9740                status_reason: String::new(),
9741                description: "Development stack".to_string(),
9742                detailed_status: String::new(),
9743                root_stack: String::new(),
9744                parent_stack: String::new(),
9745                termination_protection: false,
9746                iam_role: String::new(),
9747                tags: Vec::new(),
9748                stack_policy: String::new(),
9749                rollback_monitoring_time: String::new(),
9750                rollback_alarms: Vec::new(),
9751                notification_arns: Vec::new(),
9752            },
9753        ];
9754        app.cfn_state.table.filter = "prod".to_string();
9755
9756        let filtered = app.filtered_cloudformation_stacks();
9757        assert_eq!(filtered.len(), 1);
9758        assert_eq!(filtered[0].name, "prod-stack");
9759    }
9760
9761    #[test]
9762    fn test_cloudformation_right_arrow_expands() {
9763        let mut app = test_app();
9764        app.current_service = Service::CloudFormationStacks;
9765        app.service_selected = true;
9766        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9767        app.cfn_state.table.items = vec![CfnStack {
9768            name: "test-stack".to_string(),
9769            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
9770                .to_string(),
9771            status: "CREATE_COMPLETE".to_string(),
9772            created_time: "2024-01-01".to_string(),
9773            updated_time: String::new(),
9774            deleted_time: String::new(),
9775            drift_status: String::new(),
9776            last_drift_check_time: String::new(),
9777            status_reason: String::new(),
9778            description: "Test stack".to_string(),
9779            detailed_status: String::new(),
9780            root_stack: String::new(),
9781            parent_stack: String::new(),
9782            termination_protection: false,
9783            iam_role: String::new(),
9784            tags: Vec::new(),
9785            stack_policy: String::new(),
9786            rollback_monitoring_time: String::new(),
9787            rollback_alarms: Vec::new(),
9788            notification_arns: Vec::new(),
9789        }];
9790        app.cfn_state.table.reset();
9791
9792        assert_eq!(app.cfn_state.table.expanded_item, None);
9793
9794        app.handle_action(Action::NextPane);
9795        assert_eq!(app.cfn_state.table.expanded_item, Some(0));
9796    }
9797
9798    #[test]
9799    fn test_cloudformation_left_arrow_collapses() {
9800        let mut app = test_app();
9801        app.current_service = Service::CloudFormationStacks;
9802        app.service_selected = true;
9803        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9804        app.cfn_state.table.items = vec![CfnStack {
9805            name: "test-stack".to_string(),
9806            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
9807                .to_string(),
9808            status: "CREATE_COMPLETE".to_string(),
9809            created_time: "2024-01-01".to_string(),
9810            updated_time: String::new(),
9811            deleted_time: String::new(),
9812            drift_status: String::new(),
9813            last_drift_check_time: String::new(),
9814            status_reason: String::new(),
9815            description: "Test stack".to_string(),
9816            detailed_status: String::new(),
9817            root_stack: String::new(),
9818            parent_stack: String::new(),
9819            termination_protection: false,
9820            iam_role: String::new(),
9821            tags: Vec::new(),
9822            stack_policy: String::new(),
9823            rollback_monitoring_time: String::new(),
9824            rollback_alarms: Vec::new(),
9825            notification_arns: Vec::new(),
9826        }];
9827        app.cfn_state.table.reset();
9828        app.cfn_state.table.expanded_item = Some(0);
9829
9830        app.handle_action(Action::PrevPane);
9831        assert_eq!(app.cfn_state.table.expanded_item, None);
9832    }
9833
9834    #[test]
9835    fn test_cloudformation_enter_drills_into_stack() {
9836        let mut app = test_app();
9837        app.current_service = Service::CloudFormationStacks;
9838        app.service_selected = true;
9839        app.mode = Mode::Normal;
9840        app.tabs = vec![Tab {
9841            service: Service::CloudFormationStacks,
9842            title: "CloudFormation > Stacks".to_string(),
9843            breadcrumb: "CloudFormation > Stacks".to_string(),
9844        }];
9845        app.current_tab = 0;
9846        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9847        app.cfn_state.table.items = vec![CfnStack {
9848            name: "test-stack".to_string(),
9849            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
9850                .to_string(),
9851            status: "CREATE_COMPLETE".to_string(),
9852            created_time: "2024-01-01".to_string(),
9853            updated_time: String::new(),
9854            deleted_time: String::new(),
9855            drift_status: String::new(),
9856            last_drift_check_time: String::new(),
9857            status_reason: String::new(),
9858            description: "Test stack".to_string(),
9859            detailed_status: String::new(),
9860            root_stack: String::new(),
9861            parent_stack: String::new(),
9862            termination_protection: false,
9863            iam_role: String::new(),
9864            tags: Vec::new(),
9865            stack_policy: String::new(),
9866            rollback_monitoring_time: String::new(),
9867            rollback_alarms: Vec::new(),
9868            notification_arns: Vec::new(),
9869        }];
9870        app.cfn_state.table.reset();
9871
9872        // Verify filtering works
9873        let filtered = app.filtered_cloudformation_stacks();
9874        assert_eq!(filtered.len(), 1);
9875        assert_eq!(filtered[0].name, "test-stack");
9876
9877        assert_eq!(app.cfn_state.current_stack, None);
9878
9879        // Enter drills into stack detail view
9880        app.handle_action(Action::Select);
9881        assert_eq!(app.cfn_state.current_stack, Some("test-stack".to_string()));
9882    }
9883
9884    #[test]
9885    fn test_cloudformation_copy_to_clipboard() {
9886        let mut app = test_app();
9887        app.current_service = Service::CloudFormationStacks;
9888        app.service_selected = true;
9889        app.mode = Mode::Normal;
9890        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9891        app.cfn_state.table.items = vec![
9892            CfnStack {
9893                name: "stack1".to_string(),
9894                stack_id: "id1".to_string(),
9895                status: "CREATE_COMPLETE".to_string(),
9896                created_time: "2024-01-01".to_string(),
9897                updated_time: String::new(),
9898                deleted_time: String::new(),
9899                drift_status: String::new(),
9900                last_drift_check_time: String::new(),
9901                status_reason: String::new(),
9902                description: String::new(),
9903                detailed_status: String::new(),
9904                root_stack: String::new(),
9905                parent_stack: String::new(),
9906                termination_protection: false,
9907                iam_role: String::new(),
9908                tags: Vec::new(),
9909                stack_policy: String::new(),
9910                rollback_monitoring_time: String::new(),
9911                rollback_alarms: Vec::new(),
9912                notification_arns: Vec::new(),
9913            },
9914            CfnStack {
9915                name: "stack2".to_string(),
9916                stack_id: "id2".to_string(),
9917                status: "UPDATE_COMPLETE".to_string(),
9918                created_time: "2024-01-02".to_string(),
9919                updated_time: String::new(),
9920                deleted_time: String::new(),
9921                drift_status: String::new(),
9922                last_drift_check_time: String::new(),
9923                status_reason: String::new(),
9924                description: String::new(),
9925                detailed_status: String::new(),
9926                root_stack: String::new(),
9927                parent_stack: String::new(),
9928                termination_protection: false,
9929                iam_role: String::new(),
9930                tags: Vec::new(),
9931                stack_policy: String::new(),
9932                rollback_monitoring_time: String::new(),
9933                rollback_alarms: Vec::new(),
9934                notification_arns: Vec::new(),
9935            },
9936        ];
9937
9938        assert!(!app.snapshot_requested);
9939        app.handle_action(Action::CopyToClipboard);
9940
9941        // Should set snapshot_requested flag
9942        assert!(app.snapshot_requested);
9943    }
9944
9945    #[test]
9946    fn test_cloudformation_expansion_shows_all_visible_columns() {
9947        let mut app = test_app();
9948        app.current_service = Service::CloudFormationStacks;
9949        app.cfn_state.status_filter = crate::ui::cfn::StatusFilter::Complete;
9950        app.cfn_state.table.items = vec![CfnStack {
9951            name: "test-stack".to_string(),
9952            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
9953                .to_string(),
9954            status: "CREATE_COMPLETE".to_string(),
9955            created_time: "2024-01-01".to_string(),
9956            updated_time: "2024-01-02".to_string(),
9957            deleted_time: String::new(),
9958            drift_status: "IN_SYNC".to_string(),
9959            last_drift_check_time: "2024-01-03".to_string(),
9960            status_reason: String::new(),
9961            description: "Test description".to_string(),
9962            detailed_status: String::new(),
9963            root_stack: String::new(),
9964            parent_stack: String::new(),
9965            termination_protection: false,
9966            iam_role: String::new(),
9967            tags: Vec::new(),
9968            stack_policy: String::new(),
9969            rollback_monitoring_time: String::new(),
9970            rollback_alarms: Vec::new(),
9971            notification_arns: Vec::new(),
9972        }];
9973
9974        // Set visible columns
9975        app.cfn_visible_column_ids = [
9976            CfnColumn::Name,
9977            CfnColumn::Status,
9978            CfnColumn::CreatedTime,
9979            CfnColumn::Description,
9980        ]
9981        .iter()
9982        .map(|c| c.id())
9983        .collect();
9984
9985        app.cfn_state.table.expanded_item = Some(0);
9986
9987        // Verify all visible columns would be shown in expansion
9988        // (This is a structural test - actual rendering is in UI layer)
9989        assert_eq!(app.cfn_visible_column_ids.len(), 4);
9990        assert!(app.cfn_state.table.has_expanded_item());
9991    }
9992
9993    #[test]
9994    fn test_cloudformation_empty_list_shows_page_1() {
9995        let mut app = test_app();
9996        app.current_service = Service::CloudFormationStacks;
9997        app.cfn_state.table.items = vec![];
9998
9999        let filtered = app.filtered_cloudformation_stacks();
10000        assert_eq!(filtered.len(), 0);
10001
10002        // Pagination should still show [1] even with 0 items
10003        let page_size = app.cfn_state.table.page_size.value();
10004        let total_pages = filtered.len().div_ceil(page_size);
10005        assert_eq!(total_pages, 0);
10006
10007        // render_pagination_text(0, 0) should return "[1]"
10008        // This is tested in UI layer
10009    }
10010}
10011
10012impl App {
10013    pub fn get_filtered_regions(&self) -> Vec<AwsRegion> {
10014        let mut all = AwsRegion::all();
10015
10016        // Add latencies to regions
10017        for region in &mut all {
10018            region.latency_ms = self.region_latencies.get(region.code).copied();
10019        }
10020
10021        // Filter by search term
10022        let filtered: Vec<AwsRegion> = if self.region_filter.is_empty() {
10023            all
10024        } else {
10025            let filter_lower = self.region_filter.to_lowercase();
10026            all.into_iter()
10027                .filter(|r| {
10028                    r.name.to_lowercase().contains(&filter_lower)
10029                        || r.code.to_lowercase().contains(&filter_lower)
10030                        || r.group.to_lowercase().contains(&filter_lower)
10031                })
10032                .collect()
10033        };
10034
10035        // Sort by latency (lowest first), treat None as 1000ms
10036        let mut sorted = filtered;
10037        sorted.sort_by_key(|r| r.latency_ms.unwrap_or(1000));
10038        sorted
10039    }
10040
10041    pub fn measure_region_latencies(&mut self) {
10042        use std::time::Instant;
10043        self.region_latencies.clear();
10044
10045        let regions = AwsRegion::all();
10046        let start_all = Instant::now();
10047        tracing::info!("Starting latency measurement for {} regions", regions.len());
10048
10049        let handles: Vec<_> = regions
10050            .iter()
10051            .map(|region| {
10052                let code = region.code.to_string();
10053                std::thread::spawn(move || {
10054                    // Use STS endpoint - fastest and most reliable
10055                    let endpoint = format!("https://sts.{}.amazonaws.com", code);
10056                    let start = Instant::now();
10057
10058                    match ureq::get(&endpoint)
10059                        .timeout(std::time::Duration::from_secs(2))
10060                        .call()
10061                    {
10062                        Ok(_) => {
10063                            let latency = start.elapsed().as_millis() as u64;
10064                            Some((code, latency))
10065                        }
10066                        Err(e) => {
10067                            tracing::debug!("Failed to measure {}: {}", code, e);
10068                            Some((code, 9999))
10069                        }
10070                    }
10071                })
10072            })
10073            .collect();
10074
10075        for handle in handles {
10076            if let Ok(Some((code, latency))) = handle.join() {
10077                self.region_latencies.insert(code, latency);
10078            }
10079        }
10080
10081        tracing::info!(
10082            "Measured {} regions in {:?}",
10083            self.region_latencies.len(),
10084            start_all.elapsed()
10085        );
10086    }
10087
10088    pub fn get_filtered_profiles(&self) -> Vec<&AwsProfile> {
10089        crate::aws::filter_profiles(&self.available_profiles, &self.profile_filter)
10090    }
10091
10092    pub fn get_filtered_sessions(&self) -> Vec<&Session> {
10093        if self.session_filter.is_empty() {
10094            return self.sessions.iter().collect();
10095        }
10096        let filter_lower = self.session_filter.to_lowercase();
10097        self.sessions
10098            .iter()
10099            .filter(|s| {
10100                s.profile.to_lowercase().contains(&filter_lower)
10101                    || s.region.to_lowercase().contains(&filter_lower)
10102                    || s.account_id.to_lowercase().contains(&filter_lower)
10103                    || s.role_arn.to_lowercase().contains(&filter_lower)
10104            })
10105            .collect()
10106    }
10107
10108    pub fn get_filtered_tabs(&self) -> Vec<(usize, &Tab)> {
10109        if self.tab_filter.is_empty() {
10110            return self.tabs.iter().enumerate().collect();
10111        }
10112        let filter_lower = self.tab_filter.to_lowercase();
10113        self.tabs
10114            .iter()
10115            .enumerate()
10116            .filter(|(_, tab)| {
10117                tab.title.to_lowercase().contains(&filter_lower)
10118                    || tab.breadcrumb.to_lowercase().contains(&filter_lower)
10119            })
10120            .collect()
10121    }
10122
10123    pub fn load_aws_profiles() -> Vec<AwsProfile> {
10124        AwsProfile::load_all()
10125    }
10126
10127    pub async fn fetch_profile_accounts(&mut self) {
10128        for profile in &mut self.available_profiles {
10129            if profile.account.is_none() {
10130                let region = profile
10131                    .region
10132                    .clone()
10133                    .unwrap_or_else(|| "us-east-1".to_string());
10134                if let Ok(account) =
10135                    rusticity_core::AwsConfig::get_account_for_profile(&profile.name, &region).await
10136                {
10137                    profile.account = Some(account);
10138                }
10139            }
10140        }
10141    }
10142
10143    fn save_current_session(&mut self) {
10144        // If no tabs, delete the session if it exists
10145        if self.tabs.is_empty() {
10146            if let Some(ref session) = self.current_session {
10147                let _ = session.delete();
10148                self.current_session = None;
10149            }
10150            return;
10151        }
10152
10153        let session = if let Some(ref mut current) = self.current_session {
10154            // Update existing session
10155            current.tabs = self
10156                .tabs
10157                .iter()
10158                .map(|t| SessionTab {
10159                    service: format!("{:?}", t.service),
10160                    title: t.title.clone(),
10161                    breadcrumb: t.breadcrumb.clone(),
10162                    filter: match t.service {
10163                        Service::CloudWatchLogGroups => {
10164                            Some(self.log_groups_state.log_groups.filter.clone())
10165                        }
10166                        _ => None,
10167                    },
10168                    selected_item: None,
10169                })
10170                .collect();
10171            current.clone()
10172        } else {
10173            // Create new session
10174            let mut session = Session::new(
10175                self.profile.clone(),
10176                self.region.clone(),
10177                self.config.account_id.clone(),
10178                self.config.role_arn.clone(),
10179            );
10180            session.tabs = self
10181                .tabs
10182                .iter()
10183                .map(|t| SessionTab {
10184                    service: format!("{:?}", t.service),
10185                    title: t.title.clone(),
10186                    breadcrumb: t.breadcrumb.clone(),
10187                    filter: match t.service {
10188                        Service::CloudWatchLogGroups => {
10189                            Some(self.log_groups_state.log_groups.filter.clone())
10190                        }
10191                        _ => None,
10192                    },
10193                    selected_item: None,
10194                })
10195                .collect();
10196            self.current_session = Some(session.clone());
10197            session
10198        };
10199
10200        let _ = session.save();
10201    }
10202}
10203
10204#[cfg(test)]
10205mod iam_policy_view_tests {
10206    use super::*;
10207    use test_helpers::*;
10208
10209    #[test]
10210    fn test_enter_opens_policy_view() {
10211        let mut app = test_app();
10212        app.current_service = Service::IamRoles;
10213        app.service_selected = true;
10214        app.mode = Mode::Normal;
10215        app.view_mode = ViewMode::Detail;
10216        app.iam_state.current_role = Some("TestRole".to_string());
10217        app.iam_state.policies.items = vec![crate::iam::Policy {
10218            policy_name: "TestPolicy".to_string(),
10219            policy_type: "Inline".to_string(),
10220            attached_via: "Direct".to_string(),
10221            attached_entities: "1".to_string(),
10222            description: "Test".to_string(),
10223            creation_time: "2023-01-01".to_string(),
10224            edited_time: "2023-01-01".to_string(),
10225            policy_arn: None,
10226        }];
10227        app.iam_state.policies.reset();
10228
10229        app.handle_action(Action::Select);
10230
10231        assert_eq!(app.view_mode, ViewMode::PolicyView);
10232        assert_eq!(app.iam_state.current_policy, Some("TestPolicy".to_string()));
10233        assert_eq!(app.iam_state.policy_scroll, 0);
10234        assert!(app.iam_state.policies.loading);
10235    }
10236
10237    #[test]
10238    fn test_escape_closes_policy_view() {
10239        let mut app = test_app();
10240        app.current_service = Service::IamRoles;
10241        app.service_selected = true;
10242        app.mode = Mode::Normal;
10243        app.view_mode = ViewMode::PolicyView;
10244        app.iam_state.current_role = Some("TestRole".to_string());
10245        app.iam_state.current_policy = Some("TestPolicy".to_string());
10246        app.iam_state.policy_document = "{\n  \"test\": \"value\"\n}".to_string();
10247        app.iam_state.policy_scroll = 5;
10248
10249        app.handle_action(Action::PrevPane);
10250
10251        assert_eq!(app.view_mode, ViewMode::Detail);
10252        assert_eq!(app.iam_state.current_policy, None);
10253        assert_eq!(app.iam_state.policy_document, "");
10254        assert_eq!(app.iam_state.policy_scroll, 0);
10255    }
10256
10257    #[test]
10258    fn test_ctrl_d_scrolls_down_in_policy_view() {
10259        let mut app = test_app();
10260        app.current_service = Service::IamRoles;
10261        app.service_selected = true;
10262        app.mode = Mode::Normal;
10263        app.view_mode = ViewMode::PolicyView;
10264        app.iam_state.current_role = Some("TestRole".to_string());
10265        app.iam_state.current_policy = Some("TestPolicy".to_string());
10266        app.iam_state.policy_document = (0..100)
10267            .map(|i| format!("line {}", i))
10268            .collect::<Vec<_>>()
10269            .join("\n");
10270        app.iam_state.policy_scroll = 0;
10271
10272        app.handle_action(Action::ScrollDown);
10273
10274        assert_eq!(app.iam_state.policy_scroll, 10);
10275
10276        app.handle_action(Action::ScrollDown);
10277
10278        assert_eq!(app.iam_state.policy_scroll, 20);
10279    }
10280
10281    #[test]
10282    fn test_ctrl_u_scrolls_up_in_policy_view() {
10283        let mut app = test_app();
10284        app.current_service = Service::IamRoles;
10285        app.service_selected = true;
10286        app.mode = Mode::Normal;
10287        app.view_mode = ViewMode::PolicyView;
10288        app.iam_state.current_role = Some("TestRole".to_string());
10289        app.iam_state.current_policy = Some("TestPolicy".to_string());
10290        app.iam_state.policy_document = (0..100)
10291            .map(|i| format!("line {}", i))
10292            .collect::<Vec<_>>()
10293            .join("\n");
10294        app.iam_state.policy_scroll = 30;
10295
10296        app.handle_action(Action::ScrollUp);
10297
10298        assert_eq!(app.iam_state.policy_scroll, 20);
10299
10300        app.handle_action(Action::ScrollUp);
10301
10302        assert_eq!(app.iam_state.policy_scroll, 10);
10303    }
10304
10305    #[test]
10306    fn test_scroll_does_not_go_negative() {
10307        let mut app = test_app();
10308        app.current_service = Service::IamRoles;
10309        app.service_selected = true;
10310        app.mode = Mode::Normal;
10311        app.view_mode = ViewMode::PolicyView;
10312        app.iam_state.current_role = Some("TestRole".to_string());
10313        app.iam_state.current_policy = Some("TestPolicy".to_string());
10314        app.iam_state.policy_document = "line 1\nline 2\nline 3".to_string();
10315        app.iam_state.policy_scroll = 0;
10316
10317        app.handle_action(Action::ScrollUp);
10318
10319        assert_eq!(app.iam_state.policy_scroll, 0);
10320    }
10321
10322    #[test]
10323    fn test_scroll_does_not_exceed_max() {
10324        let mut app = test_app();
10325        app.current_service = Service::IamRoles;
10326        app.service_selected = true;
10327        app.mode = Mode::Normal;
10328        app.view_mode = ViewMode::PolicyView;
10329        app.iam_state.current_role = Some("TestRole".to_string());
10330        app.iam_state.current_policy = Some("TestPolicy".to_string());
10331        app.iam_state.policy_document = "line 1\nline 2\nline 3".to_string();
10332        app.iam_state.policy_scroll = 0;
10333
10334        app.handle_action(Action::ScrollDown);
10335
10336        assert_eq!(app.iam_state.policy_scroll, 2); // Max is 2 (3 lines - 1)
10337    }
10338
10339    #[test]
10340    fn test_policy_view_console_url() {
10341        let mut app = test_app();
10342        app.current_service = Service::IamRoles;
10343        app.service_selected = true;
10344        app.view_mode = ViewMode::PolicyView;
10345        app.iam_state.current_role = Some("TestRole".to_string());
10346        app.iam_state.current_policy = Some("TestPolicy".to_string());
10347
10348        let url = app.get_console_url();
10349
10350        assert!(url.contains("us-east-1.console.aws.amazon.com"));
10351        assert!(url.contains("/roles/details/TestRole"));
10352        assert!(url.contains("/editPolicy/TestPolicy"));
10353        assert!(url.contains("step=addPermissions"));
10354    }
10355
10356    #[test]
10357    fn test_esc_from_policy_view_goes_to_role_detail() {
10358        let mut app = test_app();
10359        app.current_service = Service::IamRoles;
10360        app.service_selected = true;
10361        app.mode = Mode::Normal;
10362        app.view_mode = ViewMode::PolicyView;
10363        app.iam_state.current_role = Some("TestRole".to_string());
10364        app.iam_state.current_policy = Some("TestPolicy".to_string());
10365        app.iam_state.policy_document = "test".to_string();
10366        app.iam_state.policy_scroll = 5;
10367
10368        app.handle_action(Action::GoBack);
10369
10370        assert_eq!(app.view_mode, ViewMode::Detail);
10371        assert_eq!(app.iam_state.current_policy, None);
10372        assert_eq!(app.iam_state.policy_document, "");
10373        assert_eq!(app.iam_state.policy_scroll, 0);
10374        assert_eq!(app.iam_state.current_role, Some("TestRole".to_string()));
10375    }
10376
10377    #[test]
10378    fn test_esc_from_role_detail_goes_to_role_list() {
10379        let mut app = test_app();
10380        app.current_service = Service::IamRoles;
10381        app.service_selected = true;
10382        app.mode = Mode::Normal;
10383        app.view_mode = ViewMode::Detail;
10384        app.iam_state.current_role = Some("TestRole".to_string());
10385
10386        app.handle_action(Action::GoBack);
10387
10388        assert_eq!(app.iam_state.current_role, None);
10389    }
10390
10391    #[test]
10392    fn test_right_arrow_expands_policy_row() {
10393        let mut app = test_app();
10394        app.current_service = Service::IamRoles;
10395        app.service_selected = true;
10396        app.mode = Mode::Normal;
10397        app.view_mode = ViewMode::Detail;
10398        app.iam_state.current_role = Some("TestRole".to_string());
10399        app.iam_state.policies.items = vec![crate::iam::Policy {
10400            policy_name: "TestPolicy".to_string(),
10401            policy_type: "Inline".to_string(),
10402            attached_via: "Direct".to_string(),
10403            attached_entities: "1".to_string(),
10404            description: "Test".to_string(),
10405            creation_time: "2023-01-01".to_string(),
10406            edited_time: "2023-01-01".to_string(),
10407            policy_arn: None,
10408        }];
10409        app.iam_state.policies.reset();
10410
10411        app.handle_action(Action::NextPane);
10412
10413        // Should expand, not drill down
10414        assert_eq!(app.view_mode, ViewMode::Detail);
10415        assert_eq!(app.iam_state.current_policy, None);
10416        assert_eq!(app.iam_state.policies.expanded_item, Some(0));
10417    }
10418}
10419
10420#[cfg(test)]
10421mod tab_filter_tests {
10422    use super::*;
10423    use test_helpers::*;
10424
10425    #[test]
10426    fn test_space_t_opens_tab_picker() {
10427        let mut app = test_app();
10428        app.tabs = vec![
10429            Tab {
10430                service: Service::CloudWatchLogGroups,
10431                title: "Tab 1".to_string(),
10432                breadcrumb: "CloudWatch > Log groups".to_string(),
10433            },
10434            Tab {
10435                service: Service::S3Buckets,
10436                title: "Tab 2".to_string(),
10437                breadcrumb: "S3 > Buckets".to_string(),
10438            },
10439        ];
10440        app.current_tab = 0;
10441
10442        app.handle_action(Action::OpenTabPicker);
10443
10444        assert_eq!(app.mode, Mode::TabPicker);
10445        assert_eq!(app.tab_picker_selected, 0);
10446    }
10447
10448    #[test]
10449    fn test_tab_filter_works() {
10450        let mut app = test_app();
10451        app.tabs = vec![
10452            Tab {
10453                service: Service::CloudWatchLogGroups,
10454                title: "CloudWatch Logs".to_string(),
10455                breadcrumb: "CloudWatch > Log groups".to_string(),
10456            },
10457            Tab {
10458                service: Service::S3Buckets,
10459                title: "S3 Buckets".to_string(),
10460                breadcrumb: "S3 > Buckets".to_string(),
10461            },
10462            Tab {
10463                service: Service::CloudWatchAlarms,
10464                title: "CloudWatch Alarms".to_string(),
10465                breadcrumb: "CloudWatch > Alarms".to_string(),
10466            },
10467        ];
10468        app.mode = Mode::TabPicker;
10469
10470        // Filter for "s3"
10471        app.handle_action(Action::FilterInput('s'));
10472        app.handle_action(Action::FilterInput('3'));
10473
10474        let filtered = app.get_filtered_tabs();
10475        assert_eq!(filtered.len(), 1);
10476        assert_eq!(filtered[0].1.title, "S3 Buckets");
10477    }
10478
10479    #[test]
10480    fn test_tab_filter_by_breadcrumb() {
10481        let mut app = test_app();
10482        app.tabs = vec![
10483            Tab {
10484                service: Service::CloudWatchLogGroups,
10485                title: "Tab 1".to_string(),
10486                breadcrumb: "CloudWatch > Log groups".to_string(),
10487            },
10488            Tab {
10489                service: Service::S3Buckets,
10490                title: "Tab 2".to_string(),
10491                breadcrumb: "S3 > Buckets".to_string(),
10492            },
10493        ];
10494        app.mode = Mode::TabPicker;
10495
10496        // Filter for "cloudwatch"
10497        app.handle_action(Action::FilterInput('c'));
10498        app.handle_action(Action::FilterInput('l'));
10499        app.handle_action(Action::FilterInput('o'));
10500        app.handle_action(Action::FilterInput('u'));
10501        app.handle_action(Action::FilterInput('d'));
10502
10503        let filtered = app.get_filtered_tabs();
10504        assert_eq!(filtered.len(), 1);
10505        assert_eq!(filtered[0].1.breadcrumb, "CloudWatch > Log groups");
10506    }
10507
10508    #[test]
10509    fn test_tab_filter_backspace() {
10510        let mut app = test_app();
10511        app.tabs = vec![
10512            Tab {
10513                service: Service::CloudWatchLogGroups,
10514                title: "CloudWatch Logs".to_string(),
10515                breadcrumb: "CloudWatch > Log groups".to_string(),
10516            },
10517            Tab {
10518                service: Service::S3Buckets,
10519                title: "S3 Buckets".to_string(),
10520                breadcrumb: "S3 > Buckets".to_string(),
10521            },
10522        ];
10523        app.mode = Mode::TabPicker;
10524
10525        app.handle_action(Action::FilterInput('s'));
10526        app.handle_action(Action::FilterInput('3'));
10527        assert_eq!(app.tab_filter, "s3");
10528
10529        app.handle_action(Action::FilterBackspace);
10530        assert_eq!(app.tab_filter, "s");
10531
10532        let filtered = app.get_filtered_tabs();
10533        assert_eq!(filtered.len(), 2); // Both match "s"
10534    }
10535
10536    #[test]
10537    fn test_tab_selection_with_filter() {
10538        let mut app = test_app();
10539        app.tabs = vec![
10540            Tab {
10541                service: Service::CloudWatchLogGroups,
10542                title: "CloudWatch Logs".to_string(),
10543                breadcrumb: "CloudWatch > Log groups".to_string(),
10544            },
10545            Tab {
10546                service: Service::S3Buckets,
10547                title: "S3 Buckets".to_string(),
10548                breadcrumb: "S3 > Buckets".to_string(),
10549            },
10550        ];
10551        app.mode = Mode::TabPicker;
10552        app.current_tab = 0;
10553
10554        // Filter for "s3"
10555        app.handle_action(Action::FilterInput('s'));
10556        app.handle_action(Action::FilterInput('3'));
10557
10558        // Select the filtered tab
10559        app.handle_action(Action::Select);
10560
10561        assert_eq!(app.current_tab, 1); // Should select the S3 tab (index 1)
10562        assert_eq!(app.mode, Mode::Normal);
10563        assert_eq!(app.tab_filter, ""); // Filter should be cleared
10564    }
10565}
10566
10567#[cfg(test)]
10568mod region_latency_tests {
10569    use super::*;
10570    use test_helpers::*;
10571
10572    #[test]
10573    fn test_regions_sorted_by_latency() {
10574        let mut app = test_app();
10575
10576        // Add some latencies
10577        app.region_latencies.insert("us-west-2".to_string(), 50);
10578        app.region_latencies.insert("us-east-1".to_string(), 10);
10579        app.region_latencies.insert("eu-west-1".to_string(), 100);
10580
10581        let filtered = app.get_filtered_regions();
10582
10583        // Should be sorted by latency (lowest first)
10584        let with_latency: Vec<_> = filtered.iter().filter(|r| r.latency_ms.is_some()).collect();
10585
10586        assert!(with_latency.len() >= 3);
10587        assert_eq!(with_latency[0].code, "us-east-1");
10588        assert_eq!(with_latency[0].latency_ms, Some(10));
10589        assert_eq!(with_latency[1].code, "us-west-2");
10590        assert_eq!(with_latency[1].latency_ms, Some(50));
10591        assert_eq!(with_latency[2].code, "eu-west-1");
10592        assert_eq!(with_latency[2].latency_ms, Some(100));
10593    }
10594
10595    #[test]
10596    fn test_regions_with_latency_before_without() {
10597        let mut app = test_app();
10598
10599        // Only add latency for one region
10600        app.region_latencies.insert("eu-west-1".to_string(), 100);
10601
10602        let filtered = app.get_filtered_regions();
10603
10604        // Region with latency should come first
10605        assert_eq!(filtered[0].code, "eu-west-1");
10606        assert_eq!(filtered[0].latency_ms, Some(100));
10607
10608        // Rest should be sorted by name
10609        for region in &filtered[1..] {
10610            assert!(region.latency_ms.is_none());
10611        }
10612    }
10613
10614    #[test]
10615    fn test_region_filter_with_latency() {
10616        let mut app = test_app();
10617
10618        app.region_latencies.insert("us-east-1".to_string(), 10);
10619        app.region_latencies.insert("us-west-2".to_string(), 50);
10620        app.region_filter = "us".to_string();
10621
10622        let filtered = app.get_filtered_regions();
10623
10624        // Should only have US regions, sorted by latency
10625        assert!(filtered.iter().all(|r| r.code.starts_with("us-")));
10626        assert_eq!(filtered[0].code, "us-east-1");
10627        assert_eq!(filtered[1].code, "us-west-2");
10628    }
10629
10630    #[test]
10631    fn test_latency_persists_across_filters() {
10632        let mut app = test_app();
10633
10634        app.region_latencies.insert("us-east-1".to_string(), 10);
10635
10636        // Filter to something else
10637        app.region_filter = "eu".to_string();
10638        let filtered = app.get_filtered_regions();
10639        assert!(filtered.iter().all(|r| !r.code.starts_with("us-")));
10640
10641        // Clear filter
10642        app.region_filter.clear();
10643        let all = app.get_filtered_regions();
10644
10645        // Latency should still be there
10646        let us_east = all.iter().find(|r| r.code == "us-east-1").unwrap();
10647        assert_eq!(us_east.latency_ms, Some(10));
10648    }
10649
10650    #[test]
10651    fn test_measure_region_latencies_clears_previous() {
10652        let mut app = test_app();
10653
10654        // Add some fake latencies
10655        app.region_latencies.insert("us-east-1".to_string(), 100);
10656        app.region_latencies.insert("eu-west-1".to_string(), 200);
10657
10658        // Measure again (will fail to connect but should clear)
10659        app.measure_region_latencies();
10660
10661        // Old latencies should be cleared
10662        assert!(
10663            app.region_latencies.is_empty() || !app.region_latencies.contains_key("fake-region")
10664        );
10665    }
10666
10667    #[test]
10668    fn test_regions_with_latency_sorted_first() {
10669        let mut app = test_app();
10670
10671        // Add latencies: one fast, one slow (>1000ms would be treated as >1s)
10672        app.region_latencies.insert("us-east-1".to_string(), 50);
10673        app.region_latencies.insert("eu-west-1".to_string(), 500);
10674
10675        let filtered = app.get_filtered_regions();
10676
10677        // Should show all regions
10678        assert!(filtered.len() > 2);
10679
10680        // Fast regions first
10681        assert_eq!(filtered[0].code, "us-east-1");
10682        assert_eq!(filtered[0].latency_ms, Some(50));
10683        assert_eq!(filtered[1].code, "eu-west-1");
10684        assert_eq!(filtered[1].latency_ms, Some(500));
10685
10686        // Regions without latency treated as 1000ms, so they come after 500ms
10687        for region in &filtered[2..] {
10688            assert!(region.latency_ms.is_none());
10689        }
10690    }
10691
10692    #[test]
10693    fn test_regions_without_latency_sorted_as_1000ms() {
10694        let mut app = test_app();
10695
10696        // Add one region with 1500ms (slower than default 1000ms)
10697        app.region_latencies
10698            .insert("ap-southeast-2".to_string(), 1500);
10699        // Add one region with 50ms (faster)
10700        app.region_latencies.insert("us-east-1".to_string(), 50);
10701
10702        let filtered = app.get_filtered_regions();
10703
10704        // Fast region first
10705        assert_eq!(filtered[0].code, "us-east-1");
10706        assert_eq!(filtered[0].latency_ms, Some(50));
10707
10708        // Regions without latency (treated as 1000ms) come before 1500ms
10709        let slow_region_idx = filtered
10710            .iter()
10711            .position(|r| r.code == "ap-southeast-2")
10712            .unwrap();
10713        assert!(slow_region_idx > 1); // Should be after fast region and regions without latency
10714
10715        // All regions between index 1 and slow_region_idx should have no latency
10716        for region in filtered.iter().take(slow_region_idx).skip(1) {
10717            assert!(region.latency_ms.is_none());
10718        }
10719    }
10720
10721    #[test]
10722    fn test_region_picker_opens_with_latencies() {
10723        let mut app = test_app();
10724
10725        // Simulate opening region picker
10726        app.region_filter.clear();
10727        app.region_picker_selected = 0;
10728        app.measure_region_latencies();
10729
10730        // Should have attempted to measure (even if all fail in test env)
10731        // The map should be initialized
10732        assert!(app.region_latencies.is_empty() || !app.region_latencies.is_empty());
10733    }
10734
10735    #[test]
10736    fn test_ecr_tab_next() {
10737        assert_eq!(EcrTab::Private.next(), EcrTab::Public);
10738        assert_eq!(EcrTab::Public.next(), EcrTab::Private);
10739    }
10740
10741    #[test]
10742    fn test_ecr_tab_switching() {
10743        let mut app = test_app();
10744        app.current_service = Service::EcrRepositories;
10745        app.service_selected = true;
10746        app.ecr_state.tab = EcrTab::Private;
10747
10748        app.handle_action(Action::NextDetailTab);
10749        assert_eq!(app.ecr_state.tab, EcrTab::Public);
10750        assert_eq!(app.ecr_state.repositories.selected, 0);
10751
10752        app.handle_action(Action::NextDetailTab);
10753        assert_eq!(app.ecr_state.tab, EcrTab::Private);
10754    }
10755
10756    #[test]
10757    fn test_ecr_navigation() {
10758        let mut app = test_app();
10759        app.current_service = Service::EcrRepositories;
10760        app.service_selected = true;
10761        app.mode = Mode::Normal;
10762        app.ecr_state.repositories.items = vec![
10763            EcrRepository {
10764                name: "repo1".to_string(),
10765                uri: "uri1".to_string(),
10766                created_at: "2023-01-01".to_string(),
10767                tag_immutability: "MUTABLE".to_string(),
10768                encryption_type: "AES256".to_string(),
10769            },
10770            EcrRepository {
10771                name: "repo2".to_string(),
10772                uri: "uri2".to_string(),
10773                created_at: "2023-01-02".to_string(),
10774                tag_immutability: "IMMUTABLE".to_string(),
10775                encryption_type: "KMS".to_string(),
10776            },
10777        ];
10778
10779        app.handle_action(Action::NextItem);
10780        assert_eq!(app.ecr_state.repositories.selected, 1);
10781
10782        app.handle_action(Action::PrevItem);
10783        assert_eq!(app.ecr_state.repositories.selected, 0);
10784    }
10785
10786    #[test]
10787    fn test_ecr_filter() {
10788        let mut app = test_app();
10789        app.current_service = Service::EcrRepositories;
10790        app.service_selected = true;
10791        app.ecr_state.repositories.items = vec![
10792            EcrRepository {
10793                name: "my-app".to_string(),
10794                uri: "uri1".to_string(),
10795                created_at: "2023-01-01".to_string(),
10796                tag_immutability: "MUTABLE".to_string(),
10797                encryption_type: "AES256".to_string(),
10798            },
10799            EcrRepository {
10800                name: "other-service".to_string(),
10801                uri: "uri2".to_string(),
10802                created_at: "2023-01-02".to_string(),
10803                tag_immutability: "IMMUTABLE".to_string(),
10804                encryption_type: "KMS".to_string(),
10805            },
10806        ];
10807
10808        app.ecr_state.repositories.filter = "app".to_string();
10809        let filtered = app.filtered_ecr_repositories();
10810        assert_eq!(filtered.len(), 1);
10811        assert_eq!(filtered[0].name, "my-app");
10812    }
10813
10814    #[test]
10815    fn test_ecr_filter_input() {
10816        let mut app = test_app();
10817        app.current_service = Service::EcrRepositories;
10818        app.service_selected = true;
10819        app.mode = Mode::FilterInput;
10820
10821        app.handle_action(Action::FilterInput('t'));
10822        app.handle_action(Action::FilterInput('e'));
10823        app.handle_action(Action::FilterInput('s'));
10824        app.handle_action(Action::FilterInput('t'));
10825        assert_eq!(app.ecr_state.repositories.filter, "test");
10826
10827        app.handle_action(Action::FilterBackspace);
10828        assert_eq!(app.ecr_state.repositories.filter, "tes");
10829    }
10830
10831    #[test]
10832    fn test_iam_users_filter_input() {
10833        let mut app = test_app();
10834        app.current_service = Service::IamUsers;
10835        app.service_selected = true;
10836        app.mode = Mode::FilterInput;
10837
10838        app.handle_action(Action::FilterInput('a'));
10839        app.handle_action(Action::FilterInput('d'));
10840        app.handle_action(Action::FilterInput('m'));
10841        app.handle_action(Action::FilterInput('i'));
10842        app.handle_action(Action::FilterInput('n'));
10843        assert_eq!(app.iam_state.users.filter, "admin");
10844
10845        app.handle_action(Action::FilterBackspace);
10846        assert_eq!(app.iam_state.users.filter, "admi");
10847    }
10848
10849    #[test]
10850    fn test_iam_policies_filter_input() {
10851        let mut app = test_app();
10852        app.current_service = Service::IamUsers;
10853        app.service_selected = true;
10854        app.iam_state.current_user = Some("testuser".to_string());
10855        app.mode = Mode::FilterInput;
10856
10857        app.handle_action(Action::FilterInput('r'));
10858        app.handle_action(Action::FilterInput('e'));
10859        app.handle_action(Action::FilterInput('a'));
10860        app.handle_action(Action::FilterInput('d'));
10861        assert_eq!(app.iam_state.policies.filter, "read");
10862
10863        app.handle_action(Action::FilterBackspace);
10864        assert_eq!(app.iam_state.policies.filter, "rea");
10865    }
10866
10867    #[test]
10868    fn test_iam_start_filter() {
10869        let mut app = test_app();
10870        app.current_service = Service::IamUsers;
10871        app.service_selected = true;
10872        app.mode = Mode::Normal;
10873
10874        app.handle_action(Action::StartFilter);
10875        assert_eq!(app.mode, Mode::FilterInput);
10876    }
10877
10878    #[test]
10879    fn test_iam_roles_filter_input() {
10880        let mut app = test_app();
10881        app.current_service = Service::IamRoles;
10882        app.service_selected = true;
10883        app.mode = Mode::FilterInput;
10884
10885        app.handle_action(Action::FilterInput('a'));
10886        app.handle_action(Action::FilterInput('d'));
10887        app.handle_action(Action::FilterInput('m'));
10888        app.handle_action(Action::FilterInput('i'));
10889        app.handle_action(Action::FilterInput('n'));
10890        assert_eq!(app.iam_state.roles.filter, "admin");
10891
10892        app.handle_action(Action::FilterBackspace);
10893        assert_eq!(app.iam_state.roles.filter, "admi");
10894    }
10895
10896    #[test]
10897    fn test_iam_roles_start_filter() {
10898        let mut app = test_app();
10899        app.current_service = Service::IamRoles;
10900        app.service_selected = true;
10901        app.mode = Mode::Normal;
10902
10903        app.handle_action(Action::StartFilter);
10904        assert_eq!(app.mode, Mode::FilterInput);
10905    }
10906
10907    #[test]
10908    fn test_iam_roles_navigation() {
10909        let mut app = test_app();
10910        app.current_service = Service::IamRoles;
10911        app.service_selected = true;
10912        app.mode = Mode::Normal;
10913        app.iam_state.roles.items = (0..10)
10914            .map(|i| crate::iam::IamRole {
10915                role_name: format!("role{}", i),
10916                path: "/".to_string(),
10917                trusted_entities: String::new(),
10918                last_activity: String::new(),
10919                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
10920                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
10921                description: String::new(),
10922                max_session_duration: Some(3600),
10923            })
10924            .collect();
10925
10926        assert_eq!(app.iam_state.roles.selected, 0);
10927
10928        app.handle_action(Action::NextItem);
10929        assert_eq!(app.iam_state.roles.selected, 1);
10930
10931        app.handle_action(Action::NextItem);
10932        assert_eq!(app.iam_state.roles.selected, 2);
10933
10934        app.handle_action(Action::PrevItem);
10935        assert_eq!(app.iam_state.roles.selected, 1);
10936    }
10937
10938    #[test]
10939    fn test_iam_roles_page_hotkey() {
10940        let mut app = test_app();
10941        app.current_service = Service::IamRoles;
10942        app.service_selected = true;
10943        app.mode = Mode::Normal;
10944        app.iam_state.roles.page_size = PageSize::Ten;
10945        app.iam_state.roles.items = (0..100)
10946            .map(|i| crate::iam::IamRole {
10947                role_name: format!("role{}", i),
10948                path: "/".to_string(),
10949                trusted_entities: String::new(),
10950                last_activity: String::new(),
10951                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
10952                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
10953                description: String::new(),
10954                max_session_duration: Some(3600),
10955            })
10956            .collect();
10957
10958        app.handle_action(Action::FilterInput('2'));
10959        app.handle_action(Action::OpenColumnSelector);
10960        assert_eq!(app.iam_state.roles.selected, 10); // Page 2 = index 10 (with page size 10)
10961    }
10962
10963    #[test]
10964    fn test_iam_users_page_hotkey() {
10965        let mut app = test_app();
10966        app.current_service = Service::IamUsers;
10967        app.service_selected = true;
10968        app.mode = Mode::Normal;
10969        app.iam_state.users.page_size = PageSize::Ten;
10970        app.iam_state.users.items = (0..100)
10971            .map(|i| crate::iam::IamUser {
10972                user_name: format!("user{}", i),
10973                path: "/".to_string(),
10974                groups: String::new(),
10975                last_activity: String::new(),
10976                mfa: String::new(),
10977                password_age: String::new(),
10978                console_last_sign_in: String::new(),
10979                access_key_id: String::new(),
10980                active_key_age: String::new(),
10981                access_key_last_used: String::new(),
10982                arn: format!("arn:aws:iam::123456789012:user/user{}", i),
10983                creation_time: "2025-01-01 00:00:00 (UTC)".to_string(),
10984                console_access: String::new(),
10985                signing_certs: String::new(),
10986            })
10987            .collect();
10988
10989        app.handle_action(Action::FilterInput('3'));
10990        app.handle_action(Action::OpenColumnSelector);
10991        assert_eq!(app.iam_state.users.selected, 20); // Page 3 = index 20 (with page size 10)
10992    }
10993
10994    #[test]
10995    fn test_ecr_scroll_navigation() {
10996        let mut app = test_app();
10997        app.current_service = Service::EcrRepositories;
10998        app.service_selected = true;
10999        app.ecr_state.repositories.items = (0..20)
11000            .map(|i| EcrRepository {
11001                name: format!("repo{}", i),
11002                uri: format!("uri{}", i),
11003                created_at: "2023-01-01".to_string(),
11004                tag_immutability: "MUTABLE".to_string(),
11005                encryption_type: "AES256".to_string(),
11006            })
11007            .collect();
11008
11009        app.handle_action(Action::ScrollDown);
11010        assert_eq!(app.ecr_state.repositories.selected, 10);
11011
11012        app.handle_action(Action::ScrollUp);
11013        assert_eq!(app.ecr_state.repositories.selected, 0);
11014    }
11015
11016    #[test]
11017    fn test_ecr_tab_switching_triggers_reload() {
11018        let mut app = test_app();
11019        app.current_service = Service::EcrRepositories;
11020        app.service_selected = true;
11021        app.ecr_state.tab = EcrTab::Private;
11022        app.ecr_state.repositories.loading = false;
11023        app.ecr_state.repositories.items = vec![EcrRepository {
11024            name: "private-repo".to_string(),
11025            uri: "uri".to_string(),
11026            created_at: "2023-01-01".to_string(),
11027            tag_immutability: "MUTABLE".to_string(),
11028            encryption_type: "AES256".to_string(),
11029        }];
11030
11031        app.handle_action(Action::NextDetailTab);
11032        assert_eq!(app.ecr_state.tab, EcrTab::Public);
11033        assert!(app.ecr_state.repositories.loading);
11034        assert_eq!(app.ecr_state.repositories.selected, 0);
11035    }
11036
11037    #[test]
11038    fn test_ecr_tab_cycles_between_private_and_public() {
11039        let mut app = test_app();
11040        app.current_service = Service::EcrRepositories;
11041        app.service_selected = true;
11042        app.ecr_state.tab = EcrTab::Private;
11043
11044        app.handle_action(Action::NextDetailTab);
11045        assert_eq!(app.ecr_state.tab, EcrTab::Public);
11046
11047        app.handle_action(Action::NextDetailTab);
11048        assert_eq!(app.ecr_state.tab, EcrTab::Private);
11049    }
11050
11051    #[test]
11052    fn test_page_size_values() {
11053        assert_eq!(PageSize::Ten.value(), 10);
11054        assert_eq!(PageSize::TwentyFive.value(), 25);
11055        assert_eq!(PageSize::Fifty.value(), 50);
11056        assert_eq!(PageSize::OneHundred.value(), 100);
11057    }
11058
11059    #[test]
11060    fn test_page_size_next() {
11061        assert_eq!(PageSize::Ten.next(), PageSize::TwentyFive);
11062        assert_eq!(PageSize::TwentyFive.next(), PageSize::Fifty);
11063        assert_eq!(PageSize::Fifty.next(), PageSize::OneHundred);
11064        assert_eq!(PageSize::OneHundred.next(), PageSize::Ten);
11065    }
11066
11067    #[test]
11068    fn test_ecr_enter_drills_into_repository() {
11069        let mut app = test_app();
11070        app.current_service = Service::EcrRepositories;
11071        app.service_selected = true;
11072        app.mode = Mode::Normal;
11073        app.ecr_state.repositories.items = vec![EcrRepository {
11074            name: "my-repo".to_string(),
11075            uri: "uri".to_string(),
11076            created_at: "2023-01-01".to_string(),
11077            tag_immutability: "MUTABLE".to_string(),
11078            encryption_type: "AES256".to_string(),
11079        }];
11080
11081        app.handle_action(Action::Select);
11082        assert_eq!(
11083            app.ecr_state.current_repository,
11084            Some("my-repo".to_string())
11085        );
11086        assert!(app.ecr_state.repositories.loading);
11087    }
11088
11089    #[test]
11090    fn test_ecr_repository_expansion() {
11091        let mut app = test_app();
11092        app.current_service = Service::EcrRepositories;
11093        app.service_selected = true;
11094        app.ecr_state.repositories.items = vec![EcrRepository {
11095            name: "my-repo".to_string(),
11096            uri: "uri".to_string(),
11097            created_at: "2023-01-01".to_string(),
11098            tag_immutability: "MUTABLE".to_string(),
11099            encryption_type: "AES256".to_string(),
11100        }];
11101        app.ecr_state.repositories.selected = 0;
11102
11103        assert_eq!(app.ecr_state.repositories.expanded_item, None);
11104
11105        app.handle_action(Action::NextPane);
11106        assert_eq!(app.ecr_state.repositories.expanded_item, Some(0));
11107
11108        app.handle_action(Action::PrevPane);
11109        assert_eq!(app.ecr_state.repositories.expanded_item, None);
11110    }
11111
11112    #[test]
11113    fn test_ecr_ctrl_d_scrolls_down() {
11114        let mut app = test_app();
11115        app.current_service = Service::EcrRepositories;
11116        app.service_selected = true;
11117        app.mode = Mode::Normal;
11118        app.ecr_state.repositories.items = (0..30)
11119            .map(|i| EcrRepository {
11120                name: format!("repo{}", i),
11121                uri: format!("uri{}", i),
11122                created_at: "2023-01-01".to_string(),
11123                tag_immutability: "MUTABLE".to_string(),
11124                encryption_type: "AES256".to_string(),
11125            })
11126            .collect();
11127        app.ecr_state.repositories.selected = 0;
11128
11129        app.handle_action(Action::PageDown);
11130        assert_eq!(app.ecr_state.repositories.selected, 10);
11131    }
11132
11133    #[test]
11134    fn test_ecr_ctrl_u_scrolls_up() {
11135        let mut app = test_app();
11136        app.current_service = Service::EcrRepositories;
11137        app.service_selected = true;
11138        app.mode = Mode::Normal;
11139        app.ecr_state.repositories.items = (0..30)
11140            .map(|i| EcrRepository {
11141                name: format!("repo{}", i),
11142                uri: format!("uri{}", i),
11143                created_at: "2023-01-01".to_string(),
11144                tag_immutability: "MUTABLE".to_string(),
11145                encryption_type: "AES256".to_string(),
11146            })
11147            .collect();
11148        app.ecr_state.repositories.selected = 15;
11149
11150        app.handle_action(Action::PageUp);
11151        assert_eq!(app.ecr_state.repositories.selected, 5);
11152    }
11153
11154    #[test]
11155    fn test_ecr_images_ctrl_d_scrolls_down() {
11156        let mut app = test_app();
11157        app.current_service = Service::EcrRepositories;
11158        app.service_selected = true;
11159        app.mode = Mode::Normal;
11160        app.ecr_state.current_repository = Some("repo".to_string());
11161        app.ecr_state.images.items = (0..30)
11162            .map(|i| EcrImage {
11163                tag: format!("tag{}", i),
11164                artifact_type: "container".to_string(),
11165                pushed_at: "2023-01-01T12:00:00Z".to_string(),
11166                size_bytes: 104857600,
11167                uri: format!("uri{}", i),
11168                digest: format!("sha256:{}", i),
11169                last_pull_time: String::new(),
11170            })
11171            .collect();
11172        app.ecr_state.images.selected = 0;
11173
11174        app.handle_action(Action::PageDown);
11175        assert_eq!(app.ecr_state.images.selected, 10);
11176    }
11177
11178    #[test]
11179    fn test_ecr_esc_goes_back_from_images_to_repos() {
11180        let mut app = test_app();
11181        app.current_service = Service::EcrRepositories;
11182        app.service_selected = true;
11183        app.mode = Mode::Normal;
11184        app.ecr_state.current_repository = Some("my-repo".to_string());
11185        app.ecr_state.images.items = vec![EcrImage {
11186            tag: "latest".to_string(),
11187            artifact_type: "container".to_string(),
11188            pushed_at: "2023-01-01T12:00:00Z".to_string(),
11189            size_bytes: 104857600,
11190            uri: "uri".to_string(),
11191            digest: "sha256:abc".to_string(),
11192            last_pull_time: String::new(),
11193        }];
11194
11195        app.handle_action(Action::GoBack);
11196        assert_eq!(app.ecr_state.current_repository, None);
11197        assert!(app.ecr_state.images.items.is_empty());
11198    }
11199
11200    #[test]
11201    fn test_ecr_esc_collapses_expanded_image_first() {
11202        let mut app = test_app();
11203        app.current_service = Service::EcrRepositories;
11204        app.service_selected = true;
11205        app.mode = Mode::Normal;
11206        app.ecr_state.current_repository = Some("my-repo".to_string());
11207        app.ecr_state.images.expanded_item = Some(0);
11208
11209        app.handle_action(Action::GoBack);
11210        assert_eq!(app.ecr_state.images.expanded_item, None);
11211        assert_eq!(
11212            app.ecr_state.current_repository,
11213            Some("my-repo".to_string())
11214        );
11215    }
11216
11217    #[test]
11218    fn test_pagination_with_lowercase_p() {
11219        let mut app = test_app();
11220        app.current_service = Service::EcrRepositories;
11221        app.service_selected = true;
11222        app.mode = Mode::Normal;
11223        app.ecr_state.repositories.items = (0..100)
11224            .map(|i| EcrRepository {
11225                name: format!("repo{}", i),
11226                uri: format!("uri{}", i),
11227                created_at: "2023-01-01".to_string(),
11228                tag_immutability: "MUTABLE".to_string(),
11229                encryption_type: "AES256".to_string(),
11230            })
11231            .collect();
11232
11233        // Type "2" then "p" to go to page 2
11234        app.handle_action(Action::FilterInput('2'));
11235        assert_eq!(app.page_input, "2");
11236
11237        app.handle_action(Action::OpenColumnSelector); // 'p' key
11238        assert_eq!(app.ecr_state.repositories.selected, 50); // Page 2 starts at index 50
11239        assert_eq!(app.page_input, ""); // Should be cleared
11240    }
11241
11242    #[test]
11243    fn test_lowercase_p_without_number_opens_preferences() {
11244        let mut app = test_app();
11245        app.current_service = Service::EcrRepositories;
11246        app.service_selected = true;
11247        app.mode = Mode::Normal;
11248
11249        app.handle_action(Action::OpenColumnSelector); // 'p' key without number
11250        assert_eq!(app.mode, Mode::ColumnSelector);
11251    }
11252
11253    #[test]
11254    fn test_ctrl_o_generates_correct_console_url() {
11255        let mut app = test_app();
11256        app.current_service = Service::EcrRepositories;
11257        app.service_selected = true;
11258        app.mode = Mode::Normal;
11259        app.config.account_id = "123456789012".to_string();
11260
11261        // Test repository list URL
11262        let url = app.get_console_url();
11263        assert!(url.contains("ecr/private-registry/repositories"));
11264        assert!(url.contains("region=us-east-1"));
11265
11266        // Test images URL
11267        app.ecr_state.current_repository = Some("my-repo".to_string());
11268        let url = app.get_console_url();
11269        assert!(url.contains("ecr/repositories/private/123456789012/my-repo"));
11270        assert!(url.contains("region=us-east-1"));
11271    }
11272
11273    #[test]
11274    fn test_page_input_display_and_reset() {
11275        let mut app = test_app();
11276        app.current_service = Service::EcrRepositories;
11277        app.service_selected = true;
11278        app.mode = Mode::Normal;
11279        app.ecr_state.repositories.items = (0..100)
11280            .map(|i| EcrRepository {
11281                name: format!("repo{}", i),
11282                uri: format!("uri{}", i),
11283                created_at: "2023-01-01".to_string(),
11284                tag_immutability: "MUTABLE".to_string(),
11285                encryption_type: "AES256".to_string(),
11286            })
11287            .collect();
11288
11289        // Type "2"
11290        app.handle_action(Action::FilterInput('2'));
11291        assert_eq!(app.page_input, "2");
11292
11293        // Press 'p' to go to page 2
11294        app.handle_action(Action::OpenColumnSelector);
11295        assert_eq!(app.page_input, ""); // Should be cleared
11296        assert_eq!(app.ecr_state.repositories.selected, 50); // Page 2 starts at index 50
11297    }
11298
11299    #[test]
11300    fn test_page_navigation_updates_scroll_offset_for_cfn() {
11301        let mut app = test_app();
11302        app.current_service = Service::CloudFormationStacks;
11303        app.service_selected = true;
11304        app.mode = Mode::Normal;
11305        app.cfn_state.table.items = (0..100)
11306            .map(|i| crate::cfn::Stack {
11307                name: format!("stack-{}", i),
11308                stack_id: format!(
11309                    "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-{}/id",
11310                    i
11311                ),
11312                status: "CREATE_COMPLETE".to_string(),
11313                created_time: "2023-01-01T00:00:00Z".to_string(),
11314                updated_time: "2023-01-01T00:00:00Z".to_string(),
11315                deleted_time: String::new(),
11316                drift_status: "IN_SYNC".to_string(),
11317                last_drift_check_time: String::new(),
11318                status_reason: String::new(),
11319                description: String::new(),
11320                detailed_status: String::new(),
11321                root_stack: String::new(),
11322                parent_stack: String::new(),
11323                termination_protection: false,
11324                iam_role: String::new(),
11325                tags: vec![],
11326                stack_policy: String::new(),
11327                rollback_monitoring_time: String::new(),
11328                rollback_alarms: vec![],
11329                notification_arns: vec![],
11330            })
11331            .collect();
11332
11333        // Type "2" then "p" to go to page 2
11334        app.handle_action(Action::FilterInput('2'));
11335        assert_eq!(app.page_input, "2");
11336
11337        app.handle_action(Action::OpenColumnSelector); // 'p' key
11338        assert_eq!(app.page_input, ""); // Should be cleared
11339
11340        // Verify both selected and scroll_offset are updated
11341        let page_size = app.cfn_state.table.page_size.value();
11342        let expected_offset = page_size; // Page 2 starts at page_size
11343        assert_eq!(app.cfn_state.table.selected, expected_offset);
11344        assert_eq!(app.cfn_state.table.scroll_offset, expected_offset);
11345
11346        // Verify pagination display shows page 2
11347        let current_page = app.cfn_state.table.scroll_offset / page_size;
11348        assert_eq!(
11349            current_page, 1,
11350            "2p should go to page 2 (0-indexed as 1), not page 3"
11351        ); // 0-indexed, so page 2 is index 1
11352    }
11353
11354    #[test]
11355    fn test_3p_goes_to_page_3_not_page_5() {
11356        let mut app = test_app();
11357        app.current_service = Service::CloudFormationStacks;
11358        app.service_selected = true;
11359        app.mode = Mode::Normal;
11360        app.cfn_state.table.items = (0..200)
11361            .map(|i| crate::cfn::Stack {
11362                name: format!("stack-{}", i),
11363                stack_id: format!(
11364                    "arn:aws:cloudformation:us-east-1:123456789012:stack/stack-{}/id",
11365                    i
11366                ),
11367                status: "CREATE_COMPLETE".to_string(),
11368                created_time: "2023-01-01T00:00:00Z".to_string(),
11369                updated_time: "2023-01-01T00:00:00Z".to_string(),
11370                deleted_time: String::new(),
11371                drift_status: "IN_SYNC".to_string(),
11372                last_drift_check_time: String::new(),
11373                status_reason: String::new(),
11374                description: String::new(),
11375                detailed_status: String::new(),
11376                root_stack: String::new(),
11377                parent_stack: String::new(),
11378                termination_protection: false,
11379                iam_role: String::new(),
11380                tags: vec![],
11381                stack_policy: String::new(),
11382                rollback_monitoring_time: String::new(),
11383                rollback_alarms: vec![],
11384                notification_arns: vec![],
11385            })
11386            .collect();
11387
11388        // Type "3" then "p" to go to page 3
11389        app.handle_action(Action::FilterInput('3'));
11390        app.handle_action(Action::OpenColumnSelector);
11391
11392        let page_size = app.cfn_state.table.page_size.value();
11393        let current_page = app.cfn_state.table.scroll_offset / page_size;
11394        assert_eq!(
11395            current_page, 2,
11396            "3p should go to page 3 (0-indexed as 2), not page 5"
11397        );
11398        assert_eq!(app.cfn_state.table.scroll_offset, 2 * page_size);
11399    }
11400
11401    #[test]
11402    fn test_log_streams_page_navigation_uses_correct_page_size() {
11403        let mut app = test_app();
11404        app.current_service = Service::CloudWatchLogGroups;
11405        app.view_mode = ViewMode::Detail;
11406        app.service_selected = true;
11407        app.mode = Mode::Normal;
11408        app.log_groups_state.log_streams = (0..100)
11409            .map(|i| LogStream {
11410                name: format!("stream-{}", i),
11411                creation_time: None,
11412                last_event_time: None,
11413            })
11414            .collect();
11415
11416        // Type "2" then "p" to go to page 2
11417        app.handle_action(Action::FilterInput('2'));
11418        app.handle_action(Action::OpenColumnSelector);
11419
11420        // Log streams use page_size=20, so page 2 starts at index 20
11421        assert_eq!(app.log_groups_state.selected_stream, 20);
11422
11423        // Verify pagination display shows page 2 (not page 3)
11424        let page_size = 20;
11425        let current_page = app.log_groups_state.selected_stream / page_size;
11426        assert_eq!(
11427            current_page, 1,
11428            "2p should go to page 2 (0-indexed as 1), not page 3"
11429        );
11430    }
11431
11432    #[test]
11433    fn test_ecr_repositories_page_navigation_uses_configurable_page_size() {
11434        let mut app = test_app();
11435        app.current_service = Service::EcrRepositories;
11436        app.service_selected = true;
11437        app.mode = Mode::Normal;
11438        app.ecr_state.repositories.page_size = PageSize::TwentyFive; // Set to 25
11439        app.ecr_state.repositories.items = (0..100)
11440            .map(|i| EcrRepository {
11441                name: format!("repo{}", i),
11442                uri: format!("uri{}", i),
11443                created_at: "2023-01-01".to_string(),
11444                tag_immutability: "MUTABLE".to_string(),
11445                encryption_type: "AES256".to_string(),
11446            })
11447            .collect();
11448
11449        // Type "3" then "p" to go to page 3
11450        app.handle_action(Action::FilterInput('3'));
11451        app.handle_action(Action::OpenColumnSelector);
11452
11453        // With page_size=25, page 3 starts at index 50
11454        assert_eq!(app.ecr_state.repositories.selected, 50);
11455
11456        let page_size = app.ecr_state.repositories.page_size.value();
11457        let current_page = app.ecr_state.repositories.selected / page_size;
11458        assert_eq!(
11459            current_page, 2,
11460            "3p with page_size=25 should go to page 3 (0-indexed as 2)"
11461        );
11462    }
11463
11464    #[test]
11465    fn test_page_navigation_updates_scroll_offset_for_alarms() {
11466        let mut app = test_app();
11467        app.current_service = Service::CloudWatchAlarms;
11468        app.service_selected = true;
11469        app.mode = Mode::Normal;
11470        app.alarms_state.table.items = (0..100)
11471            .map(|i| crate::cw::alarms::Alarm {
11472                name: format!("alarm-{}", i),
11473                state: "OK".to_string(),
11474                state_updated_timestamp: "2023-01-01T00:00:00Z".to_string(),
11475                description: String::new(),
11476                metric_name: "CPUUtilization".to_string(),
11477                namespace: "AWS/EC2".to_string(),
11478                statistic: "Average".to_string(),
11479                period: 300,
11480                comparison_operator: "GreaterThanThreshold".to_string(),
11481                threshold: 80.0,
11482                actions_enabled: true,
11483                state_reason: String::new(),
11484                resource: String::new(),
11485                dimensions: String::new(),
11486                expression: String::new(),
11487                alarm_type: "MetricAlarm".to_string(),
11488                cross_account: String::new(),
11489            })
11490            .collect();
11491
11492        // Type "2" then "p" to go to page 2
11493        app.handle_action(Action::FilterInput('2'));
11494        app.handle_action(Action::OpenColumnSelector);
11495
11496        // Verify both selected and scroll_offset are updated
11497        let page_size = app.alarms_state.table.page_size.value();
11498        let expected_offset = page_size; // Page 2 starts at page_size
11499        assert_eq!(app.alarms_state.table.selected, expected_offset);
11500        assert_eq!(app.alarms_state.table.scroll_offset, expected_offset);
11501    }
11502
11503    #[test]
11504    fn test_ecr_pagination_with_65_repos() {
11505        let mut app = test_app();
11506        app.current_service = Service::EcrRepositories;
11507        app.service_selected = true;
11508        app.mode = Mode::Normal;
11509        app.ecr_state.repositories.items = (0..65)
11510            .map(|i| EcrRepository {
11511                name: format!("repo{:02}", i),
11512                uri: format!("uri{}", i),
11513                created_at: "2023-01-01".to_string(),
11514                tag_immutability: "MUTABLE".to_string(),
11515                encryption_type: "AES256".to_string(),
11516            })
11517            .collect();
11518
11519        // Page 1: items 0-49 (50 items)
11520        assert_eq!(app.ecr_state.repositories.selected, 0);
11521        let page_size = 50;
11522        let current_page = app.ecr_state.repositories.selected / page_size;
11523        assert_eq!(current_page, 0);
11524
11525        // Go to page 2
11526        app.handle_action(Action::FilterInput('2'));
11527        app.handle_action(Action::OpenColumnSelector);
11528        assert_eq!(app.ecr_state.repositories.selected, 50);
11529
11530        // Page 2: items 50-64 (15 items)
11531        let current_page = app.ecr_state.repositories.selected / page_size;
11532        assert_eq!(current_page, 1);
11533    }
11534
11535    #[test]
11536    fn test_ecr_repos_input_focus_tab_cycling() {
11537        let mut app = test_app();
11538        app.current_service = Service::EcrRepositories;
11539        app.service_selected = true;
11540        app.mode = Mode::FilterInput;
11541        app.ecr_state.input_focus = InputFocus::Filter;
11542
11543        // Tab should cycle to Pagination
11544        app.handle_action(Action::NextFilterFocus);
11545        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
11546
11547        // Tab again should cycle back to Input
11548        app.handle_action(Action::NextFilterFocus);
11549        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
11550
11551        // Shift+Tab should cycle backwards to Pagination
11552        app.handle_action(Action::PrevFilterFocus);
11553        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
11554
11555        // Shift+Tab again should cycle back to Input
11556        app.handle_action(Action::PrevFilterFocus);
11557        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
11558    }
11559
11560    #[test]
11561    fn test_ecr_images_column_toggle_not_off_by_one() {
11562        use crate::ecr::image::Column as ImageColumn;
11563        let mut app = test_app();
11564        app.current_service = Service::EcrRepositories;
11565        app.service_selected = true;
11566        app.mode = Mode::ColumnSelector;
11567        app.ecr_state.current_repository = Some("test-repo".to_string());
11568
11569        // Start with all columns visible
11570        app.ecr_image_visible_column_ids = ImageColumn::ids();
11571        let initial_count = app.ecr_image_visible_column_ids.len();
11572
11573        // Select first column (index 0) and toggle it
11574        app.column_selector_index = 0;
11575        app.handle_action(Action::ToggleColumn);
11576
11577        // First column should be removed
11578        assert_eq!(app.ecr_image_visible_column_ids.len(), initial_count - 1);
11579        assert!(!app
11580            .ecr_image_visible_column_ids
11581            .contains(&ImageColumn::Tag.id()));
11582
11583        // Toggle it back
11584        app.handle_action(Action::ToggleColumn);
11585        assert_eq!(app.ecr_image_visible_column_ids.len(), initial_count);
11586        assert!(app
11587            .ecr_image_visible_column_ids
11588            .contains(&ImageColumn::Tag.id()));
11589    }
11590
11591    #[test]
11592    fn test_ecr_repos_column_toggle_works() {
11593        let mut app = test_app();
11594        app.current_service = Service::EcrRepositories;
11595        app.service_selected = true;
11596        app.mode = Mode::ColumnSelector;
11597        app.ecr_state.current_repository = None;
11598
11599        // Start with all columns visible
11600        app.ecr_repo_visible_column_ids = EcrColumn::ids();
11601        let initial_count = app.ecr_repo_visible_column_ids.len();
11602
11603        // Select first column (index 0) and toggle it
11604        app.column_selector_index = 0;
11605        app.handle_action(Action::ToggleColumn);
11606
11607        // First column should be removed
11608        assert_eq!(app.ecr_repo_visible_column_ids.len(), initial_count - 1);
11609        assert!(!app
11610            .ecr_repo_visible_column_ids
11611            .contains(&EcrColumn::Name.id()));
11612
11613        // Toggle it back
11614        app.handle_action(Action::ToggleColumn);
11615        assert_eq!(app.ecr_repo_visible_column_ids.len(), initial_count);
11616        assert!(app
11617            .ecr_repo_visible_column_ids
11618            .contains(&EcrColumn::Name.id()));
11619    }
11620
11621    #[test]
11622    fn test_ecr_repos_pagination_left_right_navigation() {
11623        use crate::ecr::repo::Repository as EcrRepository;
11624        let mut app = test_app();
11625        app.current_service = Service::EcrRepositories;
11626        app.service_selected = true;
11627        app.mode = Mode::FilterInput;
11628        app.ecr_state.input_focus = InputFocus::Pagination;
11629
11630        // Create 150 repos (3 pages with page size 50)
11631        app.ecr_state.repositories.items = (0..150)
11632            .map(|i| EcrRepository {
11633                name: format!("repo{:03}", i),
11634                uri: format!("uri{}", i),
11635                created_at: "2023-01-01".to_string(),
11636                tag_immutability: "MUTABLE".to_string(),
11637                encryption_type: "AES256".to_string(),
11638            })
11639            .collect();
11640
11641        // Start on page 1 (index 0)
11642        app.ecr_state.repositories.selected = 0;
11643        eprintln!(
11644            "Initial: selected={}, focus={:?}, mode={:?}",
11645            app.ecr_state.repositories.selected, app.ecr_state.input_focus, app.mode
11646        );
11647
11648        // Right arrow (PageDown) should go to page 2
11649        app.handle_action(Action::PageDown);
11650        eprintln!(
11651            "After PageDown: selected={}",
11652            app.ecr_state.repositories.selected
11653        );
11654        assert_eq!(app.ecr_state.repositories.selected, 50);
11655
11656        // Right arrow again should go to page 3
11657        app.handle_action(Action::PageDown);
11658        eprintln!(
11659            "After 2nd PageDown: selected={}",
11660            app.ecr_state.repositories.selected
11661        );
11662        assert_eq!(app.ecr_state.repositories.selected, 100);
11663
11664        // Right arrow at last page should stay at last page
11665        app.handle_action(Action::PageDown);
11666        eprintln!(
11667            "After 3rd PageDown: selected={}",
11668            app.ecr_state.repositories.selected
11669        );
11670        assert_eq!(app.ecr_state.repositories.selected, 100);
11671
11672        // Left arrow (PageUp) should go back to page 2
11673        app.handle_action(Action::PageUp);
11674        eprintln!(
11675            "After PageUp: selected={}",
11676            app.ecr_state.repositories.selected
11677        );
11678        assert_eq!(app.ecr_state.repositories.selected, 50);
11679
11680        // Left arrow again should go to page 1
11681        app.handle_action(Action::PageUp);
11682        eprintln!(
11683            "After 2nd PageUp: selected={}",
11684            app.ecr_state.repositories.selected
11685        );
11686        assert_eq!(app.ecr_state.repositories.selected, 0);
11687
11688        // Left arrow at first page should stay at first page
11689        app.handle_action(Action::PageUp);
11690        eprintln!(
11691            "After 3rd PageUp: selected={}",
11692            app.ecr_state.repositories.selected
11693        );
11694        assert_eq!(app.ecr_state.repositories.selected, 0);
11695    }
11696
11697    #[test]
11698    fn test_ecr_repos_filter_input_when_input_focused() {
11699        use crate::ecr::repo::Repository as EcrRepository;
11700        let mut app = test_app();
11701        app.current_service = Service::EcrRepositories;
11702        app.service_selected = true;
11703        app.mode = Mode::FilterInput;
11704        app.ecr_state.input_focus = InputFocus::Filter;
11705
11706        // Create some repos
11707        app.ecr_state.repositories.items = vec![
11708            EcrRepository {
11709                name: "test-repo".to_string(),
11710                uri: "uri1".to_string(),
11711                created_at: "2023-01-01".to_string(),
11712                tag_immutability: "MUTABLE".to_string(),
11713                encryption_type: "AES256".to_string(),
11714            },
11715            EcrRepository {
11716                name: "prod-repo".to_string(),
11717                uri: "uri2".to_string(),
11718                created_at: "2023-01-01".to_string(),
11719                tag_immutability: "MUTABLE".to_string(),
11720                encryption_type: "AES256".to_string(),
11721            },
11722        ];
11723
11724        // When input is focused, typing should add to filter
11725        assert_eq!(app.ecr_state.repositories.filter, "");
11726        app.handle_action(Action::FilterInput('t'));
11727        assert_eq!(app.ecr_state.repositories.filter, "t");
11728        app.handle_action(Action::FilterInput('e'));
11729        assert_eq!(app.ecr_state.repositories.filter, "te");
11730        app.handle_action(Action::FilterInput('s'));
11731        assert_eq!(app.ecr_state.repositories.filter, "tes");
11732        app.handle_action(Action::FilterInput('t'));
11733        assert_eq!(app.ecr_state.repositories.filter, "test");
11734    }
11735
11736    #[test]
11737    fn test_ecr_repos_digit_input_when_pagination_focused() {
11738        use crate::ecr::repo::Repository as EcrRepository;
11739        let mut app = test_app();
11740        app.current_service = Service::EcrRepositories;
11741        app.service_selected = true;
11742        app.mode = Mode::FilterInput;
11743        app.ecr_state.input_focus = InputFocus::Pagination;
11744
11745        // Create some repos
11746        app.ecr_state.repositories.items = vec![EcrRepository {
11747            name: "test-repo".to_string(),
11748            uri: "uri1".to_string(),
11749            created_at: "2023-01-01".to_string(),
11750            tag_immutability: "MUTABLE".to_string(),
11751            encryption_type: "AES256".to_string(),
11752        }];
11753
11754        // When pagination is focused, digits should go to page_input, not filter
11755        assert_eq!(app.ecr_state.repositories.filter, "");
11756        assert_eq!(app.page_input, "");
11757        app.handle_action(Action::FilterInput('2'));
11758        assert_eq!(app.ecr_state.repositories.filter, "");
11759        assert_eq!(app.page_input, "2");
11760
11761        // Non-digits should not be added to either
11762        app.handle_action(Action::FilterInput('a'));
11763        assert_eq!(app.ecr_state.repositories.filter, "");
11764        assert_eq!(app.page_input, "2");
11765    }
11766
11767    #[test]
11768    fn test_ecr_repos_left_right_scrolls_table_when_input_focused() {
11769        use crate::ecr::repo::Repository as EcrRepository;
11770        let mut app = test_app();
11771        app.current_service = Service::EcrRepositories;
11772        app.service_selected = true;
11773        app.mode = Mode::FilterInput;
11774        app.ecr_state.input_focus = InputFocus::Filter;
11775
11776        // Create 150 repos (3 pages)
11777        app.ecr_state.repositories.items = (0..150)
11778            .map(|i| EcrRepository {
11779                name: format!("repo{:03}", i),
11780                uri: format!("uri{}", i),
11781                created_at: "2023-01-01".to_string(),
11782                tag_immutability: "MUTABLE".to_string(),
11783                encryption_type: "AES256".to_string(),
11784            })
11785            .collect();
11786
11787        // Start on page 1
11788        app.ecr_state.repositories.selected = 0;
11789
11790        // When input is focused, left/right should scroll table (not change pages)
11791        app.handle_action(Action::PageDown);
11792        assert_eq!(
11793            app.ecr_state.repositories.selected, 10,
11794            "Should scroll down by 10"
11795        );
11796
11797        app.handle_action(Action::PageUp);
11798        assert_eq!(
11799            app.ecr_state.repositories.selected, 0,
11800            "Should scroll back up"
11801        );
11802    }
11803
11804    #[test]
11805    fn test_ecr_repos_pagination_control_actually_works() {
11806        use crate::ecr::repo::Repository as EcrRepository;
11807
11808        // Test that verifies the exact conditions needed for pagination to work
11809        let mut app = test_app();
11810        app.current_service = Service::EcrRepositories;
11811        app.service_selected = true;
11812        app.mode = Mode::FilterInput;
11813        app.ecr_state.current_repository = None;
11814        app.ecr_state.input_focus = InputFocus::Pagination;
11815
11816        // Create 100 repos (2 pages with page size 50)
11817        app.ecr_state.repositories.items = (0..100)
11818            .map(|i| EcrRepository {
11819                name: format!("repo{:03}", i),
11820                uri: format!("uri{}", i),
11821                created_at: "2023-01-01".to_string(),
11822                tag_immutability: "MUTABLE".to_string(),
11823                encryption_type: "AES256".to_string(),
11824            })
11825            .collect();
11826
11827        app.ecr_state.repositories.selected = 0;
11828
11829        // Verify all conditions are met
11830        assert_eq!(app.mode, Mode::FilterInput);
11831        assert_eq!(app.current_service, Service::EcrRepositories);
11832        assert_eq!(app.ecr_state.current_repository, None);
11833        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
11834
11835        // Now test pagination
11836        app.handle_action(Action::PageDown);
11837        assert_eq!(
11838            app.ecr_state.repositories.selected, 50,
11839            "PageDown should move to page 2"
11840        );
11841
11842        app.handle_action(Action::PageUp);
11843        assert_eq!(
11844            app.ecr_state.repositories.selected, 0,
11845            "PageUp should move back to page 1"
11846        );
11847    }
11848
11849    #[test]
11850    fn test_ecr_repos_start_filter_resets_focus_to_input() {
11851        let mut app = test_app();
11852        app.current_service = Service::EcrRepositories;
11853        app.service_selected = true;
11854        app.mode = Mode::Normal;
11855        app.ecr_state.current_repository = None;
11856
11857        // Set focus to Pagination
11858        app.ecr_state.input_focus = InputFocus::Pagination;
11859
11860        // Start filter mode
11861        app.handle_action(Action::StartFilter);
11862
11863        // Should reset to Input focus
11864        assert_eq!(app.mode, Mode::FilterInput);
11865        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
11866    }
11867
11868    #[test]
11869    fn test_ecr_repos_exact_user_flow_i_tab_arrow() {
11870        use crate::ecr::repo::Repository as EcrRepository;
11871
11872        let mut app = test_app();
11873        app.current_service = Service::EcrRepositories;
11874        app.service_selected = true;
11875        app.mode = Mode::Normal;
11876        app.ecr_state.current_repository = None;
11877
11878        // Create 100 repos (2 pages)
11879        app.ecr_state.repositories.items = (0..100)
11880            .map(|i| EcrRepository {
11881                name: format!("repo{:03}", i),
11882                uri: format!("uri{}", i),
11883                created_at: "2023-01-01".to_string(),
11884                tag_immutability: "MUTABLE".to_string(),
11885                encryption_type: "AES256".to_string(),
11886            })
11887            .collect();
11888
11889        app.ecr_state.repositories.selected = 0;
11890
11891        // User presses 'i' to enter filter mode
11892        app.handle_action(Action::StartFilter);
11893        assert_eq!(app.mode, Mode::FilterInput);
11894        assert_eq!(app.ecr_state.input_focus, InputFocus::Filter);
11895
11896        // User presses Tab to switch to pagination
11897        app.handle_action(Action::NextFilterFocus);
11898        assert_eq!(app.ecr_state.input_focus, InputFocus::Pagination);
11899
11900        // User presses right arrow (PageDown)
11901        eprintln!("Before PageDown: mode={:?}, service={:?}, current_repo={:?}, input_focus={:?}, selected={}",
11902            app.mode, app.current_service, app.ecr_state.current_repository, app.ecr_state.input_focus, app.ecr_state.repositories.selected);
11903        app.handle_action(Action::PageDown);
11904        eprintln!(
11905            "After PageDown: selected={}",
11906            app.ecr_state.repositories.selected
11907        );
11908
11909        // Should move to page 2
11910        assert_eq!(
11911            app.ecr_state.repositories.selected, 50,
11912            "Right arrow should move to page 2"
11913        );
11914
11915        // User presses left arrow (PageUp)
11916        app.handle_action(Action::PageUp);
11917        assert_eq!(
11918            app.ecr_state.repositories.selected, 0,
11919            "Left arrow should move back to page 1"
11920        );
11921    }
11922
11923    #[test]
11924    fn test_service_picker_i_key_activates_filter() {
11925        let mut app = test_app();
11926
11927        // Start in ServicePicker mode (service picker)
11928        assert_eq!(app.mode, Mode::ServicePicker);
11929        assert!(app.service_picker.filter.is_empty());
11930
11931        // Press 'i' to start filtering
11932        app.handle_action(Action::FilterInput('i'));
11933
11934        // Should still be in ServicePicker mode and filter should have 'i'
11935        assert_eq!(app.mode, Mode::ServicePicker);
11936        assert_eq!(app.service_picker.filter, "i");
11937    }
11938
11939    #[test]
11940    fn test_service_picker_typing_filters_services() {
11941        let mut app = test_app();
11942
11943        // Start in ServicePicker mode
11944        assert_eq!(app.mode, Mode::ServicePicker);
11945
11946        // Type "s3" to filter
11947        app.handle_action(Action::FilterInput('s'));
11948        app.handle_action(Action::FilterInput('3'));
11949
11950        assert_eq!(app.service_picker.filter, "s3");
11951        assert_eq!(app.mode, Mode::ServicePicker);
11952    }
11953
11954    #[test]
11955    fn test_service_picker_resets_on_open() {
11956        let mut app = test_app();
11957
11958        // Select a service to get into Normal mode
11959        app.service_selected = true;
11960        app.mode = Mode::Normal;
11961
11962        // Simulate having previous filter and selection
11963        app.service_picker.filter = "previous".to_string();
11964        app.service_picker.selected = 5;
11965
11966        // Open space menu (service picker)
11967        app.handle_action(Action::OpenSpaceMenu);
11968
11969        // Filter and selection should be reset
11970        assert_eq!(app.mode, Mode::SpaceMenu);
11971        assert!(app.service_picker.filter.is_empty());
11972        assert_eq!(app.service_picker.selected, 0);
11973    }
11974
11975    #[test]
11976    fn test_no_pii_in_test_data() {
11977        // Ensure test data uses placeholder account IDs, not real ones
11978        let test_repo = EcrRepository {
11979            name: "test-repo".to_string(),
11980            uri: "123456789012.dkr.ecr.us-east-1.amazonaws.com/test-repo".to_string(),
11981            created_at: "2024-01-01".to_string(),
11982            tag_immutability: "MUTABLE".to_string(),
11983            encryption_type: "AES256".to_string(),
11984        };
11985
11986        // Verify placeholder account ID is used
11987        assert!(test_repo.uri.starts_with("123456789012"));
11988        assert!(!test_repo.uri.contains("123456789013")); // Not a real account
11989    }
11990
11991    #[test]
11992    fn test_lambda_versions_tab_triggers_loading() {
11993        let mut app = test_app();
11994        app.current_service = Service::LambdaFunctions;
11995        app.service_selected = true;
11996
11997        // Simulate selecting a function
11998        app.lambda_state.current_function = Some("test-function".to_string());
11999        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12000
12001        // Initially no versions
12002        assert!(app.lambda_state.version_table.items.is_empty());
12003
12004        // Switch to Versions tab
12005        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12006
12007        // The main loop should detect this change and load versions
12008        // We verify the state is set up correctly for loading
12009        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
12010        assert!(app.lambda_state.current_function.is_some());
12011    }
12012
12013    #[test]
12014    fn test_lambda_versions_navigation() {
12015        use crate::lambda::Version;
12016
12017        let mut app = test_app();
12018        app.current_service = Service::LambdaFunctions;
12019        app.service_selected = true;
12020        app.lambda_state.current_function = Some("test-function".to_string());
12021        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12022
12023        // Add test versions
12024        app.lambda_state.version_table.items = vec![
12025            Version {
12026                version: "3".to_string(),
12027                aliases: "prod".to_string(),
12028                description: "".to_string(),
12029                last_modified: "".to_string(),
12030                architecture: "X86_64".to_string(),
12031            },
12032            Version {
12033                version: "2".to_string(),
12034                aliases: "".to_string(),
12035                description: "".to_string(),
12036                last_modified: "".to_string(),
12037                architecture: "X86_64".to_string(),
12038            },
12039            Version {
12040                version: "1".to_string(),
12041                aliases: "".to_string(),
12042                description: "".to_string(),
12043                last_modified: "".to_string(),
12044                architecture: "X86_64".to_string(),
12045            },
12046        ];
12047
12048        // Verify versions are loaded
12049        assert_eq!(app.lambda_state.version_table.items.len(), 3);
12050        assert_eq!(app.lambda_state.version_table.items[0].version, "3");
12051        assert_eq!(app.lambda_state.version_table.items[0].aliases, "prod");
12052
12053        // Verify selection can be changed
12054        app.lambda_state.version_table.selected = 1;
12055        assert_eq!(app.lambda_state.version_table.selected, 1);
12056    }
12057
12058    #[test]
12059    fn test_lambda_versions_with_aliases() {
12060        use crate::lambda::Version;
12061
12062        let version = Version {
12063            version: "35".to_string(),
12064            aliases: "prod, staging".to_string(),
12065            description: "Production version".to_string(),
12066            last_modified: "2024-01-01".to_string(),
12067            architecture: "X86_64".to_string(),
12068        };
12069
12070        assert_eq!(version.aliases, "prod, staging");
12071        assert!(!version.aliases.is_empty());
12072    }
12073
12074    #[test]
12075    fn test_lambda_versions_expansion() {
12076        use crate::lambda::Version;
12077
12078        let mut app = test_app();
12079        app.current_service = Service::LambdaFunctions;
12080        app.service_selected = true;
12081        app.lambda_state.current_function = Some("test-function".to_string());
12082        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12083
12084        // Add test versions
12085        app.lambda_state.version_table.items = vec![
12086            Version {
12087                version: "2".to_string(),
12088                aliases: "prod".to_string(),
12089                description: "Production".to_string(),
12090                last_modified: "2024-01-01".to_string(),
12091                architecture: "X86_64".to_string(),
12092            },
12093            Version {
12094                version: "1".to_string(),
12095                aliases: "".to_string(),
12096                description: "".to_string(),
12097                last_modified: "2024-01-01".to_string(),
12098                architecture: "Arm64".to_string(),
12099            },
12100        ];
12101
12102        app.lambda_state.version_table.selected = 0;
12103
12104        // Verify expansion can be set
12105        app.lambda_state.version_table.expanded_item = Some(0);
12106        assert_eq!(app.lambda_state.version_table.expanded_item, Some(0));
12107
12108        // Select different version
12109        app.lambda_state.version_table.selected = 1;
12110        app.lambda_state.version_table.expanded_item = Some(1);
12111        assert_eq!(app.lambda_state.version_table.expanded_item, Some(1));
12112    }
12113
12114    #[test]
12115    fn test_lambda_versions_page_navigation() {
12116        use crate::lambda::Version;
12117
12118        let mut app = test_app();
12119        app.current_service = Service::LambdaFunctions;
12120        app.service_selected = true;
12121        app.lambda_state.current_function = Some("test-function".to_string());
12122        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12123
12124        // Add 30 test versions
12125        app.lambda_state.version_table.items = (1..=30)
12126            .map(|i| Version {
12127                version: i.to_string(),
12128                aliases: "".to_string(),
12129                description: "".to_string(),
12130                last_modified: "".to_string(),
12131                architecture: "X86_64".to_string(),
12132            })
12133            .collect();
12134
12135        app.lambda_state.version_table.page_size = PageSize::Ten;
12136        app.lambda_state.version_table.selected = 0;
12137
12138        // Go to page 2
12139        app.page_input = "2".to_string();
12140        app.handle_action(Action::OpenColumnSelector);
12141
12142        // Should be at index 10 (start of page 2)
12143        assert_eq!(app.lambda_state.version_table.selected, 10);
12144    }
12145
12146    #[test]
12147    fn test_lambda_versions_pagination_arrow_keys() {
12148        use crate::lambda::Version;
12149
12150        let mut app = test_app();
12151        app.current_service = Service::LambdaFunctions;
12152        app.service_selected = true;
12153        app.lambda_state.current_function = Some("test-function".to_string());
12154        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12155        app.mode = Mode::FilterInput;
12156        app.lambda_state.version_input_focus = InputFocus::Pagination;
12157
12158        // Add 30 test versions
12159        app.lambda_state.version_table.items = (1..=30)
12160            .map(|i| Version {
12161                version: i.to_string(),
12162                aliases: "".to_string(),
12163                description: "".to_string(),
12164                last_modified: "".to_string(),
12165                architecture: "X86_64".to_string(),
12166            })
12167            .collect();
12168
12169        app.lambda_state.version_table.page_size = PageSize::Ten;
12170        app.lambda_state.version_table.selected = 0;
12171
12172        // Right arrow (PageDown) should go to next page
12173        app.handle_action(Action::PageDown);
12174        assert_eq!(app.lambda_state.version_table.selected, 10);
12175
12176        // Left arrow (PageUp) should go back
12177        app.handle_action(Action::PageUp);
12178        assert_eq!(app.lambda_state.version_table.selected, 0);
12179    }
12180
12181    #[test]
12182    fn test_lambda_versions_page_input_in_filter_mode() {
12183        use crate::lambda::Version;
12184
12185        let mut app = test_app();
12186        app.current_service = Service::LambdaFunctions;
12187        app.service_selected = true;
12188        app.lambda_state.current_function = Some("test-function".to_string());
12189        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12190        app.mode = Mode::FilterInput;
12191        app.lambda_state.version_input_focus = InputFocus::Pagination;
12192
12193        // Add 30 test versions
12194        app.lambda_state.version_table.items = (1..=30)
12195            .map(|i| Version {
12196                version: i.to_string(),
12197                aliases: "".to_string(),
12198                description: "".to_string(),
12199                last_modified: "".to_string(),
12200                architecture: "X86_64".to_string(),
12201            })
12202            .collect();
12203
12204        app.lambda_state.version_table.page_size = PageSize::Ten;
12205        app.lambda_state.version_table.selected = 0;
12206
12207        // Type "2" when focused on Pagination
12208        app.handle_action(Action::FilterInput('2'));
12209        assert_eq!(app.page_input, "2");
12210        assert_eq!(app.lambda_state.version_table.filter, ""); // Should not go to filter
12211
12212        // Press 'p' to go to page 2
12213        app.handle_action(Action::OpenColumnSelector);
12214        assert_eq!(app.lambda_state.version_table.selected, 10);
12215        assert_eq!(app.page_input, ""); // Should be cleared
12216    }
12217
12218    #[test]
12219    fn test_lambda_versions_filter_input() {
12220        use crate::lambda::Version;
12221
12222        let mut app = test_app();
12223        app.current_service = Service::LambdaFunctions;
12224        app.service_selected = true;
12225        app.lambda_state.current_function = Some("test-function".to_string());
12226        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12227        app.mode = Mode::FilterInput;
12228        app.lambda_state.version_input_focus = InputFocus::Filter;
12229
12230        // Add test versions
12231        app.lambda_state.version_table.items = vec![
12232            Version {
12233                version: "1".to_string(),
12234                aliases: "prod".to_string(),
12235                description: "Production".to_string(),
12236                last_modified: "".to_string(),
12237                architecture: "X86_64".to_string(),
12238            },
12239            Version {
12240                version: "2".to_string(),
12241                aliases: "staging".to_string(),
12242                description: "Staging".to_string(),
12243                last_modified: "".to_string(),
12244                architecture: "X86_64".to_string(),
12245            },
12246        ];
12247
12248        // Type filter text
12249        app.handle_action(Action::FilterInput('p'));
12250        app.handle_action(Action::FilterInput('r'));
12251        app.handle_action(Action::FilterInput('o'));
12252        app.handle_action(Action::FilterInput('d'));
12253        assert_eq!(app.lambda_state.version_table.filter, "prod");
12254
12255        // Backspace should work
12256        app.handle_action(Action::FilterBackspace);
12257        assert_eq!(app.lambda_state.version_table.filter, "pro");
12258    }
12259
12260    #[test]
12261    fn test_lambda_aliases_table_expansion() {
12262        use crate::lambda::Alias;
12263
12264        let mut app = test_app();
12265        app.current_service = Service::LambdaFunctions;
12266        app.service_selected = true;
12267        app.lambda_state.current_function = Some("test-function".to_string());
12268        app.lambda_state.detail_tab = LambdaDetailTab::Aliases;
12269        app.mode = Mode::Normal;
12270
12271        app.lambda_state.alias_table.items = vec![
12272            Alias {
12273                name: "prod".to_string(),
12274                versions: "1".to_string(),
12275                description: "Production alias".to_string(),
12276            },
12277            Alias {
12278                name: "staging".to_string(),
12279                versions: "2".to_string(),
12280                description: "Staging alias".to_string(),
12281            },
12282        ];
12283
12284        app.lambda_state.alias_table.selected = 0;
12285
12286        // Select first alias - should open alias detail view (no tab change)
12287        app.handle_action(Action::Select);
12288        assert_eq!(app.lambda_state.current_alias, Some("prod".to_string()));
12289        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
12290
12291        // Go back
12292        app.handle_action(Action::GoBack);
12293        assert_eq!(app.lambda_state.current_alias, None);
12294        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
12295
12296        // Select second alias
12297        app.lambda_state.alias_table.selected = 1;
12298        app.handle_action(Action::Select);
12299        assert_eq!(app.lambda_state.current_alias, Some("staging".to_string()));
12300    }
12301
12302    #[test]
12303    fn test_lambda_versions_arrow_key_expansion() {
12304        use crate::lambda::Version;
12305
12306        let mut app = test_app();
12307        app.current_service = Service::LambdaFunctions;
12308        app.service_selected = true;
12309        app.lambda_state.current_function = Some("test-function".to_string());
12310        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12311        app.mode = Mode::Normal;
12312
12313        app.lambda_state.version_table.items = vec![Version {
12314            version: "1".to_string(),
12315            aliases: "prod".to_string(),
12316            description: "Production".to_string(),
12317            last_modified: "2024-01-01".to_string(),
12318            architecture: "X86_64".to_string(),
12319        }];
12320
12321        app.lambda_state.version_table.selected = 0;
12322
12323        // Right arrow expands
12324        app.handle_action(Action::NextPane);
12325        assert_eq!(app.lambda_state.version_table.expanded_item, Some(0));
12326
12327        // Left arrow collapses
12328        app.handle_action(Action::PrevPane);
12329        assert_eq!(app.lambda_state.version_table.expanded_item, None);
12330    }
12331
12332    #[test]
12333    fn test_lambda_version_detail_view() {
12334        use crate::lambda::Function;
12335
12336        let mut app = test_app();
12337        app.current_service = Service::LambdaFunctions;
12338        app.service_selected = true;
12339        app.lambda_state.current_function = Some("test-function".to_string());
12340        app.lambda_state.detail_tab = LambdaDetailTab::Versions;
12341        app.mode = Mode::Normal;
12342
12343        app.lambda_state.table.items = vec![Function {
12344            name: "test-function".to_string(),
12345            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
12346            application: None,
12347            description: "Test".to_string(),
12348            package_type: "Zip".to_string(),
12349            runtime: "python3.12".to_string(),
12350            architecture: "X86_64".to_string(),
12351            code_size: 1024,
12352            code_sha256: "hash".to_string(),
12353            memory_mb: 128,
12354            timeout_seconds: 30,
12355            last_modified: "2024-01-01".to_string(),
12356            layers: vec![],
12357        }];
12358
12359        app.lambda_state.version_table.items = vec![crate::lambda::Version {
12360            version: "1".to_string(),
12361            aliases: "prod".to_string(),
12362            description: "Production".to_string(),
12363            last_modified: "2024-01-01".to_string(),
12364            architecture: "X86_64".to_string(),
12365        }];
12366
12367        app.lambda_state.version_table.selected = 0;
12368
12369        // Select version to open detail view
12370        app.handle_action(Action::Select);
12371        assert_eq!(app.lambda_state.current_version, Some("1".to_string()));
12372        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
12373
12374        // GoBack should go back to versions list
12375        app.handle_action(Action::GoBack);
12376        assert_eq!(app.lambda_state.current_version, None);
12377        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
12378    }
12379
12380    #[test]
12381    fn test_lambda_version_detail_tabs() {
12382        use crate::lambda::Function;
12383
12384        let mut app = test_app();
12385        app.current_service = Service::LambdaFunctions;
12386        app.service_selected = true;
12387        app.lambda_state.current_function = Some("test-function".to_string());
12388        app.lambda_state.current_version = Some("1".to_string());
12389        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12390        app.mode = Mode::Normal;
12391
12392        app.lambda_state.table.items = vec![Function {
12393            name: "test-function".to_string(),
12394            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
12395            application: None,
12396            description: "Test".to_string(),
12397            package_type: "Zip".to_string(),
12398            runtime: "python3.12".to_string(),
12399            architecture: "X86_64".to_string(),
12400            code_size: 1024,
12401            code_sha256: "hash".to_string(),
12402            memory_mb: 128,
12403            timeout_seconds: 30,
12404            last_modified: "2024-01-01".to_string(),
12405            layers: vec![],
12406        }];
12407
12408        // Tab should cycle between Code, Monitor, and Configuration
12409        app.handle_action(Action::NextDetailTab);
12410        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
12411
12412        app.handle_action(Action::NextDetailTab);
12413        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
12414
12415        app.handle_action(Action::NextDetailTab);
12416        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
12417
12418        // BackTab should cycle backward
12419        app.handle_action(Action::PrevDetailTab);
12420        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
12421
12422        app.handle_action(Action::PrevDetailTab);
12423        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
12424    }
12425
12426    #[test]
12427    fn test_lambda_aliases_arrow_key_expansion() {
12428        use crate::lambda::Alias;
12429
12430        let mut app = test_app();
12431        app.current_service = Service::LambdaFunctions;
12432        app.service_selected = true;
12433        app.lambda_state.current_function = Some("test-function".to_string());
12434        app.lambda_state.detail_tab = LambdaDetailTab::Aliases;
12435        app.mode = Mode::Normal;
12436
12437        app.lambda_state.alias_table.items = vec![Alias {
12438            name: "prod".to_string(),
12439            versions: "1".to_string(),
12440            description: "Production alias".to_string(),
12441        }];
12442
12443        app.lambda_state.alias_table.selected = 0;
12444
12445        // Right arrow expands
12446        app.handle_action(Action::NextPane);
12447        assert_eq!(app.lambda_state.alias_table.expanded_item, Some(0));
12448
12449        // Left arrow collapses
12450        app.handle_action(Action::PrevPane);
12451        assert_eq!(app.lambda_state.alias_table.expanded_item, None);
12452    }
12453
12454    #[test]
12455    fn test_lambda_functions_arrow_key_expansion() {
12456        use crate::lambda::Function;
12457
12458        let mut app = test_app();
12459        app.current_service = Service::LambdaFunctions;
12460        app.service_selected = true;
12461        app.mode = Mode::Normal;
12462
12463        app.lambda_state.table.items = vec![Function {
12464            name: "test-function".to_string(),
12465            arn: "arn".to_string(),
12466            application: None,
12467            description: "Test".to_string(),
12468            package_type: "Zip".to_string(),
12469            runtime: "python3.12".to_string(),
12470            architecture: "X86_64".to_string(),
12471            code_size: 1024,
12472            code_sha256: "hash".to_string(),
12473            memory_mb: 128,
12474            timeout_seconds: 30,
12475            last_modified: "2024-01-01".to_string(),
12476            layers: vec![],
12477        }];
12478
12479        app.lambda_state.table.selected = 0;
12480
12481        // Right arrow expands
12482        app.handle_action(Action::NextPane);
12483        assert_eq!(app.lambda_state.table.expanded_item, Some(0));
12484
12485        // Left arrow collapses
12486        app.handle_action(Action::PrevPane);
12487        assert_eq!(app.lambda_state.table.expanded_item, None);
12488    }
12489
12490    #[test]
12491    fn test_lambda_version_detail_with_application() {
12492        use crate::lambda::Function;
12493
12494        let mut app = test_app();
12495        app.current_service = Service::LambdaFunctions;
12496        app.service_selected = true;
12497        app.lambda_state.current_function = Some("storefront-studio-beta-api".to_string());
12498        app.lambda_state.current_version = Some("1".to_string());
12499        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12500        app.mode = Mode::Normal;
12501
12502        app.lambda_state.table.items = vec![Function {
12503            name: "storefront-studio-beta-api".to_string(),
12504            arn: "arn:aws:lambda:us-east-1:123456789012:function:storefront-studio-beta-api"
12505                .to_string(),
12506            application: Some("storefront-studio-beta".to_string()),
12507            description: "API function".to_string(),
12508            package_type: "Zip".to_string(),
12509            runtime: "python3.12".to_string(),
12510            architecture: "X86_64".to_string(),
12511            code_size: 1024,
12512            code_sha256: "hash".to_string(),
12513            memory_mb: 128,
12514            timeout_seconds: 30,
12515            last_modified: "2024-01-01".to_string(),
12516            layers: vec![],
12517        }];
12518
12519        // Verify function has application extracted
12520        assert_eq!(
12521            app.lambda_state.table.items[0].application,
12522            Some("storefront-studio-beta".to_string())
12523        );
12524        assert_eq!(app.lambda_state.current_version, Some("1".to_string()));
12525    }
12526
12527    #[test]
12528    fn test_lambda_layer_navigation() {
12529        use crate::lambda::{Function, Layer};
12530
12531        let mut app = test_app();
12532        app.current_service = Service::LambdaFunctions;
12533        app.service_selected = true;
12534        app.lambda_state.current_function = Some("test-function".to_string());
12535        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12536        app.mode = Mode::Normal;
12537
12538        app.lambda_state.table.items = vec![Function {
12539            name: "test-function".to_string(),
12540            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
12541            application: None,
12542            description: "Test".to_string(),
12543            package_type: "Zip".to_string(),
12544            runtime: "python3.12".to_string(),
12545            architecture: "X86_64".to_string(),
12546            code_size: 1024,
12547            code_sha256: "hash".to_string(),
12548            memory_mb: 128,
12549            timeout_seconds: 30,
12550            last_modified: "2024-01-01".to_string(),
12551            layers: vec![
12552                Layer {
12553                    merge_order: "1".to_string(),
12554                    name: "layer1".to_string(),
12555                    layer_version: "1".to_string(),
12556                    compatible_runtimes: "python3.9".to_string(),
12557                    compatible_architectures: "x86_64".to_string(),
12558                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer1:1".to_string(),
12559                },
12560                Layer {
12561                    merge_order: "2".to_string(),
12562                    name: "layer2".to_string(),
12563                    layer_version: "2".to_string(),
12564                    compatible_runtimes: "python3.9".to_string(),
12565                    compatible_architectures: "x86_64".to_string(),
12566                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer2:2".to_string(),
12567                },
12568                Layer {
12569                    merge_order: "3".to_string(),
12570                    name: "layer3".to_string(),
12571                    layer_version: "3".to_string(),
12572                    compatible_runtimes: "python3.9".to_string(),
12573                    compatible_architectures: "x86_64".to_string(),
12574                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer3:3".to_string(),
12575                },
12576            ],
12577        }];
12578
12579        assert_eq!(app.lambda_state.layer_selected, 0);
12580
12581        app.handle_action(Action::NextItem);
12582        assert_eq!(app.lambda_state.layer_selected, 1);
12583
12584        app.handle_action(Action::NextItem);
12585        assert_eq!(app.lambda_state.layer_selected, 2);
12586
12587        app.handle_action(Action::NextItem);
12588        assert_eq!(app.lambda_state.layer_selected, 2);
12589
12590        app.handle_action(Action::PrevItem);
12591        assert_eq!(app.lambda_state.layer_selected, 1);
12592
12593        app.handle_action(Action::PrevItem);
12594        assert_eq!(app.lambda_state.layer_selected, 0);
12595
12596        app.handle_action(Action::PrevItem);
12597        assert_eq!(app.lambda_state.layer_selected, 0);
12598    }
12599
12600    #[test]
12601    fn test_lambda_layer_expansion() {
12602        use crate::lambda::{Function, Layer};
12603
12604        let mut app = test_app();
12605        app.current_service = Service::LambdaFunctions;
12606        app.service_selected = true;
12607        app.lambda_state.current_function = Some("test-function".to_string());
12608        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12609        app.mode = Mode::Normal;
12610
12611        app.lambda_state.table.items = vec![Function {
12612            name: "test-function".to_string(),
12613            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
12614            application: None,
12615            description: "Test".to_string(),
12616            package_type: "Zip".to_string(),
12617            runtime: "python3.12".to_string(),
12618            architecture: "X86_64".to_string(),
12619            code_size: 1024,
12620            code_sha256: "hash".to_string(),
12621            memory_mb: 128,
12622            timeout_seconds: 30,
12623            last_modified: "2024-01-01".to_string(),
12624            layers: vec![Layer {
12625                merge_order: "1".to_string(),
12626                name: "test-layer".to_string(),
12627                layer_version: "1".to_string(),
12628                compatible_runtimes: "python3.9".to_string(),
12629                compatible_architectures: "x86_64".to_string(),
12630                version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:test-layer:1".to_string(),
12631            }],
12632        }];
12633
12634        assert_eq!(app.lambda_state.layer_expanded, None);
12635
12636        app.handle_action(Action::NextPane);
12637        assert_eq!(app.lambda_state.layer_expanded, Some(0));
12638
12639        app.handle_action(Action::PrevPane);
12640        assert_eq!(app.lambda_state.layer_expanded, None);
12641
12642        app.handle_action(Action::NextPane);
12643        assert_eq!(app.lambda_state.layer_expanded, Some(0));
12644
12645        app.handle_action(Action::NextPane);
12646        assert_eq!(app.lambda_state.layer_expanded, None);
12647    }
12648
12649    #[test]
12650    fn test_lambda_layer_selection_and_expansion_workflow() {
12651        use crate::lambda::{Function, Layer};
12652
12653        let mut app = test_app();
12654        app.current_service = Service::LambdaFunctions;
12655        app.service_selected = true;
12656        app.lambda_state.current_function = Some("test-function".to_string());
12657        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12658        app.mode = Mode::Normal;
12659
12660        app.lambda_state.table.items = vec![Function {
12661            name: "test-function".to_string(),
12662            arn: "arn:aws:lambda:us-east-1:123456789012:function:test-function".to_string(),
12663            application: None,
12664            description: "Test".to_string(),
12665            package_type: "Zip".to_string(),
12666            runtime: "python3.12".to_string(),
12667            architecture: "X86_64".to_string(),
12668            code_size: 1024,
12669            code_sha256: "hash".to_string(),
12670            memory_mb: 128,
12671            timeout_seconds: 30,
12672            last_modified: "2024-01-01".to_string(),
12673            layers: vec![
12674                Layer {
12675                    merge_order: "1".to_string(),
12676                    name: "layer1".to_string(),
12677                    layer_version: "1".to_string(),
12678                    compatible_runtimes: "python3.9".to_string(),
12679                    compatible_architectures: "x86_64".to_string(),
12680                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer1:1".to_string(),
12681                },
12682                Layer {
12683                    merge_order: "2".to_string(),
12684                    name: "layer2".to_string(),
12685                    layer_version: "2".to_string(),
12686                    compatible_runtimes: "python3.9".to_string(),
12687                    compatible_architectures: "x86_64".to_string(),
12688                    version_arn: "arn:aws:lambda:us-east-1:123456789012:layer:layer2:2".to_string(),
12689                },
12690            ],
12691        }];
12692
12693        // Start at layer 0
12694        assert_eq!(app.lambda_state.layer_selected, 0);
12695        assert_eq!(app.lambda_state.layer_expanded, None);
12696
12697        // Expand layer 0
12698        app.handle_action(Action::NextPane);
12699        assert_eq!(app.lambda_state.layer_selected, 0);
12700        assert_eq!(app.lambda_state.layer_expanded, Some(0));
12701
12702        // Navigate to layer 1 while layer 0 is expanded
12703        app.handle_action(Action::NextItem);
12704        assert_eq!(app.lambda_state.layer_selected, 1);
12705        assert_eq!(app.lambda_state.layer_expanded, Some(0)); // Still expanded
12706
12707        // Expand layer 1 (should collapse layer 0 and expand layer 1)
12708        app.handle_action(Action::NextPane);
12709        assert_eq!(app.lambda_state.layer_selected, 1);
12710        assert_eq!(app.lambda_state.layer_expanded, Some(1));
12711
12712        // Collapse layer 1
12713        app.handle_action(Action::PrevPane);
12714        assert_eq!(app.lambda_state.layer_selected, 1);
12715        assert_eq!(app.lambda_state.layer_expanded, None);
12716
12717        // Navigate back to layer 0
12718        app.handle_action(Action::PrevItem);
12719        assert_eq!(app.lambda_state.layer_selected, 0);
12720        assert_eq!(app.lambda_state.layer_expanded, None);
12721    }
12722
12723    #[test]
12724    fn test_backtab_cycles_detail_tabs_backward() {
12725        let mut app = test_app();
12726        app.mode = Mode::Normal;
12727
12728        // Test Lambda detail tabs
12729        app.current_service = Service::LambdaFunctions;
12730        app.service_selected = true;
12731        app.lambda_state.current_function = Some("test-function".to_string());
12732        app.lambda_state.detail_tab = LambdaDetailTab::Code;
12733
12734        app.handle_action(Action::PrevDetailTab);
12735        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Versions);
12736
12737        app.handle_action(Action::PrevDetailTab);
12738        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Aliases);
12739
12740        // Test IAM Roles detail tabs
12741        app.current_service = Service::IamRoles;
12742        app.iam_state.current_role = Some("test-role".to_string());
12743        app.iam_state.role_tab = RoleTab::Permissions;
12744
12745        app.handle_action(Action::PrevDetailTab);
12746        assert_eq!(app.iam_state.role_tab, RoleTab::RevokeSessions);
12747
12748        // Test IAM Users detail tabs
12749        app.current_service = Service::IamUsers;
12750        app.iam_state.current_user = Some("test-user".to_string());
12751        app.iam_state.user_tab = UserTab::Permissions;
12752
12753        app.handle_action(Action::PrevDetailTab);
12754        assert_eq!(app.iam_state.user_tab, UserTab::LastAccessed);
12755
12756        // Test IAM Groups detail tabs
12757        app.current_service = Service::IamUserGroups;
12758        app.iam_state.current_group = Some("test-group".to_string());
12759        app.iam_state.group_tab = GroupTab::Permissions;
12760
12761        app.handle_action(Action::PrevDetailTab);
12762        assert_eq!(app.iam_state.group_tab, GroupTab::Users);
12763
12764        // Test S3 object tabs
12765        app.current_service = Service::S3Buckets;
12766        app.s3_state.current_bucket = Some("test-bucket".to_string());
12767        app.s3_state.object_tab = S3ObjectTab::Properties;
12768
12769        app.handle_action(Action::PrevDetailTab);
12770        assert_eq!(app.s3_state.object_tab, S3ObjectTab::Objects);
12771
12772        // Test ECR repository tabs (Private/Public)
12773        app.current_service = Service::EcrRepositories;
12774        app.ecr_state.current_repository = None;
12775        app.ecr_state.tab = EcrTab::Private;
12776
12777        app.handle_action(Action::PrevDetailTab);
12778        assert_eq!(app.ecr_state.tab, EcrTab::Public);
12779
12780        // Test CloudFormation detail tabs
12781        app.current_service = Service::CloudFormationStacks;
12782        app.cfn_state.current_stack = Some("test-stack".to_string());
12783        app.cfn_state.detail_tab = CfnDetailTab::Resources;
12784    }
12785
12786    #[test]
12787    fn test_cloudformation_status_filter_active() {
12788        use crate::ui::cfn::StatusFilter;
12789        let filter = StatusFilter::Active;
12790        assert!(filter.matches("CREATE_IN_PROGRESS"));
12791        assert!(filter.matches("UPDATE_IN_PROGRESS"));
12792        assert!(!filter.matches("CREATE_COMPLETE"));
12793        assert!(!filter.matches("DELETE_COMPLETE"));
12794        assert!(!filter.matches("CREATE_FAILED"));
12795    }
12796
12797    #[test]
12798    fn test_cloudformation_status_filter_complete() {
12799        use crate::ui::cfn::StatusFilter;
12800        let filter = StatusFilter::Complete;
12801        assert!(filter.matches("CREATE_COMPLETE"));
12802        assert!(filter.matches("UPDATE_COMPLETE"));
12803        assert!(!filter.matches("DELETE_COMPLETE"));
12804        assert!(!filter.matches("CREATE_IN_PROGRESS"));
12805    }
12806
12807    #[test]
12808    fn test_cloudformation_status_filter_failed() {
12809        use crate::ui::cfn::StatusFilter;
12810        let filter = StatusFilter::Failed;
12811        assert!(filter.matches("CREATE_FAILED"));
12812        assert!(filter.matches("UPDATE_FAILED"));
12813        assert!(!filter.matches("CREATE_COMPLETE"));
12814    }
12815
12816    #[test]
12817    fn test_cloudformation_status_filter_deleted() {
12818        use crate::ui::cfn::StatusFilter;
12819        let filter = StatusFilter::Deleted;
12820        assert!(filter.matches("DELETE_COMPLETE"));
12821        assert!(filter.matches("DELETE_IN_PROGRESS"));
12822        assert!(!filter.matches("CREATE_COMPLETE"));
12823    }
12824
12825    #[test]
12826    fn test_cloudformation_status_filter_in_progress() {
12827        use crate::ui::cfn::StatusFilter;
12828        let filter = StatusFilter::InProgress;
12829        assert!(filter.matches("CREATE_IN_PROGRESS"));
12830        assert!(filter.matches("UPDATE_IN_PROGRESS"));
12831        assert!(filter.matches("DELETE_IN_PROGRESS"));
12832        assert!(!filter.matches("CREATE_COMPLETE"));
12833    }
12834
12835    #[test]
12836    fn test_cloudformation_status_filter_cycle() {
12837        use crate::ui::cfn::StatusFilter;
12838        let filter = StatusFilter::All;
12839        assert_eq!(filter.next(), StatusFilter::Active);
12840        assert_eq!(filter.next().next(), StatusFilter::Complete);
12841        assert_eq!(filter.next().next().next(), StatusFilter::Failed);
12842        assert_eq!(filter.next().next().next().next(), StatusFilter::Deleted);
12843        assert_eq!(
12844            filter.next().next().next().next().next(),
12845            StatusFilter::InProgress
12846        );
12847        assert_eq!(
12848            filter.next().next().next().next().next().next(),
12849            StatusFilter::All
12850        );
12851    }
12852
12853    #[test]
12854    fn test_cloudformation_default_columns() {
12855        let app = test_app();
12856        assert_eq!(app.cfn_visible_column_ids.len(), 4);
12857        assert!(app.cfn_visible_column_ids.contains(&CfnColumn::Name.id()));
12858        assert!(app.cfn_visible_column_ids.contains(&CfnColumn::Status.id()));
12859        assert!(app
12860            .cfn_visible_column_ids
12861            .contains(&CfnColumn::CreatedTime.id()));
12862        assert!(app
12863            .cfn_visible_column_ids
12864            .contains(&CfnColumn::Description.id()));
12865    }
12866
12867    #[test]
12868    fn test_cloudformation_all_columns() {
12869        let app = test_app();
12870        assert_eq!(app.cfn_column_ids.len(), 10);
12871    }
12872
12873    #[test]
12874    fn test_cloudformation_filter_by_name() {
12875        use crate::ui::cfn::StatusFilter;
12876        let mut app = test_app();
12877        app.cfn_state.status_filter = StatusFilter::Complete;
12878        app.cfn_state.table.items = vec![
12879            CfnStack {
12880                name: "my-stack".to_string(),
12881                stack_id: "id1".to_string(),
12882                status: "CREATE_COMPLETE".to_string(),
12883                created_time: "2024-01-01".to_string(),
12884                updated_time: String::new(),
12885                deleted_time: String::new(),
12886                drift_status: String::new(),
12887                last_drift_check_time: String::new(),
12888                status_reason: String::new(),
12889                description: String::new(),
12890                detailed_status: String::new(),
12891                root_stack: String::new(),
12892                parent_stack: String::new(),
12893                termination_protection: false,
12894                iam_role: String::new(),
12895                tags: Vec::new(),
12896                stack_policy: String::new(),
12897                rollback_monitoring_time: String::new(),
12898                rollback_alarms: Vec::new(),
12899                notification_arns: Vec::new(),
12900            },
12901            CfnStack {
12902                name: "other-stack".to_string(),
12903                stack_id: "id2".to_string(),
12904                status: "CREATE_COMPLETE".to_string(),
12905                created_time: "2024-01-02".to_string(),
12906                updated_time: String::new(),
12907                deleted_time: String::new(),
12908                drift_status: String::new(),
12909                last_drift_check_time: String::new(),
12910                status_reason: String::new(),
12911                description: String::new(),
12912                detailed_status: String::new(),
12913                root_stack: String::new(),
12914                parent_stack: String::new(),
12915                termination_protection: false,
12916                iam_role: String::new(),
12917                tags: Vec::new(),
12918                stack_policy: String::new(),
12919                rollback_monitoring_time: String::new(),
12920                rollback_alarms: Vec::new(),
12921                notification_arns: Vec::new(),
12922            },
12923        ];
12924
12925        app.cfn_state.table.filter = "my".to_string();
12926        let filtered = app.filtered_cloudformation_stacks();
12927        assert_eq!(filtered.len(), 1);
12928        assert_eq!(filtered[0].name, "my-stack");
12929    }
12930
12931    #[test]
12932    fn test_cloudformation_filter_by_description() {
12933        use crate::ui::cfn::StatusFilter;
12934        let mut app = test_app();
12935        app.cfn_state.status_filter = StatusFilter::Complete;
12936        app.cfn_state.table.items = vec![CfnStack {
12937            name: "stack1".to_string(),
12938            stack_id: "id1".to_string(),
12939            status: "CREATE_COMPLETE".to_string(),
12940            created_time: "2024-01-01".to_string(),
12941            updated_time: String::new(),
12942            deleted_time: String::new(),
12943            drift_status: String::new(),
12944            last_drift_check_time: String::new(),
12945            status_reason: String::new(),
12946            description: "production stack".to_string(),
12947            detailed_status: String::new(),
12948            root_stack: String::new(),
12949            parent_stack: String::new(),
12950            termination_protection: false,
12951            iam_role: String::new(),
12952            tags: Vec::new(),
12953            stack_policy: String::new(),
12954            rollback_monitoring_time: String::new(),
12955            rollback_alarms: Vec::new(),
12956            notification_arns: Vec::new(),
12957        }];
12958
12959        app.cfn_state.table.filter = "production".to_string();
12960        let filtered = app.filtered_cloudformation_stacks();
12961        assert_eq!(filtered.len(), 1);
12962    }
12963
12964    #[test]
12965    fn test_cloudformation_status_filter_applied() {
12966        use crate::ui::cfn::StatusFilter;
12967        let mut app = test_app();
12968        app.cfn_state.table.items = vec![
12969            CfnStack {
12970                name: "complete-stack".to_string(),
12971                stack_id: "id1".to_string(),
12972                status: "CREATE_COMPLETE".to_string(),
12973                created_time: "2024-01-01".to_string(),
12974                updated_time: String::new(),
12975                deleted_time: String::new(),
12976                drift_status: String::new(),
12977                last_drift_check_time: String::new(),
12978                status_reason: String::new(),
12979                description: String::new(),
12980                detailed_status: String::new(),
12981                root_stack: String::new(),
12982                parent_stack: String::new(),
12983                termination_protection: false,
12984                iam_role: String::new(),
12985                tags: Vec::new(),
12986                stack_policy: String::new(),
12987                rollback_monitoring_time: String::new(),
12988                rollback_alarms: Vec::new(),
12989                notification_arns: Vec::new(),
12990            },
12991            CfnStack {
12992                name: "failed-stack".to_string(),
12993                stack_id: "id2".to_string(),
12994                status: "CREATE_FAILED".to_string(),
12995                created_time: "2024-01-02".to_string(),
12996                updated_time: String::new(),
12997                deleted_time: String::new(),
12998                drift_status: String::new(),
12999                last_drift_check_time: String::new(),
13000                status_reason: String::new(),
13001                description: String::new(),
13002                detailed_status: String::new(),
13003                root_stack: String::new(),
13004                parent_stack: String::new(),
13005                termination_protection: false,
13006                iam_role: String::new(),
13007                tags: Vec::new(),
13008                stack_policy: String::new(),
13009                rollback_monitoring_time: String::new(),
13010                rollback_alarms: Vec::new(),
13011                notification_arns: Vec::new(),
13012            },
13013        ];
13014
13015        app.cfn_state.status_filter = StatusFilter::Complete;
13016        let filtered = app.filtered_cloudformation_stacks();
13017        assert_eq!(filtered.len(), 1);
13018        assert_eq!(filtered[0].name, "complete-stack");
13019
13020        app.cfn_state.status_filter = StatusFilter::Failed;
13021        let filtered = app.filtered_cloudformation_stacks();
13022        assert_eq!(filtered.len(), 1);
13023        assert_eq!(filtered[0].name, "failed-stack");
13024    }
13025
13026    #[test]
13027    fn test_cloudformation_default_page_size() {
13028        let app = test_app();
13029        assert_eq!(app.cfn_state.table.page_size, PageSize::Fifty);
13030    }
13031
13032    #[test]
13033    fn test_cloudformation_default_status_filter() {
13034        use crate::ui::cfn::StatusFilter;
13035        let app = test_app();
13036        assert_eq!(app.cfn_state.status_filter, StatusFilter::All);
13037    }
13038
13039    #[test]
13040    fn test_cloudformation_view_nested_default_false() {
13041        let app = test_app();
13042        assert!(!app.cfn_state.view_nested);
13043    }
13044
13045    #[test]
13046    fn test_cloudformation_pagination_hotkeys() {
13047        use crate::ui::cfn::StatusFilter;
13048        let mut app = test_app();
13049        app.current_service = Service::CloudFormationStacks;
13050        app.service_selected = true;
13051        app.cfn_state.status_filter = StatusFilter::All;
13052
13053        // Add 150 stacks
13054        for i in 0..150 {
13055            app.cfn_state.table.items.push(CfnStack {
13056                name: format!("stack-{}", i),
13057                stack_id: format!("id-{}", i),
13058                status: "CREATE_COMPLETE".to_string(),
13059                created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
13060                updated_time: String::new(),
13061                deleted_time: String::new(),
13062                drift_status: String::new(),
13063                last_drift_check_time: String::new(),
13064                status_reason: String::new(),
13065                description: String::new(),
13066                detailed_status: String::new(),
13067                root_stack: String::new(),
13068                parent_stack: String::new(),
13069                termination_protection: false,
13070                iam_role: String::new(),
13071                tags: vec![],
13072                stack_policy: String::new(),
13073                rollback_monitoring_time: String::new(),
13074                rollback_alarms: vec![],
13075                notification_arns: vec![],
13076            });
13077        }
13078
13079        // Go to page 2
13080        app.go_to_page(2);
13081        assert_eq!(app.cfn_state.table.selected, 50);
13082
13083        // Go to page 3
13084        app.go_to_page(3);
13085        assert_eq!(app.cfn_state.table.selected, 100);
13086
13087        // Go to page 1
13088        app.go_to_page(1);
13089        assert_eq!(app.cfn_state.table.selected, 0);
13090    }
13091
13092    #[test]
13093    fn test_cloudformation_tab_cycling_in_filter_mode() {
13094        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
13095        let mut app = test_app();
13096        app.current_service = Service::CloudFormationStacks;
13097        app.service_selected = true;
13098        app.mode = Mode::FilterInput;
13099        app.cfn_state.input_focus = InputFocus::Filter;
13100
13101        // Tab to StatusFilter
13102        app.handle_action(Action::NextFilterFocus);
13103        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
13104
13105        // Tab to ViewNested
13106        app.handle_action(Action::NextFilterFocus);
13107        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
13108
13109        // Tab to Pagination
13110        app.handle_action(Action::NextFilterFocus);
13111        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
13112
13113        // Tab back to Filter
13114        app.handle_action(Action::NextFilterFocus);
13115        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13116    }
13117
13118    #[test]
13119    fn test_cloudformation_timestamp_format_includes_utc() {
13120        let stack = CfnStack {
13121            name: "test-stack".to_string(),
13122            stack_id: "id-123".to_string(),
13123            status: "CREATE_COMPLETE".to_string(),
13124            created_time: "2025-08-07 15:38:02 (UTC)".to_string(),
13125            updated_time: "2025-08-08 10:00:00 (UTC)".to_string(),
13126            deleted_time: String::new(),
13127            drift_status: String::new(),
13128            last_drift_check_time: "2025-08-09 12:00:00 (UTC)".to_string(),
13129            status_reason: String::new(),
13130            description: String::new(),
13131            detailed_status: String::new(),
13132            root_stack: String::new(),
13133            parent_stack: String::new(),
13134            termination_protection: false,
13135            iam_role: String::new(),
13136            tags: vec![],
13137            stack_policy: String::new(),
13138            rollback_monitoring_time: String::new(),
13139            rollback_alarms: vec![],
13140            notification_arns: vec![],
13141        };
13142
13143        assert!(stack.created_time.contains("(UTC)"));
13144        assert!(stack.updated_time.contains("(UTC)"));
13145        assert!(stack.last_drift_check_time.contains("(UTC)"));
13146        assert_eq!(stack.created_time.len(), 25);
13147    }
13148
13149    #[test]
13150    fn test_cloudformation_enter_drills_into_stack_view() {
13151        use crate::ui::cfn::StatusFilter;
13152        let mut app = test_app();
13153        app.current_service = Service::CloudFormationStacks;
13154        app.service_selected = true;
13155        app.mode = Mode::Normal;
13156        app.cfn_state.status_filter = StatusFilter::All;
13157        app.tabs = vec![Tab {
13158            service: Service::CloudFormationStacks,
13159            title: "CloudFormation > Stacks".to_string(),
13160            breadcrumb: "CloudFormation > Stacks".to_string(),
13161        }];
13162        app.current_tab = 0;
13163
13164        app.cfn_state.table.items.push(CfnStack {
13165            name: "test-stack".to_string(),
13166            stack_id: "id-123".to_string(),
13167            status: "CREATE_COMPLETE".to_string(),
13168            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
13169            updated_time: String::new(),
13170            deleted_time: String::new(),
13171            drift_status: String::new(),
13172            last_drift_check_time: String::new(),
13173            status_reason: String::new(),
13174            description: String::new(),
13175            detailed_status: String::new(),
13176            root_stack: String::new(),
13177            parent_stack: String::new(),
13178            termination_protection: false,
13179            iam_role: String::new(),
13180            tags: vec![],
13181            stack_policy: String::new(),
13182            rollback_monitoring_time: String::new(),
13183            rollback_alarms: vec![],
13184            notification_arns: vec![],
13185        });
13186
13187        app.cfn_state.table.reset();
13188        assert_eq!(app.cfn_state.current_stack, None);
13189
13190        // Press Enter - should drill into stack detail view
13191        app.handle_action(Action::Select);
13192        assert_eq!(app.cfn_state.current_stack, Some("test-stack".to_string()));
13193    }
13194
13195    #[test]
13196    fn test_cloudformation_arrow_keys_expand_collapse() {
13197        use crate::ui::cfn::StatusFilter;
13198        let mut app = test_app();
13199        app.current_service = Service::CloudFormationStacks;
13200        app.service_selected = true;
13201        app.mode = Mode::Normal;
13202        app.cfn_state.status_filter = StatusFilter::All;
13203
13204        app.cfn_state.table.items.push(CfnStack {
13205            name: "test-stack".to_string(),
13206            stack_id: "id-123".to_string(),
13207            status: "CREATE_COMPLETE".to_string(),
13208            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
13209            updated_time: String::new(),
13210            deleted_time: String::new(),
13211            drift_status: String::new(),
13212            last_drift_check_time: String::new(),
13213            status_reason: String::new(),
13214            description: String::new(),
13215            detailed_status: String::new(),
13216            root_stack: String::new(),
13217            parent_stack: String::new(),
13218            termination_protection: false,
13219            iam_role: String::new(),
13220            tags: vec![],
13221            stack_policy: String::new(),
13222            rollback_monitoring_time: String::new(),
13223            rollback_alarms: vec![],
13224            notification_arns: vec![],
13225        });
13226
13227        app.cfn_state.table.reset();
13228        assert_eq!(app.cfn_state.table.expanded_item, None);
13229
13230        // Right arrow - should expand
13231        app.handle_action(Action::NextPane);
13232        assert_eq!(app.cfn_state.table.expanded_item, Some(0));
13233
13234        // Left arrow - should collapse
13235        app.handle_action(Action::PrevPane);
13236        assert_eq!(app.cfn_state.table.expanded_item, None);
13237
13238        // Verify current_stack is still None (not drilled in)
13239        assert_eq!(app.cfn_state.current_stack, None);
13240    }
13241
13242    #[test]
13243    fn test_cloudformation_tab_cycling() {
13244        use crate::ui::cfn::{DetailTab, StatusFilter};
13245        let mut app = test_app();
13246        app.current_service = Service::CloudFormationStacks;
13247        app.service_selected = true;
13248        app.mode = Mode::Normal;
13249        app.cfn_state.status_filter = StatusFilter::All;
13250        app.cfn_state.current_stack = Some("test-stack".to_string());
13251
13252        assert_eq!(app.cfn_state.detail_tab, DetailTab::StackInfo);
13253    }
13254
13255    #[test]
13256    fn test_cloudformation_console_url() {
13257        use crate::ui::cfn::{DetailTab, StatusFilter};
13258        let mut app = test_app();
13259        app.current_service = Service::CloudFormationStacks;
13260        app.service_selected = true;
13261        app.cfn_state.status_filter = StatusFilter::All;
13262
13263        app.cfn_state.table.items.push(CfnStack {
13264            name: "test-stack".to_string(),
13265            stack_id: "arn:aws:cloudformation:us-east-1:123456789012:stack/test-stack/abc123"
13266                .to_string(),
13267            status: "CREATE_COMPLETE".to_string(),
13268            created_time: "2025-01-01 00:00:00 (UTC)".to_string(),
13269            updated_time: String::new(),
13270            deleted_time: String::new(),
13271            drift_status: String::new(),
13272            last_drift_check_time: String::new(),
13273            status_reason: String::new(),
13274            description: String::new(),
13275            detailed_status: String::new(),
13276            root_stack: String::new(),
13277            parent_stack: String::new(),
13278            termination_protection: false,
13279            iam_role: String::new(),
13280            tags: vec![],
13281            stack_policy: String::new(),
13282            rollback_monitoring_time: String::new(),
13283            rollback_alarms: vec![],
13284            notification_arns: vec![],
13285        });
13286
13287        app.cfn_state.current_stack = Some("test-stack".to_string());
13288
13289        // Stack info URL
13290        app.cfn_state.detail_tab = DetailTab::StackInfo;
13291        let url = app.get_console_url();
13292        assert!(url.contains("stackinfo"));
13293        assert!(url.contains("arn%3Aaws%3Acloudformation"));
13294
13295        // Events URL
13296        app.cfn_state.detail_tab = DetailTab::Events;
13297        let url = app.get_console_url();
13298        assert!(url.contains("events"));
13299        assert!(url.contains("arn%3Aaws%3Acloudformation"));
13300    }
13301
13302    #[test]
13303    fn test_iam_role_select() {
13304        let mut app = test_app();
13305        app.current_service = Service::IamRoles;
13306        app.service_selected = true;
13307        app.mode = Mode::Normal;
13308
13309        app.iam_state.roles.items = vec![
13310            crate::iam::IamRole {
13311                role_name: "role1".to_string(),
13312                path: "/".to_string(),
13313                trusted_entities: "AWS Service: ec2".to_string(),
13314                last_activity: "-".to_string(),
13315                arn: "arn:aws:iam::123456789012:role/role1".to_string(),
13316                creation_time: "2025-01-01".to_string(),
13317                description: "Test role 1".to_string(),
13318                max_session_duration: Some(3600),
13319            },
13320            crate::iam::IamRole {
13321                role_name: "role2".to_string(),
13322                path: "/".to_string(),
13323                trusted_entities: "AWS Service: lambda".to_string(),
13324                last_activity: "-".to_string(),
13325                arn: "arn:aws:iam::123456789012:role/role2".to_string(),
13326                creation_time: "2025-01-02".to_string(),
13327                description: "Test role 2".to_string(),
13328                max_session_duration: Some(7200),
13329            },
13330        ];
13331
13332        // Select first role
13333        app.iam_state.roles.selected = 0;
13334        app.handle_action(Action::Select);
13335
13336        assert_eq!(
13337            app.iam_state.current_role,
13338            Some("role1".to_string()),
13339            "Should open role detail view"
13340        );
13341        assert_eq!(
13342            app.iam_state.role_tab,
13343            RoleTab::Permissions,
13344            "Should default to Permissions tab"
13345        );
13346    }
13347
13348    #[test]
13349    fn test_iam_role_back_navigation() {
13350        let mut app = test_app();
13351        app.current_service = Service::IamRoles;
13352        app.service_selected = true;
13353        app.iam_state.current_role = Some("test-role".to_string());
13354
13355        app.handle_action(Action::GoBack);
13356
13357        assert_eq!(
13358            app.iam_state.current_role, None,
13359            "Should return to roles list"
13360        );
13361    }
13362
13363    #[test]
13364    fn test_iam_role_tab_navigation() {
13365        let mut app = test_app();
13366        app.current_service = Service::IamRoles;
13367        app.service_selected = true;
13368        app.iam_state.current_role = Some("test-role".to_string());
13369        app.iam_state.role_tab = RoleTab::Permissions;
13370
13371        app.handle_action(Action::NextDetailTab);
13372
13373        assert_eq!(
13374            app.iam_state.role_tab,
13375            RoleTab::TrustRelationships,
13376            "Should move to next tab"
13377        );
13378    }
13379
13380    #[test]
13381    fn test_iam_role_tab_cycle_order() {
13382        let mut app = test_app();
13383        app.current_service = Service::IamRoles;
13384        app.service_selected = true;
13385        app.iam_state.current_role = Some("test-role".to_string());
13386        app.iam_state.role_tab = RoleTab::Permissions;
13387
13388        app.handle_action(Action::NextDetailTab);
13389        assert_eq!(app.iam_state.role_tab, RoleTab::TrustRelationships);
13390
13391        app.handle_action(Action::NextDetailTab);
13392        assert_eq!(app.iam_state.role_tab, RoleTab::Tags);
13393
13394        app.handle_action(Action::NextDetailTab);
13395        assert_eq!(app.iam_state.role_tab, RoleTab::LastAccessed);
13396
13397        app.handle_action(Action::NextDetailTab);
13398        assert_eq!(app.iam_state.role_tab, RoleTab::RevokeSessions);
13399
13400        app.handle_action(Action::NextDetailTab);
13401        assert_eq!(
13402            app.iam_state.role_tab,
13403            RoleTab::Permissions,
13404            "Should cycle back to first tab"
13405        );
13406    }
13407
13408    #[test]
13409    fn test_iam_role_pagination() {
13410        let mut app = test_app();
13411        app.current_service = Service::IamRoles;
13412        app.service_selected = true;
13413        app.iam_state.roles.page_size = crate::common::PageSize::Ten;
13414
13415        app.iam_state.roles.items = (0..25)
13416            .map(|i| crate::iam::IamRole {
13417                role_name: format!("role{}", i),
13418                path: "/".to_string(),
13419                trusted_entities: "AWS Service: ec2".to_string(),
13420                last_activity: "-".to_string(),
13421                arn: format!("arn:aws:iam::123456789012:role/role{}", i),
13422                creation_time: "2025-01-01".to_string(),
13423                description: format!("Test role {}", i),
13424                max_session_duration: Some(3600),
13425            })
13426            .collect();
13427
13428        // Jump to page 2
13429        app.go_to_page(2);
13430
13431        assert_eq!(
13432            app.iam_state.roles.selected, 10,
13433            "Should select first item of page 2"
13434        );
13435        assert_eq!(
13436            app.iam_state.roles.scroll_offset, 10,
13437            "Should update scroll offset"
13438        );
13439    }
13440
13441    #[test]
13442    fn test_tags_table_populated_on_role_detail() {
13443        let mut app = test_app();
13444        app.current_service = Service::IamRoles;
13445        app.service_selected = true;
13446        app.mode = Mode::Normal;
13447        app.iam_state.roles.items = vec![crate::iam::IamRole {
13448            role_name: "TestRole".to_string(),
13449            path: "/".to_string(),
13450            trusted_entities: String::new(),
13451            last_activity: String::new(),
13452            arn: "arn:aws:iam::123456789012:role/TestRole".to_string(),
13453            creation_time: "2025-01-01".to_string(),
13454            description: String::new(),
13455            max_session_duration: Some(3600),
13456        }];
13457
13458        // Manually populate tags to test table rendering
13459        app.iam_state.tags.items = vec![
13460            crate::iam::RoleTag {
13461                key: "Environment".to_string(),
13462                value: "Production".to_string(),
13463            },
13464            crate::iam::RoleTag {
13465                key: "Team".to_string(),
13466                value: "Platform".to_string(),
13467            },
13468        ];
13469
13470        assert_eq!(app.iam_state.tags.items.len(), 2);
13471        assert_eq!(app.iam_state.tags.items[0].key, "Environment");
13472        assert_eq!(app.iam_state.tags.items[0].value, "Production");
13473        assert_eq!(app.iam_state.tags.selected, 0);
13474    }
13475
13476    #[test]
13477    fn test_tags_table_navigation() {
13478        let mut app = test_app();
13479        app.current_service = Service::IamRoles;
13480        app.service_selected = true;
13481        app.mode = Mode::Normal;
13482        app.iam_state.current_role = Some("TestRole".to_string());
13483        app.iam_state.role_tab = RoleTab::Tags;
13484        app.iam_state.tags.items = vec![
13485            crate::iam::RoleTag {
13486                key: "Tag1".to_string(),
13487                value: "Value1".to_string(),
13488            },
13489            crate::iam::RoleTag {
13490                key: "Tag2".to_string(),
13491                value: "Value2".to_string(),
13492            },
13493        ];
13494
13495        app.handle_action(Action::NextItem);
13496        assert_eq!(app.iam_state.tags.selected, 1);
13497
13498        app.handle_action(Action::PrevItem);
13499        assert_eq!(app.iam_state.tags.selected, 0);
13500    }
13501
13502    #[test]
13503    fn test_last_accessed_table_navigation() {
13504        let mut app = test_app();
13505        app.current_service = Service::IamRoles;
13506        app.service_selected = true;
13507        app.mode = Mode::Normal;
13508        app.iam_state.current_role = Some("TestRole".to_string());
13509        app.iam_state.role_tab = RoleTab::LastAccessed;
13510        app.iam_state.last_accessed_services.items = vec![
13511            crate::iam::LastAccessedService {
13512                service: "S3".to_string(),
13513                policies_granting: "Policy1".to_string(),
13514                last_accessed: "2025-01-01".to_string(),
13515            },
13516            crate::iam::LastAccessedService {
13517                service: "EC2".to_string(),
13518                policies_granting: "Policy2".to_string(),
13519                last_accessed: "2025-01-02".to_string(),
13520            },
13521        ];
13522
13523        app.handle_action(Action::NextItem);
13524        assert_eq!(app.iam_state.last_accessed_services.selected, 1);
13525
13526        app.handle_action(Action::PrevItem);
13527        assert_eq!(app.iam_state.last_accessed_services.selected, 0);
13528    }
13529
13530    #[test]
13531    fn test_cfn_input_focus_next() {
13532        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
13533        let mut app = test_app();
13534        app.current_service = Service::CloudFormationStacks;
13535        app.mode = Mode::FilterInput;
13536        app.cfn_state.input_focus = InputFocus::Filter;
13537
13538        app.handle_action(Action::NextFilterFocus);
13539        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
13540
13541        app.handle_action(Action::NextFilterFocus);
13542        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
13543
13544        app.handle_action(Action::NextFilterFocus);
13545        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
13546
13547        app.handle_action(Action::NextFilterFocus);
13548        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13549    }
13550
13551    #[test]
13552    fn test_cfn_input_focus_prev() {
13553        use crate::ui::cfn::{STATUS_FILTER, VIEW_NESTED};
13554        let mut app = test_app();
13555        app.current_service = Service::CloudFormationStacks;
13556        app.mode = Mode::FilterInput;
13557        app.cfn_state.input_focus = InputFocus::Filter;
13558
13559        app.handle_action(Action::PrevFilterFocus);
13560        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
13561
13562        app.handle_action(Action::PrevFilterFocus);
13563        assert_eq!(app.cfn_state.input_focus, VIEW_NESTED);
13564
13565        app.handle_action(Action::PrevFilterFocus);
13566        assert_eq!(app.cfn_state.input_focus, STATUS_FILTER);
13567
13568        app.handle_action(Action::PrevFilterFocus);
13569        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13570    }
13571
13572    #[test]
13573    fn test_cw_logs_input_focus_prev() {
13574        let mut app = test_app();
13575        app.current_service = Service::CloudWatchLogGroups;
13576        app.mode = Mode::FilterInput;
13577        app.view_mode = ViewMode::Detail;
13578        app.log_groups_state.detail_tab = crate::ui::cw::logs::DetailTab::LogStreams;
13579        app.log_groups_state.input_focus = InputFocus::Filter;
13580
13581        app.handle_action(Action::PrevFilterFocus);
13582        assert_eq!(app.log_groups_state.input_focus, InputFocus::Pagination);
13583
13584        app.handle_action(Action::PrevFilterFocus);
13585        assert_eq!(
13586            app.log_groups_state.input_focus,
13587            InputFocus::Checkbox("ShowExpired")
13588        );
13589
13590        app.handle_action(Action::PrevFilterFocus);
13591        assert_eq!(
13592            app.log_groups_state.input_focus,
13593            InputFocus::Checkbox("ExactMatch")
13594        );
13595
13596        app.handle_action(Action::PrevFilterFocus);
13597        assert_eq!(app.log_groups_state.input_focus, InputFocus::Filter);
13598    }
13599
13600    #[test]
13601    fn test_cw_events_input_focus_prev() {
13602        use crate::ui::cw::logs::EventFilterFocus;
13603        let mut app = test_app();
13604        app.mode = Mode::EventFilterInput;
13605        app.log_groups_state.event_input_focus = EventFilterFocus::Filter;
13606
13607        app.handle_action(Action::PrevFilterFocus);
13608        assert_eq!(
13609            app.log_groups_state.event_input_focus,
13610            EventFilterFocus::DateRange
13611        );
13612
13613        app.handle_action(Action::PrevFilterFocus);
13614        assert_eq!(
13615            app.log_groups_state.event_input_focus,
13616            EventFilterFocus::Filter
13617        );
13618    }
13619
13620    #[test]
13621    fn test_cfn_input_focus_cycle_complete() {
13622        let mut app = test_app();
13623        app.current_service = Service::CloudFormationStacks;
13624        app.mode = Mode::FilterInput;
13625        app.cfn_state.input_focus = InputFocus::Filter;
13626
13627        // Cycle forward through all controls
13628        for _ in 0..4 {
13629            app.handle_action(Action::NextFilterFocus);
13630        }
13631        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13632
13633        // Cycle backward through all controls
13634        for _ in 0..4 {
13635            app.handle_action(Action::PrevFilterFocus);
13636        }
13637        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13638    }
13639
13640    #[test]
13641    fn test_cfn_filter_status_arrow_keys() {
13642        use crate::ui::cfn::{StatusFilter, STATUS_FILTER};
13643        let mut app = test_app();
13644        app.current_service = Service::CloudFormationStacks;
13645        app.mode = Mode::FilterInput;
13646        app.cfn_state.input_focus = STATUS_FILTER;
13647        app.cfn_state.status_filter = StatusFilter::All;
13648
13649        app.handle_action(Action::NextItem);
13650        assert_eq!(app.cfn_state.status_filter, StatusFilter::Active);
13651
13652        app.handle_action(Action::PrevItem);
13653        assert_eq!(app.cfn_state.status_filter, StatusFilter::All);
13654    }
13655
13656    #[test]
13657    fn test_cfn_filter_shift_tab_cycles_backward() {
13658        use crate::ui::cfn::STATUS_FILTER;
13659        let mut app = test_app();
13660        app.current_service = Service::CloudFormationStacks;
13661        app.mode = Mode::FilterInput;
13662        app.cfn_state.input_focus = STATUS_FILTER;
13663
13664        // Shift+Tab should go backward
13665        app.handle_action(Action::PrevFilterFocus);
13666        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13667
13668        // From Input, Shift+Tab should wrap to Pagination
13669        app.handle_action(Action::PrevFilterFocus);
13670        assert_eq!(app.cfn_state.input_focus, InputFocus::Pagination);
13671    }
13672
13673    #[test]
13674    fn test_cfn_pagination_arrow_keys() {
13675        let mut app = test_app();
13676        app.current_service = Service::CloudFormationStacks;
13677        app.mode = Mode::FilterInput;
13678        app.cfn_state.input_focus = InputFocus::Pagination;
13679        app.cfn_state.table.scroll_offset = 0;
13680        app.cfn_state.table.page_size = crate::common::PageSize::Ten;
13681
13682        // Add some test stacks
13683        app.cfn_state.table.items = (0..30)
13684            .map(|i| crate::cfn::Stack {
13685                name: format!("stack-{}", i),
13686                stack_id: format!("id-{}", i),
13687                status: "CREATE_COMPLETE".to_string(),
13688                created_time: "2024-01-01".to_string(),
13689                updated_time: String::new(),
13690                deleted_time: String::new(),
13691                drift_status: String::new(),
13692                last_drift_check_time: String::new(),
13693                status_reason: String::new(),
13694                description: String::new(),
13695                detailed_status: String::new(),
13696                root_stack: String::new(),
13697                parent_stack: String::new(),
13698                termination_protection: false,
13699                iam_role: String::new(),
13700                tags: Vec::new(),
13701                stack_policy: String::new(),
13702                rollback_monitoring_time: String::new(),
13703                rollback_alarms: Vec::new(),
13704                notification_arns: Vec::new(),
13705            })
13706            .collect();
13707
13708        // Right arrow should page forward
13709        app.handle_action(Action::PageDown);
13710        assert_eq!(app.cfn_state.table.scroll_offset, 10);
13711        // Verify page number calculation
13712        let page_size = app.cfn_state.table.page_size.value();
13713        let current_page = app.cfn_state.table.scroll_offset / page_size;
13714        assert_eq!(current_page, 1);
13715
13716        // Left arrow should page backward
13717        app.handle_action(Action::PageUp);
13718        assert_eq!(app.cfn_state.table.scroll_offset, 0);
13719        let current_page = app.cfn_state.table.scroll_offset / page_size;
13720        assert_eq!(current_page, 0);
13721    }
13722
13723    #[test]
13724    fn test_cfn_page_navigation_updates_selection() {
13725        let mut app = test_app();
13726        app.current_service = Service::CloudFormationStacks;
13727        app.mode = Mode::Normal;
13728
13729        // Add 30 test stacks
13730        app.cfn_state.table.items = (0..30)
13731            .map(|i| crate::cfn::Stack {
13732                name: format!("stack-{}", i),
13733                stack_id: format!("id-{}", i),
13734                status: "CREATE_COMPLETE".to_string(),
13735                created_time: "2024-01-01".to_string(),
13736                updated_time: String::new(),
13737                deleted_time: String::new(),
13738                drift_status: String::new(),
13739                last_drift_check_time: String::new(),
13740                status_reason: String::new(),
13741                description: String::new(),
13742                detailed_status: String::new(),
13743                root_stack: String::new(),
13744                parent_stack: String::new(),
13745                termination_protection: false,
13746                iam_role: String::new(),
13747                tags: Vec::new(),
13748                stack_policy: String::new(),
13749                rollback_monitoring_time: String::new(),
13750                rollback_alarms: Vec::new(),
13751                notification_arns: Vec::new(),
13752            })
13753            .collect();
13754
13755        app.cfn_state.table.reset();
13756        app.cfn_state.table.scroll_offset = 0;
13757
13758        // Page down should update selection
13759        app.handle_action(Action::PageDown);
13760        assert_eq!(app.cfn_state.table.selected, 10);
13761
13762        // Page down again
13763        app.handle_action(Action::PageDown);
13764        assert_eq!(app.cfn_state.table.selected, 20);
13765
13766        // Page up should update selection
13767        app.handle_action(Action::PageUp);
13768        assert_eq!(app.cfn_state.table.selected, 10);
13769    }
13770
13771    #[test]
13772    fn test_cfn_filter_input_only_when_focused() {
13773        use crate::ui::cfn::STATUS_FILTER;
13774        let mut app = test_app();
13775        app.current_service = Service::CloudFormationStacks;
13776        app.mode = Mode::FilterInput;
13777        app.cfn_state.input_focus = STATUS_FILTER;
13778        app.cfn_state.table.filter = String::new();
13779
13780        // Typing should not add to filter when focus is not on Input
13781        app.handle_action(Action::FilterInput('t'));
13782        app.handle_action(Action::FilterInput('e'));
13783        app.handle_action(Action::FilterInput('s'));
13784        app.handle_action(Action::FilterInput('t'));
13785        assert_eq!(app.cfn_state.table.filter, "");
13786
13787        // Switch to Input focus
13788        app.cfn_state.input_focus = InputFocus::Filter;
13789        app.handle_action(Action::FilterInput('t'));
13790        app.handle_action(Action::FilterInput('e'));
13791        app.handle_action(Action::FilterInput('s'));
13792        app.handle_action(Action::FilterInput('t'));
13793        assert_eq!(app.cfn_state.table.filter, "test");
13794    }
13795
13796    #[test]
13797    fn test_cfn_input_focus_resets_on_start() {
13798        let mut app = test_app();
13799        app.current_service = Service::CloudFormationStacks;
13800        app.service_selected = true;
13801        app.mode = Mode::Normal;
13802        app.cfn_state.input_focus = InputFocus::Pagination;
13803
13804        // Start filter should reset focus to Input
13805        app.handle_action(Action::StartFilter);
13806        assert_eq!(app.mode, Mode::FilterInput);
13807        assert_eq!(app.cfn_state.input_focus, InputFocus::Filter);
13808    }
13809
13810    #[test]
13811    fn test_iam_roles_input_focus_cycles_forward() {
13812        let mut app = test_app();
13813        app.current_service = Service::IamRoles;
13814        app.mode = Mode::FilterInput;
13815        app.iam_state.role_input_focus = InputFocus::Filter;
13816
13817        app.handle_action(Action::NextFilterFocus);
13818        assert_eq!(app.iam_state.role_input_focus, InputFocus::Pagination);
13819
13820        app.handle_action(Action::NextFilterFocus);
13821        assert_eq!(app.iam_state.role_input_focus, InputFocus::Filter);
13822    }
13823
13824    #[test]
13825    fn test_iam_roles_input_focus_cycles_backward() {
13826        let mut app = test_app();
13827        app.current_service = Service::IamRoles;
13828        app.mode = Mode::FilterInput;
13829        app.iam_state.role_input_focus = InputFocus::Filter;
13830
13831        app.handle_action(Action::PrevFilterFocus);
13832        assert_eq!(app.iam_state.role_input_focus, InputFocus::Pagination);
13833
13834        app.handle_action(Action::PrevFilterFocus);
13835        assert_eq!(app.iam_state.role_input_focus, InputFocus::Filter);
13836    }
13837
13838    #[test]
13839    fn test_iam_roles_filter_input_only_when_focused() {
13840        let mut app = test_app();
13841        app.current_service = Service::IamRoles;
13842        app.mode = Mode::FilterInput;
13843        app.iam_state.role_input_focus = InputFocus::Pagination;
13844        app.iam_state.roles.filter = String::new();
13845
13846        // Typing should not add to filter when focus is on Pagination
13847        app.handle_action(Action::FilterInput('t'));
13848        app.handle_action(Action::FilterInput('e'));
13849        app.handle_action(Action::FilterInput('s'));
13850        app.handle_action(Action::FilterInput('t'));
13851        assert_eq!(app.iam_state.roles.filter, "");
13852
13853        // Switch to Input focus
13854        app.iam_state.role_input_focus = InputFocus::Filter;
13855        app.handle_action(Action::FilterInput('t'));
13856        app.handle_action(Action::FilterInput('e'));
13857        app.handle_action(Action::FilterInput('s'));
13858        app.handle_action(Action::FilterInput('t'));
13859        assert_eq!(app.iam_state.roles.filter, "test");
13860    }
13861
13862    #[test]
13863    fn test_iam_roles_page_down_updates_scroll_offset() {
13864        let mut app = test_app();
13865        app.current_service = Service::IamRoles;
13866        app.mode = Mode::Normal;
13867        app.iam_state.roles.items = (0..50)
13868            .map(|i| crate::iam::IamRole {
13869                role_name: format!("role-{}", i),
13870                path: "/".to_string(),
13871                trusted_entities: "AWS Service".to_string(),
13872                last_activity: "N/A".to_string(),
13873                arn: format!("arn:aws:iam::123456789012:role/role-{}", i),
13874                creation_time: "2024-01-01".to_string(),
13875                description: String::new(),
13876                max_session_duration: Some(3600),
13877            })
13878            .collect();
13879
13880        app.iam_state.roles.selected = 0;
13881        app.iam_state.roles.scroll_offset = 0;
13882
13883        // Page down should update both selected and scroll_offset
13884        app.handle_action(Action::PageDown);
13885        assert_eq!(app.iam_state.roles.selected, 10);
13886        // scroll_offset should be updated to keep selection visible
13887        assert!(app.iam_state.roles.scroll_offset <= app.iam_state.roles.selected);
13888
13889        // Page down again
13890        app.handle_action(Action::PageDown);
13891        assert_eq!(app.iam_state.roles.selected, 20);
13892        assert!(app.iam_state.roles.scroll_offset <= app.iam_state.roles.selected);
13893    }
13894
13895    #[test]
13896    fn test_application_selection_and_deployments_tab() {
13897        use crate::lambda::Application as LambdaApplication;
13898        use LambdaApplicationDetailTab;
13899
13900        let mut app = test_app();
13901        app.current_service = Service::LambdaApplications;
13902        app.service_selected = true;
13903        app.mode = Mode::Normal;
13904
13905        app.lambda_application_state.table.items = vec![LambdaApplication {
13906            name: "test-app".to_string(),
13907            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
13908            description: "Test application".to_string(),
13909            status: "CREATE_COMPLETE".to_string(),
13910            last_modified: "2024-01-01".to_string(),
13911        }];
13912
13913        // Select application
13914        app.handle_action(Action::Select);
13915        assert_eq!(
13916            app.lambda_application_state.current_application,
13917            Some("test-app".to_string())
13918        );
13919        assert_eq!(
13920            app.lambda_application_state.detail_tab,
13921            LambdaApplicationDetailTab::Overview
13922        );
13923
13924        // Switch to Deployments tab
13925        app.handle_action(Action::NextDetailTab);
13926        assert_eq!(
13927            app.lambda_application_state.detail_tab,
13928            LambdaApplicationDetailTab::Deployments
13929        );
13930
13931        // Go back
13932        app.handle_action(Action::GoBack);
13933        assert_eq!(app.lambda_application_state.current_application, None);
13934    }
13935
13936    #[test]
13937    fn test_application_resources_filter_and_pagination() {
13938        use crate::lambda::Application as LambdaApplication;
13939        use LambdaApplicationDetailTab;
13940
13941        let mut app = test_app();
13942        app.current_service = Service::LambdaApplications;
13943        app.service_selected = true;
13944        app.mode = Mode::Normal;
13945
13946        app.lambda_application_state.table.items = vec![LambdaApplication {
13947            name: "test-app".to_string(),
13948            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
13949            description: "Test application".to_string(),
13950            status: "CREATE_COMPLETE".to_string(),
13951            last_modified: "2024-01-01".to_string(),
13952        }];
13953
13954        // Select application
13955        app.handle_action(Action::Select);
13956        assert_eq!(
13957            app.lambda_application_state.detail_tab,
13958            LambdaApplicationDetailTab::Overview
13959        );
13960
13961        // Verify resources were loaded
13962        assert!(!app.lambda_application_state.resources.items.is_empty());
13963
13964        // Test filter focus cycling
13965        app.mode = Mode::FilterInput;
13966        assert_eq!(
13967            app.lambda_application_state.resource_input_focus,
13968            InputFocus::Filter
13969        );
13970
13971        app.handle_action(Action::NextFilterFocus);
13972        assert_eq!(
13973            app.lambda_application_state.resource_input_focus,
13974            InputFocus::Pagination
13975        );
13976
13977        app.handle_action(Action::PrevFilterFocus);
13978        assert_eq!(
13979            app.lambda_application_state.resource_input_focus,
13980            InputFocus::Filter
13981        );
13982    }
13983
13984    #[test]
13985    fn test_application_deployments_filter_and_pagination() {
13986        use crate::lambda::Application as LambdaApplication;
13987        use LambdaApplicationDetailTab;
13988
13989        let mut app = test_app();
13990        app.current_service = Service::LambdaApplications;
13991        app.service_selected = true;
13992        app.mode = Mode::Normal;
13993
13994        app.lambda_application_state.table.items = vec![LambdaApplication {
13995            name: "test-app".to_string(),
13996            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
13997            description: "Test application".to_string(),
13998            status: "CREATE_COMPLETE".to_string(),
13999            last_modified: "2024-01-01".to_string(),
14000        }];
14001
14002        // Select application and switch to Deployments tab
14003        app.handle_action(Action::Select);
14004        app.handle_action(Action::NextDetailTab);
14005        assert_eq!(
14006            app.lambda_application_state.detail_tab,
14007            LambdaApplicationDetailTab::Deployments
14008        );
14009
14010        // Verify deployments were loaded
14011        assert!(!app.lambda_application_state.deployments.items.is_empty());
14012
14013        // Test filter focus cycling
14014        app.mode = Mode::FilterInput;
14015        assert_eq!(
14016            app.lambda_application_state.deployment_input_focus,
14017            InputFocus::Filter
14018        );
14019
14020        app.handle_action(Action::NextFilterFocus);
14021        assert_eq!(
14022            app.lambda_application_state.deployment_input_focus,
14023            InputFocus::Pagination
14024        );
14025
14026        app.handle_action(Action::PrevFilterFocus);
14027        assert_eq!(
14028            app.lambda_application_state.deployment_input_focus,
14029            InputFocus::Filter
14030        );
14031    }
14032
14033    #[test]
14034    fn test_application_resource_expansion() {
14035        use crate::lambda::Application as LambdaApplication;
14036        use LambdaApplicationDetailTab;
14037
14038        let mut app = test_app();
14039        app.current_service = Service::LambdaApplications;
14040        app.service_selected = true;
14041        app.mode = Mode::Normal;
14042
14043        app.lambda_application_state.table.items = vec![LambdaApplication {
14044            name: "test-app".to_string(),
14045            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
14046            description: "Test application".to_string(),
14047            status: "CREATE_COMPLETE".to_string(),
14048            last_modified: "2024-01-01".to_string(),
14049        }];
14050
14051        // Select application (Overview tab by default)
14052        app.handle_action(Action::Select);
14053        assert_eq!(
14054            app.lambda_application_state.detail_tab,
14055            LambdaApplicationDetailTab::Overview
14056        );
14057
14058        // Expand resource
14059        app.handle_action(Action::NextPane);
14060        assert_eq!(
14061            app.lambda_application_state.resources.expanded_item,
14062            Some(0)
14063        );
14064
14065        // Collapse resource
14066        app.handle_action(Action::PrevPane);
14067        assert_eq!(app.lambda_application_state.resources.expanded_item, None);
14068    }
14069
14070    #[test]
14071    fn test_application_deployment_expansion() {
14072        use crate::lambda::Application as LambdaApplication;
14073        use LambdaApplicationDetailTab;
14074
14075        let mut app = test_app();
14076        app.current_service = Service::LambdaApplications;
14077        app.service_selected = true;
14078        app.mode = Mode::Normal;
14079
14080        app.lambda_application_state.table.items = vec![LambdaApplication {
14081            name: "test-app".to_string(),
14082            arn: "arn:aws:serverlessrepo:::applications/test-app".to_string(),
14083            description: "Test application".to_string(),
14084            status: "CREATE_COMPLETE".to_string(),
14085            last_modified: "2024-01-01".to_string(),
14086        }];
14087
14088        // Select application and switch to Deployments tab
14089        app.handle_action(Action::Select);
14090        app.handle_action(Action::NextDetailTab);
14091        assert_eq!(
14092            app.lambda_application_state.detail_tab,
14093            LambdaApplicationDetailTab::Deployments
14094        );
14095
14096        // Expand deployment
14097        app.handle_action(Action::NextPane);
14098        assert_eq!(
14099            app.lambda_application_state.deployments.expanded_item,
14100            Some(0)
14101        );
14102
14103        // Collapse deployment
14104        app.handle_action(Action::PrevPane);
14105        assert_eq!(app.lambda_application_state.deployments.expanded_item, None);
14106    }
14107
14108    #[test]
14109    fn test_s3_nested_prefix_expansion() {
14110        use crate::s3::Bucket;
14111        use crate::s3::Object as S3Object;
14112
14113        let mut app = test_app();
14114        app.current_service = Service::S3Buckets;
14115        app.service_selected = true;
14116        app.mode = Mode::Normal;
14117
14118        // Setup bucket with nested prefixes (2 levels)
14119        app.s3_state.buckets.items = vec![Bucket {
14120            name: "test-bucket".to_string(),
14121            region: "us-east-1".to_string(),
14122            creation_date: "2024-01-01".to_string(),
14123        }];
14124
14125        // Level 1: bucket preview
14126        app.s3_state.bucket_preview.insert(
14127            "test-bucket".to_string(),
14128            vec![S3Object {
14129                key: "level1/".to_string(),
14130                size: 0,
14131                last_modified: "".to_string(),
14132                is_prefix: true,
14133                storage_class: "".to_string(),
14134            }],
14135        );
14136
14137        // Level 2: nested prefix
14138        app.s3_state.prefix_preview.insert(
14139            "level1/".to_string(),
14140            vec![S3Object {
14141                key: "level1/level2/".to_string(),
14142                size: 0,
14143                last_modified: "".to_string(),
14144                is_prefix: true,
14145                storage_class: "".to_string(),
14146            }],
14147        );
14148
14149        // Expand bucket (row 0)
14150        app.s3_state.selected_row = 0;
14151        app.handle_action(Action::NextPane);
14152        assert!(app.s3_state.expanded_prefixes.contains("test-bucket"));
14153
14154        // Expand level1/ (row 1)
14155        app.s3_state.selected_row = 1;
14156        app.handle_action(Action::NextPane);
14157        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
14158
14159        // Expand level2/ (row 2) - verifies nested expansion works
14160        app.s3_state.selected_row = 2;
14161        app.handle_action(Action::NextPane);
14162        assert!(app.s3_state.expanded_prefixes.contains("level1/level2/"));
14163
14164        // Verify all are still expanded
14165        assert!(app.s3_state.expanded_prefixes.contains("test-bucket"));
14166        assert!(app.s3_state.expanded_prefixes.contains("level1/"));
14167    }
14168
14169    #[test]
14170    fn test_s3_nested_prefix_collapse() {
14171        use crate::s3::Bucket;
14172        use crate::s3::Object as S3Object;
14173
14174        let mut app = test_app();
14175        app.current_service = Service::S3Buckets;
14176        app.service_selected = true;
14177        app.mode = Mode::Normal;
14178
14179        app.s3_state.buckets.items = vec![Bucket {
14180            name: "test-bucket".to_string(),
14181            region: "us-east-1".to_string(),
14182            creation_date: "2024-01-01".to_string(),
14183        }];
14184
14185        app.s3_state.bucket_preview.insert(
14186            "test-bucket".to_string(),
14187            vec![S3Object {
14188                key: "level1/".to_string(),
14189                size: 0,
14190                last_modified: "".to_string(),
14191                is_prefix: true,
14192                storage_class: "".to_string(),
14193            }],
14194        );
14195
14196        app.s3_state.prefix_preview.insert(
14197            "level1/".to_string(),
14198            vec![S3Object {
14199                key: "level1/level2/".to_string(),
14200                size: 0,
14201                last_modified: "".to_string(),
14202                is_prefix: true,
14203                storage_class: "".to_string(),
14204            }],
14205        );
14206
14207        // Pre-expand all levels
14208        app.s3_state
14209            .expanded_prefixes
14210            .insert("test-bucket".to_string());
14211        app.s3_state.expanded_prefixes.insert("level1/".to_string());
14212        app.s3_state
14213            .expanded_prefixes
14214            .insert("level1/level2/".to_string());
14215
14216        // Collapse level2/ (row 2)
14217        app.s3_state.selected_row = 2;
14218        app.handle_action(Action::PrevPane);
14219        assert!(!app.s3_state.expanded_prefixes.contains("level1/level2/"));
14220        assert!(app.s3_state.expanded_prefixes.contains("level1/")); // Parent still expanded
14221
14222        // Collapse level1/ (row 1)
14223        app.s3_state.selected_row = 1;
14224        app.handle_action(Action::PrevPane);
14225        assert!(!app.s3_state.expanded_prefixes.contains("level1/"));
14226        assert!(app.s3_state.expanded_prefixes.contains("test-bucket")); // Bucket still expanded
14227
14228        // Collapse bucket (row 0)
14229        app.s3_state.selected_row = 0;
14230        app.handle_action(Action::PrevPane);
14231        assert!(!app.s3_state.expanded_prefixes.contains("test-bucket"));
14232    }
14233}
14234
14235#[cfg(test)]
14236mod sqs_tests {
14237    use super::*;
14238    use test_helpers::*;
14239
14240    #[test]
14241    fn test_sqs_filter_input() {
14242        let mut app = test_app();
14243        app.current_service = Service::SqsQueues;
14244        app.service_selected = true;
14245        app.mode = Mode::FilterInput;
14246
14247        app.handle_action(Action::FilterInput('t'));
14248        app.handle_action(Action::FilterInput('e'));
14249        app.handle_action(Action::FilterInput('s'));
14250        app.handle_action(Action::FilterInput('t'));
14251        assert_eq!(app.sqs_state.queues.filter, "test");
14252
14253        app.handle_action(Action::FilterBackspace);
14254        assert_eq!(app.sqs_state.queues.filter, "tes");
14255    }
14256
14257    #[test]
14258    fn test_sqs_start_filter() {
14259        let mut app = test_app();
14260        app.current_service = Service::SqsQueues;
14261        app.service_selected = true;
14262        app.mode = Mode::Normal;
14263
14264        app.handle_action(Action::StartFilter);
14265        assert_eq!(app.mode, Mode::FilterInput);
14266        assert_eq!(app.sqs_state.input_focus, InputFocus::Filter);
14267    }
14268
14269    #[test]
14270    fn test_sqs_filter_focus_cycling() {
14271        let mut app = test_app();
14272        app.current_service = Service::SqsQueues;
14273        app.service_selected = true;
14274        app.mode = Mode::FilterInput;
14275        app.sqs_state.input_focus = InputFocus::Filter;
14276
14277        app.handle_action(Action::NextFilterFocus);
14278        assert_eq!(app.sqs_state.input_focus, InputFocus::Pagination);
14279
14280        app.handle_action(Action::NextFilterFocus);
14281        assert_eq!(app.sqs_state.input_focus, InputFocus::Filter);
14282
14283        app.handle_action(Action::PrevFilterFocus);
14284        assert_eq!(app.sqs_state.input_focus, InputFocus::Pagination);
14285    }
14286
14287    #[test]
14288    fn test_sqs_navigation() {
14289        let mut app = test_app();
14290        app.current_service = Service::SqsQueues;
14291        app.service_selected = true;
14292        app.mode = Mode::Normal;
14293        app.sqs_state.queues.items = (0..10)
14294            .map(|i| crate::sqs::Queue {
14295                name: format!("queue{}", i),
14296                url: String::new(),
14297                queue_type: "Standard".to_string(),
14298                created_timestamp: String::new(),
14299                messages_available: "0".to_string(),
14300                messages_in_flight: "0".to_string(),
14301                encryption: "Disabled".to_string(),
14302                content_based_deduplication: "Disabled".to_string(),
14303                last_modified_timestamp: String::new(),
14304                visibility_timeout: String::new(),
14305                message_retention_period: String::new(),
14306                maximum_message_size: String::new(),
14307                delivery_delay: String::new(),
14308                receive_message_wait_time: String::new(),
14309                high_throughput_fifo: "N/A".to_string(),
14310                deduplication_scope: "N/A".to_string(),
14311                fifo_throughput_limit: "N/A".to_string(),
14312                dead_letter_queue: "-".to_string(),
14313                messages_delayed: "0".to_string(),
14314                redrive_allow_policy: "-".to_string(),
14315                redrive_policy: "".to_string(),
14316                redrive_task_id: "-".to_string(),
14317                redrive_task_start_time: "-".to_string(),
14318                redrive_task_status: "-".to_string(),
14319                redrive_task_percent: "-".to_string(),
14320                redrive_task_destination: "-".to_string(),
14321            })
14322            .collect();
14323
14324        app.handle_action(Action::NextItem);
14325        assert_eq!(app.sqs_state.queues.selected, 1);
14326
14327        app.handle_action(Action::PrevItem);
14328        assert_eq!(app.sqs_state.queues.selected, 0);
14329    }
14330
14331    #[test]
14332    fn test_sqs_page_navigation() {
14333        let mut app = test_app();
14334        app.current_service = Service::SqsQueues;
14335        app.service_selected = true;
14336        app.mode = Mode::Normal;
14337        app.sqs_state.queues.items = (0..100)
14338            .map(|i| crate::sqs::Queue {
14339                name: format!("queue{}", i),
14340                url: String::new(),
14341                queue_type: "Standard".to_string(),
14342                created_timestamp: String::new(),
14343                messages_available: "0".to_string(),
14344                messages_in_flight: "0".to_string(),
14345                encryption: "Disabled".to_string(),
14346                content_based_deduplication: "Disabled".to_string(),
14347                last_modified_timestamp: String::new(),
14348                visibility_timeout: String::new(),
14349                message_retention_period: String::new(),
14350                maximum_message_size: String::new(),
14351                delivery_delay: String::new(),
14352                receive_message_wait_time: String::new(),
14353                high_throughput_fifo: "N/A".to_string(),
14354                deduplication_scope: "N/A".to_string(),
14355                fifo_throughput_limit: "N/A".to_string(),
14356                dead_letter_queue: "-".to_string(),
14357                messages_delayed: "0".to_string(),
14358                redrive_allow_policy: "-".to_string(),
14359                redrive_policy: "".to_string(),
14360                redrive_task_id: "-".to_string(),
14361                redrive_task_start_time: "-".to_string(),
14362                redrive_task_status: "-".to_string(),
14363                redrive_task_percent: "-".to_string(),
14364                redrive_task_destination: "-".to_string(),
14365            })
14366            .collect();
14367
14368        app.handle_action(Action::PageDown);
14369        assert_eq!(app.sqs_state.queues.selected, 10);
14370
14371        app.handle_action(Action::PageUp);
14372        assert_eq!(app.sqs_state.queues.selected, 0);
14373    }
14374
14375    #[test]
14376    fn test_sqs_queue_expansion() {
14377        let mut app = test_app();
14378        app.current_service = Service::SqsQueues;
14379        app.service_selected = true;
14380        app.sqs_state.queues.items = vec![crate::sqs::Queue {
14381            name: "my-queue".to_string(),
14382            url: "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string(),
14383            queue_type: "Standard".to_string(),
14384            created_timestamp: "2023-01-01".to_string(),
14385            messages_available: "5".to_string(),
14386            messages_in_flight: "2".to_string(),
14387            encryption: "Enabled".to_string(),
14388            content_based_deduplication: "Disabled".to_string(),
14389            last_modified_timestamp: "2023-01-02".to_string(),
14390            visibility_timeout: "30".to_string(),
14391            message_retention_period: "345600".to_string(),
14392            maximum_message_size: "262144".to_string(),
14393            delivery_delay: "0".to_string(),
14394            receive_message_wait_time: "0".to_string(),
14395            high_throughput_fifo: "N/A".to_string(),
14396            deduplication_scope: "N/A".to_string(),
14397            fifo_throughput_limit: "N/A".to_string(),
14398            dead_letter_queue: "-".to_string(),
14399            messages_delayed: "0".to_string(),
14400            redrive_allow_policy: "-".to_string(),
14401            redrive_policy: "".to_string(),
14402            redrive_task_id: "-".to_string(),
14403            redrive_task_start_time: "-".to_string(),
14404            redrive_task_status: "-".to_string(),
14405            redrive_task_percent: "-".to_string(),
14406            redrive_task_destination: "-".to_string(),
14407        }];
14408        app.sqs_state.queues.selected = 0;
14409
14410        assert_eq!(app.sqs_state.queues.expanded_item, None);
14411
14412        // Right arrow expands
14413        app.handle_action(Action::NextPane);
14414        assert_eq!(app.sqs_state.queues.expanded_item, Some(0));
14415
14416        // Right arrow again keeps it expanded
14417        app.handle_action(Action::NextPane);
14418        assert_eq!(app.sqs_state.queues.expanded_item, Some(0));
14419
14420        // Left arrow collapses
14421        app.handle_action(Action::PrevPane);
14422        assert_eq!(app.sqs_state.queues.expanded_item, None);
14423
14424        // Left arrow again keeps it collapsed
14425        app.handle_action(Action::PrevPane);
14426        assert_eq!(app.sqs_state.queues.expanded_item, None);
14427    }
14428
14429    #[test]
14430    fn test_sqs_column_toggle() {
14431        use crate::sqs::queue::Column as SqsColumn;
14432        let mut app = test_app();
14433        app.current_service = Service::SqsQueues;
14434        app.service_selected = true;
14435        app.mode = Mode::ColumnSelector;
14436
14437        // Start with all columns visible
14438        app.sqs_visible_column_ids = SqsColumn::ids();
14439        let initial_count = app.sqs_visible_column_ids.len();
14440
14441        // Select first column (index 0) and toggle it
14442        app.column_selector_index = 0;
14443        app.handle_action(Action::ToggleColumn);
14444
14445        // First column should be removed
14446        assert_eq!(app.sqs_visible_column_ids.len(), initial_count - 1);
14447        assert!(!app.sqs_visible_column_ids.contains(&SqsColumn::Name.id()));
14448
14449        // Toggle it back
14450        app.handle_action(Action::ToggleColumn);
14451        assert_eq!(app.sqs_visible_column_ids.len(), initial_count);
14452        assert!(app.sqs_visible_column_ids.contains(&SqsColumn::Name.id()));
14453    }
14454
14455    #[test]
14456    fn test_sqs_column_selector_navigation() {
14457        let mut app = test_app();
14458        app.current_service = Service::SqsQueues;
14459        app.service_selected = true;
14460        app.mode = Mode::ColumnSelector;
14461        app.column_selector_index = 0;
14462
14463        // Should be able to navigate through all columns
14464        let max_index = app.sqs_column_ids.len() - 1;
14465
14466        // Navigate to last column
14467        for _ in 0..max_index {
14468            app.handle_action(Action::NextItem);
14469        }
14470        assert_eq!(app.column_selector_index, max_index);
14471
14472        // Navigate back to first
14473        for _ in 0..max_index {
14474            app.handle_action(Action::PrevItem);
14475        }
14476        assert_eq!(app.column_selector_index, 0);
14477    }
14478
14479    #[test]
14480    fn test_sqs_queue_selection() {
14481        let mut app = test_app();
14482        app.current_service = Service::SqsQueues;
14483        app.service_selected = true;
14484        app.mode = Mode::Normal;
14485        app.sqs_state.queues.items = vec![crate::sqs::Queue {
14486            name: "my-queue".to_string(),
14487            url: "https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string(),
14488            queue_type: "Standard".to_string(),
14489            created_timestamp: "2023-01-01".to_string(),
14490            messages_available: "5".to_string(),
14491            messages_in_flight: "2".to_string(),
14492            encryption: "Enabled".to_string(),
14493            content_based_deduplication: "Disabled".to_string(),
14494            last_modified_timestamp: "2023-01-02".to_string(),
14495            visibility_timeout: "30".to_string(),
14496            message_retention_period: "345600".to_string(),
14497            maximum_message_size: "262144".to_string(),
14498            delivery_delay: "0".to_string(),
14499            receive_message_wait_time: "0".to_string(),
14500            high_throughput_fifo: "N/A".to_string(),
14501            deduplication_scope: "N/A".to_string(),
14502            fifo_throughput_limit: "N/A".to_string(),
14503            dead_letter_queue: "-".to_string(),
14504            messages_delayed: "0".to_string(),
14505            redrive_allow_policy: "-".to_string(),
14506            redrive_policy: "".to_string(),
14507            redrive_task_id: "-".to_string(),
14508            redrive_task_start_time: "-".to_string(),
14509            redrive_task_status: "-".to_string(),
14510            redrive_task_percent: "-".to_string(),
14511            redrive_task_destination: "-".to_string(),
14512        }];
14513        app.sqs_state.queues.selected = 0;
14514
14515        assert_eq!(app.sqs_state.current_queue, None);
14516
14517        // Select queue
14518        app.handle_action(Action::Select);
14519        assert_eq!(
14520            app.sqs_state.current_queue,
14521            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string())
14522        );
14523
14524        // Go back
14525        app.handle_action(Action::GoBack);
14526        assert_eq!(app.sqs_state.current_queue, None);
14527    }
14528
14529    #[test]
14530    fn test_sqs_lambda_triggers_expand_collapse() {
14531        let mut app = test_app();
14532        app.current_service = Service::SqsQueues;
14533        app.service_selected = true;
14534        app.sqs_state.current_queue =
14535            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
14536        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14537        app.sqs_state.triggers.items = vec![crate::sqs::LambdaTrigger {
14538            uuid: "test-uuid".to_string(),
14539            arn: "arn:aws:lambda:us-east-1:123456789012:function:test".to_string(),
14540            status: "Enabled".to_string(),
14541            last_modified: "2024-01-01T00:00:00Z".to_string(),
14542        }];
14543        app.sqs_state.triggers.selected = 0;
14544
14545        assert_eq!(app.sqs_state.triggers.expanded_item, None);
14546
14547        // Right arrow expands
14548        app.handle_action(Action::NextPane);
14549        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
14550
14551        // Left arrow collapses
14552        app.handle_action(Action::PrevPane);
14553        assert_eq!(app.sqs_state.triggers.expanded_item, None);
14554    }
14555
14556    #[test]
14557    fn test_sqs_lambda_triggers_expand_toggle() {
14558        let mut app = test_app();
14559        app.current_service = Service::SqsQueues;
14560        app.service_selected = true;
14561        app.sqs_state.current_queue =
14562            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
14563        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14564        app.sqs_state.triggers.items = vec![crate::sqs::LambdaTrigger {
14565            uuid: "test-uuid".to_string(),
14566            arn: "arn:aws:lambda:us-east-1:123456789012:function:test".to_string(),
14567            status: "Enabled".to_string(),
14568            last_modified: "2024-01-01T00:00:00Z".to_string(),
14569        }];
14570        app.sqs_state.triggers.selected = 0;
14571
14572        // Expand
14573        app.handle_action(Action::NextPane);
14574        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
14575
14576        // Toggle collapses
14577        app.handle_action(Action::NextPane);
14578        assert_eq!(app.sqs_state.triggers.expanded_item, None);
14579
14580        // Toggle expands again
14581        app.handle_action(Action::NextPane);
14582        assert_eq!(app.sqs_state.triggers.expanded_item, Some(0));
14583    }
14584
14585    #[test]
14586    fn test_sqs_lambda_triggers_sorted_by_last_modified_asc() {
14587        use crate::ui::sqs::filtered_lambda_triggers;
14588
14589        let mut app = test_app();
14590        app.current_service = Service::SqsQueues;
14591        app.service_selected = true;
14592        app.sqs_state.current_queue =
14593            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
14594        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14595        app.sqs_state.triggers.items = vec![
14596            crate::sqs::LambdaTrigger {
14597                uuid: "uuid-3".to_string(),
14598                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-3".to_string(),
14599                status: "Enabled".to_string(),
14600                last_modified: "2024-03-01T00:00:00Z".to_string(),
14601            },
14602            crate::sqs::LambdaTrigger {
14603                uuid: "uuid-1".to_string(),
14604                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-1".to_string(),
14605                status: "Enabled".to_string(),
14606                last_modified: "2024-01-01T00:00:00Z".to_string(),
14607            },
14608            crate::sqs::LambdaTrigger {
14609                uuid: "uuid-2".to_string(),
14610                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-2".to_string(),
14611                status: "Enabled".to_string(),
14612                last_modified: "2024-02-01T00:00:00Z".to_string(),
14613            },
14614        ];
14615
14616        let sorted = filtered_lambda_triggers(&app);
14617
14618        // Should be sorted by last_modified ASC
14619        assert_eq!(sorted.len(), 3);
14620        assert_eq!(sorted[0].uuid, "uuid-1");
14621        assert_eq!(sorted[0].last_modified, "2024-01-01T00:00:00Z");
14622        assert_eq!(sorted[1].uuid, "uuid-2");
14623        assert_eq!(sorted[1].last_modified, "2024-02-01T00:00:00Z");
14624        assert_eq!(sorted[2].uuid, "uuid-3");
14625        assert_eq!(sorted[2].last_modified, "2024-03-01T00:00:00Z");
14626    }
14627
14628    #[test]
14629    fn test_sqs_lambda_triggers_filter_input() {
14630        let mut app = test_app();
14631        app.current_service = Service::SqsQueues;
14632        app.service_selected = true;
14633        app.mode = Mode::FilterInput;
14634        app.sqs_state.current_queue =
14635            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
14636        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14637        app.sqs_state.input_focus = InputFocus::Filter;
14638
14639        assert_eq!(app.sqs_state.triggers.filter, "");
14640
14641        // Type characters
14642        app.handle_action(Action::FilterInput('t'));
14643        assert_eq!(app.sqs_state.triggers.filter, "t");
14644
14645        app.handle_action(Action::FilterInput('e'));
14646        assert_eq!(app.sqs_state.triggers.filter, "te");
14647
14648        app.handle_action(Action::FilterInput('s'));
14649        assert_eq!(app.sqs_state.triggers.filter, "tes");
14650
14651        app.handle_action(Action::FilterInput('t'));
14652        assert_eq!(app.sqs_state.triggers.filter, "test");
14653
14654        // Backspace
14655        app.handle_action(Action::FilterBackspace);
14656        assert_eq!(app.sqs_state.triggers.filter, "tes");
14657    }
14658
14659    #[test]
14660    fn test_sqs_lambda_triggers_filter_applied() {
14661        use crate::ui::sqs::filtered_lambda_triggers;
14662
14663        let mut app = test_app();
14664        app.current_service = Service::SqsQueues;
14665        app.service_selected = true;
14666        app.sqs_state.current_queue =
14667            Some("https://sqs.us-east-1.amazonaws.com/123456789012/my-queue".to_string());
14668        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14669        app.sqs_state.triggers.items = vec![
14670            crate::sqs::LambdaTrigger {
14671                uuid: "uuid-1".to_string(),
14672                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-alpha".to_string(),
14673                status: "Enabled".to_string(),
14674                last_modified: "2024-01-01T00:00:00Z".to_string(),
14675            },
14676            crate::sqs::LambdaTrigger {
14677                uuid: "uuid-2".to_string(),
14678                arn: "arn:aws:lambda:us-east-1:123456789012:function:test-beta".to_string(),
14679                status: "Enabled".to_string(),
14680                last_modified: "2024-02-01T00:00:00Z".to_string(),
14681            },
14682            crate::sqs::LambdaTrigger {
14683                uuid: "uuid-3".to_string(),
14684                arn: "arn:aws:lambda:us-east-1:123456789012:function:prod-gamma".to_string(),
14685                status: "Enabled".to_string(),
14686                last_modified: "2024-03-01T00:00:00Z".to_string(),
14687            },
14688        ];
14689
14690        // No filter - all items
14691        let filtered = filtered_lambda_triggers(&app);
14692        assert_eq!(filtered.len(), 3);
14693
14694        // Filter by "alpha"
14695        app.sqs_state.triggers.filter = "alpha".to_string();
14696        let filtered = filtered_lambda_triggers(&app);
14697        assert_eq!(filtered.len(), 1);
14698        assert_eq!(
14699            filtered[0].arn,
14700            "arn:aws:lambda:us-east-1:123456789012:function:test-alpha"
14701        );
14702
14703        // Filter by "test" - matches 2
14704        app.sqs_state.triggers.filter = "test".to_string();
14705        let filtered = filtered_lambda_triggers(&app);
14706        assert_eq!(filtered.len(), 2);
14707        assert_eq!(
14708            filtered[0].arn,
14709            "arn:aws:lambda:us-east-1:123456789012:function:test-alpha"
14710        );
14711        assert_eq!(
14712            filtered[1].arn,
14713            "arn:aws:lambda:us-east-1:123456789012:function:test-beta"
14714        );
14715
14716        // Filter by uuid
14717        app.sqs_state.triggers.filter = "uuid-3".to_string();
14718        let filtered = filtered_lambda_triggers(&app);
14719        assert_eq!(filtered.len(), 1);
14720        assert_eq!(filtered[0].uuid, "uuid-3");
14721    }
14722
14723    #[test]
14724    fn test_sqs_triggers_navigation() {
14725        let mut app = test_app();
14726        app.service_selected = true;
14727        app.mode = Mode::Normal;
14728        app.current_service = Service::SqsQueues;
14729        app.sqs_state.current_queue = Some("test-queue".to_string());
14730        app.sqs_state.detail_tab = SqsQueueDetailTab::LambdaTriggers;
14731        app.sqs_state.triggers.items = vec![
14732            crate::sqs::LambdaTrigger {
14733                uuid: "1".to_string(),
14734                arn: "arn1".to_string(),
14735                status: "Enabled".to_string(),
14736                last_modified: "2024-01-01".to_string(),
14737            },
14738            crate::sqs::LambdaTrigger {
14739                uuid: "2".to_string(),
14740                arn: "arn2".to_string(),
14741                status: "Enabled".to_string(),
14742                last_modified: "2024-01-02".to_string(),
14743            },
14744        ];
14745
14746        assert_eq!(app.sqs_state.triggers.selected, 0);
14747        app.next_item();
14748        assert_eq!(app.sqs_state.triggers.selected, 1);
14749        app.prev_item();
14750        assert_eq!(app.sqs_state.triggers.selected, 0);
14751    }
14752
14753    #[test]
14754    fn test_sqs_pipes_navigation() {
14755        let mut app = test_app();
14756        app.service_selected = true;
14757        app.mode = Mode::Normal;
14758        app.current_service = Service::SqsQueues;
14759        app.sqs_state.current_queue = Some("test-queue".to_string());
14760        app.sqs_state.detail_tab = SqsQueueDetailTab::EventBridgePipes;
14761        app.sqs_state.pipes.items = vec![
14762            crate::sqs::EventBridgePipe {
14763                name: "pipe1".to_string(),
14764                status: "RUNNING".to_string(),
14765                target: "target1".to_string(),
14766                last_modified: "2024-01-01".to_string(),
14767            },
14768            crate::sqs::EventBridgePipe {
14769                name: "pipe2".to_string(),
14770                status: "RUNNING".to_string(),
14771                target: "target2".to_string(),
14772                last_modified: "2024-01-02".to_string(),
14773            },
14774        ];
14775
14776        assert_eq!(app.sqs_state.pipes.selected, 0);
14777        app.next_item();
14778        assert_eq!(app.sqs_state.pipes.selected, 1);
14779        app.prev_item();
14780        assert_eq!(app.sqs_state.pipes.selected, 0);
14781    }
14782
14783    #[test]
14784    fn test_sqs_tags_navigation() {
14785        let mut app = test_app();
14786        app.service_selected = true;
14787        app.mode = Mode::Normal;
14788        app.current_service = Service::SqsQueues;
14789        app.sqs_state.current_queue = Some("test-queue".to_string());
14790        app.sqs_state.detail_tab = SqsQueueDetailTab::Tagging;
14791        app.sqs_state.tags.items = vec![
14792            crate::sqs::QueueTag {
14793                key: "Env".to_string(),
14794                value: "prod".to_string(),
14795            },
14796            crate::sqs::QueueTag {
14797                key: "Team".to_string(),
14798                value: "backend".to_string(),
14799            },
14800        ];
14801
14802        assert_eq!(app.sqs_state.tags.selected, 0);
14803        app.next_item();
14804        assert_eq!(app.sqs_state.tags.selected, 1);
14805        app.prev_item();
14806        assert_eq!(app.sqs_state.tags.selected, 0);
14807    }
14808
14809    #[test]
14810    fn test_sqs_queues_navigation() {
14811        let mut app = test_app();
14812        app.service_selected = true;
14813        app.mode = Mode::Normal;
14814        app.current_service = Service::SqsQueues;
14815        app.sqs_state.queues.items = vec![
14816            crate::sqs::Queue {
14817                name: "queue1".to_string(),
14818                url: "url1".to_string(),
14819                queue_type: "Standard".to_string(),
14820                created_timestamp: "".to_string(),
14821                messages_available: "0".to_string(),
14822                messages_in_flight: "0".to_string(),
14823                encryption: "Disabled".to_string(),
14824                content_based_deduplication: "Disabled".to_string(),
14825                last_modified_timestamp: "".to_string(),
14826                visibility_timeout: "".to_string(),
14827                message_retention_period: "".to_string(),
14828                maximum_message_size: "".to_string(),
14829                delivery_delay: "".to_string(),
14830                receive_message_wait_time: "".to_string(),
14831                high_throughput_fifo: "-".to_string(),
14832                deduplication_scope: "-".to_string(),
14833                fifo_throughput_limit: "-".to_string(),
14834                dead_letter_queue: "-".to_string(),
14835                messages_delayed: "0".to_string(),
14836                redrive_allow_policy: "-".to_string(),
14837                redrive_policy: "".to_string(),
14838                redrive_task_id: "-".to_string(),
14839                redrive_task_start_time: "-".to_string(),
14840                redrive_task_status: "-".to_string(),
14841                redrive_task_percent: "-".to_string(),
14842                redrive_task_destination: "-".to_string(),
14843            },
14844            crate::sqs::Queue {
14845                name: "queue2".to_string(),
14846                url: "url2".to_string(),
14847                queue_type: "Standard".to_string(),
14848                created_timestamp: "".to_string(),
14849                messages_available: "0".to_string(),
14850                messages_in_flight: "0".to_string(),
14851                encryption: "Disabled".to_string(),
14852                content_based_deduplication: "Disabled".to_string(),
14853                last_modified_timestamp: "".to_string(),
14854                visibility_timeout: "".to_string(),
14855                message_retention_period: "".to_string(),
14856                maximum_message_size: "".to_string(),
14857                delivery_delay: "".to_string(),
14858                receive_message_wait_time: "".to_string(),
14859                high_throughput_fifo: "-".to_string(),
14860                deduplication_scope: "-".to_string(),
14861                fifo_throughput_limit: "-".to_string(),
14862                dead_letter_queue: "-".to_string(),
14863                messages_delayed: "0".to_string(),
14864                redrive_allow_policy: "-".to_string(),
14865                redrive_policy: "".to_string(),
14866                redrive_task_id: "-".to_string(),
14867                redrive_task_start_time: "-".to_string(),
14868                redrive_task_status: "-".to_string(),
14869                redrive_task_percent: "-".to_string(),
14870                redrive_task_destination: "-".to_string(),
14871            },
14872        ];
14873
14874        assert_eq!(app.sqs_state.queues.selected, 0);
14875        app.next_item();
14876        assert_eq!(app.sqs_state.queues.selected, 1);
14877        app.prev_item();
14878        assert_eq!(app.sqs_state.queues.selected, 0);
14879    }
14880
14881    #[test]
14882    fn test_sqs_subscriptions_navigation() {
14883        let mut app = test_app();
14884        app.service_selected = true;
14885        app.mode = Mode::Normal;
14886        app.current_service = Service::SqsQueues;
14887        app.sqs_state.current_queue = Some("test-queue".to_string());
14888        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
14889        app.sqs_state.subscriptions.items = vec![
14890            crate::sqs::SnsSubscription {
14891                subscription_arn: "arn:aws:sns:us-east-1:123:sub1".to_string(),
14892                topic_arn: "arn:aws:sns:us-east-1:123:topic1".to_string(),
14893            },
14894            crate::sqs::SnsSubscription {
14895                subscription_arn: "arn:aws:sns:us-east-1:123:sub2".to_string(),
14896                topic_arn: "arn:aws:sns:us-east-1:123:topic2".to_string(),
14897            },
14898        ];
14899
14900        assert_eq!(app.sqs_state.subscriptions.selected, 0);
14901        app.next_item();
14902        assert_eq!(app.sqs_state.subscriptions.selected, 1);
14903        app.prev_item();
14904        assert_eq!(app.sqs_state.subscriptions.selected, 0);
14905    }
14906
14907    #[test]
14908    fn test_sqs_subscription_region_dropdown_navigation() {
14909        let mut app = test_app();
14910        app.service_selected = true;
14911        app.mode = Mode::FilterInput;
14912        app.current_service = Service::SqsQueues;
14913        app.sqs_state.current_queue = Some("test-queue".to_string());
14914        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
14915        app.sqs_state.input_focus = crate::common::InputFocus::Dropdown("SubscriptionRegion");
14916
14917        assert_eq!(app.sqs_state.subscription_region_selected, 0);
14918        app.next_item();
14919        assert_eq!(app.sqs_state.subscription_region_selected, 1);
14920        app.next_item();
14921        assert_eq!(app.sqs_state.subscription_region_selected, 2);
14922        app.prev_item();
14923        assert_eq!(app.sqs_state.subscription_region_selected, 1);
14924        app.prev_item();
14925        assert_eq!(app.sqs_state.subscription_region_selected, 0);
14926    }
14927
14928    #[test]
14929    fn test_sqs_subscription_region_selection() {
14930        let mut app = test_app();
14931        app.service_selected = true;
14932        app.mode = Mode::FilterInput;
14933        app.current_service = Service::SqsQueues;
14934        app.sqs_state.current_queue = Some("test-queue".to_string());
14935        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
14936        app.sqs_state.input_focus = crate::common::InputFocus::Dropdown("SubscriptionRegion");
14937        app.sqs_state.subscription_region_selected = 2; // us-west-1
14938
14939        assert_eq!(app.sqs_state.subscription_region_filter, "");
14940        app.handle_action(Action::ApplyFilter);
14941        assert_eq!(app.sqs_state.subscription_region_filter, "us-west-1");
14942        assert_eq!(app.mode, Mode::Normal);
14943    }
14944
14945    #[test]
14946    fn test_sqs_subscription_region_change_resets_selection() {
14947        let mut app = test_app();
14948        app.service_selected = true;
14949        app.mode = Mode::FilterInput;
14950        app.current_service = Service::SqsQueues;
14951        app.sqs_state.current_queue = Some("test-queue".to_string());
14952        app.sqs_state.detail_tab = SqsQueueDetailTab::SnsSubscriptions;
14953        app.sqs_state.input_focus = crate::common::InputFocus::Dropdown("SubscriptionRegion");
14954        app.sqs_state.subscription_region_selected = 0;
14955        app.sqs_state.subscriptions.selected = 5;
14956
14957        app.handle_action(Action::NextItem);
14958
14959        assert_eq!(app.sqs_state.subscription_region_selected, 1);
14960        assert_eq!(app.sqs_state.subscriptions.selected, 0);
14961    }
14962
14963    #[test]
14964    fn test_s3_object_filter_resets_selection() {
14965        let mut app = test_app();
14966        app.service_selected = true;
14967        app.current_service = Service::S3Buckets;
14968        app.s3_state.current_bucket = Some("test-bucket".to_string());
14969        app.s3_state.selected_row = 5;
14970        app.mode = Mode::FilterInput;
14971
14972        app.handle_action(Action::CloseMenu);
14973
14974        assert_eq!(app.s3_state.selected_row, 0);
14975        assert_eq!(app.mode, Mode::Normal);
14976    }
14977
14978    #[test]
14979    fn test_s3_bucket_filter_resets_selection() {
14980        let mut app = test_app();
14981        app.service_selected = true;
14982        app.current_service = Service::S3Buckets;
14983        app.s3_state.selected_row = 10;
14984        app.mode = Mode::FilterInput;
14985
14986        app.handle_action(Action::CloseMenu);
14987
14988        assert_eq!(app.s3_state.selected_row, 0);
14989        assert_eq!(app.mode, Mode::Normal);
14990    }
14991
14992    #[test]
14993    fn test_s3_selection_stays_in_bounds() {
14994        let mut app = test_app();
14995        app.service_selected = true;
14996        app.current_service = Service::S3Buckets;
14997        app.s3_state.selected_row = 0;
14998        app.s3_state.selected_object = 0;
14999
15000        // Simulate going up from row 0
15001        app.prev_item();
15002
15003        // Should stay at 0, not wrap to negative
15004        assert_eq!(app.s3_state.selected_row, 0);
15005        assert_eq!(app.s3_state.selected_object, 0);
15006    }
15007
15008    #[test]
15009    fn test_cfn_filter_resets_selection() {
15010        let mut app = test_app();
15011        app.service_selected = true;
15012        app.current_service = Service::CloudFormationStacks;
15013        app.cfn_state.table.selected = 10;
15014        app.mode = Mode::FilterInput;
15015
15016        app.handle_action(Action::CloseMenu);
15017
15018        assert_eq!(app.cfn_state.table.selected, 0);
15019        assert_eq!(app.mode, Mode::Normal);
15020    }
15021
15022    #[test]
15023    fn test_lambda_filter_resets_selection() {
15024        let mut app = test_app();
15025        app.service_selected = true;
15026        app.current_service = Service::LambdaFunctions;
15027        app.lambda_state.table.selected = 8;
15028        app.mode = Mode::FilterInput;
15029
15030        app.handle_action(Action::CloseMenu);
15031
15032        assert_eq!(app.lambda_state.table.selected, 0);
15033        assert_eq!(app.mode, Mode::Normal);
15034    }
15035
15036    #[test]
15037    fn test_sqs_filter_resets_selection() {
15038        let mut app = test_app();
15039        app.service_selected = true;
15040        app.current_service = Service::SqsQueues;
15041        app.sqs_state.queues.selected = 7;
15042        app.mode = Mode::FilterInput;
15043
15044        app.handle_action(Action::CloseMenu);
15045
15046        assert_eq!(app.sqs_state.queues.selected, 0);
15047        assert_eq!(app.mode, Mode::Normal);
15048    }
15049
15050    #[test]
15051    fn test_cfn_status_filter_change_resets_selection() {
15052        use crate::ui::cfn::{StatusFilter, STATUS_FILTER};
15053        let mut app = test_app();
15054        app.service_selected = true;
15055        app.current_service = Service::CloudFormationStacks;
15056        app.mode = Mode::FilterInput;
15057        app.cfn_state.input_focus = STATUS_FILTER;
15058        app.cfn_state.status_filter = StatusFilter::All;
15059        app.cfn_state.table.items = vec![
15060            CfnStack {
15061                name: "stack1".to_string(),
15062                stack_id: "id1".to_string(),
15063                status: "CREATE_COMPLETE".to_string(),
15064                created_time: "2024-01-01".to_string(),
15065                updated_time: String::new(),
15066                deleted_time: String::new(),
15067                drift_status: String::new(),
15068                last_drift_check_time: String::new(),
15069                status_reason: String::new(),
15070                description: String::new(),
15071                detailed_status: String::new(),
15072                root_stack: String::new(),
15073                parent_stack: String::new(),
15074                termination_protection: false,
15075                iam_role: String::new(),
15076                tags: Vec::new(),
15077                stack_policy: String::new(),
15078                rollback_monitoring_time: String::new(),
15079                rollback_alarms: Vec::new(),
15080                notification_arns: Vec::new(),
15081            },
15082            CfnStack {
15083                name: "stack2".to_string(),
15084                stack_id: "id2".to_string(),
15085                status: "UPDATE_IN_PROGRESS".to_string(),
15086                created_time: "2024-01-02".to_string(),
15087                updated_time: String::new(),
15088                deleted_time: String::new(),
15089                drift_status: String::new(),
15090                last_drift_check_time: String::new(),
15091                status_reason: String::new(),
15092                description: String::new(),
15093                detailed_status: String::new(),
15094                root_stack: String::new(),
15095                parent_stack: String::new(),
15096                termination_protection: false,
15097                iam_role: String::new(),
15098                tags: Vec::new(),
15099                stack_policy: String::new(),
15100                rollback_monitoring_time: String::new(),
15101                rollback_alarms: Vec::new(),
15102                notification_arns: Vec::new(),
15103            },
15104        ];
15105        app.cfn_state.table.selected = 1;
15106
15107        app.handle_action(Action::NextItem);
15108
15109        assert_eq!(app.cfn_state.status_filter, StatusFilter::Active);
15110        assert_eq!(app.cfn_state.table.selected, 0);
15111    }
15112
15113    #[test]
15114    fn test_cfn_view_nested_toggle_resets_selection() {
15115        use crate::ui::cfn::VIEW_NESTED;
15116        let mut app = test_app();
15117        app.service_selected = true;
15118        app.current_service = Service::CloudFormationStacks;
15119        app.mode = Mode::FilterInput;
15120        app.cfn_state.input_focus = VIEW_NESTED;
15121        app.cfn_state.view_nested = false;
15122        app.cfn_state.table.items = vec![CfnStack {
15123            name: "stack1".to_string(),
15124            stack_id: "id1".to_string(),
15125            status: "CREATE_COMPLETE".to_string(),
15126            created_time: "2024-01-01".to_string(),
15127            updated_time: String::new(),
15128            deleted_time: String::new(),
15129            drift_status: String::new(),
15130            last_drift_check_time: String::new(),
15131            status_reason: String::new(),
15132            description: String::new(),
15133            detailed_status: String::new(),
15134            root_stack: String::new(),
15135            parent_stack: String::new(),
15136            termination_protection: false,
15137            iam_role: String::new(),
15138            tags: Vec::new(),
15139            stack_policy: String::new(),
15140            rollback_monitoring_time: String::new(),
15141            rollback_alarms: Vec::new(),
15142            notification_arns: Vec::new(),
15143        }];
15144        app.cfn_state.table.selected = 5;
15145
15146        app.handle_action(Action::ToggleFilterCheckbox);
15147
15148        assert!(app.cfn_state.view_nested);
15149        assert_eq!(app.cfn_state.table.selected, 0);
15150    }
15151
15152    #[test]
15153    fn test_cfn_template_scroll_up() {
15154        let mut app = test_app();
15155        app.service_selected = true;
15156        app.current_service = Service::CloudFormationStacks;
15157        app.cfn_state.current_stack = Some("test-stack".to_string());
15158        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15159        app.cfn_state.template_scroll = 20;
15160
15161        app.page_up();
15162
15163        assert_eq!(app.cfn_state.template_scroll, 10);
15164    }
15165
15166    #[test]
15167    fn test_cfn_template_scroll_down() {
15168        let mut app = test_app();
15169        app.service_selected = true;
15170        app.current_service = Service::CloudFormationStacks;
15171        app.cfn_state.current_stack = Some("test-stack".to_string());
15172        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15173        app.cfn_state.template_body = "line1\nline2\nline3\nline4\nline5\nline6\nline7\nline8\nline9\nline10\nline11\nline12\nline13\nline14\nline15".to_string();
15174        app.cfn_state.template_scroll = 0;
15175
15176        app.page_down();
15177
15178        assert_eq!(app.cfn_state.template_scroll, 10);
15179    }
15180
15181    #[test]
15182    fn test_cfn_template_scroll_down_respects_max() {
15183        let mut app = test_app();
15184        app.service_selected = true;
15185        app.current_service = Service::CloudFormationStacks;
15186        app.cfn_state.current_stack = Some("test-stack".to_string());
15187        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15188        app.cfn_state.template_body = "line1\nline2\nline3".to_string();
15189        app.cfn_state.template_scroll = 0;
15190
15191        app.page_down();
15192
15193        // Should not scroll past the last line (3 lines = max scroll of 2)
15194        assert_eq!(app.cfn_state.template_scroll, 2);
15195    }
15196
15197    #[test]
15198    fn test_cfn_template_arrow_up() {
15199        let mut app = test_app();
15200        app.service_selected = true;
15201        app.current_service = Service::CloudFormationStacks;
15202        app.mode = Mode::Normal;
15203        app.cfn_state.current_stack = Some("test-stack".to_string());
15204        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15205        app.cfn_state.template_scroll = 5;
15206
15207        app.prev_item();
15208
15209        assert_eq!(app.cfn_state.template_scroll, 4);
15210    }
15211
15212    #[test]
15213    fn test_cfn_template_arrow_down() {
15214        let mut app = test_app();
15215        app.service_selected = true;
15216        app.current_service = Service::CloudFormationStacks;
15217        app.mode = Mode::Normal;
15218        app.cfn_state.current_stack = Some("test-stack".to_string());
15219        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15220        app.cfn_state.template_body = "line1\nline2\nline3\nline4\nline5".to_string();
15221        app.cfn_state.template_scroll = 2;
15222
15223        app.next_item();
15224
15225        assert_eq!(app.cfn_state.template_scroll, 3);
15226    }
15227
15228    #[test]
15229    fn test_cfn_template_arrow_down_respects_max() {
15230        let mut app = test_app();
15231        app.service_selected = true;
15232        app.current_service = Service::CloudFormationStacks;
15233        app.mode = Mode::Normal;
15234        app.cfn_state.current_stack = Some("test-stack".to_string());
15235        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15236        app.cfn_state.template_body = "line1\nline2".to_string();
15237        app.cfn_state.template_scroll = 1;
15238
15239        app.next_item();
15240
15241        // Should stay at max (2 lines = max scroll of 1)
15242        assert_eq!(app.cfn_state.template_scroll, 1);
15243    }
15244}
15245
15246#[cfg(test)]
15247mod lambda_version_tab_tests {
15248    use super::*;
15249    use test_helpers::*;
15250
15251    #[test]
15252    fn test_lambda_version_tab_cycling_next() {
15253        let mut app = test_app();
15254        app.current_service = Service::LambdaFunctions;
15255        app.lambda_state.current_function = Some("test-function".to_string());
15256        app.lambda_state.current_version = Some("1".to_string());
15257        app.lambda_state.detail_tab = LambdaDetailTab::Code;
15258
15259        // Code -> Monitor
15260        app.handle_action(Action::NextDetailTab);
15261        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
15262        assert!(app.lambda_state.metrics_loading);
15263
15264        // Monitor -> Configuration
15265        app.lambda_state.metrics_loading = false;
15266        app.handle_action(Action::NextDetailTab);
15267        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
15268
15269        // Configuration -> Code
15270        app.handle_action(Action::NextDetailTab);
15271        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
15272    }
15273
15274    #[test]
15275    fn test_lambda_version_tab_cycling_prev() {
15276        let mut app = test_app();
15277        app.current_service = Service::LambdaFunctions;
15278        app.lambda_state.current_function = Some("test-function".to_string());
15279        app.lambda_state.current_version = Some("1".to_string());
15280        app.lambda_state.detail_tab = LambdaDetailTab::Code;
15281
15282        // Code -> Configuration
15283        app.handle_action(Action::PrevDetailTab);
15284        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Configuration);
15285
15286        // Configuration -> Monitor
15287        app.handle_action(Action::PrevDetailTab);
15288        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
15289        assert!(app.lambda_state.metrics_loading);
15290
15291        // Monitor -> Code
15292        app.lambda_state.metrics_loading = false;
15293        app.handle_action(Action::PrevDetailTab);
15294        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Code);
15295    }
15296
15297    #[test]
15298    fn test_lambda_version_monitor_clears_metrics() {
15299        let mut app = test_app();
15300        app.current_service = Service::LambdaFunctions;
15301        app.lambda_state.current_function = Some("test-function".to_string());
15302        app.lambda_state.current_version = Some("1".to_string());
15303        app.lambda_state.detail_tab = LambdaDetailTab::Code;
15304
15305        // Add some fake metric data
15306        app.lambda_state.metric_data_invocations = vec![(1, 10.0), (2, 20.0)];
15307        app.lambda_state.monitoring_scroll = 5;
15308
15309        // Switch to Monitor tab
15310        app.handle_action(Action::NextDetailTab);
15311
15312        assert_eq!(app.lambda_state.detail_tab, LambdaDetailTab::Monitor);
15313        assert!(app.lambda_state.metrics_loading);
15314        assert_eq!(app.lambda_state.monitoring_scroll, 0);
15315        assert!(app.lambda_state.metric_data_invocations.is_empty());
15316    }
15317
15318    #[test]
15319    fn test_cfn_parameters_expand_collapse() {
15320        let mut app = test_app();
15321        app.current_service = Service::CloudFormationStacks;
15322        app.service_selected = true;
15323        app.cfn_state.current_stack = Some("test-stack".to_string());
15324        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Parameters;
15325        app.cfn_state.parameters.items = vec![rusticity_core::cfn::StackParameter {
15326            key: "Param1".to_string(),
15327            value: "Value1".to_string(),
15328            resolved_value: "Resolved1".to_string(),
15329        }];
15330        app.cfn_state.parameters.reset();
15331
15332        assert_eq!(app.cfn_state.parameters.expanded_item, None);
15333
15334        // Right arrow expands
15335        app.handle_action(Action::NextPane);
15336        assert_eq!(app.cfn_state.parameters.expanded_item, Some(0));
15337
15338        // Left arrow collapses
15339        app.handle_action(Action::PrevPane);
15340        assert_eq!(app.cfn_state.parameters.expanded_item, None);
15341    }
15342
15343    #[test]
15344    fn test_cfn_parameters_filter_resets_selection() {
15345        let mut app = test_app();
15346        app.current_service = Service::CloudFormationStacks;
15347        app.service_selected = true;
15348        app.cfn_state.current_stack = Some("test-stack".to_string());
15349        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Parameters;
15350        app.cfn_state.parameters.items = vec![
15351            rusticity_core::cfn::StackParameter {
15352                key: "DatabaseName".to_string(),
15353                value: "mydb".to_string(),
15354                resolved_value: "mydb".to_string(),
15355            },
15356            rusticity_core::cfn::StackParameter {
15357                key: "InstanceType".to_string(),
15358                value: "t2.micro".to_string(),
15359                resolved_value: "t2.micro".to_string(),
15360            },
15361            rusticity_core::cfn::StackParameter {
15362                key: "Environment".to_string(),
15363                value: "production".to_string(),
15364                resolved_value: "production".to_string(),
15365            },
15366        ];
15367        app.cfn_state.parameters.selected = 2; // Select third item
15368        app.mode = Mode::FilterInput;
15369        app.cfn_state.parameters_input_focus = InputFocus::Filter;
15370
15371        // Type a filter character - should reset selection
15372        app.handle_action(Action::FilterInput('D'));
15373        assert_eq!(app.cfn_state.parameters.selected, 0);
15374        assert_eq!(app.cfn_state.parameters.filter, "D");
15375
15376        // Select another item
15377        app.cfn_state.parameters.selected = 1;
15378
15379        // Type another character - should reset again
15380        app.handle_action(Action::FilterInput('a'));
15381        assert_eq!(app.cfn_state.parameters.selected, 0);
15382        assert_eq!(app.cfn_state.parameters.filter, "Da");
15383
15384        // Select another item
15385        app.cfn_state.parameters.selected = 1;
15386
15387        // Backspace - should also reset
15388        app.handle_action(Action::FilterBackspace);
15389        assert_eq!(app.cfn_state.parameters.selected, 0);
15390        assert_eq!(app.cfn_state.parameters.filter, "D");
15391    }
15392
15393    #[test]
15394    fn test_cfn_template_tab_no_preferences() {
15395        let mut app = test_app();
15396        app.current_service = Service::CloudFormationStacks;
15397        app.service_selected = true;
15398        app.cfn_state.current_stack = Some("test-stack".to_string());
15399        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Template;
15400        app.mode = Mode::Normal;
15401
15402        // Try to open preferences - should be ignored
15403        app.handle_action(Action::OpenColumnSelector);
15404        assert_eq!(app.mode, Mode::Normal); // Should stay in Normal mode
15405
15406        // GitSync tab should also not allow preferences
15407        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::GitSync;
15408        app.handle_action(Action::OpenColumnSelector);
15409        assert_eq!(app.mode, Mode::Normal); // Should stay in Normal mode
15410
15411        // Parameters tab should allow preferences
15412        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Parameters;
15413        app.handle_action(Action::OpenColumnSelector);
15414        assert_eq!(app.mode, Mode::ColumnSelector); // Should open preferences
15415
15416        // Outputs tab should allow preferences
15417        app.mode = Mode::Normal;
15418        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Outputs;
15419        app.handle_action(Action::OpenColumnSelector);
15420        assert_eq!(app.mode, Mode::ColumnSelector); // Should open preferences
15421    }
15422
15423    #[test]
15424    fn test_cfn_outputs_expand_collapse() {
15425        let mut app = test_app();
15426        app.current_service = Service::CloudFormationStacks;
15427        app.service_selected = true;
15428        app.cfn_state.current_stack = Some("test-stack".to_string());
15429        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Outputs;
15430        app.cfn_state.outputs.items = vec![rusticity_core::cfn::StackOutput {
15431            key: "Output1".to_string(),
15432            value: "Value1".to_string(),
15433            description: "Description1".to_string(),
15434            export_name: "Export1".to_string(),
15435        }];
15436        app.cfn_state.outputs.reset();
15437
15438        assert_eq!(app.cfn_state.outputs.expanded_item, None);
15439
15440        // Right arrow expands
15441        app.handle_action(Action::NextPane);
15442        assert_eq!(app.cfn_state.outputs.expanded_item, Some(0));
15443
15444        // Left arrow collapses
15445        app.handle_action(Action::PrevPane);
15446        assert_eq!(app.cfn_state.outputs.expanded_item, None);
15447    }
15448
15449    #[test]
15450    fn test_cfn_outputs_filter_resets_selection() {
15451        let mut app = test_app();
15452        app.current_service = Service::CloudFormationStacks;
15453        app.service_selected = true;
15454        app.cfn_state.current_stack = Some("test-stack".to_string());
15455        app.cfn_state.detail_tab = crate::ui::cfn::DetailTab::Outputs;
15456        app.cfn_state.outputs.items = vec![
15457            rusticity_core::cfn::StackOutput {
15458                key: "ApiUrl".to_string(),
15459                value: "https://api.example.com".to_string(),
15460                description: "API endpoint".to_string(),
15461                export_name: "MyApiUrl".to_string(),
15462            },
15463            rusticity_core::cfn::StackOutput {
15464                key: "BucketName".to_string(),
15465                value: "my-bucket".to_string(),
15466                description: "S3 bucket".to_string(),
15467                export_name: "MyBucket".to_string(),
15468            },
15469        ];
15470        app.cfn_state.outputs.reset();
15471        app.cfn_state.outputs.selected = 1;
15472
15473        // Start filter mode
15474        app.handle_action(Action::StartFilter);
15475        assert_eq!(app.mode, Mode::FilterInput);
15476
15477        // Type a character - should reset selection
15478        app.handle_action(Action::FilterInput('A'));
15479        assert_eq!(app.cfn_state.outputs.selected, 0);
15480        assert_eq!(app.cfn_state.outputs.filter, "A");
15481
15482        // Type more
15483        app.cfn_state.outputs.selected = 1;
15484        app.handle_action(Action::FilterInput('p'));
15485        assert_eq!(app.cfn_state.outputs.selected, 0);
15486
15487        // Backspace should also reset selection
15488        app.cfn_state.outputs.selected = 1;
15489        app.handle_action(Action::FilterBackspace);
15490        assert_eq!(app.cfn_state.outputs.selected, 0);
15491    }
15492
15493    #[test]
15494    fn test_ec2_service_in_picker() {
15495        let app = test_app();
15496        assert!(app.service_picker.services.contains(&"EC2 > Instances"));
15497    }
15498
15499    #[test]
15500    fn test_ec2_state_filter_cycles() {
15501        let mut app = test_app();
15502        app.current_service = Service::Ec2Instances;
15503        app.service_selected = true;
15504        app.mode = Mode::FilterInput;
15505        app.ec2_state.input_focus = EC2_STATE_FILTER;
15506
15507        let initial = app.ec2_state.state_filter;
15508        assert_eq!(initial, Ec2StateFilter::AllStates);
15509
15510        // Cycle through filters using ToggleFilterCheckbox
15511        app.handle_action(Action::ToggleFilterCheckbox);
15512        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
15513
15514        app.handle_action(Action::ToggleFilterCheckbox);
15515        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopped);
15516
15517        app.handle_action(Action::ToggleFilterCheckbox);
15518        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Terminated);
15519
15520        app.handle_action(Action::ToggleFilterCheckbox);
15521        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Pending);
15522
15523        app.handle_action(Action::ToggleFilterCheckbox);
15524        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::ShuttingDown);
15525
15526        app.handle_action(Action::ToggleFilterCheckbox);
15527        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopping);
15528
15529        app.handle_action(Action::ToggleFilterCheckbox);
15530        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
15531    }
15532
15533    #[test]
15534    fn test_ec2_filter_resets_table() {
15535        let mut app = test_app();
15536        app.current_service = Service::Ec2Instances;
15537        app.service_selected = true;
15538        app.mode = Mode::FilterInput;
15539        app.ec2_state.input_focus = EC2_STATE_FILTER;
15540        app.ec2_state.table.selected = 5;
15541
15542        app.handle_action(Action::ToggleFilterCheckbox);
15543        assert_eq!(app.ec2_state.table.selected, 0);
15544    }
15545
15546    #[test]
15547    fn test_ec2_columns_visible() {
15548        let app = test_app();
15549        assert_eq!(app.ec2_visible_column_ids.len(), 16); // Default visible columns
15550        assert_eq!(app.ec2_column_ids.len(), 52); // Total available columns
15551    }
15552
15553    #[test]
15554    fn test_ec2_breadcrumbs() {
15555        let mut app = test_app();
15556        app.current_service = Service::Ec2Instances;
15557        app.service_selected = true;
15558        let breadcrumb = app.breadcrumbs();
15559        assert_eq!(breadcrumb, "EC2 > Instances");
15560    }
15561
15562    #[test]
15563    fn test_ec2_console_url() {
15564        let mut app = test_app();
15565        app.current_service = Service::Ec2Instances;
15566        app.service_selected = true;
15567        let url = app.get_console_url();
15568        assert!(url.contains("ec2"));
15569        assert!(url.contains("Instances"));
15570    }
15571
15572    #[test]
15573    fn test_ec2_filter_handling() {
15574        let mut app = test_app();
15575        app.current_service = Service::Ec2Instances;
15576        app.service_selected = true;
15577        app.mode = Mode::FilterInput;
15578
15579        app.handle_action(Action::FilterInput('t'));
15580        app.handle_action(Action::FilterInput('e'));
15581        app.handle_action(Action::FilterInput('s'));
15582        app.handle_action(Action::FilterInput('t'));
15583
15584        assert_eq!(app.ec2_state.table.filter, "test");
15585
15586        app.handle_action(Action::FilterBackspace);
15587        assert_eq!(app.ec2_state.table.filter, "tes");
15588    }
15589
15590    #[test]
15591    fn test_column_selector_page_down_ec2() {
15592        let mut app = test_app();
15593        app.current_service = Service::Ec2Instances;
15594        app.service_selected = true;
15595        app.mode = Mode::ColumnSelector;
15596        app.column_selector_index = 0;
15597
15598        app.handle_action(Action::PageDown);
15599        assert_eq!(app.column_selector_index, 10);
15600
15601        app.handle_action(Action::PageDown);
15602        assert_eq!(app.column_selector_index, 20);
15603    }
15604
15605    #[test]
15606    fn test_column_selector_page_up_ec2() {
15607        let mut app = test_app();
15608        app.current_service = Service::Ec2Instances;
15609        app.service_selected = true;
15610        app.mode = Mode::ColumnSelector;
15611        app.column_selector_index = 30;
15612
15613        app.handle_action(Action::PageUp);
15614        assert_eq!(app.column_selector_index, 20);
15615
15616        app.handle_action(Action::PageUp);
15617        assert_eq!(app.column_selector_index, 10);
15618    }
15619
15620    #[test]
15621    fn test_ec2_state_filter_dropdown_focus() {
15622        let mut app = test_app();
15623        app.current_service = Service::Ec2Instances;
15624        app.service_selected = true;
15625        app.mode = Mode::FilterInput;
15626
15627        // Tab to state filter
15628        app.handle_action(Action::NextFilterFocus);
15629        assert_eq!(app.ec2_state.input_focus, EC2_STATE_FILTER);
15630
15631        // Dropdown should show when focused (tested in render)
15632        // Verify we can cycle the filter
15633        app.handle_action(Action::ToggleFilterCheckbox);
15634        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
15635    }
15636
15637    #[test]
15638    fn test_column_selector_ctrl_d_scrolling() {
15639        let mut app = test_app();
15640        app.current_service = Service::LambdaFunctions;
15641        app.mode = Mode::ColumnSelector;
15642        app.column_selector_index = 0;
15643
15644        app.handle_action(Action::PageDown);
15645        assert_eq!(app.column_selector_index, 10);
15646
15647        // Second PageDown should be capped at max
15648        let max = app.get_column_selector_max();
15649        app.handle_action(Action::PageDown);
15650        assert_eq!(app.column_selector_index, max);
15651    }
15652
15653    #[test]
15654    fn test_column_selector_ctrl_u_scrolling() {
15655        let mut app = test_app();
15656        app.current_service = Service::CloudFormationStacks;
15657        app.mode = Mode::ColumnSelector;
15658        app.column_selector_index = 25;
15659
15660        app.handle_action(Action::PageUp);
15661        assert_eq!(app.column_selector_index, 15);
15662
15663        app.handle_action(Action::PageUp);
15664        assert_eq!(app.column_selector_index, 5);
15665    }
15666
15667    #[test]
15668    fn test_prev_preferences_lambda() {
15669        let mut app = test_app();
15670        app.current_service = Service::LambdaFunctions;
15671        app.mode = Mode::ColumnSelector;
15672        let page_size_idx = app.lambda_state.function_column_ids.len() + 2;
15673        app.column_selector_index = page_size_idx;
15674
15675        app.handle_action(Action::PrevPreferences);
15676        assert_eq!(app.column_selector_index, 0);
15677
15678        app.handle_action(Action::PrevPreferences);
15679        assert_eq!(app.column_selector_index, page_size_idx);
15680    }
15681
15682    #[test]
15683    fn test_prev_preferences_cloudformation() {
15684        let mut app = test_app();
15685        app.current_service = Service::CloudFormationStacks;
15686        app.mode = Mode::ColumnSelector;
15687        let page_size_idx = app.cfn_column_ids.len() + 2;
15688        app.column_selector_index = page_size_idx;
15689
15690        app.handle_action(Action::PrevPreferences);
15691        assert_eq!(app.column_selector_index, 0);
15692
15693        app.handle_action(Action::PrevPreferences);
15694        assert_eq!(app.column_selector_index, page_size_idx);
15695    }
15696
15697    #[test]
15698    fn test_prev_preferences_alarms() {
15699        let mut app = test_app();
15700        app.current_service = Service::CloudWatchAlarms;
15701        app.mode = Mode::ColumnSelector;
15702        app.column_selector_index = 28; // WrapLines
15703
15704        app.handle_action(Action::PrevPreferences);
15705        assert_eq!(app.column_selector_index, 22); // PageSize
15706
15707        app.handle_action(Action::PrevPreferences);
15708        assert_eq!(app.column_selector_index, 18); // ViewAs
15709
15710        app.handle_action(Action::PrevPreferences);
15711        assert_eq!(app.column_selector_index, 0); // Columns
15712
15713        app.handle_action(Action::PrevPreferences);
15714        assert_eq!(app.column_selector_index, 28); // Wrap to WrapLines
15715    }
15716
15717    #[test]
15718    fn test_ec2_page_size_in_preferences() {
15719        let mut app = test_app();
15720        app.current_service = Service::Ec2Instances;
15721        app.mode = Mode::ColumnSelector;
15722        app.ec2_state.table.page_size = PageSize::Fifty;
15723
15724        // Navigate to page size section
15725        let page_size_idx = app.ec2_column_ids.len() + 3; // First page size option (10)
15726        app.column_selector_index = page_size_idx;
15727        app.handle_action(Action::ToggleColumn);
15728
15729        assert_eq!(app.ec2_state.table.page_size, PageSize::Ten);
15730    }
15731
15732    #[test]
15733    fn test_ec2_next_preferences_with_page_size() {
15734        let mut app = test_app();
15735        app.current_service = Service::Ec2Instances;
15736        app.mode = Mode::ColumnSelector;
15737        app.column_selector_index = 0;
15738
15739        let page_size_idx = app.ec2_column_ids.len() + 2;
15740        app.handle_action(Action::NextPreferences);
15741        assert_eq!(app.column_selector_index, page_size_idx);
15742
15743        app.handle_action(Action::NextPreferences);
15744        assert_eq!(app.column_selector_index, 0);
15745    }
15746
15747    #[test]
15748    fn test_ec2_dropdown_next_item() {
15749        let mut app = test_app();
15750        app.current_service = Service::Ec2Instances;
15751        app.mode = Mode::FilterInput;
15752        app.ec2_state.input_focus = EC2_STATE_FILTER;
15753        app.ec2_state.state_filter = Ec2StateFilter::AllStates;
15754
15755        app.handle_action(Action::NextItem);
15756        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
15757
15758        app.handle_action(Action::NextItem);
15759        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopped);
15760    }
15761
15762    #[test]
15763    fn test_ec2_dropdown_prev_item() {
15764        let mut app = test_app();
15765        app.current_service = Service::Ec2Instances;
15766        app.mode = Mode::FilterInput;
15767        app.ec2_state.input_focus = EC2_STATE_FILTER;
15768        app.ec2_state.state_filter = Ec2StateFilter::Stopped;
15769
15770        app.handle_action(Action::PrevItem);
15771        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Running);
15772
15773        app.handle_action(Action::PrevItem);
15774        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
15775    }
15776
15777    #[test]
15778    fn test_ec2_dropdown_cycles_with_arrows() {
15779        let mut app = test_app();
15780        app.current_service = Service::Ec2Instances;
15781        app.mode = Mode::FilterInput;
15782        app.ec2_state.input_focus = EC2_STATE_FILTER;
15783        app.ec2_state.state_filter = Ec2StateFilter::Stopping;
15784
15785        // Next wraps to AllStates
15786        app.handle_action(Action::NextItem);
15787        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::AllStates);
15788
15789        // Prev wraps to Stopping
15790        app.handle_action(Action::PrevItem);
15791        assert_eq!(app.ec2_state.state_filter, Ec2StateFilter::Stopping);
15792    }
15793}