use super::filter::ScoredCommand;
use super::command::CommandCategory;
use crate::app::AppState;
use crate::ui::style::{self, Emphasis};
use ratatui::Frame;
use ratatui::layout::{Constraint, Direction, Layout, Rect};
use ratatui::widgets::{Block, Borders, Clear, List, ListItem, ListState, Paragraph};
use ratatui::text::{Line, Span};
use ratatui::style::Modifier;
use std::collections::HashMap;

pub struct PaletteRenderer;

impl PaletteRenderer {
    pub fn render(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        commands: &[ScoredCommand],
    ) {
        // Handle empty state
        if commands.is_empty() {
            Self::render_empty(frame, area, state);
            return;
        }
        
        let max_idx = commands.len().saturating_sub(1);
        let selected = state.palette_selected.min(max_idx);
        
        // Check for category filter (e.g., "@git")
        let (query, category_filter) = Self::parse_category_filter(&state.palette_input);
        
        // Only group by category if query is empty (showing all commands)
        // When filtering, preserve the filtered order for correct indexing
        let should_group = query.is_empty() && !state.palette_input.starts_with("@");
        
        // Determine layout based on result count and screen size
        let (width_percent, height_percent, use_two_column) = Self::determine_layout(commands.len(), area.width);
        
        let popup = Self::center_rect(width_percent, height_percent, area);
        frame.render_widget(Clear, popup);
        
        // Split into list and preview areas
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Min(3),
                Constraint::Length(4),  // Preview area with more space
            ])
            .split(popup);
        
        let list_area = chunks[0];
        let preview_area = chunks[1];
        
        // Get custom commands that match query
        let custom_commands = Self::get_custom_commands(state, &query);
        let total_items = commands.len() + custom_commands.len();
        let adjusted_selected = selected.min(total_items.saturating_sub(1));
        
        // Determine if selected item is a custom command
        let is_custom_selected = adjusted_selected >= commands.len();
        let custom_index = if is_custom_selected {
            adjusted_selected - commands.len()
        } else {
            0
        };
        
        // Render command list (grouped or flat based on query)
        let preview_command_idx = if should_group {
            let grouped = Self::group_by_category(commands, category_filter);
            // When grouped, we need to map the visual selected index back to the original command index
            let (original_idx, _) = Self::render_command_list_grouped(frame, list_area, state, &grouped, selected.min(commands.len().saturating_sub(1)), use_two_column, commands);
            original_idx
        } else {
            // Preserve filtered order - no grouping, include custom commands
            Self::render_command_list_with_custom(frame, list_area, state, commands, &custom_commands, adjusted_selected);
            adjusted_selected
        };
        
        // Render preview for selected command
        if is_custom_selected {
            if let Some((name, cmd)) = custom_commands.get(custom_index) {
                Self::render_custom_preview(frame, preview_area, state, name, cmd);
            }
        } else if let Some(scored) = commands.get(preview_command_idx.min(commands.len().saturating_sub(1))) {
            Self::render_preview(frame, preview_area, state, scored);
        }
    }
    
    fn parse_category_filter(input: &str) -> (String, Option<CommandCategory>) {
        if let Some(category_str) = input.strip_prefix("@") {
            // Find where category name ends (space or end of string)
            let category_end = category_str
                .char_indices()
                .find(|(_, c)| c.is_whitespace())
                .map(|(i, _)| i)
                .unwrap_or(category_str.len());
            
            let category_name = &category_str[..category_end];
            let remaining = category_str[category_end..].trim();
            
            let category = match category_name.to_lowercase().as_str() {
                "git" | "gitoperations" => Some(CommandCategory::GitOperations),
                "staging" => Some(CommandCategory::Staging),
                "remote" => Some(CommandCategory::Remote),
                "rebase" => Some(CommandCategory::Rebase),
                "reset" => Some(CommandCategory::Reset),
                "log" => Some(CommandCategory::Log),
                "navigation" | "nav" => Some(CommandCategory::Navigation),
                "config" | "configuration" => Some(CommandCategory::Configuration),
                "conflicts" | "conflict" => Some(CommandCategory::Conflicts),
                "pr" => Some(CommandCategory::Pr),
                "autofetch" | "fetch" => Some(CommandCategory::AutoFetch),
                "file" | "fileoperations" => Some(CommandCategory::FileOperations),
                "merge" | "mergerebase" => Some(CommandCategory::MergeRebase),
                _ => None,
            };
            
            if category.is_some() {
                return (remaining.to_string(), category);
            }
        }
        (input.to_string(), None)
    }
    
    fn group_by_category(
        commands: &[ScoredCommand],
        category_filter: Option<CommandCategory>,
    ) -> HashMap<CommandCategory, Vec<&ScoredCommand>> {
        let mut grouped: HashMap<CommandCategory, Vec<&ScoredCommand>> = HashMap::new();
        
        for cmd in commands {
            // If category filter is set, only include matching categories
            if let Some(filter) = category_filter {
                if cmd.command.category != filter {
                    continue;
                }
            }
            
            grouped
                .entry(cmd.command.category)
                .or_insert_with(Vec::new)
                .push(cmd);
        }
        
        // Sort commands within each category by score
        for commands_in_category in grouped.values_mut() {
            commands_in_category.sort_by(|a, b| b.score.cmp(&a.score));
        }
        
        grouped
    }
    
    fn get_custom_commands(state: &AppState, query: &str) -> Vec<(String, String)> {
        let query_lower = query.to_lowercase();
        state.custom_commands
            .iter()
            .filter(|(name, _)| {
                query.is_empty() || name.to_lowercase().contains(&query_lower)
            })
            .map(|(name, cmd)| (name.clone(), cmd.clone()))
            .collect()
    }
    
    fn render_command_list_with_custom(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        commands: &[ScoredCommand],
        custom_commands: &[(String, String)],
        selected: usize,
    ) {
        let mut items = Vec::new();
        
        // Add regular commands
        for (i, scored) in commands.iter().enumerate() {
            let cmd = scored.command;
            let is_selected = i == selected;
            items.push(Self::build_list_item(cmd, scored, &cmd.category, is_selected, false, true, state));
        }
        
        // Add separator if both regular and custom commands exist
        if !commands.is_empty() && !custom_commands.is_empty() {
            items.push(ListItem::new(Line::from(vec![
                Span::styled("─── Custom Commands ───", style::text(&state.theme, Emphasis::Muted)),
            ])));
        }
        
        // Add custom commands
        let custom_start = commands.len();
        for (i, (name, _)) in custom_commands.iter().enumerate() {
            let idx = custom_start + i;
            let is_selected = idx == selected;
            let style = if is_selected {
                style::selection(&state.theme)
            } else {
                style::body_style(&state.theme)
            };
            items.push(ListItem::new(Line::from(vec![
                Span::styled(format!("🔧 {}", name), style),
            ])).style(style));
        }
        
        let list = List::new(items)
            .style(style::body_style(&state.theme))
            .block(style::pane_block(&state.theme, "Commands", false));
        
        frame.render_widget(list, area);
    }
    
    fn render_custom_preview(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        name: &str,
        command: &str,
    ) {
        let lines = vec![
            Line::from(vec![
                Span::styled("Custom Command", style::text(&state.theme, Emphasis::Header)),
            ]),
            Line::from(""),
            Line::from(vec![
                Span::styled("Name: ", style::text(&state.theme, Emphasis::Muted)),
                Span::styled(name, style::text(&state.theme, Emphasis::Normal)),
            ]),
            Line::from(vec![
                Span::styled("Command: ", style::text(&state.theme, Emphasis::Muted)),
                Span::styled(command, style::text(&state.theme, Emphasis::Normal)),
            ]),
        ];
        
        let paragraph = Paragraph::new(lines)
            .style(style::text(&state.theme, Emphasis::Normal))
            .block(style::pane_block(&state.theme, "Preview", false));
        
        frame.render_widget(paragraph, area);
    }
    
    fn determine_layout(count: usize, screen_width: u16) -> (u16, u16, bool) {
        let use_two_column = count >= 10 && count < 30 && screen_width >= 120;
        
        let (width, height) = if count < 10 {
            (50, 35)  // Compact for small result sets
        } else if count < 30 {
            if use_two_column {
                (75, 45)  // Wider for two-column layout
            } else {
                (65, 45)  // Medium for medium result sets
            }
        } else {
            (75, 55)  // Larger for large result sets with categories
        };
        
        (width, height, use_two_column)
    }
    
    #[allow(dead_code)]
    fn render_command_list_flat(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        commands: &[ScoredCommand],
        selected: usize,
    ) {
        // Render commands in filtered order (no grouping)
        let items: Vec<ListItem> = commands
            .iter()
            .enumerate()
            .map(|(i, scored)| {
                let cmd = scored.command;
                let is_selected = i == selected;
                
                // Determine detail level based on position
                let show_full_details = i < 5;  // Top 5: full details
                let show_description = i < 15;  // Next 10: name + shortcut, rest: name only
                
                Self::build_list_item(cmd, scored, &cmd.category, is_selected, show_full_details, show_description, state)
            })
            .collect();
        
        let title = if state.palette_input.is_empty() {
            "Command Palette".to_string()
        } else {
            format!("Palette: {} [{} / {}]", state.palette_input, selected + 1, commands.len().max(1))
        };
        
        let mut list_state = ListState::default();
        list_state.select(Some(selected.min(commands.len().saturating_sub(1))));
        
        let list = List::new(items)
            .style(style::body_style(&state.theme))
            .block(style::pane_block(&state.theme, title.as_str(), true));
        
        frame.render_stateful_widget(list, area, &mut list_state);
    }
    
    fn render_command_list_grouped(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        grouped: &HashMap<CommandCategory, Vec<&ScoredCommand>>,
        selected: usize,
        _use_two_column: bool,
        original_commands: &[ScoredCommand],
    ) -> (usize, usize) {
        // Flatten grouped commands while preserving category info and original index
        // Returns (original_command_index, visual_selected_index)
        let mut flat_commands: Vec<(CommandCategory, &ScoredCommand, usize)> = Vec::new();
        
        // Sort categories for consistent ordering
        let mut categories: Vec<_> = grouped.keys().collect();
        categories.sort_by_key(|c| format!("{:?}", c));
        
        for category in categories {
            let commands_in_category = &grouped[category];
            
            for cmd in commands_in_category {
                // Find the original index of this command in the original commands array
                let original_idx = original_commands
                    .iter()
                    .position(|c| std::ptr::eq(c.command, cmd.command))
                    .unwrap_or(flat_commands.len());
                flat_commands.push((*category, *cmd, original_idx));
            }
        }
        
        // Find the original command index for the selected visual position
        let visual_selected = selected.min(flat_commands.len().saturating_sub(1));
        let original_idx = flat_commands
            .get(visual_selected)
            .map(|(_, _, idx)| *idx)
            .unwrap_or(selected);
        
        // Progressive disclosure: show top 5 with full details, next 10 with name+shortcut
        let items: Vec<ListItem> = flat_commands
            .iter()
            .enumerate()
            .map(|(i, (category, scored, _global_idx))| {
                let cmd = scored.command;
                let is_selected = i == visual_selected;
                
                // Determine detail level based on position
                let show_full_details = i < 5;  // Top 5: full details
                let show_description = i < 15;  // Next 10: name + shortcut, rest: name only
                
                Self::build_list_item(cmd, scored, category, is_selected, show_full_details, show_description, state)
            })
            .collect();
        
        let title = if state.palette_input.is_empty() {
            "Command Palette".to_string()
        } else {
            format!("Palette: {} [{} / {}]", state.palette_input, visual_selected + 1, flat_commands.len().max(1))
        };
        
        let mut list_state = ListState::default();
        list_state.select(Some(visual_selected));
        
        let list = List::new(items)
            .style(style::body_style(&state.theme))
            .block(style::pane_block(&state.theme, title.as_str(), true));
        
        frame.render_stateful_widget(list, area, &mut list_state);
        
        (original_idx, visual_selected)
    }
    
    fn build_list_item(
        cmd: &super::command::CommandDef,
        scored: &ScoredCommand,
        category: &CommandCategory,
        is_selected: bool,
        show_full_details: bool,
        show_description: bool,
        state: &AppState,
    ) -> ListItem<'static> {
        let mut spans = Vec::new();
        
        // Category indicator (colored)
        let category_color = Self::category_color(category, &state.theme);
        let category_abbr = Self::category_abbreviation(category);
        spans.push(Span::styled(
            format!("[{}] ", category_abbr),
            category_color,
        ));
        
        // Command name with match highlighting
        let name_spans = Self::highlight_matches(cmd.name, &scored.match_ranges, state);
        spans.extend(name_spans);
        
        // Shortcut badge
        spans.push(Span::styled(
            format!(" ({})", cmd.key),
            style::text(&state.theme, Emphasis::Muted).add_modifier(Modifier::BOLD),
        ));
        
        // Description (only for full detail level)
        if show_full_details && show_description {
            spans.push(Span::styled(
                format!(" — {}", cmd.description),
                style::text(&state.theme, Emphasis::Muted),
            ));
        }
        
        let line = Line::from(spans);
        let item_style = if is_selected {
            style::selection(&state.theme)
        } else {
            style::text(&state.theme, Emphasis::Normal)
        };
        
        ListItem::new(line).style(item_style)
    }
    
    fn highlight_matches(
        text: &str,
        match_ranges: &[(usize, usize)],
        state: &AppState,
    ) -> Vec<Span<'static>> {
        if match_ranges.is_empty() {
            return vec![Span::styled(
                text.to_string(),
                style::text(&state.theme, Emphasis::Normal),
            )];
        }
        
        let mut spans = Vec::new();
        let mut last_idx = 0;
        
        // Sort ranges by start position
        let mut sorted_ranges = match_ranges.to_vec();
        sorted_ranges.sort_by_key(|r| r.0);
        
        for (start, end) in sorted_ranges {
            // Defensive: ensure ranges are valid and within bounds
            let start = start.min(text.len());
            let end = end.min(text.len());
            
            // Skip invalid ranges (start >= end or out of bounds)
            if start >= end || start > text.len() || end > text.len() {
                continue;
            }
            
            // Add text before match
            if start > last_idx {
                spans.push(Span::styled(
                    text[last_idx..start].to_string(),
                    style::text(&state.theme, Emphasis::Normal),
                ));
            }
            
            // Add highlighted match
            spans.push(Span::styled(
                text[start..end].to_string(),
                style::text(&state.theme, Emphasis::Normal)
                    .add_modifier(Modifier::BOLD)
                    .add_modifier(Modifier::UNDERLINED),
            ));
            
            last_idx = end;
        }
        
        // Add remaining text
        if last_idx < text.len() {
            spans.push(Span::styled(
                text[last_idx..].to_string(),
                style::text(&state.theme, Emphasis::Normal),
            ));
        }
        
        spans
    }
    
    fn category_color(category: &CommandCategory, theme: &crate::config::ThemeConfig) -> ratatui::style::Style {
        use CommandCategory::*;
        let color = match category {
            GitOperations => theme.diff_add_color(),
            Staging => theme.staged_color(),
            Remote => theme.diff_context_color(),
            Rebase | Reset => theme.warning_color(),
            Log => theme.muted_color(),
            Navigation => theme.border_focused_color(),
            Configuration => theme.footer_color(),
            Conflicts => theme.error_color(),
            AutoFetch => theme.diff_add_color(),
            Pr => theme.accent_color(),
            FileOperations => theme.diff_context_color(),
            MergeRebase => theme.warning_color(),
        };
        ratatui::style::Style::default().fg(color)
    }
    
    fn category_abbreviation(category: &CommandCategory) -> &'static str {
        use CommandCategory::*;
        match category {
            GitOperations => "Git",
            Staging => "Stg",
            Remote => "Rem",
            Rebase => "Reb",
            Reset => "Rst",
            Log => "Log",
            Navigation => "Nav",
            Configuration => "Cfg",
            Conflicts => "Cnf",
            AutoFetch => "Fch",
            Pr => "PR",
            FileOperations => "File",
            MergeRebase => "Merge",
        }
    }
    
    fn render_preview(
        frame: &mut Frame,
        area: Rect,
        state: &AppState,
        scored: &ScoredCommand,
    ) {
        let cmd = scored.command;
        
        let preview_lines = vec![
            Line::from(vec![
                Span::styled("Command: ", style::text(&state.theme, Emphasis::Header)),
                Span::styled(cmd.name, style::text(&state.theme, Emphasis::Normal).add_modifier(Modifier::BOLD)),
            ]),
            Line::from(vec![
                Span::styled("Shortcut: ", style::text(&state.theme, Emphasis::Header)),
                Span::styled(
                    format!("[{}]", cmd.key),
                    style::text(&state.theme, Emphasis::Muted).add_modifier(Modifier::BOLD),
                ),
            ]),
            Line::from(vec![
                Span::styled("Description: ", style::text(&state.theme, Emphasis::Header)),
                Span::styled(cmd.description, style::text(&state.theme, Emphasis::Normal)),
            ]),
            Line::from(vec![
                Span::styled("Category: ", style::text(&state.theme, Emphasis::Header)),
                Span::styled(
                    format!("{:?}", cmd.category),
                    Self::category_color(&cmd.category, &state.theme),
                ),
            ]),
        ];
        
        let block = Block::default()
            .borders(Borders::TOP)
            .border_style(style::border(&state.theme, false))
            .title("Preview");
        
        let paragraph = Paragraph::new(preview_lines)
            .block(block)
            .style(style::body_style(&state.theme));
        
        frame.render_widget(paragraph, area);
    }
    
    fn render_empty(frame: &mut Frame, area: Rect, state: &AppState) {
        let popup = Self::center_rect(55, 25, area);
        frame.render_widget(Clear, popup);
        
        let block = style::pane_block(&state.theme, "Command Palette", true);
        
        let text = if state.palette_input.is_empty() {
            vec![
                Line::from("Type to search commands..."),
                Line::from(""),
                Line::from(vec![
                    Span::styled("Tip: ", style::text(&state.theme, Emphasis::Header)),
                    Span::styled("Use @category to filter (e.g., @git, @staging)", style::text(&state.theme, Emphasis::Muted)),
                ]),
                Line::from(""),
                Line::from(vec![
                    Span::styled("Examples: ", style::text(&state.theme, Emphasis::Header)),
                    Span::styled("@git push", style::text(&state.theme, Emphasis::Normal)),
                ]),
            ]
        } else {
            vec![
                Line::from("No matching commands found"),
                Line::from(""),
                Line::from(vec![
                    Span::styled("Try: ", style::text(&state.theme, Emphasis::Header)),
                    Span::styled("• Different keywords", style::text(&state.theme, Emphasis::Muted)),
                ]),
                Line::from(vec![
                    Span::styled("     ", style::text(&state.theme, Emphasis::Muted)),
                    Span::styled("• Category filter: @git, @staging, etc.", style::text(&state.theme, Emphasis::Muted)),
                ]),
            ]
        };
        
        let paragraph = Paragraph::new(text)
            .block(block)
            .style(style::body_style(&state.theme))
            .alignment(ratatui::layout::Alignment::Left);
        
        frame.render_widget(paragraph, popup);
    }
    
    fn center_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
        let vertical = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Percentage((100 - percent_y) / 2),
                Constraint::Percentage(percent_y),
                Constraint::Percentage((100 - percent_y) / 2),
            ])
            .split(r);
        let horizontal = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([
                Constraint::Percentage((100 - percent_x) / 2),
                Constraint::Percentage(percent_x),
                Constraint::Percentage((100 - percent_x) / 2),
            ])
            .split(vertical[1]);
        horizontal[1]
    }
}