use cagire_ratatui::ListSelect;
use ratatui::layout::{Constraint, Layout, Rect};
use ratatui::style::{Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Paragraph, Row, Table};
use ratatui::Frame;

use crate::app::App;
use crate::engine::LinkState;
use crate::midi;
use crate::state::{DeviceKind, EngineSection, LinkSetting, SettingKind};
use crate::theme;
use crate::widgets::{
    render_scroll_indicators, render_section_header, IndicatorAlign, Scope,
};

pub fn layout(area: Rect) -> [Rect; 3] {
    Layout::horizontal([
        Constraint::Percentage(55),
        Constraint::Length(2),
        Constraint::Percentage(45),
    ])
    .areas(area)
}

fn is_narrow(area: Rect) -> bool {
    area.width < 100
}

pub fn render(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    if is_narrow(area) {
        render_narrow(frame, app, link, area);
    } else {
        render_wide(frame, app, link, area);
    }
}

fn render_wide(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let [left_col, _, right_col] = layout(area);
    render_config_column(frame, app, link, left_col);
    render_monitoring_column(frame, app, link, right_col);
}

fn render_narrow(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let theme = theme::get();
    let block = Block::default()
        .borders(Borders::ALL)
        .title(" Engine ")
        .border_style(Style::new().fg(theme.engine.border_magenta));
    let inner = block.inner(area);
    frame.render_widget(block, area);

    let padded = Rect {
        x: inner.x + 1,
        y: inner.y,
        width: inner.width.saturating_sub(2),
        height: inner.height,
    };

    if padded.height < 2 {
        return;
    }

    // Compact metrics banner at top
    let [banner_area, _, content_area] = Layout::vertical([
        Constraint::Length(1),
        Constraint::Length(1),
        Constraint::Fill(1),
    ])
    .areas(padded);

    render_compact_banner(frame, app, banner_area);
    render_config_sections(frame, app, link, content_area);
}

fn render_compact_banner(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let cpu_pct = (app.metrics.cpu_load * 100.0).min(100.0);
    let voices = app.metrics.active_voices;
    let max_voices = app.audio.config.max_voices;

    let left_db = amplitude_to_db(app.metrics.peak_left);
    let right_db = amplitude_to_db(app.metrics.peak_right);

    let left_bar = mini_meter(app.metrics.peak_left, 8);
    let right_bar = mini_meter(app.metrics.peak_right, 8);

    let meter_color = Style::new().fg(theme.meter.low);
    let dim = Style::new().fg(theme.engine.dim);
    let cpu_color = cpu_style(cpu_pct, &theme);

    let line = Line::from(vec![
        Span::styled("L:", dim),
        Span::styled(left_bar, meter_color),
        Span::styled(format!("{left_db:+.0}"), dim),
        Span::styled(" R:", dim),
        Span::styled(right_bar, meter_color),
        Span::styled(format!("{right_db:+.0}"), dim),
        Span::styled(format!("  CPU {cpu_pct:.0}%"), cpu_color),
        Span::styled(format!("  V:{voices}/{max_voices}"), dim),
    ]);
    frame.render_widget(Paragraph::new(line), area);
}

fn mini_meter(amplitude: f32, width: usize) -> String {
    let db = amplitude_to_db(amplitude);
    let norm = ((db + 48.0) / 51.0).clamp(0.0, 1.0);
    let filled = (norm * width as f32).round() as usize;
    let empty = width.saturating_sub(filled);
    format!("{}{}", "\u{2588}".repeat(filled), "\u{2591}".repeat(empty))
}

fn amplitude_to_db(amp: f32) -> f32 {
    if amp <= 0.0 {
        -48.0
    } else {
        (20.0 * amp.log10()).clamp(-48.0, 3.0)
    }
}

fn cpu_style(pct: f32, theme: &theme::ThemeColors) -> Style {
    if pct >= 80.0 {
        Style::new().fg(theme.flash.error_fg)
    } else if pct >= 50.0 {
        Style::new().fg(theme.ui.accent)
    } else {
        Style::new().fg(theme.engine.dim)
    }
}

fn render_config_column(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let theme = theme::get();
    let block = Block::default()
        .borders(Borders::ALL)
        .title(" Engine ")
        .border_style(Style::new().fg(theme.engine.border_magenta));

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

    let padded = Rect {
        x: inner.x + 1,
        y: inner.y + 1,
        width: inner.width.saturating_sub(2),
        height: inner.height.saturating_sub(1),
    };

    render_config_sections(frame, app, link, padded);
}

fn render_config_sections(frame: &mut Frame, app: &App, link: &LinkState, padded: Rect) {
    let theme = theme::get();
    let plugin_mode = app.plugin_mode;

    // Build section list with heights
    struct Section {
        kind: EngineSection,
        height: usize,
    }

    let mut sections: Vec<Section> = Vec::new();

    if !plugin_mode {
        sections.push(Section {
            kind: EngineSection::Devices,
            height: devices_section_height(app) as usize,
        });
    }

    let settings_rows = if plugin_mode { 4 } else { 6 };
    sections.push(Section {
        kind: EngineSection::Settings,
        height: 2 + settings_rows,
    });

    if !plugin_mode {
        sections.push(Section {
            kind: EngineSection::Link,
            height: 2 + 3 + 1 + 3, // header + 3 settings + blank + 3 session readouts
        });
        sections.push(Section {
            kind: EngineSection::MidiOutput,
            height: 2 + 4,
        });
        sections.push(Section {
            kind: EngineSection::MidiInput,
            height: 2 + 4,
        });
    }

    let sample_content = app.audio.config.sample_paths.len().max(2);
    sections.push(Section {
        kind: EngineSection::Samples,
        height: 2 + sample_content,
    });

    let gap = 1usize;
    let total_lines: usize =
        sections.iter().map(|s| s.height).sum::<usize>() + gap * sections.len().saturating_sub(1);
    let max_visible = padded.height as usize;

    // Find focused section offset
    let mut focus_start = 0usize;
    let mut focus_height = 0usize;
    let mut offset = 0usize;
    for (i, sec) in sections.iter().enumerate() {
        if sec.kind == app.audio.section {
            focus_start = offset;
            focus_height = sec.height;
            break;
        }
        offset += sec.height;
        if i + 1 < sections.len() {
            offset += gap;
        }
    }

    let scroll_offset = if total_lines <= max_visible {
        0
    } else {
        let focus_end = focus_start + focus_height;
        if focus_end <= max_visible {
            0
        } else {
            focus_start.min(total_lines.saturating_sub(max_visible))
        }
    };

    let viewport_top = padded.y as i32;
    let viewport_bottom = (padded.y + padded.height) as i32;
    let mut y = viewport_top - scroll_offset as i32;

    for (i, sec) in sections.iter().enumerate() {
        let sec_top = y;
        let sec_bottom = y + sec.height as i32;

        if sec_bottom > viewport_top && sec_top < viewport_bottom {
            let clipped_y = sec_top.max(viewport_top) as u16;
            let clipped_height =
                (sec_bottom.min(viewport_bottom) - sec_top.max(viewport_top)) as u16;
            let sec_area = Rect {
                x: padded.x,
                y: clipped_y,
                width: padded.width,
                height: clipped_height,
            };

            match sec.kind {
                EngineSection::Devices => render_devices(frame, app, sec_area),
                EngineSection::Settings => render_settings(frame, app, sec_area),
                EngineSection::Link => render_link(frame, app, link, sec_area),
                EngineSection::MidiOutput => render_midi_output(frame, app, sec_area),
                EngineSection::MidiInput => render_midi_input(frame, app, sec_area),
                EngineSection::Samples => render_samples(frame, app, sec_area),
            }
        }

        y += sec.height as i32;
        if i + 1 < sections.len() {
            y += gap as i32;
        }
    }

    render_scroll_indicators(
        frame,
        padded,
        scroll_offset,
        max_visible,
        total_lines,
        theme.engine.scroll_indicator,
        IndicatorAlign::Right,
    );
}

fn render_monitoring_column(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let theme = theme::get();

    let block = Block::default()
        .borders(Borders::ALL)
        .title(" Metrics ")
        .border_style(Style::new().fg(theme.engine.border_green));
    let inner = block.inner(area);
    frame.render_widget(block, area);
    let area = Rect {
        x: inner.x + 1,
        y: inner.y + 1,
        width: inner.width.saturating_sub(2),
        height: inner.height.saturating_sub(1),
    };

    // Error display takes priority at top
    let (error_height, content_area) = if let Some(ref err) = app.audio.error {
        let err_h = 2u16;
        let [err_area, _, rest] = Layout::vertical([
            Constraint::Length(err_h),
            Constraint::Length(1),
            Constraint::Fill(1),
        ])
        .areas(area);
        let err_style = Style::new().fg(theme.flash.error_fg);
        let truncated = if err.len() > area.width as usize * 2 {
            format!("{}...", &err[..area.width as usize * 2 - 3])
        } else {
            err.clone()
        };
        frame.render_widget(
            Paragraph::new(vec![
                Line::from(Span::styled(
                    "ERROR",
                    err_style.add_modifier(Modifier::BOLD),
                )),
                Line::from(Span::styled(truncated, err_style)),
            ]),
            err_area,
        );
        (err_h, rest)
    } else {
        (0, area)
    };
    let _ = error_height;

    // Split: VU meters (3 rows) + gap + STATUS block + gap + Scope (fill)
    let [vu_area, _, status_area, _, scope_area] = Layout::vertical([
        Constraint::Length(3),
        Constraint::Length(1),
        Constraint::Length(9),
        Constraint::Length(1),
        Constraint::Fill(1),
    ])
    .areas(content_area);

    // VU meters
    render_vu_meters(frame, app, vu_area);

    // Status metrics
    render_status(frame, app, link, status_area);

    // Scope
    render_compact_scope(frame, app, scope_area);
}

fn render_vu_meters(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();

    let left_db = amplitude_to_db(app.metrics.peak_left);
    let right_db = amplitude_to_db(app.metrics.peak_right);

    // Row 0: label + horizontal bar + dB reading
    let label_w = 3u16;
    let db_w = 10u16;
    let bar_w = area.width.saturating_sub(label_w + db_w);

    let label_style = Style::new().fg(theme.engine.label);
    let dim = Style::new().fg(theme.engine.dim);

    // Left channel
    if area.height >= 1 {
        let y = area.y;
        let mut spans = vec![Span::styled(" L ", label_style)];
        spans.extend(horizontal_meter_spans(app.metrics.peak_left, bar_w as usize, &theme));
        spans.push(Span::styled(format!("  {left_db:+.1} dB"), dim));
        frame.render_widget(
            Paragraph::new(Line::from(spans)),
            Rect::new(area.x, y, area.width, 1),
        );
    }

    // Right channel
    if area.height >= 3 {
        let y = area.y + 2;
        let mut spans = vec![Span::styled(" R ", label_style)];
        spans.extend(horizontal_meter_spans(app.metrics.peak_right, bar_w as usize, &theme));
        spans.push(Span::styled(format!("  {right_db:+.1} dB"), dim));
        frame.render_widget(
            Paragraph::new(Line::from(spans)),
            Rect::new(area.x, y, area.width, 1),
        );
    }
}

fn horizontal_meter_spans(amplitude: f32, width: usize, theme: &theme::ThemeColors) -> Vec<Span<'static>> {
    let db = amplitude_to_db(amplitude);
    let norm = ((db + 48.0) / 51.0).clamp(0.0, 1.0);
    let filled = (norm * width as f32).round() as usize;
    let empty = width.saturating_sub(filled);

    let mut spans = Vec::new();

    let green_end = (width as f32 * 0.75).round() as usize;
    let yellow_end = (width as f32 * 0.9).round() as usize;

    let green_chars = filled.min(green_end);
    let yellow_chars = if filled > green_end {
        (filled - green_end).min(yellow_end - green_end)
    } else {
        0
    };
    let red_chars = filled.saturating_sub(yellow_end);

    if green_chars > 0 {
        spans.push(Span::styled(
            "\u{2588}".repeat(green_chars),
            Style::new().fg(theme.meter.low),
        ));
    }
    if yellow_chars > 0 {
        spans.push(Span::styled(
            "\u{2588}".repeat(yellow_chars),
            Style::new().fg(theme.meter.mid),
        ));
    }
    if red_chars > 0 {
        spans.push(Span::styled(
            "\u{2588}".repeat(red_chars),
            Style::new().fg(theme.meter.high),
        ));
    }
    if empty > 0 {
        spans.push(Span::styled(
            "\u{2591}".repeat(empty),
            Style::new().fg(theme.engine.dim),
        ));
    }

    spans
}

fn render_status(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let theme = theme::get();
    let section_header_style = Style::new()
        .fg(theme.engine.header)
        .add_modifier(Modifier::BOLD);
    let label_style = Style::new().fg(theme.engine.label);
    let value_style = Style::new().fg(theme.engine.value);

    let cpu_pct = (app.metrics.cpu_load * 100.0).min(100.0);
    let cpu_bar_w = 12usize;
    let cpu_filled = (cpu_pct / 100.0 * cpu_bar_w as f32).round() as usize;
    let cpu_empty = cpu_bar_w.saturating_sub(cpu_filled);
    let cpu_bar_color = if cpu_pct >= 80.0 {
        theme.flash.error_fg
    } else if cpu_pct >= 50.0 {
        theme.ui.accent
    } else {
        theme.meter.low
    };

    let voices = app.metrics.active_voices;
    let max_voices = app.audio.config.max_voices;
    let peak_voices = app.metrics.peak_voices;
    let events = app.metrics.event_count;
    let depth = app.metrics.schedule_depth;
    let nudge_ms = app.metrics.nudge_ms;
    let sample_rate = app.audio.config.sample_rate;
    let host = if app.audio.config.host_name.is_empty() {
        "-"
    } else {
        &app.audio.config.host_name
    };
    let peers = link.peers();

    let nudge_label = if nudge_ms == 0.0 {
        "0 ms".to_string()
    } else {
        format!("{nudge_ms:+.1} ms")
    };

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

    lines.push(Line::from(Span::styled("STATUS", section_header_style)));
    lines.push(Line::from(Span::styled(
        "\u{2500}".repeat(area.width as usize),
        Style::new().fg(theme.engine.divider),
    )));

    // CPU
    lines.push(Line::from(vec![
        Span::styled(format!("  CPU     {cpu_pct:>3.0}%  "), label_style),
        Span::styled(
            "\u{2588}".repeat(cpu_filled),
            Style::new().fg(cpu_bar_color),
        ),
        Span::styled(
            "\u{2591}".repeat(cpu_empty),
            Style::new().fg(theme.engine.dim),
        ),
    ]));

    // Voices
    lines.push(Line::from(vec![
        Span::styled(
            format!("  Voices  {voices}/{max_voices}"),
            label_style,
        ),
        Span::styled(format!(" (peak: {peak_voices})"), value_style),
    ]));

    // Events
    lines.push(Line::from(Span::styled(
        format!("  Events  {events}"),
        label_style,
    )));

    // Depth
    lines.push(Line::from(Span::styled(
        format!("  Depth   {depth}"),
        label_style,
    )));

    // Nudge
    lines.push(Line::from(Span::styled(
        format!("  Nudge   {nudge_label}"),
        label_style,
    )));

    // Sample rate
    lines.push(Line::from(Span::styled(
        format!("  Rate    {sample_rate:.0} Hz"),
        label_style,
    )));

    if !app.plugin_mode {
        // Host
        lines.push(Line::from(Span::styled(
            format!("  Host    {host}"),
            label_style,
        )));

        // Link peers
        if link.is_enabled() && peers > 0 {
            lines.push(Line::from(Span::styled(
                format!("  Peers   {peers}"),
                label_style,
            )));
        }
    }

    let visible = lines.len().min(area.height as usize);
    frame.render_widget(
        Paragraph::new(lines.into_iter().take(visible).collect::<Vec<_>>()),
        area,
    );
}

fn render_compact_scope(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();

    if area.height < 3 {
        return;
    }

    let block = Block::default()
        .borders(Borders::ALL)
        .title(" Scope ")
        .border_style(Style::new().fg(theme.engine.border_green));

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

    let gain = viz_gain(&app.metrics.scope, &app.audio.config);
    let scope = Scope::new(&app.metrics.scope)
        .color(theme.meter.low)
        .gain(gain);
    frame.render_widget(scope, inner);
}

fn viz_gain(data: &[f32], config: &crate::state::audio::AudioConfig) -> f32 {
    if config.normalize_viz {
        let peak = data.iter().fold(0.0_f32, |m, s| m.max(s.abs()));
        if peak > 0.0001 {
            1.0 / peak
        } else {
            1.0
        }
    } else {
        config.gain_boost
    }
}

// --- Config sections ---

fn truncate_name(name: &str, max_len: usize) -> String {
    if name.len() > max_len {
        format!("{}...", &name[..max_len.saturating_sub(3)])
    } else {
        name.to_string()
    }
}

pub fn list_height(item_count: usize) -> u16 {
    let visible = item_count.min(5) as u16;
    if item_count > 5 {
        visible + 1
    } else {
        visible
    }
}

pub fn devices_section_height(app: &App) -> u16 {
    let output_h = list_height(app.audio.output_devices.len());
    let input_h = list_height(app.audio.input_devices.len());
    3 + output_h.max(input_h)
}

fn render_devices(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::Devices;

    let [header_area, content_area] =
        Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);

    render_section_header(frame, "DEVICES", section_focused, header_area);

    let [output_col, separator, input_col] = Layout::horizontal([
        Constraint::Percentage(48),
        Constraint::Length(3),
        Constraint::Percentage(48),
    ])
    .areas(content_area);

    let output_focused = section_focused && app.audio.device_kind == DeviceKind::Output;
    let input_focused = section_focused && app.audio.device_kind == DeviceKind::Input;

    render_device_column(
        frame,
        output_col,
        "Output",
        &app.audio.output_devices,
        app.audio.current_output_device_index(),
        app.audio.output_list.cursor,
        app.audio.output_list.scroll_offset,
        output_focused,
        section_focused,
    );

    let sep_style = Style::new().fg(theme.engine.separator);
    let sep_lines: Vec<Line> = (0..separator.height)
        .map(|_| Line::from(Span::styled("\u{2502}", sep_style)))
        .collect();
    frame.render_widget(Paragraph::new(sep_lines), separator);

    render_device_column(
        frame,
        input_col,
        "Input",
        &app.audio.input_devices,
        app.audio.current_input_device_index(),
        app.audio.input_list.cursor,
        app.audio.input_list.scroll_offset,
        input_focused,
        section_focused,
    );
}

#[allow(clippy::too_many_arguments)]
fn render_device_column(
    frame: &mut Frame,
    area: Rect,
    label: &str,
    devices: &[doux::audio::AudioDeviceInfo],
    selected_idx: usize,
    cursor: usize,
    scroll_offset: usize,
    focused: bool,
    section_focused: bool,
) {
    let theme = theme::get();
    let [label_area, list_area] =
        Layout::vertical([Constraint::Length(1), Constraint::Min(1)]).areas(area);

    let label_style = if focused {
        Style::new()
            .fg(theme.engine.focused)
            .add_modifier(Modifier::BOLD)
    } else if section_focused {
        Style::new().fg(theme.engine.label_focused)
    } else {
        Style::new().fg(theme.engine.label_dim)
    };

    let arrow = if focused { "> " } else { "  " };
    frame.render_widget(
        Paragraph::new(format!("{arrow}{label}")).style(label_style),
        label_area,
    );

    let items: Vec<String> = devices.iter().map(|d| truncate_name(&d.name, 25)).collect();

    ListSelect::new(&items, selected_idx, cursor)
        .focused(focused)
        .scroll_offset(scroll_offset)
        .render(frame, list_area);
}

fn render_settings(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::Settings;

    let [header_area, content_area] =
        Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);

    render_section_header(frame, "SETTINGS", section_focused, header_area);

    let highlight = Style::new()
        .fg(theme.engine.focused)
        .add_modifier(Modifier::BOLD);
    let normal = Style::new().fg(theme.engine.normal);
    let label_style = Style::new().fg(theme.engine.label);

    let polyphony_focused = section_focused && app.audio.setting_kind == SettingKind::Polyphony;
    let nudge_focused = section_focused && app.audio.setting_kind == SettingKind::Nudge;
    let nudge_ms = app.metrics.nudge_ms;
    let nudge_label = if nudge_ms == 0.0 {
        "0 ms".to_string()
    } else {
        format!("{nudge_ms:+.1} ms")
    };

    let mut rows = Vec::new();

    if !app.plugin_mode {
        let channels_focused =
            section_focused && app.audio.setting_kind == SettingKind::Channels;
        let buffer_focused =
            section_focused && app.audio.setting_kind == SettingKind::BufferSize;

        rows.push(Row::new(vec![
            Span::styled(
                if channels_focused {
                    "> Channels"
                } else {
                    "  Channels"
                },
                label_style,
            ),
            render_selector(
                &format!("{}", app.audio.config.channels),
                channels_focused,
                highlight,
                normal,
            ),
        ]));
        rows.push(Row::new(vec![
            Span::styled(
                if buffer_focused {
                    "> Buffer"
                } else {
                    "  Buffer"
                },
                label_style,
            ),
            render_selector(
                &if app.audio.config.host_name.to_lowercase().contains("jack") {
                    "JACK managed".to_string()
                } else {
                    format!("{}", app.audio.config.buffer_size)
                },
                buffer_focused,
                highlight,
                normal,
            ),
        ]));
    }

    rows.push(Row::new(vec![
        Span::styled(
            if polyphony_focused {
                "> Voices"
            } else {
                "  Voices"
            },
            label_style,
        ),
        render_selector(
            &format!("{}", app.audio.config.max_voices),
            polyphony_focused,
            highlight,
            normal,
        ),
    ]));
    rows.push(Row::new(vec![
        Span::styled(
            if nudge_focused { "> Nudge" } else { "  Nudge" },
            label_style,
        ),
        render_selector(&nudge_label, nudge_focused, highlight, normal),
    ]));

    let table = Table::new(rows, [Constraint::Length(14), Constraint::Fill(1)]);
    frame.render_widget(table, content_area);
}

fn render_link(frame: &mut Frame, app: &App, link: &LinkState, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::Link;

    let enabled = link.is_enabled();
    let peers = link.peers();
    let (status_text, status_color) = if !enabled {
        ("DISABLED", theme.link_status.disabled)
    } else if peers > 0 {
        ("CONNECTED", theme.link_status.connected)
    } else {
        ("LISTENING", theme.link_status.listening)
    };

    let [header_area, content_area] =
        Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);

    // Custom header with status badge
    let header_style = if section_focused {
        Style::new()
            .fg(theme.engine.header_focused)
            .add_modifier(Modifier::BOLD)
    } else {
        Style::new()
            .fg(theme.engine.header)
            .add_modifier(Modifier::BOLD)
    };

    let [title_row, divider_row] =
        Layout::vertical([Constraint::Length(1), Constraint::Length(1)]).areas(header_area);

    let header_line = Line::from(vec![
        Span::styled("ABLETON LINK", header_style),
        Span::raw("  "),
        Span::styled(
            status_text,
            Style::new().fg(status_color).add_modifier(Modifier::BOLD),
        ),
    ]);
    frame.render_widget(Paragraph::new(header_line), title_row);
    frame.render_widget(
        Paragraph::new("\u{2500}".repeat(area.width as usize))
            .style(Style::new().fg(theme.engine.divider)),
        divider_row,
    );

    let highlight = Style::new()
        .fg(theme.engine.focused)
        .add_modifier(Modifier::BOLD);
    let normal = Style::new().fg(theme.engine.normal);
    let label_style = Style::new().fg(theme.engine.label);
    let value_style = Style::new().fg(theme.engine.value);

    let quantum_str = format!("{:.0}", link.quantum());
    let tempo_str = format!("{:.1} BPM", link.tempo());
    let beat_str = format!("{:.2}", link.beat());
    let phase_str = format!("{:.2}", link.phase());

    let enabled_focused = section_focused && app.audio.link_setting == LinkSetting::Enabled;
    let sss_focused = section_focused && app.audio.link_setting == LinkSetting::StartStopSync;
    let quantum_focused = section_focused && app.audio.link_setting == LinkSetting::Quantum;

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

    lines.push(Line::from(vec![
        Span::styled(
            if enabled_focused {
                "> Enabled    "
            } else {
                "  Enabled    "
            },
            label_style,
        ),
        render_selector(
            if enabled { "On" } else { "Off" },
            enabled_focused,
            highlight,
            normal,
        ),
    ]));
    lines.push(Line::from(vec![
        Span::styled(
            if sss_focused {
                "> Start/Stop "
            } else {
                "  Start/Stop "
            },
            label_style,
        ),
        render_selector(
            if link.is_start_stop_sync_enabled() {
                "On"
            } else {
                "Off"
            },
            sss_focused,
            highlight,
            normal,
        ),
    ]));
    lines.push(Line::from(vec![
        Span::styled(
            if quantum_focused {
                "> Quantum    "
            } else {
                "  Quantum    "
            },
            label_style,
        ),
        render_selector(&quantum_str, quantum_focused, highlight, normal),
    ]));

    // Session readouts
    let tempo_style = Style::new()
        .fg(theme.values.tempo)
        .add_modifier(Modifier::BOLD);
    lines.push(Line::from(""));
    lines.push(Line::from(vec![
        Span::styled("  Tempo      ", label_style),
        Span::styled(format!("  {tempo_str}"), tempo_style),
    ]));
    lines.push(Line::from(vec![
        Span::styled("  Beat       ", label_style),
        Span::styled(format!("  {beat_str}"), value_style),
    ]));
    lines.push(Line::from(vec![
        Span::styled("  Phase      ", label_style),
        Span::styled(format!("  {phase_str}"), value_style),
    ]));

    let visible = lines.len().min(content_area.height as usize);
    frame.render_widget(
        Paragraph::new(lines.into_iter().take(visible).collect::<Vec<_>>()),
        content_area,
    );
}

fn render_midi_output(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::MidiOutput;

    let [header_area, content_area] =
        Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);

    render_section_header(frame, "MIDI OUTPUTS", section_focused, header_area);

    let midi_outputs = midi::list_midi_outputs();
    let label_style = Style::new().fg(theme.engine.label);
    let value_style = Style::new().fg(theme.engine.normal);
    let highlight = Style::new()
        .fg(theme.engine.focused)
        .add_modifier(Modifier::BOLD);

    let mut lines: Vec<Line> = Vec::new();
    for slot in 0..4 {
        let is_focused = section_focused && app.audio.midi_output_slot == slot;
        let display = midi_display_name(&midi_outputs, app.midi.selected_outputs[slot]);
        let prefix = if is_focused { "> " } else { "  " };
        let style = if is_focused { highlight } else { label_style };
        let val_style = if is_focused { highlight } else { value_style };
        lines.push(Line::from(vec![
            Span::styled(format!("{prefix}Output {slot}  "), style),
            Span::styled(display, val_style),
        ]));
    }

    let visible = lines.len().min(content_area.height as usize);
    frame.render_widget(
        Paragraph::new(lines.into_iter().take(visible).collect::<Vec<_>>()),
        content_area,
    );
}

fn render_midi_input(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::MidiInput;

    let [header_area, content_area] =
        Layout::vertical([Constraint::Length(2), Constraint::Min(1)]).areas(area);

    render_section_header(frame, "MIDI INPUTS", section_focused, header_area);

    let midi_inputs = midi::list_midi_inputs();
    let label_style = Style::new().fg(theme.engine.label);
    let value_style = Style::new().fg(theme.engine.normal);
    let highlight = Style::new()
        .fg(theme.engine.focused)
        .add_modifier(Modifier::BOLD);

    let mut lines: Vec<Line> = Vec::new();
    for slot in 0..4 {
        let is_focused = section_focused && app.audio.midi_input_slot == slot;
        let display = midi_display_name(&midi_inputs, app.midi.selected_inputs[slot]);
        let prefix = if is_focused { "> " } else { "  " };
        let style = if is_focused { highlight } else { label_style };
        let val_style = if is_focused { highlight } else { value_style };
        lines.push(Line::from(vec![
            Span::styled(format!("{prefix}Input {slot}   "), style),
            Span::styled(display, val_style),
        ]));
    }

    let visible = lines.len().min(content_area.height as usize);
    frame.render_widget(
        Paragraph::new(lines.into_iter().take(visible).collect::<Vec<_>>()),
        content_area,
    );
}

fn midi_display_name(devices: &[midi::MidiDeviceInfo], selected: Option<usize>) -> String {
    if let Some(idx) = selected {
        devices
            .get(idx)
            .map(|d| d.name.clone())
            .unwrap_or_else(|| "(disconnected)".to_string())
    } else if devices.is_empty() {
        "(none found)".to_string()
    } else {
        "(not connected)".to_string()
    }
}

fn render_samples(frame: &mut Frame, app: &App, area: Rect) {
    let theme = theme::get();
    let section_focused = app.audio.section == EngineSection::Samples;

    let [header_area, content_area] = Layout::vertical([
        Constraint::Length(2),
        Constraint::Min(1),
    ])
    .areas(area);

    let path_count = app.audio.config.sample_paths.len();
    let sample_count: usize = app.audio.total_sample_count();
    let header_text = format!("SAMPLES  {path_count} paths \u{00b7} {sample_count} indexed");
    render_section_header(frame, &header_text, section_focused, header_area);

    let dim = Style::new().fg(theme.engine.dim);
    let path_style = Style::new().fg(theme.engine.path);
    let cursor_style = Style::new()
        .fg(theme.engine.focused)
        .add_modifier(Modifier::BOLD);

    let cursor = app.audio.sample_list.cursor;
    let scroll_offset = app.audio.sample_list.scroll_offset;
    let visible_rows = content_area.height as usize;

    let mut lines: Vec<Line> = Vec::new();
    if app.audio.config.sample_paths.is_empty() {
        lines.push(Line::from(Span::styled(
            "  No sample paths configured",
            dim,
        )));
        lines.push(Line::from(Span::styled(
            "  Add folders containing audio files",
            dim,
        )));
    } else {
        for (i, path) in app
            .audio
            .config
            .sample_paths
            .iter()
            .enumerate()
            .skip(scroll_offset)
            .take(visible_rows)
        {
            let is_cursor = section_focused && i == cursor;
            let prefix = if is_cursor { "> " } else { "  " };
            let count = app.audio.config.sample_counts.get(i).copied().unwrap_or(0);
            let path_str = path.to_string_lossy();
            let count_str = format!("  ({count})");
            let max_path = (content_area.width as usize)
                .saturating_sub(prefix.len() + count_str.len());
            let display = truncate_name(&path_str, max_path);
            let style = if is_cursor { cursor_style } else { path_style };
            lines.push(Line::from(vec![
                Span::styled(prefix.to_string(), if is_cursor { cursor_style } else { dim }),
                Span::styled(display, style),
                Span::styled(count_str, dim),
            ]));
        }
    }
    frame.render_widget(Paragraph::new(lines), content_area);
}

fn render_selector(value: &str, focused: bool, highlight: Style, normal: Style) -> Span<'static> {
    let style = if focused { highlight } else { normal };
    if focused {
        Span::styled(format!("< {value} >"), style)
    } else {
        Span::styled(format!("  {value}  "), style)
    }
}
