// File: src/ui.rs
// Author: Hadi Cahyadi <cumulus13@gmail.com>

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

use crate::audio::AudioChannel;

/// Top-level render function called from the main loop.
pub fn render(frame: &mut Frame, channels: &[AudioChannel], selected: usize, status: Option<&str>) {
    let area = frame.area();

    let chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(3), // title bar
            Constraint::Min(0),    // channel columns
            Constraint::Length(3), // keyboard hint bar
            Constraint::Length(1), // status line
        ])
        .split(area);

    render_title(frame, chunks[0]);
    render_channels(frame, chunks[1], channels, selected);
    render_controls(frame, chunks[2]);
    render_status(frame, chunks[3], status);
}

// ── Title ─────────────────────────────────────────────────────────────────────

fn render_title(frame: &mut Frame, area: Rect) {
    let title = Paragraph::new(Line::from(vec![
        Span::styled(
            " 🎚  alsamixer ",
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD),
        ),
        Span::styled(
            "— Windows Audio Mixer",
            Style::default().fg(Color::DarkGray),
        ),
    ]))
    .alignment(Alignment::Center)
    .block(
        Block::default()
            .borders(Borders::ALL)
            .border_type(BorderType::Rounded)
            .border_style(Style::default().fg(Color::Cyan)),
    );
    frame.render_widget(title, area);
}

// ── Channel columns ───────────────────────────────────────────────────────────

fn render_channels(frame: &mut Frame, area: Rect, channels: &[AudioChannel], selected: usize) {
    if channels.is_empty() {
        let msg = Paragraph::new("No audio channels found.\n\nMake sure an audio output device is enabled\nand at least one application is playing sound.")
            .alignment(Alignment::Center)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .border_type(BorderType::Rounded)
                    .title(" Channels "),
            );
        frame.render_widget(msg, area);
        return;
    }

    let count = channels.len() as u32;
    let constraints: Vec<Constraint> = channels
        .iter()
        .map(|_| Constraint::Ratio(1, count))
        .collect();

    let cols = Layout::default()
        .direction(Direction::Horizontal)
        .constraints(constraints)
        .split(area);

    for (i, (channel, &col)) in channels.iter().zip(cols.iter()).enumerate() {
        render_single_channel(frame, col, channel, i == selected);
    }
}

fn render_single_channel(frame: &mut Frame, area: Rect, channel: &AudioChannel, selected: bool) {
    let (border_style, border_type) = if selected {
        (Style::default().fg(Color::Yellow), BorderType::Double)
    } else {
        (Style::default().fg(Color::DarkGray), BorderType::Plain)
    };

    let block = Block::default()
        .borders(Borders::ALL)
        .border_type(border_type)
        .border_style(border_style);

    let inner_area = block.inner(area);
    frame.render_widget(block, area);

    if inner_area.height < 4 || inner_area.width == 0 {
        return;
    }

    // Volume percentage label
    let vol_span = if channel.muted {
        Span::styled(
            " MM ",
            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
        )
    } else {
        Span::styled(
            format!("{:3}%", channel.volume),
            Style::default()
                .fg(if selected {
                    Color::Yellow
                } else {
                    Color::White
                })
                .add_modifier(Modifier::BOLD),
        )
    };

    let sub_chunks = Layout::default()
        .direction(Direction::Vertical)
        .constraints([
            Constraint::Length(1), // volume %
            Constraint::Min(4),    // vertical bar
            Constraint::Length(1), // channel name
            Constraint::Length(1), // mute indicator
        ])
        .split(inner_area);

    frame.render_widget(
        Paragraph::new(Line::from(vol_span)).alignment(Alignment::Center),
        sub_chunks[0],
    );

    render_vbar(frame, sub_chunks[1], channel);

    // Channel name — truncated to column width using display width
    let max_w = sub_chunks[2].width as usize;
    let name = truncate_display_width(&channel.name, max_w);
    let name_style = if selected {
        Style::default()
            .fg(Color::Black)
            .bg(Color::Yellow)
            .add_modifier(Modifier::BOLD)
    } else {
        Style::default().fg(Color::Cyan)
    };
    frame.render_widget(
        Paragraph::new(Line::from(Span::styled(name, name_style))).alignment(Alignment::Center),
        sub_chunks[2],
    );

    // Mute / speaker indicator
    let mute_span = if channel.muted {
        Span::styled(
            "MUTE",
            Style::default().fg(Color::Red).add_modifier(Modifier::BOLD),
        )
    } else {
        Span::styled(" 🔊 ", Style::default().fg(Color::Green))
    };
    frame.render_widget(
        Paragraph::new(Line::from(mute_span)).alignment(Alignment::Center),
        sub_chunks[3],
    );
}

// ── Vertical volume bar ───────────────────────────────────────────────────────

fn render_vbar(frame: &mut Frame, area: Rect, channel: &AudioChannel) {
    if area.height == 0 || area.width == 0 {
        return;
    }

    let height = area.height as usize;
    let filled = if channel.muted {
        0
    } else {
        (channel.volume as usize * height + 99) / 100 // round up so 1% still shows 1 cell
    };

    let mut lines: Vec<Line> = Vec::with_capacity(height);

    for row in 0..height {
        let from_bottom = height - 1 - row; // 0 = bottom cell

        let cell: Span = if channel.muted {
            if row % 2 == 0 {
                Span::styled("▓▓▓", Style::default().fg(Color::DarkGray))
            } else {
                Span::styled("░░░", Style::default().fg(Color::DarkGray))
            }
        } else if from_bottom < filled {
            Span::styled("███", Style::default().fg(bar_color(channel.volume)))
        } else {
            Span::styled("   ", Style::default().fg(Color::DarkGray))
        };

        lines.push(Line::from(vec![
            Span::styled("│", Style::default().fg(Color::White)),
            cell,
            Span::styled("│", Style::default().fg(Color::White)),
        ]));
    }

    frame.render_widget(Paragraph::new(lines).alignment(Alignment::Center), area);
}

fn bar_color(volume: u8) -> Color {
    match volume {
        90..=100 => Color::LightRed,
        75..=89 => Color::Red,
        60..=74 => Color::Yellow,
        45..=59 => Color::LightGreen,
        30..=44 => Color::Green,
        15..=29 => Color::Cyan,
        _ => Color::Blue,
    }
}

// ── Controls bar ──────────────────────────────────────────────────────────────

fn render_controls(frame: &mut Frame, area: Rect) {
    let keys: &[(&str, &str)] = &[
        ("←/→", "Select"),
        ("Tab", "Next"),
        ("↑/↓", "Vol ±5"),
        ("M", "Mute"),
        ("Q/Esc", "Quit"),
    ];

    let mut spans: Vec<Span> = Vec::new();
    for (i, (key, desc)) in keys.iter().enumerate() {
        if i > 0 {
            spans.push(Span::raw("   "));
        }
        spans.push(Span::styled(
            format!("[{key}]"),
            Style::default()
                .fg(Color::Cyan)
                .add_modifier(Modifier::BOLD),
        ));
        spans.push(Span::styled(
            format!(" {desc}"),
            Style::default().fg(Color::White),
        ));
    }

    let controls = Paragraph::new(Line::from(spans))
        .alignment(Alignment::Center)
        .block(
            Block::default()
                .borders(Borders::ALL)
                .border_type(BorderType::Rounded)
                .border_style(Style::default().fg(Color::DarkGray))
                .title(Span::styled(
                    " Controls ",
                    Style::default().fg(Color::DarkGray),
                )),
        );
    frame.render_widget(controls, area);
}

// ── Status line ───────────────────────────────────────────────────────────────

fn render_status(frame: &mut Frame, area: Rect, status: Option<&str>) {
    let content = status.unwrap_or("alsamixer — Windows Audio Mixer  |  WASAPI backend");
    frame.render_widget(
        Paragraph::new(content)
            .style(Style::default().fg(Color::DarkGray))
            .alignment(Alignment::Center),
        area,
    );
}

// ── Helpers ───────────────────────────────────────────────────────────────────

/// Truncate `s` so its *display width* (counting wide characters as 2 columns)
/// fits within `max_cols`. Appends '…' if truncation occurred.
fn truncate_display_width(s: &str, max_cols: usize) -> String {
    if max_cols == 0 {
        return String::new();
    }
    if s.width() <= max_cols {
        return s.to_string();
    }
    // Reserve one column for the ellipsis
    let target = max_cols.saturating_sub(1);
    let mut width = 0;
    let mut end = 0;
    for ch in s.chars() {
        let w = unicode_width::UnicodeWidthChar::width(ch).unwrap_or(0);
        if width + w > target {
            break;
        }
        width += w;
        end += ch.len_utf8();
    }
    format!("{}…", &s[..end])
}
