// SPDX-FileCopyrightText: 2023 CELESTIFYX Team
// SPDX-License-Identifier: GPL-3.0-or-later

use std::collections::HashSet;

use ratatui::{
    Frame,

    layout::{
        Constraint,
        Direction,
        Layout,
        Rect
    },

    style::{
        Color,
        Modifier,
        Style
    },

    text::{
        Line,
        Span,
        Text
    },

    widgets::{
        Block,
        Borders,
        Clear,
        List,
        ListItem,
        ListState,
        Paragraph,
        Wrap
    }
};

use super::{
    pkg::Action,

    app::{
        App,
        ActionMenu,
        Mode,
        Toast,
        ToastKind
    },

    ports::{
        Port,
        Status
    }
};

fn port_line(port: &Port, multi_selected: bool) -> Line<'_> {
    let (label, color): (&str, Color) = if port.needs_update() {
        ("[OUTDATED]     ", Color::Yellow)
    } else {
        match port.status {
            Status::Installed    => ("[INSTALLED]    ", Color::Green),
            Status::NotInstalled => ("[NOT INSTALLED]", Color::Red)
        }
    };

    let checkbox: Span = if multi_selected {
        Span::styled("[+] ", Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD))
    } else {
        Span::styled("[ ] ", Style::default().fg(Color::DarkGray))
    };

    Line::from(vec![
        checkbox,

        Span::styled(label, Style::default().fg(color)),
        Span::raw(" "),
        Span::raw(&port.rel_path)
    ])
}

fn render_list(f: &mut Frame, area: Rect, view: &[Port], selected: usize, multi_selection: &HashSet<String>) -> () {
    let items: Vec<ListItem> = view.iter().map(|p: &Port| {
        let is_selected: bool = multi_selection.contains(&p.name);
        ListItem::new(port_line(p, is_selected))
    }).collect();

    let list: List = List::new(items).block(Block::default().borders(Borders::ALL).title(" Ports ")).highlight_style(Style::default().bg(Color::Black).fg(Color::Rgb(255, 170, 0)).add_modifier(Modifier::BOLD)).highlight_symbol("▶ ");
    let mut state: ListState = ListState::default();

    (&mut state).select(Some(selected));
    (&mut *f).render_stateful_widget(list, area, &mut state);
}

fn parse_info_text(raw: &str) -> Text<'static> {
    let lines: Vec<Line<'static>> = raw.lines().map(|line: &str| {
        if let Some(pos) = line.find(": ") {
            let key:  String = line[..pos].to_string();
            let rest: String = line[pos + 2..].to_string();

            Line::from(vec![
                Span::styled(format!("{}: ", key), Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)),
                Span::raw(rest)
            ])
        } else if line.trim_end().ends_with(':') {
            Line::from(Span::styled(line.to_string(), Style::default().fg(Color::Cyan).add_modifier(Modifier::BOLD)))
        } else {
            Line::from(Span::raw(line.to_string()))
        }
    }).collect();

    Text::from(lines)
}

fn render_preview(f: &mut Frame, area: Rect, port: Option<&Port>, text: &str) -> () {
    let title: String = port.map(|p: &Port| format!(" info — {} ", p.name)).unwrap_or_else(|| " info ".to_string());
    let block: Block = Block::default().borders(Borders::ALL).title(title);

    let para: Paragraph = Paragraph::new(parse_info_text(text)).block(block).wrap(Wrap {
        trim: false
    });

    (&mut *f).render_widget(para, area);
}

fn render_toast(f: &mut Frame, area: Rect, toast: &Toast) -> () {
    let (color, prefix): (Color, &str) = match toast.kind {
        ToastKind::Success => (Color::Green, "✓"),
        ToastKind::Error   => (Color::Red, "✗")
    };

    let line: Line = Line::from(vec![
        Span::styled(format!(" {} ", prefix), Style::default().fg(color).add_modifier(Modifier::BOLD)),
        Span::raw(&toast.text)
    ]);

    (&mut *f).render_widget(Paragraph::new(line).style(Style::default().bg(Color::Black)), area);
}

fn render_action_menu(f: &mut Frame, area: Rect, menu: &ActionMenu) -> () {
    let title: String = format!(" {} packages ", menu.ports.len());
    let names: String = menu.ports.iter().map(|p: &Port| p.name.as_str()).collect::<Vec<&str>>().join(", ");
    let names_wrapped: Vec<Line> = wrap_text(&names, 44);

    let action_lines: u16 = menu.actions.len() as u16;
    let name_lines:   u16 = names_wrapped.len().max(1) as u16;
    let popup_width:  u16 = 48u16;
    let popup_height: u16 = action_lines + name_lines + 4;

    let x: u16 = area.x + area.width.saturating_sub(popup_width) / 2;
    let y: u16 = area.y + area.height.saturating_sub(popup_height) / 2;

    let popup_area: Rect = Rect {
        x: x.min(area.x + area.width.saturating_sub(popup_width)),
        y: y.min(area.y + area.height.saturating_sub(popup_height)),

        width:  popup_width.min(area.width),
        height: popup_height.min(area.height)
    };

    (&mut *f).render_widget(Clear, popup_area);

    let block: Block = Block::default().borders(Borders::ALL).title(title).style(Style::default().bg(Color::Black));
    let inner: Rect = block.inner(popup_area);
    (&mut *f).render_widget(block, popup_area);

    if menu.actions.is_empty() {
        let line: Line = Line::from(Span::styled("No actions available", Style::default().fg(Color::DarkGray)));
        (&mut *f).render_widget(Paragraph::new(line), inner);

        return;
    }

    let chunks = Layout::default().direction(Direction::Vertical).constraints([
        Constraint::Length(name_lines),
        Constraint::Length(1),
        Constraint::Length(action_lines)
    ]).split(inner);

    let name_text: Text = Text::from(names_wrapped);
    (&mut *f).render_widget(Paragraph::new(name_text), chunks[0]);

    let action_items: Vec<ListItem> = menu.actions.iter().enumerate().map(|(i, action): (usize, &Action)| {
        let style: Style = if i == menu.selected {
            Style::default().bg(Color::Black).fg(Color::Rgb(255, 170, 0)).add_modifier(Modifier::BOLD)
        } else {
            Style::default().bg(Color::Black).fg(Color::DarkGray)
        };

        ListItem::new(Span::styled(action.label(), style))
    }).collect();

    let action_list: List = List::new(action_items).highlight_style(Style::default().bg(Color::Black).fg(Color::Rgb(255, 170, 0)).add_modifier(Modifier::BOLD)).highlight_symbol("▶ ");
    let mut action_state: ListState = ListState::default();

    (&mut action_state).select(Some(menu.selected));
    (&mut *f).render_stateful_widget(action_list, chunks[2], &mut action_state);
}

fn wrap_text(text: &str, max_width: usize) -> Vec<Line<'_>> {
    let mut lines: Vec<Line> = Vec::new();
    let mut current: String = String::new();

    for word in text.split(", ") {
        let with_comma: String = if current.is_empty() {
            word.to_string()
        } else {
            format!(", {}", word)
        };

        if ((current.len() + with_comma.len()) > max_width) && !current.is_empty() {
            (&mut lines).push(Line::from(Span::styled(current, Style::default().fg(Color::DarkGray))));
            current = word.to_string();

            continue;
        }

        (&mut current).push_str(&with_comma);
    }

    if !current.is_empty() {
        (&mut lines).push(Line::from(Span::styled(current, Style::default().fg(Color::DarkGray))));
    }

    lines
}

pub(super) fn draw(f: &mut Frame, app: &App) -> () {
    if app.mode == Mode::Running {
        return;
    }

    let view: Vec<Port> = app.filtered_view();

    let selected: usize = if view.is_empty() {
        0
    } else {
        app.selected_index().min(view.len() - 1)
    };

    let preview_port: Option<&Port> = view.get(selected);
    let preview_text: String = preview_port.filter(|p: &&Port| app.preview_target.as_deref() == Some(&p.name)).and_then(|p: &Port| app.previews.get(&p.name).cloned()).unwrap_or_else(|| "Navigate the list — preview loads automatically.".to_string());

    let chunks = Layout::default().direction(Direction::Vertical).constraints([
        Constraint::Min(3),
        Constraint::Length(3),
        Constraint::Length(1)
    ]).split(f.area());

    let body = Layout::default().direction(Direction::Horizontal).constraints([
        Constraint::Percentage(50),
        Constraint::Percentage(50)
    ]).split(chunks[0]);

    render_list(f, body[0], &view, selected, &app.multi_selection);
    render_preview(f, body[1], preview_port, &preview_text);

    let filter_area: Rect = chunks[1];

    if app.mode == Mode::ActionMenu {
        let block: Block = Block::default().borders(Borders::ALL).title(" Action Menu — ↑↓: select  Enter: confirm  Esc: cancel ");
        (&mut *f).render_widget(Paragraph::new("").block(block), filter_area);
    } else {
        let filter_label: &str = app.status_filter.label();
        let multi_count: usize = app.multi_selection.len();

        let selected_text: String = if multi_count > 0 {
            format!(" | Selected: {}", multi_count)
        } else {
            String::new()
        };

        let title: String = format!(" {} | [Tab] View | [s] Sync | [Space] Toggle | [Enter] Action | [q] Quit | {}/{}{} ", filter_label, view.len(), app.ports.len(), selected_text);
        let block: Block = Block::default().borders(Borders::ALL).title(title);

        let spans: Vec<Span> = vec![
            Span::styled("> ", Style::default().fg(Color::Rgb(255, 170, 0))),
            Span::raw(&app.filter),
            Span::styled("█", Style::default().fg(Color::DarkGray))
        ];

        (&mut *f).render_widget(Paragraph::new(Line::from(spans)).block(block), filter_area);
    }

    if let Some(toast) = &app.toast {
        render_toast(f, chunks[2], toast);
    }

    if let Some(menu) = &app.action_menu {
        render_action_menu(f, f.area(), menu);
    }
}
