use ratatui::Frame;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, BorderType, Borders, List, ListItem, Padding, Paragraph, Wrap};

use crate::app::{App, PendingTask};
use crate::tui::render::icons::icons;
use crate::tui::render::logo::{logo_fits, logo_lines, logo_lines_for_area, welcome_lines};
use crate::tui::render::{get_theme, render_statusbar};
use crate::tui::syntax::{parse_markdown_lines, wrap_lines, wrap_text_to_lines};

pub(crate) fn render_chat(frame: &mut Frame, app: &App) {
    let area = frame.area();
    let suggestions = app.command_suggestions();
    let suggestion_height = if suggestions.is_empty() {
        0
    } else {
        suggestions.len().min(3) as u16 + 2
    };

    // Max 6 lines for input (border=2, padding=2, so inner width = area.width - 4)
    let input_inner_w = if app.chat.messages.is_empty() {
        // Centered welcome input box is 70% of screen width
        ((area.width as u32 * 70 / 100) as u16)
            .saturating_sub(4)
            .max(1)
    } else {
        area.width.saturating_sub(4).max(1)
    };
    let wrapped_lines = wrap_text_to_lines(app.chat.input.as_str(), input_inner_w as usize);
    let display_lines = wrapped_lines.len() as u16;
    let input_height = display_lines.clamp(1, 5) + 2;

    let (
        messages_area,
        suggestions_area,
        queue_area,
        input_area,
        statusbar_area,
        logo_area,
        tips_area,
    ) = if app.chat.messages.is_empty() {
        let logo_lines = logo_lines();
        let logo_fits_flag = logo_fits(&logo_lines, area.width, 5);

        let remaining_height = area
            .height
            .saturating_sub(input_height + 1 + suggestion_height); // 1 for status bar
        let half_height = remaining_height / 2;
        let top_height = half_height;
        let bottom_height = remaining_height.saturating_sub(half_height);

        let main_chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(top_height),
                Constraint::Length(suggestion_height),
                Constraint::Length(input_height),
                Constraint::Length(bottom_height),
                Constraint::Length(1), // Status bar
            ])
            .split(area);

        let logo_spacer = if top_height >= 8 {
            3
        } else if top_height >= 7 {
            2
        } else if top_height >= 6 {
            1
        } else {
            0
        };

        let top_chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Min(0),
                Constraint::Length(5), // Logo (5 lines)
                Constraint::Length(logo_spacer),
            ])
            .split(main_chunks[0]);

        let bottom_chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(1), // Spacer
                Constraint::Length(1), // Tips line
                Constraint::Min(0),
            ])
            .split(main_chunks[3]);

        let centered_suggestions_box = if suggestion_height > 0 {
            Some(
                Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([
                        Constraint::Percentage(15),
                        Constraint::Percentage(70),
                        Constraint::Percentage(15),
                    ])
                    .split(main_chunks[1])[1],
            )
        } else {
            None
        };

        let centered_input_box = Layout::default()
            .direction(Direction::Horizontal)
            .constraints([
                Constraint::Percentage(15),
                Constraint::Percentage(70),
                Constraint::Percentage(15),
            ])
            .split(main_chunks[2])[1];

        let logo_area = if logo_fits_flag && top_height >= 5 {
            Some(top_chunks[1])
        } else {
            None
        };

        let tips_area = if bottom_height >= 2 {
            Some(bottom_chunks[1])
        } else {
            None
        };

        (
            None,
            centered_suggestions_box,
            None,
            Some(centered_input_box),
            main_chunks[4],
            logo_area,
            tips_area,
        )
    } else {
        let queue_height = if app.chat.message_queue.is_empty() {
            0
        } else {
            (app.chat.message_queue.len().min(3) as u16) + 1
        };

        let normal_chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Min(4),
                Constraint::Length(suggestion_height),
                Constraint::Length(queue_height),
                Constraint::Length(input_height),
                Constraint::Length(1),
            ])
            .split(area);

        (
            Some(normal_chunks[0]),
            if suggestion_height > 0 {
                Some(normal_chunks[1])
            } else {
                None
            },
            if queue_height > 0 {
                Some(normal_chunks[2])
            } else {
                None
            },
            Some(normal_chunks[3]),
            normal_chunks[4],
            None,
            None,
        )
    };

    if let Some(logo_area) = logo_area {
        let logo = logo_lines_for_area(logo_area.width, 5);
        frame.render_widget(Paragraph::new(logo).alignment(Alignment::Center), logo_area);
    }

    if let Some(tips_area) = tips_area {
        let tips_line = Line::from(vec![
            Span::styled(
                "Ctrl+S",
                Style::default()
                    .fg(Color::Rgb(236, 72, 153))
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled(" Setup  •  ", Style::default().fg(Color::DarkGray)),
            Span::styled(
                "Ctrl+P",
                Style::default()
                    .fg(Color::Rgb(168, 85, 247))
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled(" Model  •  ", Style::default().fg(Color::DarkGray)),
            Span::styled(
                "/help",
                Style::default()
                    .fg(Color::Rgb(59, 130, 246))
                    .add_modifier(Modifier::BOLD),
            ),
            Span::styled(" Help", Style::default().fg(Color::DarkGray)),
        ]);
        frame.render_widget(
            Paragraph::new(tips_line).alignment(Alignment::Center),
            tips_area,
        );
    }

    if let Some(messages_area) = messages_area {
        if !app.chat.todos.is_empty() {
            if messages_area.width >= 90 {
                let layout = Layout::default()
                    .direction(Direction::Horizontal)
                    .constraints([Constraint::Min(40), Constraint::Length(32)])
                    .split(messages_area);
                render_messages(frame, app, layout[0]);
                render_todos(frame, app, layout[1]);
            } else {
                let layout = Layout::default()
                    .direction(Direction::Vertical)
                    .constraints([Constraint::Min(10), Constraint::Length(8)])
                    .split(messages_area);
                render_messages(frame, app, layout[0]);
                render_todos(frame, app, layout[1]);
            }
        } else {
            render_messages(frame, app, messages_area);
        }
    }

    if let Some(suggestions_area) = suggestions_area {
        render_command_suggestions(frame, app, suggestions_area);
    }

    if let Some(queue_area) = queue_area {
        let mut queue_lines = vec![Line::from(Span::styled(
            " Queued Prompts: ",
            Style::default()
                .fg(Color::DarkGray)
                .add_modifier(Modifier::BOLD),
        ))];
        for (idx, item) in app.chat.message_queue.iter().enumerate().take(3) {
            let truncated_item = if item.chars().count() > 60 {
                let s: String = item.chars().take(57).collect();
                format!("{}...", s)
            } else {
                item.clone()
            };
            queue_lines.push(Line::from(vec![
                Span::styled(
                    format!("  [{}] ", idx + 1),
                    Style::default().fg(Color::DarkGray),
                ),
                Span::styled(truncated_item, Style::default().fg(Color::DarkGray)),
            ]));
        }
        if app.chat.message_queue.len() > 3 {
            let remaining = app.chat.message_queue.len() - 3;
            queue_lines.push(Line::from(Span::styled(
                format!("  ... and {} more", remaining),
                Style::default().fg(Color::DarkGray),
            )));
        }
        frame.render_widget(Paragraph::new(queue_lines), queue_area);
    }

    let input_box = input_area.unwrap();

    // --- Render input paragraph with scroll ---
    // inner content width: border(1 each side) + padding(1 each side) = -4 total
    let input_inner_width = input_box.width.saturating_sub(4).max(1);
    let total_visual_lines = display_lines;

    // Compute cursor visual row and column
    let mut cursor_visual_row: u16 = 0;
    let mut cursor_col_in_logical: u16 = 0; // position within current logical line
    for (i, c) in app.chat.input.chars().enumerate() {
        if i == app.chat.cursor {
            break;
        }
        if c == '\n' {
            cursor_visual_row += 1;
            cursor_col_in_logical = 0;
        } else {
            cursor_col_in_logical += 1;
            if cursor_col_in_logical.is_multiple_of(input_inner_width) {
                cursor_visual_row += 1;
            }
        }
    }
    let cursor_x = cursor_col_in_logical % input_inner_width;

    // Auto-scroll: keep cursor visible within the 5-row viewport (rows 0..4 inside box)
    let max_visible: u16 = (input_height - 2).min(5); // rows inside border
    let mut input_scroll = app.chat.input_scroll;
    if cursor_visual_row < input_scroll {
        input_scroll = cursor_visual_row;
    } else if cursor_visual_row >= input_scroll + max_visible {
        input_scroll = cursor_visual_row + 1 - max_visible;
    }
    let max_scroll = total_visual_lines.saturating_sub(max_visible);
    input_scroll = input_scroll.min(max_scroll);

    let paragraph_content: Vec<Line> = wrapped_lines.into_iter().map(Line::from).collect();

    let border_style = if app.chat.shell_focused {
        Style::default().fg(Color::DarkGray)
    } else {
        Style::default()
    };

    frame.render_widget(
        Paragraph::new(paragraph_content)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(BorderType::Rounded)
                    .border_style(border_style)
                    .title(" Message (Alt+Enter: newline) ")
                    .padding(Padding::horizontal(1)),
            )
            .scroll((input_scroll, 0)),
        input_box,
    );

    // Cursor position: border(1) + padding(1) = offset 2 on x; border(1) on y
    let cursor_y_in_box = cursor_visual_row.saturating_sub(input_scroll);
    let target_y = input_box.y + 1 + cursor_y_in_box;
    let max_y = input_box.bottom().saturating_sub(2);

    if !app.chat.shell_focused && target_y <= max_y && target_y > input_box.y {
        frame.set_cursor_position((input_box.x + 2 + cursor_x, target_y));
    }

    render_statusbar(frame, app, statusbar_area);

    if let Some(PendingTask::ConfirmFunction { name, args }) = &app.pending {
        let popup_area = if name == "edit" || name == "write" || name == "patch" {
            centered_rect(80, 70, area)
        } else {
            centered_rect(60, 50, area)
        };
        let inner_width = (popup_area.width as usize).saturating_sub(4);

        let mut text = vec![
            Line::from(vec![
                Span::styled("Tool: ", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(name),
            ]),
            Line::from(""),
        ];

        if name == "edit" {
            if let Some(edits) = args.get("edits").and_then(|v| v.as_array()) {
                for (idx, edit_val) in edits.iter().enumerate() {
                    let path = edit_val.get("path").and_then(|v| v.as_str()).unwrap_or("");
                    let old_str = edit_val
                        .get("old_string")
                        .and_then(|v| v.as_str())
                        .unwrap_or("");
                    let new_str = edit_val
                        .get("new_string")
                        .and_then(|v| v.as_str())
                        .unwrap_or("");

                    text.push(Line::from(vec![
                        Span::styled(
                            format!("Edit #{} (", idx + 1),
                            Style::default().add_modifier(Modifier::BOLD),
                        ),
                        Span::styled(path, Style::default().fg(Color::Cyan)),
                        Span::styled("):", Style::default().add_modifier(Modifier::BOLD)),
                    ]));

                    for line in old_str.lines() {
                        let formatted = format!("- {line}");
                        let padded = format!("{formatted:<width$}", width = inner_width);
                        text.push(Line::from(Span::styled(
                            padded,
                            Style::default()
                                .fg(Color::Rgb(255, 180, 180))
                                .bg(Color::Rgb(70, 20, 20)),
                        )));
                    }
                    for line in new_str.lines() {
                        let formatted = format!("+ {line}");
                        let padded = format!("{formatted:<width$}", width = inner_width);
                        text.push(Line::from(Span::styled(
                            padded,
                            Style::default()
                                .fg(Color::Rgb(180, 255, 180))
                                .bg(Color::Rgb(20, 60, 20)),
                        )));
                    }
                    text.push(Line::from(""));
                }
            } else if args.get("start_line").is_some() {
                let path = args.get("path").and_then(|v| v.as_str()).unwrap_or("");
                let start_line =
                    args.get("start_line").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
                let end_line = args.get("end_line").and_then(|v| v.as_u64()).unwrap_or(1) as usize;
                let new_str = args
                    .get("new_content")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                text.push(Line::from(vec![
                    Span::styled("File: ", Style::default().add_modifier(Modifier::BOLD)),
                    Span::styled(path, Style::default().fg(Color::Cyan)),
                    Span::raw(format!(" (lines {}-{})", start_line, end_line)),
                ]));
                text.push(Line::from(""));
                text.push(Line::from(Span::styled(
                    "Diff Preview:",
                    Style::default().add_modifier(Modifier::BOLD),
                )));

                if let Ok(content) = std::fs::read_to_string(path) {
                    let lines: Vec<&str> = content.lines().collect();
                    if start_line > 0 && start_line <= lines.len() {
                        let end_idx = std::cmp::min(end_line, lines.len());
                        for line in lines.iter().take(end_idx).skip(start_line - 1) {
                            let formatted = format!("- {line}");
                            let padded = format!("{formatted:<width$}", width = inner_width);
                            text.push(Line::from(Span::styled(
                                padded,
                                Style::default()
                                    .fg(Color::Rgb(255, 180, 180))
                                    .bg(Color::Rgb(70, 20, 20)),
                            )));
                        }
                    }
                }
                for line in new_str.lines() {
                    let formatted = format!("+ {line}");
                    let padded = format!("{formatted:<width$}", width = inner_width);
                    text.push(Line::from(Span::styled(
                        padded,
                        Style::default()
                            .fg(Color::Rgb(180, 255, 180))
                            .bg(Color::Rgb(20, 60, 20)),
                    )));
                }
                text.push(Line::from(""));
            } else {
                let path = args.get("path").and_then(|v| v.as_str()).unwrap_or("");
                let old_str = args
                    .get("old_string")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");
                let new_str = args
                    .get("new_string")
                    .and_then(|v| v.as_str())
                    .unwrap_or("");

                text.push(Line::from(vec![
                    Span::styled("File: ", Style::default().add_modifier(Modifier::BOLD)),
                    Span::styled(path, Style::default().fg(Color::Cyan)),
                ]));
                text.push(Line::from(""));
                text.push(Line::from(Span::styled(
                    "Diff Preview:",
                    Style::default().add_modifier(Modifier::BOLD),
                )));

                for line in old_str.lines() {
                    let formatted = format!("- {line}");
                    let padded = format!("{formatted:<width$}", width = inner_width);
                    text.push(Line::from(Span::styled(
                        padded,
                        Style::default()
                            .fg(Color::Rgb(255, 180, 180))
                            .bg(Color::Rgb(70, 20, 20)),
                    )));
                }
                for line in new_str.lines() {
                    let formatted = format!("+ {line}");
                    let padded = format!("{formatted:<width$}", width = inner_width);
                    text.push(Line::from(Span::styled(
                        padded,
                        Style::default()
                            .fg(Color::Rgb(180, 255, 180))
                            .bg(Color::Rgb(20, 60, 20)),
                    )));
                }
                text.push(Line::from(""));
            }
        } else if name == "write" {
            let path = args.get("path").and_then(|v| v.as_str()).unwrap_or("");
            let content = args.get("content").and_then(|v| v.as_str()).unwrap_or("");

            text.push(Line::from(vec![
                Span::styled("File: ", Style::default().add_modifier(Modifier::BOLD)),
                Span::styled(path, Style::default().fg(Color::Cyan)),
            ]));
            text.push(Line::from(""));
            text.push(Line::from(Span::styled(
                "Content to Write:",
                Style::default().add_modifier(Modifier::BOLD),
            )));

            for line in content.lines() {
                let formatted = format!("+ {line}");
                let padded = format!("{formatted:<width$}", width = inner_width);
                text.push(Line::from(Span::styled(
                    padded,
                    Style::default()
                        .fg(Color::Rgb(180, 255, 180))
                        .bg(Color::Rgb(20, 60, 20)),
                )));
            }
            text.push(Line::from(""));
        } else if name == "patch" {
            let patch = args.get("patch").and_then(|v| v.as_str()).unwrap_or("");
            text.push(Line::from(Span::styled(
                "Patch Preview:",
                Style::default().add_modifier(Modifier::BOLD),
            )));
            text.push(Line::from(""));

            for line in patch.lines().take(15) {
                let style = if line.starts_with('+') && !line.starts_with("+++") {
                    Style::default()
                        .fg(Color::Rgb(180, 255, 180))
                        .bg(Color::Rgb(20, 60, 20))
                } else if line.starts_with('-') && !line.starts_with("---") {
                    Style::default()
                        .fg(Color::Rgb(255, 180, 180))
                        .bg(Color::Rgb(70, 20, 20))
                } else if line.starts_with("@@") {
                    Style::default().fg(Color::Cyan)
                } else {
                    Style::default()
                };
                let padded = format!("{line:<width$}", width = inner_width);
                text.push(Line::from(Span::styled(padded, style)));
            }
            if patch.lines().count() > 15 {
                text.push(Line::from(Span::styled(
                    format!("... and {} more lines", patch.lines().count() - 15),
                    Style::default().add_modifier(Modifier::ITALIC),
                )));
            }
            text.push(Line::from(""));
        } else {
            text.push(Line::from(vec![
                Span::styled("Args: ", Style::default().add_modifier(Modifier::BOLD)),
                Span::raw(serde_json::to_string_pretty(args).unwrap_or_default()),
            ]));
            text.push(Line::from(""));
        }

        text.push(Line::from(vec![
            Span::styled(
                "[Y]",
                Style::default()
                    .fg(Color::Green)
                    .add_modifier(Modifier::BOLD),
            ),
            Span::raw(" Allow   "),
            Span::styled(
                "[N]",
                Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
            ),
            Span::raw(" Deny"),
        ]));

        frame.render_widget(ratatui::widgets::Clear, popup_area);
        frame.render_widget(
            Paragraph::new(text)
                .block(
                    Block::default()
                        .borders(Borders::ALL)
                        .border_type(BorderType::Rounded)
                        .title(" Tool Access Request (↑/↓ to scroll) "),
                )
                .wrap(Wrap { trim: true })
                .scroll((app.confirm_scroll, 0)),
            popup_area,
        );
    }
}

pub(crate) fn render_messages(frame: &mut Frame, app: &App, area: Rect) {
    let shell_focused = app.chat.shell_focused;
    if app.chat.messages.is_empty() {
        app.chat.messages_area.set(Some(area));
        *app.chat.message_line_ranges.borrow_mut() = Vec::new();
        let lines = welcome_lines(area);
        frame.render_widget(Paragraph::new(lines).wrap(Wrap { trim: false }), area);
        return;
    }

    let mut all_lines = Vec::new();
    let width = area.width.saturating_sub(4);

    let push_margin = |lines: &mut Vec<Line<'static>>| {
        if let Some(last) = lines.last()
            && (!last.spans.is_empty() || last.width() > 0)
        {
            lines.push(Line::from(""));
        }
    };

    let last_shell_idx = app.chat.messages.iter().rposition(|m| m.is_shell);
    let mut prev_is_tool = false;
    let mut message_line_ranges = Vec::new();

    for (msg_idx, message) in app.chat.messages.iter().enumerate() {
        let mut content = if message.pending {
            app.busy_label().unwrap_or_else(|| "Working...".to_owned())
        } else {
            message.text.clone()
        };

        let is_tool = message.is_tool;
        let is_shell = message.is_shell;

        // Strip "(empty)" prefix and any leading/trailing whitespace/newlines that follow it
        if !message.pending && !is_tool && !is_shell && message.author == "Darwin" {
            let trimmed = content.trim_start();
            if let Some(rest) = trimmed.strip_prefix("(empty)") {
                content = rest.trim_start().to_owned();
            }
        }

        // Skip completely empty assistant messages
        if !message.pending
            && !is_tool
            && !is_shell
            && message.author == "Darwin"
            && content.trim().is_empty()
        {
            continue;
        }

        let cached_ok = {
            let cache = message.cached_wrapped.borrow();
            if let Some((w, t, ref cached_lines)) = *cache {
                if w == width as usize && t == get_theme(app) {
                    Some(cached_lines.clone())
                } else {
                    None
                }
            } else {
                None
            }
        };

        let msg_lines = if let Some(lines) = cached_ok {
            lines
        } else {
            let mut msg_lines = Vec::new();
            if is_shell {
                let is_focused_shell = if shell_focused {
                    if let Some(ref active_id) = app.chat.focused_shell_session_id {
                        let last_of_session =
                            app.chat.messages.iter().enumerate().rposition(|(_, m)| {
                                m.is_shell && m.shell_session_id.as_ref() == Some(active_id)
                            });
                        Some(msg_idx) == last_of_session
                    } else if let Some(pid) = app.chat.focused_shell_pid {
                        message.shell_pid == Some(pid)
                    } else {
                        Some(msg_idx) == last_shell_idx
                    }
                } else {
                    false
                };

                let border_color = if is_focused_shell {
                    Color::Rgb(59, 130, 246)
                } else {
                    Color::DarkGray
                };
                let border_style = Style::default().fg(border_color);

                let title_style = if is_focused_shell {
                    Style::default()
                        .fg(Color::Rgb(59, 130, 246))
                        .add_modifier(Modifier::BOLD)
                } else {
                    Style::default().add_modifier(Modifier::BOLD)
                };

                let cmd_single_line = message.shell_cmd.replace(['\n', '\r'], " ");
                let title_w = (width as usize).saturating_sub(1);
                let max_title_cmd_len = title_w.saturating_sub(12); // "✗ Shell " is 8 chars, some margin
                let cmd_vis_w = visual_width(&cmd_single_line);
                let cmd_summary = if cmd_vis_w > max_title_cmd_len {
                    let truncate_w = max_title_cmd_len.saturating_sub(3);
                    let (truncated, _) =
                        clean_and_truncate_to_visual_width(&cmd_single_line, truncate_w);
                    format!("{}...", truncated)
                } else {
                    let (cleaned, _) =
                        clean_and_truncate_to_visual_width(&cmd_single_line, max_title_cmd_len);
                    cleaned
                };
                let icon = if message.shell_success {
                    icons::SHELL_OK
                } else {
                    icons::SHELL_ERR
                };
                let title = format!("{icon} Shell {cmd_summary}");

                msg_lines.push(Line::from(Span::styled(
                    format!("╭{}╮", "─".repeat(width as usize)),
                    border_style,
                )));

                let (cleaned_title, title_vis_w) =
                    clean_and_truncate_to_visual_width(&title, title_w);
                let padded_title = format!(
                    "{}{}",
                    cleaned_title,
                    " ".repeat(title_w.saturating_sub(title_vis_w))
                );

                msg_lines.push(Line::from(vec![
                    Span::styled("│ ", border_style),
                    Span::styled(padded_title, title_style),
                    Span::styled("│", border_style),
                ]));

                let body_w = (width as usize).saturating_sub(2);
                let wrapped_body_lines = wrap_text_to_lines(&content, body_w);
                let max_shell_lines = 25;
                let (display_lines, _truncated_count) =
                    if wrapped_body_lines.len() > max_shell_lines {
                        let keep_start = 10;
                        let keep_end = 12;
                        let mut lines_to_show = Vec::new();
                        for l in wrapped_body_lines.iter().take(keep_start) {
                            lines_to_show.push(l.clone());
                        }
                        lines_to_show.push(format!(
                            "... [{} lines truncated] ...",
                            wrapped_body_lines.len() - keep_start - keep_end
                        ));
                        for l in wrapped_body_lines
                            .iter()
                            .skip(wrapped_body_lines.len() - keep_end)
                        {
                            lines_to_show.push(l.clone());
                        }
                        (
                            lines_to_show,
                            wrapped_body_lines.len() - keep_start - keep_end,
                        )
                    } else {
                        (wrapped_body_lines, 0)
                    };

                for line in display_lines {
                    let is_truncation_marker = line.starts_with("... [") && line.ends_with("] ...");
                    let (cleaned, line_vis_w) = clean_and_truncate_to_visual_width(&line, body_w);
                    let padded_line = format!(
                        "{}{}",
                        cleaned,
                        " ".repeat(body_w.saturating_sub(line_vis_w))
                    );

                    let style = if is_truncation_marker {
                        Style::default()
                            .fg(Color::Yellow)
                            .add_modifier(Modifier::ITALIC)
                    } else {
                        Style::default().fg(Color::DarkGray)
                    };

                    msg_lines.push(Line::from(vec![
                        Span::styled("│  ", border_style),
                        Span::styled(padded_line, style),
                        Span::styled("│", border_style),
                    ]));
                }
                msg_lines.push(Line::from(Span::styled(
                    format!("╰{}╯", "─".repeat(width as usize)),
                    border_style,
                )));
            } else {
                let lines_count = content.lines().count();
                let limit = 500;
                let (display_content, is_truncated) = if lines_count > limit {
                    let truncated: String = content
                        .lines()
                        .take(limit)
                        .collect::<Vec<&str>>()
                        .join("\n");
                    (truncated, true)
                } else {
                    (content.clone(), false)
                };

                let parsed_lines = parse_markdown_lines(&display_content);
                let mut wrapped_parsed_lines = wrap_lines(parsed_lines, width as usize);
                if is_truncated {
                    wrapped_parsed_lines.push(Line::from(Span::styled(
                        format!("... [Message truncated: {} more lines. Use a paging tool or scroll inside editor to view full text.]", lines_count - limit),
                        Style::default().fg(Color::Yellow).add_modifier(Modifier::ITALIC)
                    )));
                }

                match message.author {
                    "You" => {
                        let theme = get_theme(app);
                        let (user_bg, user_fg) = match theme {
                            crate::config::Theme::Dark | crate::config::Theme::Auto => {
                                (Color::Rgb(255, 255, 255), Color::Rgb(0, 0, 0))
                            }
                            crate::config::Theme::Light => {
                                (Color::Rgb(0, 0, 0), Color::Rgb(255, 255, 255))
                            }
                        };
                        let user_style = Style::default().bg(user_bg).fg(user_fg);
                        let block_width = area.width as usize;

                        msg_lines.push(Line::from(Span::styled(
                            " ".repeat(block_width),
                            user_style,
                        )));

                        for line in wrapped_parsed_lines {
                            let mut spans = vec![Span::styled("  ", user_style)];
                            let line_text_width = line.width();
                            for s in &line.spans {
                                let style = s.style.patch(user_style);
                                spans.push(s.clone().style(style));
                            }

                            let remaining = block_width.saturating_sub(line_text_width + 2);
                            if remaining > 0 {
                                spans.push(Span::styled(" ".repeat(remaining), user_style));
                            }
                            msg_lines.push(Line::from(spans));
                        }

                        msg_lines.push(Line::from(Span::styled(
                            " ".repeat(block_width),
                            user_style,
                        )));
                    }
                    "System" => {
                        msg_lines.push(Line::from(vec![Span::styled(
                            "system error",
                            Style::default().add_modifier(Modifier::BOLD).fg(Color::Red),
                        )]));
                        for line in wrapped_parsed_lines {
                            let mut spans = Vec::new();
                            for span in line.spans {
                                spans.push(span.style(Style::default().fg(Color::Red)));
                            }
                            msg_lines.push(Line::from(spans));
                        }
                    }
                    _ => {
                        for line in wrapped_parsed_lines {
                            let mut spans = vec![Span::raw("  ")];
                            spans.extend(line.spans);
                            msg_lines.push(Line::from(spans));
                        }
                    }
                }
            }
            *message.cached_wrapped.borrow_mut() =
                Some((width as usize, get_theme(app), msg_lines.clone()));
            msg_lines
        };

        if !all_lines.is_empty()
            && (is_shell
                || message.author == "You"
                || message.author == "System"
                || !is_tool
                || !prev_is_tool)
        {
            push_margin(&mut all_lines);
        }

        let start_line = all_lines.len();
        all_lines.extend(msg_lines);
        let end_line = all_lines.len();
        message_line_ranges.push((msg_idx, start_line, end_line));
        prev_is_tool = is_tool;
    }

    app.chat.messages_area.set(Some(area));
    *app.chat.message_line_ranges.borrow_mut() = message_line_ranges;

    // Line-by-line scrolling
    let total_lines = all_lines.len();
    let viewport_height = area.height as usize;

    let max_scroll = total_lines.saturating_sub(viewport_height);
    let scroll_offset = (app.chat.scroll as usize).min(max_scroll);
    let scroll_y = max_scroll.saturating_sub(scroll_offset);

    let start_idx = scroll_y;
    let end_idx = (start_idx + viewport_height).min(total_lines);
    let visible_lines = all_lines[start_idx..end_idx].to_vec();

    frame.render_widget(Paragraph::new(visible_lines), area);
}

fn render_command_suggestions(frame: &mut Frame, app: &App, area: Rect) {
    let suggestions = app.command_suggestions();
    let is_ref = suggestions.iter().any(|s| s.name.starts_with('@'));
    let title = if is_ref { " References " } else { " Commands " };

    let items = suggestions
        .into_iter()
        .take(3)
        .map(|suggestion| {
            ListItem::new(Line::from(vec![
                Span::styled(
                    suggestion.name,
                    Style::default().add_modifier(Modifier::BOLD),
                ),
                Span::raw("  "),
                Span::raw(suggestion.description),
            ]))
        })
        .collect::<Vec<_>>();

    frame.render_widget(
        List::new(items).block(
            Block::default()
                .borders(Borders::ALL)
                .border_type(BorderType::Rounded)
                .title(title)
                .padding(Padding::horizontal(1)),
        ),
        area,
    );
}

fn centered_rect(percent_x: u16, percent_y: u16, r: Rect) -> Rect {
    let popup_layout = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Percentage((100 - percent_y) / 2),
            Constraint::Percentage(percent_y),
            Constraint::Percentage((100 - percent_y) / 2),
        ])
        .split(r);

    Layout::default()
        .direction(Direction::Horizontal)
        .constraints([
            Constraint::Percentage((100 - percent_x) / 2),
            Constraint::Percentage(percent_x),
            Constraint::Percentage((100 - percent_x) / 2),
        ])
        .split(popup_layout[1])[1]
}

fn visual_width(s: &str) -> usize {
    use unicode_width::UnicodeWidthChar;
    let mut w = 0;
    for c in s.chars() {
        if c == '\t' {
            w += 4;
        } else if c == '\r' || c == '\n' {
            // ignore
        } else if !c.is_control() {
            w += c.width().unwrap_or(0);
        }
    }
    w
}

fn clean_and_truncate_to_visual_width(s: &str, max_w: usize) -> (String, usize) {
    use unicode_width::UnicodeWidthChar;
    let mut res = String::new();
    let mut current_w = 0;
    for c in s.chars() {
        if c == '\r' || c == '\n' {
            continue;
        }
        let char_w = if c == '\t' {
            4
        } else if c.is_control() {
            0
        } else {
            c.width().unwrap_or(0)
        };
        if current_w + char_w > max_w {
            break;
        }
        if c == '\t' {
            res.push_str("    ");
        } else if !c.is_control() {
            res.push(c);
        }
        current_w += char_w;
    }
    (res, current_w)
}

fn render_todos(frame: &mut Frame, app: &App, area: Rect) {
    let mut lines = Vec::new();
    for item in &app.chat.todos {
        let status_span = match item.status.as_str() {
            "completed" => Span::styled("✓ ", Style::default().fg(Color::Rgb(34, 197, 94))),
            "in_progress" => Span::styled("▶ ", Style::default().fg(Color::Rgb(234, 179, 8))),
            "pending" => Span::styled("○ ", Style::default().fg(Color::DarkGray)),
            "cancelled" => Span::styled("✗ ", Style::default().fg(Color::DarkGray)),
            _ => Span::raw("  "),
        };

        let priority_span = match item.priority.as_str() {
            "high" => Span::styled("▲ ", Style::default().fg(Color::Rgb(239, 68, 68))),
            "medium" => Span::styled("◆ ", Style::default().fg(Color::Rgb(59, 130, 246))),
            "low" => Span::styled("▼ ", Style::default().fg(Color::DarkGray)),
            _ => Span::raw("  "),
        };

        let content_style = match item.status.as_str() {
            "completed" => Style::default().fg(Color::DarkGray),
            "cancelled" => Style::default()
                .fg(Color::DarkGray)
                .add_modifier(Modifier::ITALIC),
            "in_progress" => Style::default().add_modifier(Modifier::BOLD),
            _ => Style::default(),
        };

        let content_width = (area.width as usize).saturating_sub(6).max(1);
        let wrapped = wrap_text_to_lines(&item.content, content_width);
        for (idx, line_text) in wrapped.iter().enumerate() {
            if idx == 0 {
                lines.push(Line::from(vec![
                    status_span.clone(),
                    priority_span.clone(),
                    Span::styled(line_text.clone(), content_style),
                ]));
            } else {
                lines.push(Line::from(vec![
                    Span::raw("    "),
                    Span::styled(line_text.clone(), content_style),
                ]));
            }
        }
        lines.push(Line::from(""));
    }

    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(BorderType::Rounded)
        .title(" Todo List ")
        .padding(Padding::horizontal(1));

    frame.render_widget(Paragraph::new(lines).block(block), area);
}
