use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Paragraph};
use ratatui::Frame;

use crate::app::App;
use crate::engine::SequencerSnapshot;
use crate::model::{MAX_BANKS, MAX_PATTERNS};
use crate::state::PatternsColumn;
use crate::theme;
use crate::widgets::{render_scroll_indicators, IndicatorAlign};

const MIN_ROW_HEIGHT: u16 = 1;

pub fn layout(area: Rect) -> [Rect; 3] {
    Layout::horizontal([Constraint::Fill(1), Constraint::Length(1), Constraint::Fill(1)]).areas(area)
}

pub fn render(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
    let [banks_area, gap, patterns_area] = layout(area);

    render_banks(frame, app, snapshot, banks_area);
    // gap is just empty space
    let _ = gap;
    render_patterns(frame, app, snapshot, patterns_area);
}

fn render_banks(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
    let theme = theme::get();
    let is_focused = matches!(app.patterns_nav.column, PatternsColumn::Banks);

    let [title_area, inner] =
        Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);

    let title_color = if is_focused { theme.ui.header } else { theme.ui.unfocused };
    let title = Paragraph::new("Banks")
        .style(Style::new().fg(title_color))
        .alignment(ratatui::layout::Alignment::Center);
    frame.render_widget(title, title_area);

    let banks_with_playback: Vec<usize> = snapshot
        .active_patterns
        .iter()
        .map(|p| p.bank)
        .collect();

    let banks_with_staged: Vec<usize> = app
        .playback
        .staged_changes
        .iter()
        .filter_map(|c| match &c.change {
            crate::engine::PatternChange::Start { bank, .. } => Some(*bank),
            _ => None,
        })
        .collect();

    let cursor = app.patterns_nav.bank_cursor;
    let max_visible = (inner.height / MIN_ROW_HEIGHT) as usize;
    let max_visible = max_visible.max(1);

    let scroll_offset = if MAX_BANKS <= max_visible {
        0
    } else {
        cursor
            .saturating_sub(max_visible / 2)
            .min(MAX_BANKS - max_visible)
    };

    let visible_count = MAX_BANKS.min(max_visible);
    let row_height = inner.height / visible_count as u16;
    let row_height = row_height.max(MIN_ROW_HEIGHT);

    for visible_idx in 0..visible_count {
        let idx = scroll_offset + visible_idx;
        let y = inner.y + (visible_idx as u16) * row_height;
        if y >= inner.y + inner.height {
            break;
        }

        let row_area = Rect {
            x: inner.x,
            y,
            width: inner.width,
            height: row_height.min(inner.y + inner.height - y),
        };

        let is_cursor = is_focused && idx == app.patterns_nav.bank_cursor;
        let is_selected = idx == app.patterns_nav.bank_cursor;
        let is_edit = idx == app.editor_ctx.bank;
        let is_playing = banks_with_playback.contains(&idx);
        let is_staged = banks_with_staged.contains(&idx);
        let is_in_range = is_focused
            && app
                .patterns_nav
                .bank_selection_range()
                .is_some_and(|r| r.contains(&idx));

        // Check if any pattern in this bank is muted/soloed (applied)
        let has_muted = (0..MAX_PATTERNS).any(|p| app.mute.is_muted(idx, p));
        let has_soloed = (0..MAX_PATTERNS).any(|p| app.mute.is_soloed(idx, p));

        // Check if any pattern in this bank has staged mute/solo
        let has_staged_mute = (0..MAX_PATTERNS).any(|p| app.playback.has_staged_mute(idx, p));
        let has_staged_solo = (0..MAX_PATTERNS).any(|p| app.playback.has_staged_solo(idx, p));
        let has_staged_mute_solo = has_staged_mute || has_staged_solo;

        let (bg, fg, prefix) = if is_cursor {
            (theme.selection.cursor, theme.selection.cursor_fg, "")
        } else if is_in_range {
            (theme.selection.in_range_bg, theme.selection.in_range_fg, "")
        } else if is_playing {
            if has_staged_mute_solo {
                (theme.list.staged_play_bg, theme.list.staged_play_fg, ">*")
            } else if has_soloed {
                (theme.list.soloed_bg, theme.list.soloed_fg, ">S")
            } else if has_muted {
                (theme.list.muted_bg, theme.list.muted_fg, ">M")
            } else {
                (theme.list.playing_bg, theme.list.playing_fg, "> ")
            }
        } else if is_staged {
            (theme.list.staged_play_bg, theme.list.staged_play_fg, "+ ")
        } else if has_staged_mute_solo {
            (theme.list.staged_play_bg, theme.list.staged_play_fg, " *")
        } else if has_soloed && is_selected {
            (theme.list.soloed_bg, theme.list.soloed_fg, " S")
        } else if has_muted && is_selected {
            (theme.list.muted_bg, theme.list.muted_fg, " M")
        } else if is_selected {
            (theme.list.hover_bg, theme.list.hover_fg, "")
        } else if is_edit {
            (theme.list.edit_bg, theme.list.edit_fg, "")
        } else {
            (theme.ui.bg, theme.ui.text_muted, "")
        };

        let name = app.project_state.project.banks[idx]
            .name
            .as_deref()
            .unwrap_or("");
        let label = if name.is_empty() {
            format!("{}{:02}", prefix, idx + 1)
        } else {
            format!("{}{:02} {}", prefix, idx + 1, name)
        };

        let style = Style::new().bg(bg).fg(fg);
        let style = if is_playing || is_staged {
            style.add_modifier(Modifier::BOLD)
        } else {
            style
        };

        // Fill the entire row with background color
        let bg_block = Block::default().style(Style::new().bg(bg));
        frame.render_widget(bg_block, row_area);

        let text_y = if row_height > 1 {
            row_area.y + (row_height - 1) / 2
        } else {
            row_area.y
        };
        let text_area = Rect {
            x: row_area.x,
            y: text_y,
            width: row_area.width,
            height: 1,
        };
        let para = Paragraph::new(label).style(style);
        frame.render_widget(para, text_area);
    }

    render_scroll_indicators(
        frame,
        inner,
        scroll_offset,
        visible_count,
        MAX_BANKS,
        theme.ui.text_muted,
        IndicatorAlign::Center,
    );
}

fn render_patterns(frame: &mut Frame, app: &App, snapshot: &SequencerSnapshot, area: Rect) {
    use crate::model::PatternSpeed;

    let theme = theme::get();
    let is_focused = matches!(app.patterns_nav.column, PatternsColumn::Patterns);

    let [title_area, inner] =
        Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);

    let title_color = if is_focused { theme.ui.header } else { theme.ui.unfocused };

    let bank = app.patterns_nav.bank_cursor;
    let bank_name = app.project_state.project.banks[bank].name.as_deref();
    let title_text = match bank_name {
        Some(name) => format!("Patterns ({name})"),
        None => format!("Patterns (Bank {:02})", bank + 1),
    };
    let title = Paragraph::new(title_text)
        .style(Style::new().fg(title_color))
        .alignment(ratatui::layout::Alignment::Center);
    frame.render_widget(title, title_area);

    let playing_patterns: Vec<usize> = snapshot
        .active_patterns
        .iter()
        .filter(|p| p.bank == bank)
        .map(|p| p.pattern)
        .collect();

    let staged_to_play: Vec<usize> = app
        .playback
        .staged_changes
        .iter()
        .filter_map(|c| match &c.change {
            crate::engine::PatternChange::Start {
                bank: b, pattern, ..
            } if *b == bank => Some(*pattern),
            _ => None,
        })
        .collect();

    let staged_to_stop: Vec<usize> = app
        .playback
        .staged_changes
        .iter()
        .filter_map(|c| match &c.change {
            crate::engine::PatternChange::Stop {
                bank: b,
                pattern,
            } if *b == bank => Some(*pattern),
            _ => None,
        })
        .collect();

    let edit_pattern = if app.editor_ctx.bank == bank {
        Some(app.editor_ctx.pattern)
    } else {
        None
    };

    let cursor = app.patterns_nav.pattern_cursor;
    let max_visible = (inner.height / MIN_ROW_HEIGHT) as usize;
    let max_visible = max_visible.max(1);

    let scroll_offset = if MAX_PATTERNS <= max_visible {
        0
    } else {
        cursor
            .saturating_sub(max_visible / 2)
            .min(MAX_PATTERNS - max_visible)
    };

    let visible_count = MAX_PATTERNS.min(max_visible);
    let row_height = inner.height / visible_count as u16;
    let row_height = row_height.max(MIN_ROW_HEIGHT);

    for visible_idx in 0..visible_count {
        let idx = scroll_offset + visible_idx;
        let y = inner.y + (visible_idx as u16) * row_height;
        if y >= inner.y + inner.height {
            break;
        }

        let row_area = Rect {
            x: inner.x,
            y,
            width: inner.width,
            height: row_height.min(inner.y + inner.height - y),
        };

        let is_cursor = is_focused && idx == app.patterns_nav.pattern_cursor;
        let is_selected = idx == app.patterns_nav.pattern_cursor;
        let is_edit = edit_pattern == Some(idx);
        let is_playing = playing_patterns.contains(&idx);
        let is_staged_play = staged_to_play.contains(&idx);
        let is_staged_stop = staged_to_stop.contains(&idx);
        let is_in_range = is_focused
            && app
                .patterns_nav
                .pattern_selection_range()
                .is_some_and(|r| r.contains(&idx));

        // Current applied mute/solo state
        let is_muted = app.mute.is_muted(bank, idx);
        let is_soloed = app.mute.is_soloed(bank, idx);

        // Staged mute/solo (will toggle on commit)
        let has_staged_mute = app.playback.has_staged_mute(bank, idx);
        let has_staged_solo = app.playback.has_staged_solo(bank, idx);
        let has_staged_props = app.playback.has_staged_props(bank, idx);

        // Preview state (what it will be after commit)
        let preview_muted = is_muted ^ has_staged_mute;
        let preview_soloed = is_soloed ^ has_staged_solo;
        let is_effectively_muted = app.mute.is_effectively_muted(bank, idx);

        let (bg, fg, prefix) = if is_cursor {
            (theme.selection.cursor, theme.selection.cursor_fg, "")
        } else if is_in_range {
            (theme.selection.in_range_bg, theme.selection.in_range_fg, "")
        } else if is_playing {
            // Playing patterns
            if is_staged_stop {
                (theme.list.staged_stop_bg, theme.list.staged_stop_fg, "- ")
            } else if has_staged_solo {
                // Staged solo toggle on playing pattern
                if preview_soloed {
                    (theme.list.soloed_bg, theme.list.soloed_fg, "+S")
                } else {
                    (theme.list.playing_bg, theme.list.playing_fg, "-S")
                }
            } else if has_staged_mute {
                // Staged mute toggle on playing pattern
                if preview_muted {
                    (theme.list.muted_bg, theme.list.muted_fg, "+M")
                } else {
                    (theme.list.playing_bg, theme.list.playing_fg, "-M")
                }
            } else if is_soloed {
                (theme.list.soloed_bg, theme.list.soloed_fg, ">S")
            } else if is_muted {
                (theme.list.muted_bg, theme.list.muted_fg, ">M")
            } else if is_effectively_muted {
                (theme.list.muted_bg, theme.list.muted_fg, "> ")
            } else {
                (theme.list.playing_bg, theme.list.playing_fg, "> ")
            }
        } else if is_staged_play {
            (theme.list.staged_play_bg, theme.list.staged_play_fg, "+ ")
        } else if has_staged_solo {
            // Staged solo on non-playing pattern
            if preview_soloed {
                (theme.list.soloed_bg, theme.list.soloed_fg, "+S")
            } else {
                (theme.ui.bg, theme.ui.text_muted, "-S")
            }
        } else if has_staged_mute {
            // Staged mute on non-playing pattern
            if preview_muted {
                (theme.list.muted_bg, theme.list.muted_fg, "+M")
            } else {
                (theme.ui.bg, theme.ui.text_muted, "-M")
            }
        } else if is_soloed {
            (theme.list.soloed_bg, theme.list.soloed_fg, " S")
        } else if is_muted {
            (theme.list.muted_bg, theme.list.muted_fg, " M")
        } else if is_selected {
            (theme.list.hover_bg, theme.list.hover_fg, "")
        } else if is_edit {
            (theme.list.edit_bg, theme.list.edit_fg, "")
        } else {
            (theme.ui.bg, theme.ui.text_muted, "")
        };

        let pattern = &app.project_state.project.banks[bank].patterns[idx];
        let name = pattern.name.as_deref().unwrap_or("");
        let length = pattern.length;
        let speed = pattern.speed;

        let base_style = Style::new().bg(bg).fg(fg);
        let bold_style = base_style.add_modifier(Modifier::BOLD);

        // Fill the entire row with background color
        let bg_block = Block::default().style(Style::new().bg(bg));
        frame.render_widget(bg_block, row_area);

        let text_y = if row_height > 1 {
            row_area.y + (row_height - 1) / 2
        } else {
            row_area.y
        };

        let text_area = Rect {
            x: row_area.x,
            y: text_y,
            width: row_area.width,
            height: 1,
        };

        // Build the line: [prefix][idx] [name] ... [length] [speed]
        let name_style = if is_playing || is_staged_play {
            bold_style
        } else {
            base_style
        };
        let dim_style = base_style.remove_modifier(Modifier::BOLD);

        let mut spans = vec![Span::styled(format!("{}{:02}", prefix, idx + 1), name_style)];
        if !name.is_empty() {
            spans.push(Span::styled(format!(" {name}"), name_style));
        }

        // Right-aligned info: length, speed, and staged props indicator
        let speed_str = if speed != PatternSpeed::NORMAL {
            format!(" {}", speed.label())
        } else {
            String::new()
        };
        let props_indicator = if has_staged_props { "~" } else { "" };
        let right_info = format!("{props_indicator}{length}{speed_str}");
        let left_width: usize = spans.iter().map(|s| s.content.chars().count()).sum();
        let right_width = right_info.chars().count();
        let padding = (text_area.width as usize).saturating_sub(left_width + right_width + 1);

        spans.push(Span::raw(" ".repeat(padding)));
        spans.push(Span::styled(right_info, dim_style));

        frame.render_widget(Paragraph::new(Line::from(spans)), text_area);
    }

    render_scroll_indicators(
        frame,
        inner,
        scroll_offset,
        visible_count,
        MAX_PATTERNS,
        theme.ui.text_muted,
        IndicatorAlign::Center,
    );
}
