mod utils;
mod renderers;
mod event_handlers;
mod shortcut_handler;

use crate::components::{Component, StatusPane, BranchesPane, LogPane, StashPane, TagsPane, ReflogPane, DiffPane, HelpPane};
use crate::app::{AppState, Action, reducer};
use crate::app::state::{FocusPane, DiffMode};
use crate::errors::ComponentError;
use crate::input::InputEvent;
use crate::services::GitService;
use crate::palette::PaletteHandler;
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use tracing::{debug, warn};
use std::collections::HashMap;
use std::sync::Arc;

pub struct ComponentManager {
    pub components: HashMap<FocusPane, Box<dyn Component>>,
    active_component: FocusPane,
    git_service: Arc<GitService>,
    diff: DiffPane,
    help: HelpPane,
    palette_handler: PaletteHandler,
    shortcut_handler: shortcut_handler::ShortcutHandler,
}

impl ComponentManager {
    pub fn new(git_service: Arc<GitService>) -> Self {
        let mut components: HashMap<FocusPane, Box<dyn Component>> = HashMap::new();
        
        // Initialize StatusPane
        components.insert(
            FocusPane::Status,
            Box::new(StatusPane::new(git_service.clone())),
        );
        
        // Initialize BranchesPane
        components.insert(
            FocusPane::Branches,
            Box::new(BranchesPane::new(git_service.clone())),
        );
        
        // Initialize LogPane
        components.insert(
            FocusPane::Log,
            Box::new(LogPane::new(git_service.clone())),
        );
        
        // Initialize StashPane
        components.insert(
            FocusPane::Stash,
            Box::new(StashPane::new(git_service.clone())),
        );
        
        // Initialize TagsPane
        components.insert(
            FocusPane::Tags,
            Box::new(TagsPane::new(git_service.clone())),
        );
        
        // Initialize ReflogPane
        components.insert(
            FocusPane::Reflog,
            Box::new(ReflogPane::new(git_service.clone())),
        );

        Self {
            components,
            active_component: FocusPane::Status,
            git_service: git_service.clone(),
            diff: DiffPane::new(),
            help: HelpPane::new(),
            palette_handler: PaletteHandler::new(),
            shortcut_handler: shortcut_handler::ShortcutHandler::new(),
        }
    }
    
    /// Get active component
    pub fn active_mut(&mut self) -> Option<&mut Box<dyn Component>> {
        self.components.get_mut(&self.active_component)
    }
    
    /// Switch active component
    pub fn switch_to(&mut self, pane: FocusPane) {
        self.active_component = pane;
    }
    
    /// Handle event with active component
    pub fn handle_event(
        &mut self,
        event: InputEvent,
        state: &AppState,
    ) -> Result<Option<Action>, ComponentError> {
        // Use new palette handler if command palette is open
        if state.command_palette && !state.theme_picker {
            return self.palette_handler.handle_event(event, state);
        }
        event_handlers::handle_event(self, event, state)
    }
    
    /// Update component with action
    pub fn update(
        &mut self,
        action: Action,
        state: &mut AppState,
    ) -> Result<(), ComponentError> {
        // Handle StartAmend - load last commit info
        if matches!(action, Action::StartAmend) {
            match self.git_service.last_commit_info(&state.repo_path) {
                Ok(info) => {
                    *state = reducer(state.clone(), Action::SetLastCommitInfo(Some(info)));
                }
                Err(e) => {
                    tracing::warn!(error = %e, "Failed to load last commit info");
                    *state = reducer(state.clone(), Action::SetStatusError(Some(format!("Failed to load last commit: {e}"))));
                }
            }
        }
        
        if let Some(target) = self.target_pane(&action) {
            if let Some(component) = self.components.get_mut(&target) {
                component.update(action.clone(), state)?;
            }
        } else if let Some(component) = self.active_mut() {
            component.update(action.clone(), state)?;
        }
        
        // Handle global actions
        match action {
            Action::FocusNext => {
                let next = self.next_pane();
                self.switch_to(next);
                state.focus = next;
            }
            Action::FocusPrev => {
                let prev = self.prev_pane();
                self.switch_to(prev);
                state.focus = prev;
            }
            Action::ShowMergeBasePicker => {
                if let Ok(list) = self.git_service.branch_names(&state.repo_path) {
                    state.merge_base_candidates = list;
                    state.merge_base_selected = 0;
                }
                state.merge_base_picker = true;
            }
            _ => {}
        }

        Ok(())
    }
    
    /// Render all components using existing UI module
    pub fn render(&mut self, frame: &mut Frame, state: &AppState) -> Result<(), ComponentError> {
        let area = frame.size();
        self.render_main(frame, area, state)?;
        renderers::render_feedback(self, frame, area, state);
        // Note: Workflow context detection is still used for smart palette suggestions
        // but we removed the separate UI panel to avoid bloat
        if state.show_help {
            self.help.render(frame, area, state);
        }
        if state.command_palette && !state.theme_picker {
            // Use new palette renderer (includes custom commands)
            use crate::palette::PaletteRenderer;
            let commands = self.palette_handler.filter_commands(&state.palette_input, state);
            PaletteRenderer::render(frame, area, state, &commands);
        } else if state.command_palette {
            // Fallback to old renderer for theme picker
            renderers::render_palette(self, frame, area, state);
        }
        if state.theme_picker {
            renderers::render_theme_picker(self, frame, area, state);
        }
        if state.conflicts_popup {
            renderers::render_conflicts_popup(self, frame, area, state);
        }
        if state.conflicts_guided {
            renderers::render_conflicts_guided(self, frame, area, state);
        }
        if state.pr_helper {
            renderers::render_pr_helper(self, frame, area, state);
        }
        if state.show_merge_log {
            renderers::render_merge_log(self, frame, area, state);
        }
        if state.show_rebase_todo {
            renderers::render_rebase_todo(self, frame, area, state);
        }
        if state.show_op_log {
            renderers::render_op_log(self, frame, area, state);
        }
        if state.merge_base_picker {
            renderers::render_merge_base_picker(self, frame, area, state);
        }
        if state.commit_mode {
            renderers::render_commit_input(self, frame, area, state);
        }
        if state.branch_create_mode {
            renderers::render_branch_create_input(self, frame, area, state);
        }
        if state.stash_create_mode {
            renderers::render_stash_create_input(self, frame, area, state);
        }
        if state.rebase_editor_open {
            renderers::render_rebase_builder(self, frame, area, state);
        }
        if state.reword_modal_open {
            renderers::render_reword_modal(self, frame, area, state);
        }
        if state.edit_modal_open {
            renderers::render_edit_modal(self, frame, area, state);
        }
        // Log action menu should be rendered last to ensure it's on top
        if state.log_action_menu {
            renderers::render_log_action_menu(self, frame, area, state);
        }
        Ok(())
    }
    
    /// Get next pane in focus order
    fn next_pane(&self) -> FocusPane {
        match self.active_component {
            FocusPane::Status => FocusPane::Branches,
            FocusPane::Branches => FocusPane::Log,
            FocusPane::Log => FocusPane::Stash,
            FocusPane::Stash => FocusPane::Tags,
            FocusPane::Tags => FocusPane::Reflog,
            FocusPane::Reflog => FocusPane::Status,
        }
    }
    
    /// Get previous pane in focus order
    fn prev_pane(&self) -> FocusPane {
        match self.active_component {
            FocusPane::Status => FocusPane::Reflog,
            FocusPane::Branches => FocusPane::Status,
            FocusPane::Log => FocusPane::Branches,
            FocusPane::Stash => FocusPane::Log,
            FocusPane::Tags => FocusPane::Stash,
            FocusPane::Reflog => FocusPane::Tags,
        }
    }

    fn target_pane(&self, action: &Action) -> Option<FocusPane> {
        match action {
            Action::RefreshStatus
            | Action::StatusUp
            | Action::StatusDown
            | Action::StatusPageUp
            | Action::StatusPageDown
            | Action::StatusTop
            | Action::StatusBottom
            | Action::SetStatusEntries(_)
            | Action::SetSelectedPath(_)
            | Action::ToggleDiffMode => Some(FocusPane::Status),
            Action::RefreshBranches
            | Action::BranchUp
            | Action::BranchDown
            | Action::SetBranches(_) => Some(FocusPane::Branches),
            Action::RefreshCommits
            | Action::CommitUp
            | Action::CommitDown
            | Action::SetCommits(_)
            | Action::SetCommitDetail(_)
            | Action::ToggleLogGraph
            | Action::SetLogGraphText(_) => Some(FocusPane::Log),
            Action::RefreshStashes | Action::StashUp | Action::StashDown | Action::SetStashes(_) => {
                Some(FocusPane::Stash)
            }
            Action::RefreshTags | Action::TagUp | Action::TagDown | Action::SetTags(_) => {
                Some(FocusPane::Tags)
            }
            Action::RefreshReflog | Action::ReflogUp | Action::ReflogDown | Action::SetReflog(_) => {
                Some(FocusPane::Reflog)
            }
            _ => None,
        }
    }

    fn render_main(
        &mut self,
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
    ) -> Result<(), ComponentError> {
        match self.active_component {
            FocusPane::Status => {
                let chunks = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Percentage(40), Constraint::Percentage(60)])
                    .split(area);
                if let Some(component) = self.components.get_mut(&FocusPane::Status) {
                    component.render(frame, chunks[0], state);
                }
                self.diff.render(frame, chunks[1], state);
            }
            FocusPane::Branches => {
                self.render_single(frame, area, FocusPane::Branches, state);
            }
            FocusPane::Log => {
                self.render_single(frame, area, FocusPane::Log, state);
            }
            FocusPane::Stash => {
                self.render_single(frame, area, FocusPane::Stash, state);
            }
            FocusPane::Tags => {
                self.render_single(frame, area, FocusPane::Tags, state);
            }
            FocusPane::Reflog => {
                self.render_single(frame, area, FocusPane::Reflog, state);
            }
        }
        Ok(())
    }

    fn render_single(
        &mut self,
        frame: &mut Frame,
        area: Rect,
        pane: FocusPane,
        state: &AppState,
    ) {
        if let Some(component) = self.components.get_mut(&pane) {
            component.render(frame, area, state);
        }
    }

    /// Synchronizes the status selection with the diff view.
    /// 
    /// This function ensures:
    /// 1. The selected index is within valid bounds
    /// 2. The correct diff (staged vs working) is displayed based on file state
    /// 3. Diff state is properly updated and errors are handled gracefully
    /// 
    /// **Diff Selection Logic:**
    /// - Files with unstaged changes → show working tree diff (most relevant)
    /// - Files with only staged changes → show staged diff
    /// - Files with both → prefer working diff (shows current state)
    /// - Untracked files → use current diff mode setting
    /// 
    /// **Fallback Behavior:**
    /// If working diff is empty but file has staged changes, automatically
    /// switch to staged diff to show the actual changes.
    pub(crate) fn sync_status_selection(&self, state: &mut AppState) {
        // Step 1: Ensure selected index is within bounds
        let max_idx = state.status_entries.len().saturating_sub(1);
        let selected = state.status_selected.min(max_idx);
        state.status_selected = selected;
        
        // Step 2: Get the selected entry, or clear diff if none selected
        let Some(entry) = state.status_entries.get(selected) else {
            // No entry selected or out of bounds - clear diff
            state.last_diff.clear();
            state.last_diff_path.clear();
            state.last_status_error = None;
            debug!("No status entry selected, cleared diff");
            return;
        };
        
        // Step 3: Update selected path
        state.status_selected_path = entry.path.clone();
        let current_diff_mode_is_staged = matches!(state.diff_mode, DiffMode::Staged);
        
        // Step 4: Determine which diff type to show based on file state
        let should_show_staged_diff = Self::determine_diff_mode(entry, current_diff_mode_is_staged);
        
        // Step 5: Fetch the diff
        let mut diff_result = self.git_service.diff(
            &state.repo_path,
            &state.status_selected_path,
            should_show_staged_diff,
            state.diff_context,
        );

        // Step 6: Fallback logic - if working diff is empty but file has staged changes, try staged diff
        let final_diff_mode = if Self::should_fallback_to_staged_diff(&diff_result, should_show_staged_diff, entry) {
            state.diff_mode = DiffMode::Staged;
            debug!(path = %state.status_selected_path, "Working diff empty, falling back to staged diff");
            diff_result = self.git_service.diff(
                &state.repo_path,
                &state.status_selected_path,
                true, // Force staged diff
                state.diff_context,
            );
            true
        } else {
            should_show_staged_diff
        };

        // Step 7: Update state with diff result
        Self::update_diff_state(state, diff_result, final_diff_mode);
    }
    
    /// Determines which diff mode to use based on file entry state.
    /// 
    /// Returns `true` for staged diff, `false` for working diff.
    fn determine_diff_mode(entry: &crate::git::parsers::status::StatusEntry, current_mode_is_staged: bool) -> bool {
        if entry.unstaged {
            // Has unstaged changes → show working diff (most relevant)
            false
        } else if entry.staged {
            // Only staged → show staged diff
            true
        } else {
            // Untracked or no changes → use current mode
            current_mode_is_staged
        }
    }
    
    /// Checks if we should fallback to staged diff when working diff is empty.
    fn should_fallback_to_staged_diff(
        diff_result: &Result<String, crate::errors::GitError>,
        currently_showing_staged: bool,
        entry: &crate::git::parsers::status::StatusEntry,
    ) -> bool {
        // Only fallback if:
        // 1. We're currently showing working diff (not already showing staged)
        // 2. The working diff is empty
        // 3. The file has staged changes
        !currently_showing_staged
            && diff_result.as_ref().map(|d| d.is_empty()).unwrap_or(false)
            && entry.staged
    }
    
    /// Updates the application state with the diff result.
    fn update_diff_state(
        state: &mut AppState,
        diff_result: Result<String, crate::errors::GitError>,
        is_staged: bool,
    ) {
        match diff_result {
            Ok(diff) => {
                state.last_diff = diff;
                state.last_diff_path = state.status_selected_path.clone();
                state.detail_scroll = 0;
                state.load_full_diff = false;
                state.last_diff_truncated = false;
                state.last_diff_total = state.last_diff.lines().count();
                state.last_status_error = None;
                debug!(
                    path = %state.status_selected_path,
                    staged = is_staged,
                    lines = state.last_diff_total,
                    "Diff refreshed successfully"
                );
            }
                Err(e) => {
                    state.last_diff.clear();
                    // Enhanced error message with actionable context
                    let error_msg = format!(
                        "Failed to load {} diff for '{}': {}. \
                        Check file permissions and Git repository state.",
                        if is_staged { "staged" } else { "working" },
                        state.status_selected_path,
                        e
                    );
                    state.last_status_error = Some(error_msg.clone());
                    warn!(
                        error = %e,
                        path = %state.status_selected_path,
                        staged = is_staged,
                        operation = "diff_refresh",
                        "Diff refresh failed - file may be deleted or repository corrupted"
                    );
                }
        }
    }
}