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

use crate::app::App;
use crate::model::categories::{get_category_name, CatEntry, CATEGORIES};
use crate::model::{Word, WORDS};
use crate::state::DictFocus;
use crate::theme;
use crate::widgets::{render_search_bar, CategoryItem, CategoryList, Selection};

use CatEntry::{Category, Section};

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

pub fn render(frame: &mut Frame, app: &App, area: Rect) {
    let [cat_area, words_area] = layout(area);

    let is_searching = !app.ui.dict_search_query.is_empty();
    render_categories(frame, app, cat_area, is_searching);
    render_words(frame, app, words_area, is_searching);
}

fn render_categories(frame: &mut Frame, app: &App, area: Rect, dimmed: bool) {
    let theme = theme::get();
    let focused = app.ui.dict_focus == DictFocus::Categories && !dimmed;

    let mut section_idx = 0usize;
    let items: Vec<CategoryItem> = CATEGORIES
        .iter()
        .map(|entry| match entry {
            Section(name) => {
                let collapsed = app
                    .ui
                    .dict_collapsed
                    .get(section_idx)
                    .copied()
                    .unwrap_or(false);
                section_idx += 1;
                CategoryItem {
                    label: name,
                    is_section: true,
                    collapsed,
                }
            }
            Category(name) => CategoryItem {
                label: name,
                is_section: false,
                collapsed: false,
            },
        })
        .collect();

    let selection = match app.ui.dict_on_section {
        Some(s) => Selection::Section(s),
        None => Selection::Item(app.ui.dict_category),
    };

    let mut list = CategoryList::new(&items, selection)
        .focused(focused)
        .title("Categories");

    if dimmed {
        list = list.dimmed(theme.dict.category_dimmed);
    }

    list.render(frame, area);
}

fn render_words(frame: &mut Frame, app: &App, area: Rect, is_searching: bool) {
    let theme = theme::get();
    let focused = app.ui.dict_focus == DictFocus::Words;

    let words: Vec<&Word> = if is_searching {
        let query = app.ui.dict_search_query.to_lowercase();
        WORDS
            .iter()
            .filter(|w| {
                w.name.to_lowercase().contains(&query)
                    || w.aliases.iter().any(|a| a.to_lowercase().contains(&query))
            })
            .collect()
    } else {
        let category = get_category_name(app.ui.dict_category);
        WORDS.iter().filter(|w| w.category == category).collect()
    };

    let show_search = app.ui.dict_search_active || is_searching;
    let (search_area, content_area) = if show_search {
        let [s, c] = Layout::vertical([Constraint::Length(1), Constraint::Fill(1)]).areas(area);
        (Some(s), c)
    } else {
        (None, area)
    };

    if let Some(sa) = search_area {
        render_search_bar(
            frame,
            sa,
            &app.ui.dict_search_query,
            app.ui.dict_search_active,
        );
    }

    let content_width = content_area.width.saturating_sub(2) as usize;

    let mut lines: Vec<RLine> = Vec::new();

    for word in &words {
        let name_bg = theme.dict.word_bg;
        let name_style = Style::new()
            .fg(theme.dict.word_name)
            .bg(name_bg)
            .add_modifier(Modifier::BOLD);
        let alias_style = Style::new().fg(theme.dict.alias).bg(name_bg);
        let name_text = if word.aliases.is_empty() {
            format!(" {}", word.name)
        } else {
            format!(" {} ({})", word.name, word.aliases.join(", "))
        };
        let padding = " ".repeat(content_width.saturating_sub(name_text.chars().count()));
        if word.aliases.is_empty() {
            lines.push(RLine::from(Span::styled(
                format!("{name_text}{padding}"),
                name_style,
            )));
        } else {
            lines.push(RLine::from(vec![
                Span::styled(format!(" {}", word.name), name_style),
                Span::styled(format!(" ({})", word.aliases.join(", ")), alias_style),
                Span::styled(padding, name_style),
            ]));
        }

        let stack_style = Style::new().fg(theme.dict.stack_sig);
        lines.push(RLine::from(vec![
            Span::raw("  "),
            Span::styled(word.stack.to_string(), stack_style),
        ]));

        let desc_style = Style::new().fg(theme.dict.description);
        lines.push(RLine::from(vec![
            Span::raw("  "),
            Span::styled(word.desc.to_string(), desc_style),
        ]));

        let example_style = Style::new().fg(theme.dict.example);
        lines.push(RLine::from(vec![
            Span::raw("  "),
            Span::styled(format!("e.g. {}", word.example), example_style),
        ]));

        lines.push(RLine::from(""));
    }

    let visible_height = content_area.height.saturating_sub(2) as usize;
    let total_lines = lines.len();
    let max_scroll = total_lines.saturating_sub(visible_height);
    app.ui.dict_max_scroll.set(max_scroll);
    let scroll = app.ui.dict_scroll().min(max_scroll);

    let title = if is_searching {
        format!("Search: {} matches", words.len())
    } else {
        let category = get_category_name(app.ui.dict_category);
        format!("{category} ({} words)", words.len())
    };
    let border_color = if focused {
        theme.dict.border_focused
    } else {
        theme.dict.border_normal
    };
    let block = Block::default()
        .borders(Borders::ALL)
        .border_style(Style::new().fg(border_color))
        .title(title);
    let para = Paragraph::new(lines)
        .scroll((scroll as u16, 0))
        .block(block);
    frame.render_widget(para, content_area);
}
