use cagire_markdown::{CodeHighlighter, MarkdownTheme};
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line as RLine, Span};
use ratatui::widgets::{Block, Borders, Padding, Paragraph, Wrap};
use ratatui::Frame;
use tui_big_text::{BigText, PixelSize};

use crate::app::App;
use crate::model::docs::{get_topic, DocEntry, DOCS};
use crate::state::HelpFocus;
use crate::theme;
use crate::views::highlight;
use crate::widgets::{render_search_bar, CategoryItem, CategoryList, Selection};

use DocEntry::{Section, Topic};

struct AppTheme;

impl MarkdownTheme for AppTheme {
    fn h1(&self) -> Style {
        Style::new()
            .fg(theme::get().markdown.h1)
            .add_modifier(Modifier::BOLD | Modifier::UNDERLINED)
    }

    fn h2(&self) -> Style {
        Style::new()
            .fg(theme::get().markdown.h2)
            .add_modifier(Modifier::BOLD)
    }

    fn h3(&self) -> Style {
        Style::new()
            .fg(theme::get().markdown.h3)
            .add_modifier(Modifier::BOLD)
    }

    fn text(&self) -> Style {
        Style::new().fg(theme::get().markdown.text)
    }

    fn code(&self) -> Style {
        Style::new().fg(theme::get().markdown.code)
    }

    fn code_border(&self) -> Style {
        Style::new().fg(theme::get().markdown.code_border)
    }

    fn link(&self) -> Style {
        Style::new()
            .fg(theme::get().markdown.link)
            .add_modifier(Modifier::UNDERLINED)
    }

    fn link_url(&self) -> Style {
        Style::new().fg(theme::get().markdown.link_url)
    }

    fn quote(&self) -> Style {
        Style::new().fg(theme::get().markdown.quote)
    }

    fn list(&self) -> Style {
        Style::new().fg(theme::get().markdown.list)
    }

    fn table_header_bg(&self) -> Color {
        theme::get().ui.surface
    }

    fn table_row_even(&self) -> Color {
        theme::get().table.row_even
    }

    fn table_row_odd(&self) -> Color {
        theme::get().table.row_odd
    }
}

struct ForthHighlighter;

impl CodeHighlighter for ForthHighlighter {
    fn highlight(&self, line: &str) -> Vec<(Style, String)> {
        highlight::highlight_line(line)
            .into_iter()
            .map(|(s, t, _)| (s, t))
            .collect()
    }
}

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

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

    render_topics(frame, app, topics_area);
    render_content(frame, app, content_area);
}

fn render_topics(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let focused = app.ui.help_focus == HelpFocus::Topics;

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

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

    CategoryList::new(&items, selection)
        .focused(focused)
        .title("Topics")
        .selected_color(theme.dict.category_selected)
        .normal_color(theme.ui.text_primary)
        .render(frame, area);
}

const WELCOME_TOPIC: usize = 0;
const BIG_TITLE_HEIGHT: u16 = 6;

fn render_content(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let Some((_, md)) = get_topic(app.ui.help_topic) else {
        return;
    };

    let is_welcome = app.ui.help_topic == WELCOME_TOPIC;
    let md_area = if is_welcome {
        let [title_area, rest] =
            Layout::vertical([Constraint::Length(BIG_TITLE_HEIGHT), Constraint::Fill(1)])
                .areas(area);

        let big_title = BigText::builder()
            .pixel_size(PixelSize::Quadrant)
            .style(Style::new().fg(theme.markdown.h1).bold())
            .lines(vec!["CAGIRE".into()])
            .centered()
            .build();

        let subtitle = Paragraph::new(RLine::from(Span::styled(
            "A Forth Sequencer",
            Style::new().fg(theme.ui.text_primary),
        )))
        .alignment(ratatui::layout::Alignment::Center);

        let [big_area, subtitle_area] =
            Layout::vertical([Constraint::Length(4), Constraint::Length(2)]).areas(title_area);

        frame.render_widget(big_title, big_area);
        frame.render_widget(subtitle, subtitle_area);
        rest
    } else {
        area
    };
    let query = &app.ui.help_search_query;
    let has_query = !query.is_empty();
    let query_lower = query.to_lowercase();

    // Populate parse cache for this topic
    {
        let mut cache = app.ui.help_parsed.borrow_mut();
        if cache[app.ui.help_topic].is_none() {
            cache[app.ui.help_topic] =
                Some(cagire_markdown::parse(md, &AppTheme, &ForthHighlighter));
        }
    }
    let cache = app.ui.help_parsed.borrow();
    let parsed = cache[app.ui.help_topic].as_ref().unwrap();

    let has_search_bar = app.ui.help_search_active || has_query;
    let content_area = if has_search_bar {
        let [content, search] =
            Layout::vertical([Constraint::Fill(1), Constraint::Length(1)]).areas(md_area);
        render_search_bar(
            frame,
            search,
            &app.ui.help_search_query,
            app.ui.help_search_active,
        );
        content
    } else {
        md_area
    };

    // Calculate dimensions: 2 borders + 4 padding (2 left + 2 right)
    let content_width = content_area.width.saturating_sub(6) as usize;
    // 2 borders + 4 padding (2 top + 2 bottom)
    let visible_height = content_area.height.saturating_sub(6) as usize;

    // Calculate total wrapped line count for accurate max_scroll
    let total_wrapped: usize = parsed
        .lines
        .iter()
        .map(|l| wrapped_line_count(l, content_width))
        .sum();
    let max_scroll = total_wrapped.saturating_sub(visible_height);
    app.ui.help_max_scroll.set(max_scroll);
    let scroll = app.ui.help_scroll().min(max_scroll);

    let mut lines = parsed.lines.clone();

    // Highlight focused code block with background tint
    if let Some(block_idx) = app.ui.help_focused_block {
        if let Some(block) = parsed.code_blocks.get(block_idx) {
            let tint_bg = theme.ui.surface;
            for line in lines.iter_mut().take(block.end_line).skip(block.start_line) {
                for (i, span) in line.spans.iter_mut().enumerate() {
                    let style = if i < 2 {
                        span.style.fg(theme.ui.accent).bg(tint_bg)
                    } else {
                        span.style.bg(tint_bg)
                    };
                    *span = Span::styled(span.content.clone(), style);
                }
            }
        }
    }

    let lines: Vec<RLine> = if has_query {
        lines
            .into_iter()
            .map(|line| highlight_line(line, &query_lower))
            .collect()
    } else {
        lines
    };

    let focused = app.ui.help_focus == HelpFocus::Content;
    let border_color = if focused {
        theme.dict.border_focused
    } else {
        theme.dict.border_normal
    };
    let para = Paragraph::new(lines)
        .scroll((scroll as u16, 0))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_style(Style::new().fg(border_color))
                .padding(Padding::new(2, 2, 2, 2)),
        )
        .wrap(Wrap { trim: false });
    frame.render_widget(para, content_area);
}

fn wrapped_line_count(line: &RLine, width: usize) -> usize {
    let char_count: usize = line.spans.iter().map(|s| s.content.chars().count()).sum();
    if char_count == 0 || width == 0 {
        1
    } else {
        char_count.div_ceil(width)
    }
}

fn highlight_line<'a>(line: RLine<'a>, query: &str) -> RLine<'a> {
    let theme = theme::get();
    let mut result: Vec<Span<'a>> = Vec::new();
    for span in line.spans {
        let lower = span.content.to_lowercase();
        if !lower.contains(query) {
            result.push(span);
            continue;
        }
        let content = span.content.to_string();
        let base_style = span.style;
        let hl_style = base_style
            .bg(theme.search.match_bg)
            .fg(theme.search.match_fg);
        let mut start = 0;
        let lower_bytes = lower.as_bytes();
        let query_bytes = query.as_bytes();
        while let Some(pos) = find_bytes(&lower_bytes[start..], query_bytes) {
            let abs = start + pos;
            if abs > start {
                result.push(Span::styled(content[start..abs].to_string(), base_style));
            }
            result.push(Span::styled(
                content[abs..abs + query.len()].to_string(),
                hl_style,
            ));
            start = abs + query.len();
        }
        if start < content.len() {
            result.push(Span::styled(content[start..].to_string(), base_style));
        }
    }
    RLine::from(result)
}

fn find_bytes(haystack: &[u8], needle: &[u8]) -> Option<usize> {
    haystack.windows(needle.len()).position(|w| w == needle)
}
