//! Full interactive control point demo with Ratatui-powered UI.
//!
//! This version replaces the legacy println!-driven interface with a
//! Crossterm + Ratatui dashboard featuring menus, overlays and live updates.

use std::collections::HashMap;
use std::io::{self, Stdout};
use std::process;
use std::sync::Arc;
use std::sync::mpsc::{self, Sender};
use std::thread;
use std::time::{Duration, Instant};

use anyhow::{Context, Result, anyhow};
use crossterm::event::{self, DisableMouseCapture, EnableMouseCapture, Event, KeyCode, KeyEvent};
use crossterm::execute;
use crossterm::terminal::{
    EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode,
};
use pmocontrol::model::TrackMetadata;
use pmocontrol::{
    ControlPoint, DeviceRegistryRead, MediaBrowser, MediaEntry, MediaServerEvent, PlaybackItem,
    PlaybackPosition, PlaybackPositionInfo, PlaybackStatus, RendererEvent, RendererInfo,
    TransportControl, UpnpMediaServer, UpnpMediaServer, VolumeControl,
};
use ratatui::Terminal;
use ratatui::backend::CrosstermBackend;
use ratatui::layout::{Alignment, Constraint, Direction, Layout, Rect};
use ratatui::style::{Color, Modifier, Style};
use ratatui::text::{Line, Span};
use ratatui::widgets::{Block, Borders, Clear, Gauge, List, ListItem, ListState, Paragraph};

const DEFAULT_TIMEOUT_SECS: u64 = 5;
const DEFAULT_DISCOVERY_SECS: u64 = 15;
const TICK_RATE: Duration = Duration::from_millis(200);

#[derive(Clone)]
struct UiState {
    renderer_name: String,
    server_name: Option<String>,
    playback_state: Option<pmocontrol::capabilities::PlaybackState>,
    position: Option<PlaybackPositionInfo>,
    volume: Option<u16>,
    mute: Option<bool>,
    metadata: Option<TrackMetadata>,
    last_status: Option<String>,
    current_track_uri: Option<String>,
}

impl UiState {
    fn new(renderer_name: String) -> Self {
        Self {
            renderer_name,
            server_name: None,
            playback_state: None,
            position: None,
            volume: None,
            mute: None,
            metadata: None,
            last_status: Some("Interface initialisée.".to_string()),
            current_track_uri: None,
        }
    }

    fn placeholder() -> Self {
        Self::new("<renderer non sélectionné>".to_string())
    }

    fn set_status<S: Into<String>>(&mut self, status: S) {
        self.last_status = Some(status.into());
    }
}

fn main() -> Result<()> {
    let _ = tracing_subscriber::fmt::try_init();

    println!("=== Full Control Point Ratatui Demo ===");
    println!(
        "Starting control point with timeout={}s",
        DEFAULT_TIMEOUT_SECS
    );

    let control_point =
        ControlPoint::spawn(DEFAULT_TIMEOUT_SECS).context("Failed to start control point")?;

    println!(
        "Discovery running for {} seconds...",
        DEFAULT_DISCOVERY_SECS
    );
    std::thread::sleep(Duration::from_secs(DEFAULT_DISCOVERY_SECS));

    let registry = control_point.registry();
    let renderers = {
        let reg = registry.read().expect("registry poisoned");
        let list = reg.list_renderers();
        if list.is_empty() {
            eprintln!("No renderers discovered. Exiting.");
            process::exit(1);
        }
        list
    };

    let servers = {
        let reg = registry.read().expect("registry poisoned");
        let list: Vec<UpnpMediaServer> = reg
            .list_servers()
            .into_iter()
            .filter(|s| s.has_content_directory && s.content_directory_control_url.is_some())
            .collect();
        if list.is_empty() {
            eprintln!("No media servers with ContentDirectory discovered. Exiting.");
            process::exit(1);
        }
        list
    };

    let control_point = Arc::new(control_point);
    let app = App::new(control_point, renderers, servers);
    if let Err(err) = run_app(app) {
        eprintln!("Application exited with error: {err}");
    }

    println!("\nExiting. Goodbye!");
    Ok(())
}

struct App {
    control_point: Arc<ControlPoint>,
    renderers: Vec<RendererInfo>,
    renderer_index: usize,
    renderer_info: Option<RendererInfo>,
    servers: Vec<UpnpMediaServer>,
    server_index: usize,
    server_info: Option<UpnpMediaServer>,
    music_server: Option<UpnpMediaServer>,
    browser: Option<BrowserState>,
    mode: Mode,
    ui_state: UiState,
    queue_snapshot: Vec<PlaybackItem>,
    queue_current_index: Option<usize>,
    show_queue_overlay: bool,
    show_help_overlay: bool,
    pending_binding_container: Option<String>,
    status_line: String,
    known_tracks: std::collections::HashMap<String, TrackMetadata>,
}

enum Mode {
    SelectRenderer,
    SelectServer,
    Browse,
    BindingPrompt,
    Control,
}

struct BrowserState {
    nav_state: NavigationState,
    entries: Vec<MediaEntry>,
    selected_index: usize,
}

enum AppEvent {
    Renderer(RendererEvent),
    Media(MediaServerEvent),
}

impl App {
    fn new(
        control_point: Arc<ControlPoint>,
        renderers: Vec<RendererInfo>,
        servers: Vec<UpnpMediaServer>,
    ) -> Self {
        Self {
            control_point,
            renderers,
            renderer_index: 0,
            renderer_info: None,
            servers,
            server_index: 0,
            server_info: None,
            music_server: None,
            browser: None,
            mode: Mode::SelectRenderer,
            ui_state: UiState::placeholder(),
            queue_snapshot: Vec::new(),
            queue_current_index: None,
            show_queue_overlay: false,
            show_help_overlay: false,
            pending_binding_container: None,
            status_line: "Sélectionne un renderer avec ↑/↓ et Entrée".to_string(),
            known_tracks: HashMap::new(),
        }
    }

    fn renderer_id(&self) -> Option<pmocontrol::model::ServiceId> {
        self.renderer_info.as_ref().map(|info| info.id.clone())
    }

    fn draw(&self, terminal: &mut ratatui::Frame<'_>) {
        match self.mode {
            Mode::SelectRenderer => self.draw_renderer_selection(terminal),
            Mode::SelectServer => self.draw_server_selection(terminal),
            Mode::Browse => self.draw_browser(terminal),
            _ => self.draw_control_screen(terminal),
        }

        if self.show_queue_overlay {
            self.draw_queue_overlay(terminal);
        }

        if self.show_help_overlay {
            self.draw_help_overlay(terminal);
        }

        if matches!(self.mode, Mode::BindingPrompt) {
            self.draw_binding_prompt(terminal);
        }
    }

    fn draw_renderer_selection(&self, f: &mut ratatui::Frame<'_>) {
        let area = f.size();
        let block = Block::default()
            .borders(Borders::ALL)
            .title("Sélection du renderer");
        let items: Vec<ListItem> = self
            .renderers
            .iter()
            .map(|info| {
                let text = format!("{} | {}", info.friendly_name, info.model_name);
                ListItem::new(text)
            })
            .collect();
        let list = List::new(items)
            .block(block)
            .highlight_style(
                Style::default()
                    .fg(Color::Yellow)
                    .add_modifier(Modifier::BOLD),
            )
            .highlight_symbol("▶ ");
        let mut state = ListState::default();
        state.select(Some(self.renderer_index));
        f.render_stateful_widget(list, area, &mut state);
        self.draw_status_line(f);
    }

    fn draw_server_selection(&self, f: &mut ratatui::Frame<'_>) {
        let area = f.size();
        let block = Block::default()
            .borders(Borders::ALL)
            .title("Sélection du serveur");
        let items: Vec<ListItem> = self
            .servers
            .iter()
            .map(|info| {
                let text = format!("{} | {}", info.friendly_name, info.model_name);
                ListItem::new(text)
            })
            .collect();
        let list = List::new(items)
            .block(block)
            .highlight_style(
                Style::default()
                    .fg(Color::Cyan)
                    .add_modifier(Modifier::BOLD),
            )
            .highlight_symbol("▶ ");
        let mut state = ListState::default();
        state.select(Some(self.server_index));
        f.render_stateful_widget(list, area, &mut state);
        self.draw_status_line(f);
    }

    fn draw_browser(&self, f: &mut ratatui::Frame<'_>) {
        let area = f.size();
        let Some(browser) = &self.browser else {
            self.draw_status_line(f);
            return;
        };

        let block = Block::default().borders(Borders::ALL).title(Span::styled(
            format!(
                "Navigation: {} (id: {})",
                browser.nav_state.current_container_title, browser.nav_state.current_container_id
            ),
            Style::default()
                .fg(Color::White)
                .add_modifier(Modifier::BOLD),
        ));

        let items: Vec<ListItem> = browser
            .entries
            .iter()
            .map(|entry| {
                let icon = if entry.is_container { "📁" } else { "♪" };
                let label = format!("{} {}", icon, entry.title);
                ListItem::new(label)
            })
            .collect();

        let list = List::new(items)
            .block(block)
            .highlight_style(
                Style::default()
                    .fg(Color::Green)
                    .add_modifier(Modifier::BOLD),
            )
            .highlight_symbol("▶ ");
        let mut state = ListState::default();
        state.select(Some(browser.selected_index));
        f.render_stateful_widget(list, area, &mut state);
        self.draw_status_line(f);
    }

    fn draw_control_screen(&self, f: &mut ratatui::Frame<'_>) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([
                Constraint::Length(5),
                Constraint::Min(8),
                Constraint::Length(3),
            ])
            .split(f.size());

        self.draw_header(f, chunks[0]);
        self.draw_playback_panel(f, chunks[1]);
        self.draw_help(f, chunks[2]);

        self.draw_status_line(f);
    }

    fn draw_header(&self, f: &mut ratatui::Frame<'_>, area: Rect) {
        let ui = &self.ui_state;
        let renderer = &ui.renderer_name;
        let server = ui.server_name.as_deref().unwrap_or("<serveur?>");
        let state = ui
            .playback_state
            .as_ref()
            .map(|s| format!("{:?}", s))
            .unwrap_or_else(|| "Inconnu".to_string());
        let volume = ui
            .volume
            .map(|v| v.to_string())
            .unwrap_or_else(|| "--".to_string());
        let mute = match ui.mute {
            Some(true) => "ON",
            Some(false) => "OFF",
            None => "??",
        };

        let text = vec![
            Line::from(vec![Span::styled(
                format!("Renderer : {renderer}"),
                Style::default().fg(Color::Yellow),
            )]),
            Line::from(vec![Span::raw(format!("Serveur  : {server}"))]),
            Line::from(vec![Span::raw(format!(
                "État     : {state}  Volume {volume} | Mute {mute}"
            ))]),
        ];

        let paragraph = Paragraph::new(text)
            .block(Block::default().borders(Borders::ALL).title("Statut"))
            .alignment(Alignment::Left);
        f.render_widget(paragraph, area);
    }

    fn draw_playback_panel(&self, f: &mut ratatui::Frame<'_>, area: Rect) {
        let chunks = Layout::default()
            .direction(Direction::Vertical)
            .constraints([Constraint::Min(6), Constraint::Length(3)])
            .split(area);

        let meta_lines = render_metadata_block(self.ui_state.metadata.as_ref());
        let paragraph = Paragraph::new(meta_lines)
            .block(
                Block::default()
                    .borders(Borders::ALL)
                    .title("Lecture en cours"),
            )
            .wrap(ratatui::widgets::Wrap { trim: true });
        f.render_widget(paragraph, chunks[0]);

        let gauge_area = chunks[1];
        let gauge = match self.ui_state.position.as_ref() {
            Some(pos) => build_progress_gauge(pos),
            None => Gauge::default()
                .block(Block::default().borders(Borders::ALL).title("Progression"))
                .label("en attente...")
                .ratio(0.0),
        };
        f.render_widget(gauge, gauge_area);
    }

    fn draw_help(&self, f: &mut ratatui::Frame<'_>, area: Rect) {
        let lines = vec![
            Line::from("Commandes: h=Aide détaillée | R=Renderer | S=Serveur"),
            Line::from("           r=Play p=Pause s=Stop n=Next"),
            Line::from("           +=Vol+ -=Vol- m=Mute i=Infos k=Queue b=Binding"),
            Line::from("           q=Quit | ESC ferme les overlays"),
        ];
        let paragraph =
            Paragraph::new(lines).block(Block::default().borders(Borders::ALL).title("Raccourcis"));
        f.render_widget(paragraph, area);
    }

    fn draw_help_overlay(&self, f: &mut ratatui::Frame<'_>) {
        let area = centered_rect(70, 60, f.size());
        let lines = vec![
            Line::from("Raccourcis disponibles:"),
            Line::from("  R / S   : re-sélection du renderer / serveur"),
            Line::from("  n / p   : navigation dans la file"),
            Line::from("  +/-/m   : volume et mute"),
            Line::from("  k       : afficher la queue actuelle"),
            Line::from("  b       : afficher le binding playlist"),
            Line::from("  h       : fermer cette aide"),
            Line::from("  ESC     : fermer les overlays"),
        ];
        let block = Block::default()
            .title("Aide détaillée (h pour fermer)")
            .borders(Borders::ALL)
            .style(Style::default().bg(Color::Black));
        f.render_widget(Clear, area);
        f.render_widget(Paragraph::new(lines).block(block), area);
    }

    fn draw_queue_overlay(&self, f: &mut ratatui::Frame<'_>) {
        let area = centered_rect(80, 70, f.size());
        let mut lines = Vec::new();
        lines.push(Line::from("Playlist actuelle (▶ = en cours):"));
        if self.queue_snapshot.is_empty() {
            lines.push(Line::from("  <vide>"));
        } else {
            for (idx, item) in self.queue_snapshot.iter().enumerate() {
                let meta = item.metadata.as_ref();
                let title = meta.and_then(|m| m.title.as_deref()).unwrap_or("<titre>");
                let artist = meta.and_then(|m| m.artist.as_deref()).unwrap_or("");
                let prefix = match self.queue_current_index {
                    Some(current) if current == idx => "▶",
                    _ => " ",
                };
                let line = if artist.is_empty() {
                    format!("{prefix} [{idx}] {title}")
                } else {
                    format!("{prefix} [{idx}] {artist} - {title}")
                };
                lines.push(Line::from(line));
            }
        }
        let block = Block::default()
            .title("Playlist (fermer avec k ou Esc)")
            .borders(Borders::ALL)
            .style(Style::default().bg(Color::Black));
        f.render_widget(Clear, area);
        f.render_widget(Paragraph::new(lines).block(block), area);
    }

    fn draw_binding_prompt(&self, f: &mut ratatui::Frame<'_>) {
        let area = centered_rect(60, 40, f.size());
        let lines = vec![
            Line::from("Attacher la file au conteneur pour auto-refresh ?"),
            Line::from("y = oui | n = non"),
        ];
        let block = Block::default()
            .title("Binding playlist")
            .borders(Borders::ALL)
            .style(Style::default().bg(Color::Black));
        f.render_widget(Clear, area);
        f.render_widget(Paragraph::new(lines).block(block), area);
    }

    fn draw_status_line(&self, f: &mut ratatui::Frame<'_>) {
        let area = Rect {
            x: 0,
            y: f.size().height.saturating_sub(1),
            width: f.size().width,
            height: 1,
        };
        let status = self
            .ui_state
            .last_status
            .clone()
            .unwrap_or_else(|| self.status_line.clone());
        let paragraph = Paragraph::new(status).style(Style::default().fg(Color::Gray));
        f.render_widget(paragraph, area);
    }

    fn handle_key(&mut self, key: KeyEvent) -> Result<bool> {
        match self.mode {
            Mode::SelectRenderer => self.handle_renderer_key(key),
            Mode::SelectServer => self.handle_server_key(key),
            Mode::Browse => self.handle_browse_key(key),
            Mode::BindingPrompt => self.handle_binding_key(key),
            Mode::Control => self.handle_control_key(key),
        }
    }

    fn handle_renderer_key(&mut self, key: KeyEvent) -> Result<bool> {
        match key.code {
            KeyCode::Char('q') => return Ok(true),
            KeyCode::Up => {
                if self.renderer_index > 0 {
                    self.renderer_index -= 1;
                }
            }
            KeyCode::Down => {
                if self.renderer_index + 1 < self.renderers.len() {
                    self.renderer_index += 1;
                }
            }
            KeyCode::Enter => {
                let info = self.renderers[self.renderer_index].clone();
                if let Some(current) = &self.renderer_info {
                    if current.id != info.id {
                        let _ = self.stop_current_renderer_playback();
                    }
                }
                self.ui_state = UiState::new(info.friendly_name.clone());
                self.renderer_info = Some(info);
                self.server_info = None;
                self.music_server = None;
                self.browser = None;
                self.queue_snapshot.clear();
                self.queue_current_index = None;
                self.known_tracks.clear();
                self.pending_binding_container = None;
                self.ui_state.current_track_uri = None;
                self.ui_state.server_name = None;
                self.show_queue_overlay = false;
                self.show_help_overlay = false;
                self.mode = Mode::SelectServer;
                self.update_renderer_status_from_device();
                self.status_line = "Sélectionne un serveur avec ↑/↓ et Entrée".to_string();
            }
            _ => {}
        }
        Ok(false)
    }

    fn handle_server_key(&mut self, key: KeyEvent) -> Result<bool> {
        match key.code {
            KeyCode::Char('q') => return Ok(true),
            KeyCode::Up => {
                if self.server_index > 0 {
                    self.server_index -= 1;
                }
            }
            KeyCode::Down => {
                if self.server_index + 1 < self.servers.len() {
                    self.server_index += 1;
                }
            }
            KeyCode::Enter => {
                let info = self.servers[self.server_index].clone();
                match UpnpMediaServer::from_info(&info, Duration::from_secs(DEFAULT_TIMEOUT_SECS)) {
                    Ok(server) => {
                        let entries = server.browse_root()?;
                        let browser = BrowserState::new(entries);
                        self.music_server = Some(server);
                        self.server_info = Some(info.clone());
                        self.browser = Some(browser);
                        self.mode = Mode::Browse;
                        self.ui_state.server_name = Some(info.friendly_name.clone());
                        self.status_line =
                            "Navigue avec ↑/↓, Entrée pour ouvrir, s pour sélectionner".to_string();
                    }
                    Err(err) => {
                        self.ui_state
                            .set_status(format!("MusicServer init failed: {err}"));
                    }
                }
            }
            _ => {}
        }
        Ok(false)
    }

    fn handle_browse_key(&mut self, key: KeyEvent) -> Result<bool> {
        if key.code == KeyCode::Char('q') {
            return Ok(true);
        }
        let Some(browser) = self.browser.as_mut() else {
            return Ok(false);
        };
        let Some(server) = self.music_server.as_mut() else {
            return Ok(false);
        };

        match key.code {
            KeyCode::Up => {
                if browser.selected_index > 0 {
                    browser.selected_index -= 1;
                }
            }
            KeyCode::Down => {
                if browser.selected_index + 1 < browser.entries.len() {
                    browser.selected_index += 1;
                }
            }
            KeyCode::Enter => {
                if let Some(entry) = browser.current_entry() {
                    if entry.is_container {
                        match server.browse_children(&entry.id, 0, 100) {
                            Ok(children) => {
                                browser
                                    .nav_state
                                    .enter_container(entry.id.clone(), entry.title.clone());
                                browser.entries = children;
                                browser.selected_index = 0;
                            }
                            Err(err) => {
                                self.ui_state
                                    .set_status(format!("Impossible d'ouvrir: {err}"));
                            }
                        }
                    }
                }
            }
            KeyCode::Char('b') => {
                if browser.nav_state.go_back() {
                    let entries = if browser.nav_state.current_container_id == "0" {
                        server.browse_root()?
                    } else {
                        server.browse_children(&browser.nav_state.current_container_id, 0, 100)?
                    };
                    browser.entries = entries;
                    browser.selected_index = 0;
                }
            }
            KeyCode::Char('s') => {
                self.enqueue_current_container()?;
            }
            _ => {}
        }
        Ok(false)
    }

    fn handle_binding_key(&mut self, key: KeyEvent) -> Result<bool> {
        match key.code {
            KeyCode::Char('y') => {
                self.attach_binding(true)?;
            }
            KeyCode::Char('n') | KeyCode::Esc => {
                self.attach_binding(false)?;
            }
            KeyCode::Char('q') => return Ok(true),
            _ => {}
        }
        Ok(false)
    }

    fn handle_control_key(&mut self, key: KeyEvent) -> Result<bool> {
        match key.code {
            KeyCode::Char('q') => return Ok(true),
            KeyCode::Char('R') => {
                self.open_renderer_menu();
            }
            KeyCode::Char('S') => {
                self.open_server_menu();
            }
            KeyCode::Char('h') => {
                self.show_help_overlay = !self.show_help_overlay;
                if self.show_help_overlay {
                    self.ui_state.set_status("Aide ouverte");
                } else {
                    self.ui_state.set_status("Aide fermée");
                }
            }
            KeyCode::Char('p') => {
                self.pause_renderer()?;
            }
            KeyCode::Char('r') => {
                self.resume_renderer()?;
            }
            KeyCode::Char('s') => {
                self.stop_renderer()?;
            }
            KeyCode::Char('n') => {
                self.play_next()?;
            }
            KeyCode::Char('+') | KeyCode::Char('=') => {
                self.adjust_volume(5)?;
            }
            KeyCode::Char('-') => {
                self.adjust_volume(-5)?;
            }
            KeyCode::Char('m') => {
                self.toggle_mute()?;
            }
            KeyCode::Char('i') => {
                self.show_renderer_info()?;
            }
            KeyCode::Char('k') => {
                self.show_queue_overlay = !self.show_queue_overlay;
            }
            KeyCode::Char('b') => {
                self.show_binding();
            }
            KeyCode::Esc => {
                self.show_queue_overlay = false;
                self.show_help_overlay = false;
            }
            _ => {}
        }
        Ok(false)
    }

    fn enqueue_current_container(&mut self) -> Result<()> {
        let Some(browser) = self.browser.as_ref() else {
            return Ok(());
        };
        let current_container_id = browser.nav_state.current_container_id.clone();
        let entries = browser.entries.clone();
        let Some(server) = self.music_server.as_ref() else {
            return Ok(());
        };
        let renderer_id = self
            .renderer_id()
            .ok_or_else(|| anyhow!("Renderer not selected"))?;

        let items = collect_playable_items(server, &entries)?;
        if items.is_empty() {
            self.ui_state.set_status("Aucun média dans ce conteneur");
            return Ok(());
        }

        self.control_point.clear_queue(&renderer_id)?;
        self.control_point.enqueue_items(&renderer_id, items)?;
        let (full_queue, current_index) = self
            .control_point
            .get_full_queue_snapshot(&renderer_id)
            .context("failed to snapshot queue after enqueue")?;
        self.queue_snapshot = full_queue;
        self.queue_current_index = current_index;
        self.record_queue_metadata();
        self.pending_binding_container = Some(current_container_id);
        self.status_line = format!("File prête ({})", self.queue_snapshot.len());
        self.ui_state.set_status(&self.status_line);
        self.mode = Mode::BindingPrompt;
        Ok(())
    }

    fn attach_binding(&mut self, attach: bool) -> Result<()> {
        if attach {
            if let (Some(server), Some(container), Some(renderer_id)) = (
                &self.server_info,
                self.pending_binding_container.clone(),
                self.renderer_id(),
            ) {
                self.control_point
                    .attach_queue_to_playlist(&renderer_id, server.id.clone(), container.clone())
                    .context("Failed to attach queue to playlist")?;
                self.ui_state
                    .set_status(format!("File liée à '{}'", container));
            }
        } else {
            self.ui_state.set_status("File locale uniquement");
        }

        self.pending_binding_container = None;
        self.mode = Mode::Control;
        self.start_playback()?;
        self.update_renderer_status_from_device();
        Ok(())
    }

    fn start_playback(&mut self) -> Result<()> {
        let renderer_id = self
            .renderer_id()
            .ok_or_else(|| anyhow!("Renderer not selected"))?;
        let next_item = self.peek_next_queue_item(&renderer_id);
        self.control_point.play_next_from_queue(&renderer_id)?;
        if let Some(item) = next_item {
            self.apply_item_as_current(&item);
        } else {
            self.ui_state.set_status("File vide");
        }
        self.refresh_queue_snapshot(&renderer_id);
        Ok(())
    }

    fn pause_renderer(&mut self) -> Result<()> {
        let renderer = self.get_renderer()?;
        renderer.pause()?;
        self.ui_state.set_status("Lecture en pause");
        Ok(())
    }

    fn resume_renderer(&mut self) -> Result<()> {
        let renderer = self.get_renderer()?;
        renderer.play()?;
        self.ui_state.set_status("Lecture reprise");
        Ok(())
    }

    fn stop_renderer(&mut self) -> Result<()> {
        let renderer = self.get_renderer()?;
        renderer.stop()?;
        self.ui_state.set_status("Lecture arrêtée");
        Ok(())
    }

    fn play_next(&mut self) -> Result<()> {
        let renderer_id = self
            .renderer_id()
            .ok_or_else(|| anyhow!("Renderer not selected"))?;
        let next_item = self.peek_next_queue_item(&renderer_id);
        self.control_point.play_next_from_queue(&renderer_id)?;
        if let Some(item) = next_item {
            self.apply_item_as_current(&item);
        } else {
            self.ui_state.set_status("Piste suivante (file vide)");
        }
        self.refresh_queue_snapshot(&renderer_id);
        Ok(())
    }

    fn adjust_volume(&mut self, delta: i32) -> Result<()> {
        let renderer = self.get_renderer()?;
        let current = renderer.volume()?;
        let new_volume = (current as i32 + delta).clamp(0, 100) as u16;
        renderer.set_volume(new_volume)?;
        self.ui_state.set_status(format!("Volume → {new_volume}"));
        self.ui_state.volume = Some(new_volume);
        Ok(())
    }

    fn toggle_mute(&mut self) -> Result<()> {
        let renderer = self.get_renderer()?;
        let current = renderer.mute()?;
        renderer.set_mute(!current)?;
        self.ui_state.mute = Some(!current);
        self.ui_state.set_status(if !current {
            "Mute activé"
        } else {
            "Mute désactivé"
        });
        Ok(())
    }

    fn show_renderer_info(&mut self) -> Result<()> {
        let renderer = self.get_renderer()?;
        let info = renderer.info();
        self.ui_state.set_status(format!(
            "Renderer: {} ({})",
            info.friendly_name, info.model_name
        ));
        Ok(())
    }

    fn show_binding(&mut self) {
        if let Some(renderer_id) = self.renderer_id() {
            if let Some((server_id, container_id, has_seen_update)) = self
                .control_point
                .current_queue_playlist_binding(&renderer_id)
            {
                self.ui_state.set_status(format!(
                    "Binding: {} -> {} (maj vue: {})",
                    server_id.0, container_id, has_seen_update
                ));
            } else {
                self.ui_state.set_status("Pas de binding actif");
            }
        }
    }

    fn stop_current_renderer_playback(&self) -> Result<()> {
        if let Some(renderer_id) = self.renderer_id() {
            if let Some(renderer) = self.control_point.music_renderer_by_id(&renderer_id) {
                renderer.stop()?;
            }
        }
        Ok(())
    }

    fn update_renderer_status_from_device(&mut self) {
        if let Ok(renderer) = self.get_renderer() {
            if let Ok(volume) = renderer.volume() {
                self.ui_state.volume = Some(volume);
            }
            if let Ok(mute) = renderer.mute() {
                self.ui_state.mute = Some(mute);
            }
            if let Ok(state) = renderer.playback_state() {
                self.ui_state.playback_state = Some(state);
            }
            if let Ok(position) = renderer.playback_position() {
                self.ui_state.position = Some(position);
            }
        }
    }

    fn open_renderer_menu(&mut self) {
        self.mode = Mode::SelectRenderer;
        self.status_line = "Sélectionne un renderer avec ↑/↓ et Entrée".to_string();
        self.ui_state.set_status("Menu renderer ouvert");
        self.show_queue_overlay = false;
        self.show_help_overlay = false;
    }

    fn open_server_menu(&mut self) {
        if self.renderer_info.is_some() {
            self.mode = Mode::SelectServer;
            self.status_line = "Sélectionne un serveur avec ↑/↓ et Entrée".to_string();
            self.ui_state.set_status("Menu serveur ouvert");
            self.show_queue_overlay = false;
            self.show_help_overlay = false;
        } else {
            self.ui_state.set_status("Sélectionne d'abord un renderer");
        }
    }

    fn record_queue_metadata(&mut self) {
        for item in &self.queue_snapshot {
            if let Some(meta) = playback_metadata_from_item(item) {
                self.known_tracks.insert(item.uri.clone(), meta);
            }
        }
    }

    fn apply_item_as_current(&mut self, item: &PlaybackItem) {
        if let Some(meta) = playback_metadata_from_item(item) {
            self.known_tracks.insert(item.uri.clone(), meta.clone());
            self.ui_state.metadata = Some(meta.clone());
            self.ui_state.set_status(format_track_status(&meta));
        } else {
            let label = item
                .metadata
                .as_ref()
                .and_then(|meta| meta.title.as_deref())
                .map(|t| t.to_string())
                .unwrap_or_else(|| item.uri.clone());
            self.ui_state.metadata = None;
            self.ui_state.set_status(format!("Lecture: {label}"));
        }
        self.ui_state.current_track_uri = Some(item.uri.clone());
    }

    fn update_metadata_from_uri(&mut self, uri: &str) {
        self.ui_state.current_track_uri = Some(uri.to_string());
        if let Some(meta) = self.known_tracks.get(uri).cloned() {
            self.ui_state.metadata = Some(meta.clone());
            self.ui_state.set_status(format_track_status(&meta));
        } else {
            self.ui_state.metadata = None;
            self.ui_state.set_status(format!("Lecture: {uri}"));
        }
    }

    fn refresh_queue_snapshot(&mut self, renderer_id: &pmocontrol::model::ServiceId) {
        if let Ok((queue, current_index)) = self.control_point.get_full_queue_snapshot(renderer_id)
        {
            self.queue_snapshot = queue;
            self.queue_current_index = current_index;
            self.record_queue_metadata();
            if let Some(idx) = self.queue_current_index {
                if let Some(item) = self.queue_snapshot.get(idx).cloned() {
                    let needs_update =
                        self.ui_state.current_track_uri.as_deref() != Some(item.uri.as_str());
                    if needs_update {
                        self.apply_item_as_current(&item);
                    }
                }
            }
        }
    }

    fn peek_next_queue_item(
        &self,
        renderer_id: &pmocontrol::model::ServiceId,
    ) -> Option<PlaybackItem> {
        self.control_point
            .get_queue_snapshot(renderer_id)
            .ok()
            .and_then(|queue| queue.into_iter().next())
    }

    fn get_renderer(&self) -> Result<pmocontrol::MusicRendererBackend> {
        let renderer_id = self
            .renderer_id()
            .ok_or_else(|| anyhow!("Renderer not selected"))?;
        self.control_point
            .music_renderer_by_id(&renderer_id)
            .ok_or_else(|| anyhow!("Renderer not found"))
    }

    fn handle_app_event(&mut self, event: AppEvent) {
        match event {
            AppEvent::Renderer(ev) => self.handle_renderer_event(ev),
            AppEvent::Media(ev) => self.handle_media_event(ev),
        }
    }

    fn handle_renderer_event(&mut self, event: RendererEvent) {
        let Some(renderer_id) = self.renderer_id() else {
            return;
        };
        match event {
            RendererEvent::StateChanged { id, state } => {
                if id == renderer_id {
                    self.ui_state.playback_state = Some(state.clone());
                    self.ui_state.set_status(format!("État: {:?}", state));
                }
            }
            RendererEvent::PositionChanged { id, position } => {
                if id == renderer_id {
                    let track_changed =
                        position.track_uri.as_ref() != self.ui_state.current_track_uri.as_ref();
                    self.ui_state.position = Some(position.clone());
                    if track_changed {
                        if let Some(uri) = position.track_uri.as_deref() {
                            self.update_metadata_from_uri(uri);
                            self.refresh_queue_snapshot(&renderer_id);
                        } else {
                            self.ui_state.current_track_uri = None;
                        }
                    }
                }
            }
            RendererEvent::VolumeChanged { id, volume } => {
                if id == renderer_id {
                    self.ui_state.volume = Some(volume);
                    self.ui_state.set_status(format!("Volume → {volume}"));
                }
            }
            RendererEvent::MuteChanged { id, mute } => {
                if id == renderer_id {
                    self.ui_state.mute = Some(mute);
                    self.ui_state.set_status(if mute {
                        "Mute activé"
                    } else {
                        "Mute désactivé"
                    });
                }
            }
            RendererEvent::MetadataChanged { id, metadata } => {
                if id == renderer_id {
                    self.ui_state.metadata = Some(metadata.clone());
                    self.ui_state.set_status(format_track_status(&metadata));
                    if let Some(uri) = self.ui_state.current_track_uri.clone() {
                        self.known_tracks.insert(uri, metadata);
                    }
                }
            }
            RendererEvent::QueueUpdated { id, queue_length } => {
                if id == renderer_id {
                    self.refresh_queue_snapshot(&renderer_id);
                    self.ui_state
                        .set_status(format!("File mise à jour ({queue_length})"));
                }
            }
            RendererEvent::BindingChanged { .. } => {
                // Binding events are surfaced via the REST API; the demo UI does not
                // expose binding info yet, so we ignore them.
            }
        }
    }

    fn handle_media_event(&mut self, event: MediaServerEvent) {
        match event {
            MediaServerEvent::GlobalUpdated {
                server_id,
                system_update_id,
            } => {
                self.ui_state.set_status(format!(
                    "MAJ serveur {} (SystemUpdateID={})",
                    server_id.0,
                    system_update_id.unwrap_or(0)
                ));
            }
            MediaServerEvent::ContainersUpdated {
                server_id,
                container_ids,
            } => {
                let mut status = format!(
                    "Conteneurs mis à jour sur {}: {:?}",
                    server_id.0, container_ids
                );
                if let Some(renderer_id) = self.renderer_id() {
                    if let Some((bound_server, bound_container, _)) = self
                        .control_point
                        .current_queue_playlist_binding(&renderer_id)
                    {
                        if bound_server == server_id && container_ids.contains(&bound_container) {
                            status
                                .push_str(&format!(" | Playlist '{}' rafraîchie", bound_container));
                            self.refresh_queue_snapshot(&renderer_id);
                        }
                    }
                }
                self.ui_state.set_status(status);
            }
        }
    }
}

impl BrowserState {
    fn new(entries: Vec<MediaEntry>) -> Self {
        Self {
            nav_state: NavigationState::new("0".to_string(), "Root".to_string()),
            entries,
            selected_index: 0,
        }
    }

    fn current_entry(&self) -> Option<&MediaEntry> {
        self.entries.get(self.selected_index)
    }
}

fn run_app(mut app: App) -> Result<()> {
    let mut terminal = setup_terminal()?;
    let (event_tx, event_rx) = mpsc::channel();
    let mut renderer_events_started = false;
    let mut last_tick = Instant::now();

    loop {
        terminal.draw(|f| app.draw(f))?;

        while let Ok(event) = event_rx.try_recv() {
            app.handle_app_event(event);
        }

        if !renderer_events_started {
            if app.renderer_id().is_some() {
                start_event_threads(Arc::clone(&app.control_point), event_tx.clone());
                renderer_events_started = true;
            }
        }

        let timeout = TICK_RATE
            .checked_sub(last_tick.elapsed())
            .unwrap_or_else(|| Duration::from_secs(0));

        if event::poll(timeout)? {
            if let Event::Key(key) = event::read()? {
                if app.handle_key(key)? {
                    let _ = app.stop_current_renderer_playback();
                    break;
                }
            }
        }

        if last_tick.elapsed() >= TICK_RATE {
            last_tick = Instant::now();
        }
    }

    restore_terminal(&mut terminal)?;
    Ok(())
}

fn setup_terminal() -> Result<Terminal<CrosstermBackend<Stdout>>> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen, EnableMouseCapture)?;
    let backend = CrosstermBackend::new(stdout);
    let terminal = Terminal::new(backend)?;
    Ok(terminal)
}

fn restore_terminal(terminal: &mut Terminal<CrosstermBackend<Stdout>>) -> Result<()> {
    disable_raw_mode()?;
    execute!(
        terminal.backend_mut(),
        LeaveAlternateScreen,
        DisableMouseCapture
    )?;
    terminal.show_cursor()?;
    Ok(())
}

fn start_event_threads(control_point: Arc<ControlPoint>, tx: Sender<AppEvent>) {
    let renderer_cp = Arc::clone(&control_point);
    let renderer_tx = tx.clone();
    thread::spawn(move || {
        let event_rx = renderer_cp.subscribe_events();
        for event in event_rx {
            if renderer_tx.send(AppEvent::Renderer(event)).is_err() {
                break;
            }
        }
    });

    thread::spawn(move || {
        let media_rx = control_point.subscribe_media_server_events();
        for event in media_rx {
            if tx.send(AppEvent::Media(event)).is_err() {
                break;
            }
        }
    });
}

/// Navigation state for ContentDirectory browsing.
struct NavigationState {
    path_stack: Vec<(String, String)>,
    current_container_id: String,
    current_container_title: String,
}

impl NavigationState {
    fn new(root_id: String, root_title: String) -> Self {
        Self {
            path_stack: Vec::new(),
            current_container_id: root_id,
            current_container_title: root_title,
        }
    }

    fn enter_container(&mut self, container_id: String, container_title: String) {
        self.path_stack.push((
            self.current_container_id.clone(),
            self.current_container_title.clone(),
        ));
        self.current_container_id = container_id;
        self.current_container_title = container_title;
    }

    fn go_back(&mut self) -> bool {
        if let Some((parent_id, parent_title)) = self.path_stack.pop() {
            self.current_container_id = parent_id;
            self.current_container_title = parent_title;
            true
        } else {
            false
        }
    }
}

/// Collect playable items from MediaEntry list (including nested containers).
fn collect_playable_items(
    server: &UpnpMediaServer,
    entries: &[MediaEntry],
) -> Result<Vec<PlaybackItem>> {
    let mut items = Vec::new();
    for entry in entries {
        if entry.is_container {
            match server.browse_children(&entry.id, 0, 100) {
                Ok(children) => {
                    items.extend(collect_playable_items(server, &children)?);
                }
                Err(err) => {
                    eprintln!(
                        "Warning: failed to browse container '{}': {err}",
                        entry.title
                    );
                }
            }
        } else if let Some(item) = playback_item_from_entry(server, entry) {
            items.push(item);
        }
    }
    Ok(items)
}

/// Convert MediaEntry to PlaybackItem.
fn playback_item_from_entry(server: &UpnpMediaServer, entry: &MediaEntry) -> Option<PlaybackItem> {
    let resource = entry.resources.iter().find(|res| res.is_audio())?;
    let metadata = TrackMetadata {
        title: Some(entry.title.clone()),
        artist: entry.artist.clone(),
        album: entry.album.clone(),
        genre: entry.genre.clone(),
        album_art_uri: entry.album_art_uri.clone(),
        date: entry.date.clone(),
        track_number: entry.track_number.clone(),
        creator: entry.creator.clone(),
    };
    Some(PlaybackItem {
        media_server_id: server.id().clone(),
        didl_id: entry.id.clone(),
        uri: resource.uri.clone(),
        protocol_info: resource.protocol_info.clone(),
        metadata: Some(metadata),
    })
}

fn playback_metadata_from_item(item: &PlaybackItem) -> Option<TrackMetadata> {
    item.metadata.clone()
}

fn render_metadata_block(metadata: Option<&TrackMetadata>) -> Vec<Line<'static>> {
    let mut lines = Vec::new();
    if let Some(meta) = metadata {
        let title = meta
            .title
            .clone()
            .unwrap_or_else(|| "<Titre inconnu>".to_string());
        lines.push(Line::from(format!("Titre  : {title}")));
        if let Some(artist) = meta.artist.as_deref() {
            lines.push(Line::from(format!("Artiste: {artist}")));
        }
        if let Some(album) = meta.album.as_deref() {
            lines.push(Line::from(format!("Album  : {album}")));
        }
        if let Some(genre) = meta.genre.as_deref() {
            lines.push(Line::from(format!("Genre  : {genre}")));
        }
        if let Some(date) = meta.date.as_deref() {
            lines.push(Line::from(format!("Date   : {}", format_date(date))));
        }
        if let Some(track) = meta.track_number.as_deref() {
            lines.push(Line::from(format!("Piste  : {track}")));
        }
        if let Some(art) = meta.album_art_uri.as_deref() {
            lines.push(Line::from(format!("Cover  : {art}")));
        }
    } else {
        lines.push(Line::from("(En attente des métadonnées...)"));
    }
    lines
}

fn format_date(raw: &str) -> String {
    let parts: Vec<&str> = raw.split('-').collect();
    if parts.len() == 3 && parts[1] == "01" && parts[2] == "01" {
        return parts[0].to_string();
    }
    raw.to_string()
}

fn build_progress_gauge(position: &PlaybackPositionInfo) -> Gauge<'static> {
    let rel_secs = position.rel_time.as_deref().and_then(parse_time_to_seconds);
    let dur_secs = position
        .track_duration
        .as_deref()
        .and_then(parse_time_to_seconds);

    let ratio = match (rel_secs, dur_secs) {
        (Some(rel), Some(dur)) if dur > 0 => rel as f64 / dur as f64,
        _ => 0.0,
    };
    let ratio = ratio.clamp(0.0, 1.0);

    let rel_label = position
        .rel_time
        .clone()
        .unwrap_or_else(|| "--:--".to_string());
    let dur_label = position
        .track_duration
        .clone()
        .unwrap_or_else(|| "--:--".to_string());

    let label = format!("{} / {}", rel_label, dur_label);
    Gauge::default()
        .block(Block::default().borders(Borders::ALL).title("Progression"))
        .gauge_style(Style::default().fg(Color::Magenta))
        .ratio(ratio)
        .label(label)
}

fn format_track_status(meta: &TrackMetadata) -> String {
    let title = meta.title.as_deref().unwrap_or("<Titre inconnu>");
    let artist = meta.artist.as_deref().unwrap_or("");
    if artist.is_empty() {
        format!("Lecture: {title}")
    } else {
        format!("Lecture: {artist} - {title}")
    }
}

fn parse_time_to_seconds(raw: &str) -> Option<u64> {
    let trimmed = raw.trim();
    if trimmed.is_empty() || trimmed == "-" {
        return None;
    }
    if let Ok(seconds) = trimmed.parse::<u64>() {
        return Some(seconds);
    }

    let parts: Vec<&str> = trimmed.split(':').collect();
    match parts.len() {
        3 => {
            let hours = parts[0].parse::<u64>().ok()?;
            let minutes = parts[1].parse::<u64>().ok()?;
            let seconds = parse_seconds(parts[2])?;
            Some(hours * 3600 + minutes * 60 + seconds)
        }
        2 => {
            let minutes = parts[0].parse::<u64>().ok()?;
            let seconds = parse_seconds(parts[1])?;
            Some(minutes * 60 + seconds)
        }
        _ => None,
    }
}

fn parse_seconds(fragment: &str) -> Option<u64> {
    fragment
        .split('.')
        .next()
        .and_then(|s| s.parse::<u64>().ok())
}

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),
            ]
            .as_ref(),
        )
        .split(r);

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