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

use crate::state::{EditorState, EditorMode, ReplaceFocus};

fn clamp_cursor(f: &Frame, x: u16, y: u16) -> (u16, u16) {
    let a = f.area();
    (x.min(a.right().saturating_sub(1)), y.min(a.bottom().saturating_sub(1)))
}

// ── public entry points ───────────────────────────────────────────────────────

pub fn render_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    if area.height == 0 { return; }
    match editor.mode {
        EditorMode::Welcome => {}
        EditorMode::Glide   => render_glide_shortcuts(editor, f, area),
        EditorMode::Edit    => render_edit_shortcuts(editor, f, area),
        EditorMode::Search  => render_search_shortcuts(editor, f, area),
        EditorMode::Replace => render_replace_shortcuts(editor, f, area),
        EditorMode::Save    => render_save_shortcuts(editor, f, area),
        EditorMode::Open    => render_open_shortcuts(editor, f, area),
        EditorMode::Help    => render_help_shortcuts(editor, f, area),
        EditorMode::Quit    => render_quit_shortcuts(editor, f, area),
        EditorMode::Goto    => render_goto_shortcuts(editor, f, area),
    }
}

pub fn render_status_bar(editor: &EditorState, f: &mut Frame, area: Rect) {
    let status = inline_status(editor);
    let wide   = area.width >= 80;
    let label = match editor.mode {
        EditorMode::Welcome => " cozy".to_string(),
        EditorMode::Help    => format!(" Help{}", status),
        EditorMode::Edit    => {
            let row   = editor.cursor.y + 1;
            let total = editor.buffer.lines.len();
            let name  = editor.filename.as_ref()
                .and_then(|p| p.file_name())
                .and_then(|n| n.to_str())
                .unwrap_or("[No Name]")
                .to_string();
            let loc      = format!("L{}/{}", row, total);
            let name_max = area.width.saturating_sub(loc.len() as u16 + 3) as usize;
            let name_d   = if name.len() > name_max { name[..name_max].to_string() } else { name };
            format!(" {}  {}{}", name_d, loc, status)
        }
        EditorMode::Glide => {
            let mut pend = String::new();
            pend.push_str(&editor.glide_count);
            if let Some(op) = editor.pending_operator { pend.push(op.key()); }
            if let Some(p)  = editor.glide_prefix    { pend.push(p); }
            let hint  = if pend.is_empty() { String::new() } else { format!(" [{}]", pend) };
            let row   = editor.cursor.y + 1;
            let total = editor.buffer.lines.len();
            format!(" Glide{}  L{}/{}{}", hint, row, total, status)
        }
        EditorMode::Search  => {
            format!(" Find:{}{}{}", search_mode_label(editor.search_mode, wide), match_count_str(editor), status)
        }
        EditorMode::Replace => {
            format!(" Replace:{}{}{}", search_mode_label(editor.search_mode, wide), match_count_str(editor), status)
        }
        EditorMode::Save  => format!(" Save{}", status),
        EditorMode::Open  => format!(" Open{}", status),
        EditorMode::Goto  => format!(" Goto: {}", editor.goto_line_buffer),
        EditorMode::Quit  => format!(" Exit{}", status),
    };
    f.render_widget(
        Paragraph::new(label).style(Style::default().bg(Color::DarkGray).fg(Color::White)),
        area,
    );
}

// ── key label styling ─────────────────────────────────────────────────────────

/// The shortcut key itself, as a bold accent (no background) so it stands out
/// from the gutter and status bar instead of sharing their gray fill.
fn key_span(key: &str) -> Span<'static> {
    Span::styled(
        key.to_string(),
        Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD),
    )
}

/// The dimmed description that follows a key.
fn desc_span(desc: &str) -> Span<'static> {
    Span::styled(format!(" {}", desc), Style::default().fg(Color::Gray))
}

fn shortcut_line(_editor: &EditorState, narrow: bool, pairs: &[(&str, &str)]) -> Line<'static> {
    let mut spans: Vec<Span<'static>> = Vec::new();
    for (i, (key, desc)) in pairs.iter().enumerate() {
        if i > 0 { spans.push(Span::raw("   ")); }
        let key = if narrow { key.replace("Ctrl+", "^") } else { key.to_string() };
        spans.push(key_span(&key));
        spans.push(desc_span(desc));
    }
    Line::from(spans)
}

fn compact_line(_editor: &EditorState, pairs: &[(&str, &str)]) -> Line<'static> {
    let mut spans: Vec<Span<'static>> = Vec::new();
    for (i, (key, desc)) in pairs.iter().enumerate() {
        if i > 0 { spans.push(Span::raw("  ")); }
        spans.push(key_span(key));
        spans.push(desc_span(desc));
    }
    Line::from(spans)
}

// ── layout helpers ────────────────────────────────────────────────────────────

fn narrow_layout(area: Rect) -> std::rc::Rc<[Rect]> {
    Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1),
            Constraint::Length(1),
            Constraint::Length(1),
            Constraint::Length(1),
        ])
        .split(area)
}

fn wide_layout(area: Rect) -> std::rc::Rc<[Rect]> {
    Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Length(1), Constraint::Length(1)])
        .split(area)
}

// ── misc helpers ──────────────────────────────────────────────────────────────

fn search_mode_label(mode: crate::state::SearchMode, wide: bool) -> &'static str {
    use crate::state::SearchMode::*;
    match (mode, wide) {
        (MatchCase, false) => "MC",
        (MatchCase, true)  => "Case",
        (Regex,     false) => "Rx",
        (Regex,     true)  => "Regex",
        (ByWord,    false) => "Wrd",
        (ByWord,    true)  => "Word",
    }
}

fn match_count_str(editor: &EditorState) -> String {
    if editor.search_matches.is_empty() {
        if editor.search_buffer.is_empty() { String::new() } else { " 0".to_string() }
    } else {
        format!(" {}/{}", editor.search_current + 1, editor.search_matches.len())
    }
}

fn inline_status(editor: &EditorState) -> String {
    if let Some(msg) = &editor.status_message {
        if editor.should_show_status() {
            return format!("   {}", msg);
        }
    }
    String::new()
}

// ── shortcut renderers ────────────────────────────────────────────────────────

fn render_glide_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    if area.width < 50 {
        let layout = narrow_layout(area);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("hjkl", "Move"), ("w/b", "Wrd"), ("0/$", "Ln"),
        ])), layout[0]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("gg/G", "Fil"), ("i/a", "Ins"), ("Esc", "Ret"),
        ])), layout[1]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("x", "Del"), ("dd", "DelLn"), ("f/r", "Srch"),
        ])), layout[2]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("+/-", "Ln↕"), (".", "Rep"),
        ])), layout[3]);
    } else {
        let narrow = area.width < 80;
        let layout = wide_layout(area);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("hjkl/←↓↑→", "Move"), ("w/b", "Word"), ("0/$", "Line"), ("gg/G", "File Top/Bot"), ("+/-", "Line↕"),
        ])), layout[0]);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("i/I/a/A", "Edit"), ("x/X", "Del Char"), ("dd/D", "Del Line/End"), ("f/r", "Find/Replace"), ("Esc", "Return"),
        ])), layout[1]);
    }
}

fn render_edit_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    if area.width < 50 {
        let layout = narrow_layout(area);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("^S", "Save"), ("^O", "Open"), ("^X", "Exit"),
        ])), layout[0]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("^F", "Find"), ("^R", "Repl"), ("^H", "Help"),
        ])), layout[1]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("^K", "Cut"), ("^J", "Jump"), ("^G", "Glide"),
        ])), layout[2]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("^Z", "Undo"), ("^Q", "Quit"),
        ])), layout[3]);
    } else {
        let narrow = area.width < 80;
        let layout = wide_layout(area);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Ctrl+S", "Save"), ("Ctrl+O", "Open"), ("Ctrl+X", "Exit"),
            ("Ctrl+F", "Find"), ("Ctrl+R", "Replace"),
        ])), layout[0]);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Ctrl+H", "Help"), ("Ctrl+K", "Cut Line"),
            ("Ctrl+J", "Jump"), ("Ctrl+G", "Glide"), ("Ctrl+Z", "Undo"),
        ])), layout[1]);
    }
}

fn render_search_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let prefix = "Find: [";
    let line_find = format!("{}{}]", prefix, editor.search_buffer);
    let buf    = &editor.search_buffer;
    let before = &buf[..editor.search_cursor.min(buf.len())];
    let col    = area.x + UnicodeWidthStr::width(prefix) as u16 + UnicodeWidthStr::width(before) as u16;

    let layout = wide_layout(area);
    f.render_widget(Paragraph::new(line_find), layout[0]);
    if area.width < 50 {
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("^N", "Next"), ("^P", "Prev"), ("^T", "Tog"),
        ])), layout[1]);
    } else {
        let narrow = area.width < 80;
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Ctrl+N", "Next"), ("Ctrl+P", "Prev"), ("Ctrl+T", "Toggle"), ("Esc", "Return"),
        ])), layout[1]);
    }
    let (cx, cy) = clamp_cursor(f, col, area.y);
    f.set_cursor_position((cx, cy));
}

fn render_replace_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    // ASCII marker (width 1) instead of ▶ (U+25B6): ambiguous-width glyph
    // throws off cursor-column math on CJK terminals.
    let (q_mark, r_mark) = match editor.replace_focus {
        ReplaceFocus::Query   => ("> ", "  "),
        ReplaceFocus::Replace => ("  ", "> "),
    };
    let sc = editor.search_cursor;

    if area.width < 50 {
        let layout = narrow_layout(area);
        f.render_widget(Paragraph::new(format!("{}Find: [{}]",    q_mark, editor.search_buffer)),  layout[0]);
        f.render_widget(Paragraph::new(format!("{}Replace: [{}]", r_mark, editor.replace_buffer)), layout[1]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Tab", "Sw"), ("^N", "Next"), ("^P", "Prev"),
        ])), layout[2]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Enter", "Repl"), ("^R", "All"), ("^T", "Tog"),
        ])), layout[3]);
        let (col, row) = match editor.replace_focus {
            ReplaceFocus::Query => {
                let prefix = format!("{}Find: [", q_mark);
                let before = &editor.search_buffer[..sc.min(editor.search_buffer.len())];
                (area.x + UnicodeWidthStr::width(prefix.as_str()) as u16 + UnicodeWidthStr::width(before) as u16, area.y)
            }
            ReplaceFocus::Replace => {
                let prefix = format!("{}Replace: [", r_mark);
                let before = &editor.replace_buffer[..sc.min(editor.replace_buffer.len())];
                (area.x + UnicodeWidthStr::width(prefix.as_str()) as u16 + UnicodeWidthStr::width(before) as u16, area.y + 1)
            }
        };
        let (cx, cy) = clamp_cursor(f, col, row);
        f.set_cursor_position((cx, cy));
    } else {
        let fields = format!("{}Find: [{}]   {}Replace: [{}]",
            q_mark, editor.search_buffer, r_mark, editor.replace_buffer);
        // Tab swaps the two fields above and Ctrl+T toggles search mode, so
        // both hints live on the field row rather than crowding the action
        // row below (which otherwise overflows and clips "Esc Return").
        let line_fields = Line::from(vec![
            Span::raw(fields),
            Span::raw("   "),
            key_span("Tab"),
            desc_span("Switch"),
            Span::raw("   "),
            key_span("Ctrl+T"),
            desc_span("Toggle"),
        ]);
        let col = match editor.replace_focus {
            ReplaceFocus::Query => {
                let prefix = format!("{}Find: [", q_mark);
                let before = &editor.search_buffer[..sc.min(editor.search_buffer.len())];
                area.x + UnicodeWidthStr::width(prefix.as_str()) as u16 + UnicodeWidthStr::width(before) as u16
            }
            ReplaceFocus::Replace => {
                let prefix = format!("{}Find: [{}]   {}Replace: [", q_mark, editor.search_buffer, r_mark);
                let before = &editor.replace_buffer[..sc.min(editor.replace_buffer.len())];
                area.x + UnicodeWidthStr::width(prefix.as_str()) as u16 + UnicodeWidthStr::width(before) as u16
            }
        };
        let narrow = area.width < 80;
        let layout = wide_layout(area);
        f.render_widget(Paragraph::new(line_fields), layout[0]);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Ctrl+N", "Next"), ("Ctrl+P", "Prev"), ("Enter", "Replace"),
            ("Ctrl+R", "All"), ("Esc", "Return"),
        ])), layout[1]);
        let (cx, cy) = clamp_cursor(f, col, area.y);
        f.set_cursor_position((cx, cy));
    }
}

fn render_save_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let buf    = &editor.save_filename_buffer;
    let prefix = "File: [";
    let before = &buf[..editor.filename_cursor.min(buf.len())];
    let col    = area.x + prefix.len() as u16 + UnicodeWidthStr::width(before) as u16;
    let layout = wide_layout(area);
    f.render_widget(Paragraph::new(format!("{}{}]", prefix, buf)), layout[0]);
    if area.width < 50 {
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Enter", "Save"), ("Esc", "Ret"),
        ])), layout[1]);
    } else {
        let narrow = area.width < 80;
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Enter", "Save"), ("Esc", "Return"),
        ])), layout[1]);
    }
    let (cx, cy) = clamp_cursor(f, col, area.y);
    f.set_cursor_position((cx, cy));
}

fn render_open_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let buf    = &editor.open_filename_buffer;
    let prefix = "File: [";
    let before = &buf[..editor.filename_cursor.min(buf.len())];
    let col    = area.x + prefix.len() as u16 + UnicodeWidthStr::width(before) as u16;
    let layout = wide_layout(area);
    f.render_widget(Paragraph::new(format!("{}{}]", prefix, buf)), layout[0]);
    if area.width < 50 {
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Enter", "Open"), ("Esc", "Ret"),
        ])), layout[1]);
    } else {
        let narrow = area.width < 80;
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Enter", "Open"), ("Esc", "Return"),
        ])), layout[1]);
    }
    let (cx, cy) = clamp_cursor(f, col, area.y);
    f.set_cursor_position((cx, cy));
}

fn render_help_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let narrow = area.width < 80;
    f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
        ("↑↓", "Scroll"), ("Esc", "Return"),
    ])), Rect::new(area.x, area.y, area.width, 1.min(area.height)));
}

fn render_goto_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let total     = editor.buffer.lines.len();
    let line_goto = format!("Line: [{}]   (1-{})", editor.goto_line_buffer, total);
    let layout    = wide_layout(area);
    f.render_widget(Paragraph::new(line_goto), layout[0]);
    if area.width < 50 {
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Enter", "Jump"), ("Esc", "Ret"),
        ])), layout[1]);
    } else {
        let narrow = area.width < 80;
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Enter", "Jump"), ("Esc", "Return"),
        ])), layout[1]);
    }
}

fn render_quit_shortcuts(editor: &EditorState, f: &mut Frame, area: Rect) {
    let buf = &editor.save_filename_buffer;
    if area.width < 50 {
        let prefix = "File: [";
        let layout = wide_layout(area);
        f.render_widget(Paragraph::new(format!("{}{}]", prefix, buf)), layout[0]);
        f.render_widget(Paragraph::new(compact_line(editor, &[
            ("Enter", "Save+Exit"), ("^Q", "Quit"), ("Esc", "Ret"),
        ])), layout[1]);
        let before = &buf[..editor.filename_cursor.min(buf.len())];
        let col = area.x + prefix.len() as u16 + UnicodeWidthStr::width(before) as u16;
        let (cx, cy) = clamp_cursor(f, col, area.y);
        f.set_cursor_position((cx, cy));
    } else {
        let prefix = "Filename: [";
        let narrow = area.width < 80;
        let layout = wide_layout(area);
        f.render_widget(Paragraph::new(format!("{}{}]", prefix, buf)), layout[0]);
        f.render_widget(Paragraph::new(shortcut_line(editor, narrow, &[
            ("Enter", "Save and Exit"), ("Ctrl+Q", "Quit"), ("Esc", "Return"),
        ])), layout[1]);
        let before = &buf[..editor.filename_cursor.min(buf.len())];
        let col = area.x + prefix.len() as u16 + UnicodeWidthStr::width(before) as u16;
        let (cx, cy) = clamp_cursor(f, col, area.y);
        f.set_cursor_position((cx, cy));
    }
}
