use ratatui::Frame;
/// TUI rendering logic.
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, List, ListItem, ListState, Paragraph, Wrap};

use crate::tui::app::{App, AppMode};

/// Product Hunt brand colors
const PH_ORANGE: Color = Color::Rgb(218, 85, 47);
const ACCENT_RED: Color = Color::Red;

const SPINNER_FRAMES: [&str; 10] = ["⠋", "⠙", "⠹", "⠸", "⠼", "⠴", "⠦", "⠧", "⠇", "⠏"];

/// Main render entry point.
pub fn render(frame: &mut Frame, app: &mut App) {
    let area = frame.area();
    app.set_size(area.width, area.height);
    app.spinner_tick = app.spinner_tick.wrapping_add(1);

    // Main layout: header + content + keybind bar
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3), // Header
            Constraint::Min(1),    // Content
            Constraint::Length(3), // Keybind bar
        ])
        .split(area);

    frame.render_widget(render_header(), chunks[0]);

    match app.mode {
        AppMode::Home => render_three_panel(frame, app, chunks[1]),
        AppMode::Search => render_search(frame, app, chunks[1]),
        AppMode::Login => render_login(frame, app, chunks[1]),
        AppMode::Help => render_help(frame, chunks[1]),
        AppMode::Loading => {
            let spinner = SPINNER_FRAMES[app.spinner_tick % 10];
            let loading = Paragraph::new(format!("{} Loading...", spinner))
                .style(Style::default().fg(Color::DarkGray))
                .block(
                    Block::default()
                        .borders(Borders::ALL)
                        .border_style(Style::default().fg(Color::DarkGray)),
                );
            frame.render_widget(loading, chunks[1]);
        }
        AppMode::Error => render_error(frame, app, chunks[1]),
    }

    frame.render_widget(render_keybind_bar(app), chunks[2]);
}

/// Render the header bar.
fn render_header() -> Paragraph<'static> {
    let header = Line::from(vec![
        Span::styled(
            " P ",
            Style::default()
                .fg(Color::Black)
                .bg(PH_ORANGE)
                .add_modifier(Modifier::BOLD),
        ),
        Span::raw("  "),
        Span::styled(
            "Product Hunt",
            Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD),
        ),
    ]);

    Paragraph::new(header)
        .alignment(Alignment::Center)
        .style(Style::default().bg(Color::Reset))
        .block(
            Block::default()
                .borders(Borders::BOTTOM)
                .border_style(Style::default().fg(Color::DarkGray)),
        )
}

// ─── Three-Panel Layout ────────────────────────────────────────────────────

/// Render the main three-panel view: list | content | comments.
fn render_three_panel(frame: &mut Frame, app: &App, area: Rect) {
    let chunks = Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage(25), // Posts list
            Constraint::Percentage(25), // Content
            Constraint::Percentage(50), // Comments
        ])
        .split(area);

    render_posts_list(frame, app, chunks[0]);
    render_content_panel(frame, app, chunks[1]);
    render_comments_panel(frame, app, chunks[2]);
}

/// Left panel: Today's Launches list.
fn render_posts_list(frame: &mut Frame, app: &App, area: Rect) {
    if app.loading && app.posts.is_empty() {
        let spinner = SPINNER_FRAMES[app.spinner_tick % 10];
        let loading = Paragraph::new(format!("{} Fetching launches...", spinner))
            .style(Style::default().fg(Color::DarkGray))
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(loading, area);
        return;
    }

    let items: Vec<ListItem> = app
        .posts
        .iter()
        .enumerate()
        .map(|(i, post)| {
            let is_selected = i == app.selected_index;
            let name_style = if is_selected {
                Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD)
            } else {
                Style::default().fg(PH_ORANGE)
            };

            let topics: String = post
                .topics
                .iter()
                .take(2)
                .map(|t| t.name.as_str())
                .collect::<Vec<_>>()
                .join(" • ");

            let mut lines = vec![
                Line::from(vec![Span::styled(&post.name, name_style)]),
                Line::from(vec![Span::styled(
                    &post.tagline,
                    Style::default().fg(Color::DarkGray),
                )]),
                Line::from(vec![Span::styled(
                    format!(
                        "▲ {}  •  {} comments",
                        post.votes_count, post.comments_count
                    ),
                    Style::default().fg(Color::Gray),
                )]),
            ];

            if !topics.is_empty() {
                lines.push(Line::from(vec![Span::styled(
                    topics,
                    Style::default().fg(Color::DarkGray),
                )]));
            }

            // Blank separator
            lines.push(Line::from(""));

            ListItem::new(lines)
        })
        .collect();

    let list = List::new(items)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Today's Launches ")
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        )
        .highlight_style(
            Style::default()
                .bg(Color::Rgb(60, 30, 10))
                .fg(PH_ORANGE)
                .add_modifier(Modifier::BOLD),
        );

    let mut state = ListState::default();
    state.select(Some(app.selected_index));
    frame.render_stateful_widget(list, area, &mut state);
}

/// Middle panel: Content (auto-updates on selection).
fn render_content_panel(frame: &mut Frame, app: &App, area: Rect) {
    let Some(post) = app.selected_post() else {
        let placeholder = Paragraph::new("No posts loaded")
            .style(Style::default().fg(Color::DarkGray))
            .block(
                Block::default()
                    .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM)
                    .title(" Content ")
                    .title_style(Style::default().fg(PH_ORANGE))
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(placeholder, area);
        return;
    };

    let mut lines = vec![
        Line::from(vec![Span::styled(
            &post.name,
            Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD),
        )]),
        Line::from(vec![Span::styled(
            &post.tagline,
            Style::default().fg(Color::Reset),
        )]),
        Line::from(""),
        Line::from(vec![Span::styled(
            format!(
                "▲ {}  •  {} comments",
                post.votes_count, post.comments_count
            ),
            Style::default().fg(Color::Gray),
        )]),
    ];

    // Description
    if let Some(ref desc) = post.description {
        let stripped = strip_html(desc);
        lines.push(Line::from(""));
        lines.push(Line::from(stripped));
    }

    // Topics
    if !post.topics.is_empty() {
        let topics = post
            .topics
            .iter()
            .map(|t| t.name.as_str())
            .collect::<Vec<_>>()
            .join(" • ");
        lines.push(Line::from(""));
        lines.push(Line::from(vec![
            Span::styled("Topics:  ", Style::default().fg(Color::DarkGray)),
            Span::styled(topics, Style::default().fg(Color::Gray)),
        ]));
    }

    // Makers
    if !post.makers.is_empty() {
        let maker_names: Vec<&str> = post
            .makers
            .iter()
            .map(|m| display_maker_name(&m.name))
            .collect();
        let all_unknown = maker_names.iter().all(|&n| n == "Unknown");
        let maker_display = if all_unknown {
            "Unknown".to_string()
        } else {
            maker_names
                .into_iter()
                .filter(|&n| n != "Unknown")
                .collect::<Vec<_>>()
                .join(", ")
        };
        lines.push(Line::from(""));
        lines.push(Line::from(vec![
            Span::styled("Makers:  ", Style::default().fg(Color::DarkGray)),
            Span::styled(maker_display, Style::default().fg(Color::Gray)),
        ]));
    }

    let info = Paragraph::new(lines)
        .style(Style::default().fg(Color::Reset))
        .wrap(Wrap { trim: true })
        .block(
            Block::default()
                .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM)
                .title(" Content ")
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

    frame.render_widget(info, area);
}

/// Right panel: Comments.
fn render_comments_panel(frame: &mut Frame, app: &App, area: Rect) {
    // If no post loaded yet, show placeholder
    if app.current_post.is_none() {
        let text = if app.loading {
            format!(
                "{} Loading comments...",
                SPINNER_FRAMES[app.spinner_tick % 10]
            )
        } else {
            "Select a post and press Enter to load comments".to_string()
        };
        let placeholder = Paragraph::new(text)
            .style(Style::default().fg(Color::DarkGray))
            .wrap(Wrap { trim: true })
            .block(
                Block::default()
                    .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM)
                    .title(" Comments ")
                    .title_style(Style::default().fg(PH_ORANGE))
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(placeholder, area);
        return;
    }

    let Some(detail) = &app.current_post else {
        return;
    };

    // If comments are empty and we're loading, show spinner
    if detail.comments.is_empty() && app.loading {
        let text = format!(
            "{} Loading comments...",
            SPINNER_FRAMES[app.spinner_tick % 10]
        );
        let placeholder = Paragraph::new(text)
            .style(Style::default().fg(Color::DarkGray))
            .wrap(Wrap { trim: true })
            .block(
                Block::default()
                    .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM)
                    .title(" Comments ")
                    .title_style(Style::default().fg(PH_ORANGE))
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(placeholder, area);
        return;
    }

    let flat_comments = flatten_comments_with_depth(&detail.comments, 0);

    let mut all_lines = Vec::new();
    for (comment, depth) in &flat_comments {
        let username = display_username(&comment.user.username);
        let body = strip_html(&comment.body);
        let indent = "  ".repeat(*depth);

        let header_prefix = if *depth > 0 {
            format!("{}↳ ", indent)
        } else {
            indent.clone()
        };

        all_lines.push(Line::from(vec![Span::styled(
            format!("{}{}  •  {} votes", header_prefix, username, comment.votes),
            Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD),
        )]));

        let body_prefix = if *depth > 0 {
            format!("{}  ", indent)
        } else {
            String::new()
        };

        all_lines.push(Line::from(vec![Span::styled(
            format!("{}{}", body_prefix, body),
            Style::default().fg(Color::Reset),
        )]));

        all_lines.push(Line::from(""));
    }

    let total_with_replies = count_all_comments(&detail.comments);

    let comments = Paragraph::new(all_lines)
        .style(Style::default().fg(Color::Reset))
        .wrap(Wrap { trim: true })
        .scroll((app.comment_scroll, 0))
        .block(
            Block::default()
                .borders(Borders::LEFT | Borders::TOP | Borders::BOTTOM)
                .title(format!(" {} comments ", total_with_replies))
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

    frame.render_widget(comments, area);
}

fn flatten_comments_with_depth(
    comments: &[ph_cli_core::Comment],
    depth: usize,
) -> Vec<(ph_cli_core::Comment, usize)> {
    let mut result = Vec::new();
    for comment in comments {
        result.push((comment.clone(), depth));
        if !comment.child_comments.is_empty() {
            result.extend(flatten_comments_with_depth(
                &comment.child_comments,
                depth + 1,
            ));
        }
    }
    result
}

fn count_all_comments(comments: &[ph_cli_core::Comment]) -> usize {
    comments
        .iter()
        .map(|c| 1 + count_all_comments(&c.child_comments))
        .sum()
}

// ─── Other Modes ───────────────────────────────────────────────────────────

fn render_search(frame: &mut Frame, app: &App, area: Rect) {
    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([Constraint::Length(3), Constraint::Min(1)])
        .split(area);

    let input = Paragraph::new(format!("  🔍  {}", app.search_input))
        .style(Style::default().fg(Color::Reset))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Search ")
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );
    frame.render_widget(input, chunks[0]);

    if app.loading {
        let spinner = SPINNER_FRAMES[app.spinner_tick % 10];
        let loading = Paragraph::new(format!("{} Searching...", spinner))
            .style(Style::default().fg(Color::DarkGray))
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(loading, chunks[1]);
    } else if app.search_results.is_empty() {
        let msg = if app.search_input.is_empty() {
            "Type to search products..."
        } else {
            "No results found."
        };
        let help = Paragraph::new(msg)
            .style(Style::default().fg(Color::DarkGray))
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_style(Style::default().fg(Color::DarkGray)),
            );
        frame.render_widget(help, chunks[1]);
    } else {
        let items: Vec<ListItem> = app
            .search_results
            .iter()
            .enumerate()
            .map(|(i, result)| {
                let is_selected = i == app.selected_index;
                let style = if is_selected {
                    Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD)
                } else {
                    Style::default().fg(Color::Reset)
                };

                let icon = match result {
                    ph_cli_core::SearchResult::Post(_) => "🔶",
                    ph_cli_core::SearchResult::User(_) => "👤",
                };

                let prefix = if is_selected { "▸ " } else { "  " };
                ListItem::new(Line::from(vec![
                    Span::raw(prefix),
                    Span::raw(icon),
                    Span::raw(" "),
                    Span::styled(result.label(), style),
                    Span::raw(" - "),
                    Span::styled(result.sub_label(), Style::default().fg(Color::DarkGray)),
                ]))
            })
            .collect();

        let list = List::new(items).block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Results ")
                .title_style(Style::default().fg(PH_ORANGE))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

        let mut state = ListState::default();
        state.select(Some(app.selected_index));
        frame.render_stateful_widget(list, chunks[1], &mut state);
    }
}

fn render_login(frame: &mut Frame, _app: &App, area: Rect) {
    let lines = vec![
        Line::from(vec![Span::styled(
            "Authentication",
            Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD),
        )]),
        Line::from(""),
        Line::from(vec![Span::styled(
            "Run `ph login` in your terminal to authenticate.",
            Style::default().fg(Color::Reset),
        )]),
        Line::from(""),
        Line::from(vec![Span::styled(
            "1. Go to https://www.producthunt.com/v2/oauth/applications",
            Style::default().fg(Color::DarkGray),
        )]),
        Line::from(vec![Span::styled(
            "2. Create a new app and copy the Developer Token",
            Style::default().fg(Color::DarkGray),
        )]),
        Line::from(vec![Span::styled(
            "3. Run `ph login` and paste the token",
            Style::default().fg(Color::DarkGray),
        )]),
        Line::from(""),
        Line::from(vec![Span::styled(
            "Press Esc or q to go back",
            Style::default().fg(Color::DarkGray),
        )]),
    ];

    let block = Paragraph::new(lines)
        .style(Style::default().fg(Color::Reset))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Login ")
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

    frame.render_widget(block, area);
}

fn render_help(frame: &mut Frame, area: Rect) {
    let lines = vec![
        Line::from(vec![Span::styled(
            "Keybindings",
            Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD),
        )]),
        Line::from(""),
        Line::from(vec![
            Span::styled(
                "j / ↓",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("         Move down"),
        ]),
        Line::from(vec![
            Span::styled(
                "k / ↑",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("         Move up"),
        ]),
        Line::from(vec![
            Span::styled(
                "Enter",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("       Load post comments"),
        ]),
        Line::from(vec![
            Span::styled(
                "/ or s",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("       Open search"),
        ]),
        Line::from(vec![
            Span::styled(
                "r",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("           Refresh today's launches"),
        ]),
        Line::from(vec![
            Span::styled(
                "Esc",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("         Unload comments / go back"),
        ]),
        Line::from(vec![
            Span::styled(
                "q",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("           Quit"),
        ]),
        Line::from(vec![
            Span::styled(
                "?",
                Style::default()
                    .fg(Color::Reset)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw("           Show this help"),
        ]),
        Line::from(""),
        Line::from(vec![Span::styled(
            "Press any key to close",
            Style::default().fg(Color::DarkGray),
        )]),
    ];

    let help = Paragraph::new(lines)
        .style(Style::default().fg(Color::Reset))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Help ")
                .title_style(Style::default().fg(PH_ORANGE).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

    frame.render_widget(help, area);
}

fn render_error(frame: &mut Frame, app: &App, area: Rect) {
    let mut lines = vec![
        Line::from(vec![Span::styled(
            "Error",
            Style::default().fg(ACCENT_RED).add_modifier(Modifier::BOLD),
        )]),
        Line::from(""),
    ];

    if let Some(ref msg) = app.error_message {
        lines.push(Line::from(vec![Span::styled(
            msg,
            Style::default().fg(Color::Reset),
        )]));
    }

    lines.push(Line::from(""));
    lines.push(Line::from(vec![Span::styled(
        "Press Esc or q to go back",
        Style::default().fg(Color::DarkGray),
    )]));

    let error = Paragraph::new(lines)
        .style(Style::default().fg(Color::Reset))
        .block(
            Block::default()
                .borders(Borders::ALL)
                .title(" Error ")
                .title_style(Style::default().fg(ACCENT_RED).add_modifier(Modifier::BOLD))
                .border_style(Style::default().fg(Color::DarkGray)),
        );

    frame.render_widget(error, area);
}

// ─── Keybind Bar ───────────────────────────────────────────────────────────

fn render_keybind_bar(app: &App) -> Paragraph<'_> {
    // Two lines: position/status on line 1, all keybinds on line 2
    let mut lines = vec![];

    if let Some(ref msg) = app.status_message {
        lines.push(Line::from(vec![Span::styled(
            msg,
            Style::default().fg(Color::DarkGray),
        )]));
    } else if !app.posts.is_empty() && app.mode == AppMode::Home {
        lines.push(Line::from(vec![Span::styled(
            format!(" {}/{} ", app.selected_index + 1, app.posts.len()),
            Style::default().fg(Color::DarkGray),
        )]));
    } else {
        lines.push(Line::from(""));
    }

    let hints = match app.mode {
        AppMode::Home => {
            if app.current_post.is_some() {
                "j/k scroll  •  Esc back  •  / search  •  r refresh  •  ? help  •  q quit"
            } else {
                "j/k nav  •  Enter comments  •  / search  •  r refresh  •  ? help  •  q quit"
            }
        }
        AppMode::Search => "type to search  •  ↑↓ navigate  •  Enter open  •  Esc back",
        AppMode::Help | AppMode::Error | AppMode::Login => "Esc / q to go back",
        AppMode::Loading => {
            let s = format!("{} Loading...", SPINNER_FRAMES[app.spinner_tick % 10]);
            lines.push(Line::from(vec![Span::styled(
                s,
                Style::default().fg(Color::Gray),
            )]));
            return Paragraph::new(lines)
                .alignment(Alignment::Center)
                .style(Style::default().bg(Color::Reset))
                .wrap(Wrap { trim: true })
                .block(
                    Block::default()
                        .borders(Borders::TOP)
                        .border_style(Style::default().fg(Color::DarkGray)),
                );
        }
    };

    lines.push(Line::from(vec![Span::styled(
        hints,
        Style::default().fg(Color::Gray),
    )]));

    Paragraph::new(lines)
        .alignment(Alignment::Center)
        .style(Style::default().bg(Color::Reset))
        .wrap(Wrap { trim: true })
        .block(
            Block::default()
                .borders(Borders::TOP)
                .border_style(Style::default().fg(Color::DarkGray)),
        )
}

// ─── Utility Functions ─────────────────────────────────────────────────────

/// Strip HTML tags and decode common entities.
fn strip_html(html: &str) -> String {
    let mut result = String::with_capacity(html.len());
    let mut inside_tag = false;

    for c in html.chars() {
        match c {
            '<' => inside_tag = true,
            '>' => inside_tag = false,
            _ if !inside_tag => result.push(c),
            _ => {}
        }
    }

    result
        .replace("&amp;", "&")
        .replace("&lt;", "<")
        .replace("&gt;", ">")
        .replace("&quot;", "\"")
        .replace("&#39;", "'")
        .replace("&apos;", "'")
        .replace("&nbsp;", " ")
        .split_whitespace()
        .collect::<Vec<_>>()
        .join(" ")
}

/// Return a display name for makers, falling back to "Unknown" for anonymous.
fn display_maker_name(name: &str) -> &str {
    let trimmed = name.trim();
    if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("[REDACTED]") {
        "Unknown"
    } else {
        trimmed
    }
}

/// Return a display username, falling back to "anonymous" for redacted/empty.
fn display_username(username: &str) -> String {
    let trimmed = username.trim();
    if trimmed.is_empty() || trimmed.eq_ignore_ascii_case("[REDACTED]") {
        "anonymous".to_string()
    } else {
        trimmed.to_string()
    }
}
