use crate::config::MergedConfig;
use crate::parser::{SensorData, get_color_for_index, parse_sensor_data};
use crate::serial::{
    get_timestamp, parse_data_bits, parse_flow_control, parse_parity, parse_stop_bits,
};
use crossterm::{
    event::{self, KeyCode, KeyModifiers},
    execute,
    terminal::{EnterAlternateScreen, LeaveAlternateScreen, disable_raw_mode, enable_raw_mode},
};
use inline_colorization::*;
use ratatui::{
    Terminal,
    backend::CrosstermBackend,
    layout::{Constraint, Direction, Layout},
    prelude::*,
    style::{Color, Modifier, Style},
    symbols,
    text::{Line, Span},
    widgets::*,
};
use std::collections::HashMap;
use std::fs::OpenOptions;
use std::io::{self, BufWriter, Read, Write};
use std::time::{Duration, Instant};

use ratatui_wireframe::WireframeWidget;

#[cfg(feature = "ratty")]
use ratatui_ratty::{ObjectFormat, RattyGraphic, RattyGraphicSettings};

#[derive(PartialEq)]
enum ActiveTab {
    Chart2D,
    Wireframe3D,
}

fn detect_terminal() -> String {
    if std::env::var("TERM_PROGRAM").is_ok_and(|v| v.to_lowercase() == "ratty") {
        return if cfg!(feature = "ratty") {
            "Ratty (GPU 3D)".to_string()
        } else {
            "Ratty (Braille)".to_string()
        };
    }

    if std::env::var("WEZTERM_EXECUTABLE").is_ok() {
        return "WezTerm (Braille)".to_string();
    }

    if let Ok(prog) = std::env::var("TERM_PROGRAM") {
        let prog_lower = prog.to_lowercase();
        if prog_lower == "ghostty" {
            return "Ghostty (Braille)".to_string();
        } else if prog_lower == "kitty" {
            return "Kitty (Braille)".to_string();
        }
    }

    if std::env::var("TERM").is_ok_and(|v| v.to_lowercase() == "foot") {
        return "Foot (Braille)".to_string();
    }

    "Standard TTY (Braille)".to_string()
}

// ── Plotter state ─────────────────────────────────────────────────────────────
struct PlotterState {
    sensors: HashMap<String, SensorData>,
    /// Ordered list of sensor names (insertion order)
    sensor_order: Vec<String>,
    x: f64,
    global_y_min: f64,
    global_y_max: f64,
    paused: bool,
    /// Total samples received (never paused)
    total_samples: u64,
    samples_this_second: u32,
    sample_rate: u32,
    last_rate_update: Instant,
    start_time: Instant,
    lines_discarded: usize,
    /// Buffer for incomplete serial lines
    receive_buf: String,
    /// Errors reported to the status bar
    last_error: Option<String>,

    export_data: HashMap<String, Vec<(f64, f64)>>,
    pub export_limit: usize,
    pub csv_streamer: Option<crate::export::CsvStreamer>,
    active_tab: ActiveTab,
    terminal_type: String,

    custom_model: Option<ratatui_wireframe::model::Model>,

    #[cfg(feature = "ratty")]
    ratty_engines: Option<RattyGraphic<'static>>,
}

const DISCARD_FIRST_LINES: usize = 3;

impl PlotterState {
    fn new(
        export_limit: usize,
        csv_streamer: Option<crate::export::CsvStreamer>,
        _obj_file: Option<String>,
    ) -> Self {
        let now = Instant::now();

        #[cfg(feature = "ratty")]
        let ratty_engines = {
            let is_ratty = std::env::var("TERM_PROGRAM")
                .map(|v| v.to_lowercase() == "ratty")
                .unwrap_or(false);

            if is_ratty {
                // Cube
                let default_obj = b"v -1 -1 1\nv 1 -1 1\nv -1 1 1\nv 1 1 1\nv -1 -1 -1\nv 1 -1 -1\nv -1 1 -1\nv 1 1 -1\nvn 0 0 1\nvn 0 0 -1\nvn 0 1 0\nvn 0 -1 0\nvn 1 0 0\nvn -1 0 0\ns off\nf 1//1 2//1 4//1\nf 1//1 4//1 3//1\nf 6//2 5//2 7//2\nf 6//2 7//2 8//2\nf 3//3 4//3 8//3\nf 3//3 8//3 7//3\nf 5//4 6//4 2//4\nf 5//4 2//4 1//4\nf 2//5 6//5 8//5\nf 2//5 8//5 4//5\nf 5//6 1//6 3//6\nf 5//6 3//6 7//6\n";

                let (name, payload) = if let Some(path) = _obj_file {
                    match std::fs::read(&path) {
                        Ok(bytes) => (path, bytes),
                        Err(err) => {
                            eprintln!(
                                "Failed to read .obj file '{}': {err}. Falling back to default cube.",
                                path
                            );
                            ("cube.obj".to_string(), default_obj.to_vec())
                        }
                    }
                } else {
                    ("cube.obj".to_string(), default_obj.to_vec())
                };

                let name_static: &'static str = Box::leak(name.into_boxed_str());

                let graphic = RattyGraphic::new(
                    RattyGraphicSettings::new(name_static)
                        .id(1)
                        .format(ObjectFormat::Obj)
                        .scale(0.25)
                        .depth(3.0)
                        .brightness(1.8)
                        .animate(false),
                );

                let _ = graphic.register_payload(&payload);
                let _ = graphic.update();

                Some(graphic)
            } else {
                None
            }
        };

        PlotterState {
            sensors: HashMap::new(),
            sensor_order: Vec::new(),
            x: 0.0,
            global_y_min: f64::INFINITY,
            global_y_max: f64::NEG_INFINITY,
            paused: false,
            total_samples: 0,
            samples_this_second: 0,
            sample_rate: 0,
            last_rate_update: now,
            start_time: now,
            lines_discarded: 0,
            receive_buf: String::new(),
            last_error: None,
            export_data: HashMap::new(),
            export_limit,
            csv_streamer,
            active_tab: ActiveTab::Chart2D,
            terminal_type: detect_terminal(),
            custom_model: None,

            #[cfg(feature = "ratty")]
            ratty_engines,
        }
    }

    fn get_or_create_sensor(&mut self, name: &str) -> &mut SensorData {
        if !self.sensors.contains_key(name) {
            let color = get_color_for_index(self.sensor_order.len());
            self.sensors
                .insert(name.to_string(), SensorData::new(name.to_string(), color));
            self.sensor_order.push(name.to_string());
        }
        self.sensors.get_mut(name).unwrap()
    }

    fn ingest_line(&mut self, line: &str, max_points: usize) {
        let clean = line.trim();

        if self.lines_discarded < DISCARD_FIRST_LINES {
            self.lines_discarded += 1;
            return;
        }

        if self.paused {
            return;
        }

        let readings = parse_sensor_data(clean);
        if readings.is_empty() {
            return;
        }

        if let Some(streamer) = &mut self.csv_streamer {
            let _ = streamer.write_row(&readings);
        }

        for (name, value) in readings {
            let x = self.x;
            let sensor = self.get_or_create_sensor(name.as_ref());
            sensor.add_point(x, value, max_points);

            // Feed the hashmap (Never trims data)
            let series = self.export_data.entry(name.to_string()).or_default();

            series.push((x, value));

            if series.len() > self.export_limit {
                let drop_count = self.export_limit / 10; // Drop oldest 10% of data
                series.drain(0..drop_count);
            }

            if value < self.global_y_min {
                self.global_y_min = value;
            }
            if value > self.global_y_max {
                self.global_y_max = value;
            }
        }

        self.x += 1.0;
        self.total_samples += 1;
        self.samples_this_second += 1;

        // Update sample rate every second
        let elapsed = self.last_rate_update.elapsed();
        if elapsed >= Duration::from_secs(1) {
            self.sample_rate = self.samples_this_second;
            self.samples_this_second = 0;
            self.last_rate_update = Instant::now();
        }
    }

    fn x_bounds(&self) -> [f64; 2] {
        let mut min_x = f64::INFINITY;
        let mut max_x = f64::NEG_INFINITY;

        for sensor in self.sensors.values() {
            if let (Some(first), Some(last)) = (sensor.data.first(), sensor.data.last()) {
                if first.0 < min_x {
                    min_x = first.0;
                }
                if last.0 > max_x {
                    max_x = last.0;
                }
            }
        }

        if min_x.is_finite() && max_x.is_finite() {
            [min_x, max_x]
        } else {
            [0.0, 10.0]
        }
    }

    fn y_bounds(&self) -> [f64; 2] {
        if self.global_y_min.is_finite() && self.global_y_max.is_finite() {
            if self.global_y_min == self.global_y_max {
                [self.global_y_min - 1.0, self.global_y_max + 1.0]
            } else {
                let padding = (self.global_y_max - self.global_y_min) * 0.1;
                [self.global_y_min - padding, self.global_y_max + padding]
            }
        } else {
            [-1.0, 1.0]
        }
    }

    fn uptime_str(&self) -> String {
        let secs = self.start_time.elapsed().as_secs();
        format!(
            "{:02}:{:02}:{:02}",
            secs / 3600,
            (secs % 3600) / 60,
            secs % 60
        )
    }
}

// ── Main entry point ──────────────────────────────────────────────────────────

pub fn run_plotter_mode(
    config: MergedConfig,
    port_name: String,
) -> Result<(), Box<dyn std::error::Error>> {
    enable_raw_mode()?;
    let mut stdout = io::stdout();
    execute!(stdout, EnterAlternateScreen)?;
    let backend = CrosstermBackend::new(stdout);
    let mut terminal = Terminal::new(backend)?;

    let data_bits = parse_data_bits(config.data_bits)?;
    let stop_bits = parse_stop_bits(config.stop_bits)?;
    let parity = parse_parity(&config.parity)?;
    let flow_control = parse_flow_control(&config.flow_control)?;

    let mut port = if config.simulate || config.replay_file.is_some() {
        None
    } else {
        Some(
            serialport::new(&port_name, config.baud)
                .timeout(Duration::from_millis(config.timeout_ms))
                .data_bits(data_bits)
                .stop_bits(stop_bits)
                .parity(parity)
                .flow_control(flow_control)
                .open()?,
        )
    };
    let mut log_writer: Option<BufWriter<std::fs::File>> =
        if let Some(ref log_path) = config.log_file {
            let file = OpenOptions::new()
                .create(true)
                .append(true)
                .open(log_path)?;
            Some(BufWriter::new(file))
        } else {
            None
        };

    std::thread::sleep(Duration::from_millis(config.reset_delay_ms));

    if let Some(p) = port.as_mut() {
        let _ = p.clear(serialport::ClearBuffer::Input);
    }

    let csv_streamer = config
        .csv_file
        .as_ref()
        .and_then(|path| crate::export::CsvStreamer::new(path).ok());

    let mut session_replayer = if let Some(ref path) = config.replay_file {
        Some(
            crate::replay::SessionReplayer::new(path)
                .map_err(|e| format!("Failed to open replay file '{}' : {}", path, e))?,
        )
    } else {
        None
    };

    let mut state = PlotterState::new(config.export_limit, csv_streamer, config.obj_file);
    let mut serial_buf = [0u8; 1024];

    if let crate::config::BrailleModel::Custom(ref path) = config.braille {
        match wrfm::WrfmModel::from_file(path) {
            Ok(parsed_wrfm) => {
                let render_model = ratatui_wireframe::model::Model::from_wrfm(parsed_wrfm);
                state.custom_model = Some(render_model);
            }
            Err(e) => {
                state.last_error = Some(format!("Failed to load {}: {}", path, e));
            }
        }
    }

    loop {
        // ── Input handling ────────────────────────────────────────────────────
        if event::poll(Duration::from_millis(5))?
            && let event::Event::Key(key) = event::read()?
        {
            match key.code {
                KeyCode::Char('q') | KeyCode::Esc => break,
                KeyCode::Char('c') if key.modifiers.contains(KeyModifiers::CONTROL) => break,

                // Space: pause / resume
                KeyCode::Char(' ') => {
                    state.paused = !state.paused;
                }

                // Tab changing
                KeyCode::Char('1') => {
                    state.active_tab = ActiveTab::Chart2D;
                }
                KeyCode::Char('2') => {
                    state.active_tab = ActiveTab::Wireframe3D;
                }

                KeyCode::Tab => {
                    state.active_tab = match state.active_tab {
                        ActiveTab::Chart2D => ActiveTab::Wireframe3D,
                        ActiveTab::Wireframe3D => ActiveTab::Chart2D,
                    }
                }

                // 'c': clear all data
                KeyCode::Char('c') => {
                    state.sensors.clear();
                    state.sensor_order.clear();
                    state.export_data.clear();
                    state.x = 0.0;
                    state.global_y_min = f64::INFINITY;
                    state.global_y_max = f64::NEG_INFINITY;
                    state.total_samples = 0;
                }

                // "CTRL+S" to export data
                KeyCode::Char('s') if key.modifiers.contains(KeyModifiers::CONTROL) => {
                    let timestamp = get_timestamp().replace(":", "-");

                    let filename = format!("comchan_plot_{}.svg", timestamp);

                    match crate::export::export_to_svg(
                        &state.export_data,
                        &filename,
                        &state.sensor_order,
                        &config.plot_title,
                    ) {
                        Ok(_) => {
                            state.last_error = Some(format!("✅ Exported to {}", filename));
                        }
                        Err(e) => {
                            state.last_error = Some(format!("❌ Export failed: {}", e));
                        }
                    }
                }

                _ => {}
            }
        }

        // ── Serial read ───────────────────────────────────────────────────────
        if config.simulate {
            let t = state.x * 0.1;
            let pitch = (t * 0.5).sin() * 45.0;
            let roll = (t * 0.8).cos() * 30.0;
            let yaw = (t * 2.0) % 360.0;

            state.ingest_line(&format!("Pitch: {:.2}", pitch), config.plot_points);
            state.ingest_line(&format!("Roll: {:.2}", roll), config.plot_points);
            state.ingest_line(&format!("Yaw: {:.2}", yaw), config.plot_points);

            std::thread::sleep(Duration::from_millis(50));
        } else if let Some(ref mut replayer) = session_replayer {
            match replayer.next_payload() {
                crate::replay::ReplayEvent::Payload(payload) => {
                    state.ingest_line(&payload, config.plot_points);
                }
                crate::replay::ReplayEvent::Waiting => {}
                crate::replay::ReplayEvent::Eof => {
                    std::thread::sleep(Duration::from_millis(100));
                }
            }
        } else if let Some(p) = port.as_mut() {
            match p.read(&mut serial_buf) {
                Ok(n) if n > 0 => {
                    let chunk = String::from_utf8_lossy(&serial_buf[..n]);
                    state.receive_buf.push_str(&chunk);

                    while let Some(pos) = state.receive_buf.find('\n') {
                        let line = state.receive_buf.drain(..=pos).collect::<String>();

                        if let Some(ref mut writer) = log_writer {
                            let _ =
                                writeln!(writer, "RX [{}]: {}", get_timestamp(), line.trim_end());
                            let _ = writer.flush();
                        }

                        state.ingest_line(&line, config.plot_points);
                    }
                }
                Ok(_) => {}
                Err(ref e) if e.kind() == io::ErrorKind::TimedOut => {}
                Err(e) => {
                    state.last_error = Some(format!("Read error: {}", e));
                }
            }
        }

        // ── Render ────────────────────────────────────────────────────────────
        let x_bounds = state.x_bounds();
        let y_bounds = state.y_bounds();

        // Pre-compute labels before the closure borrows state
        let x_labels = [
            format!("{:.0}", x_bounds[0]),
            format!("{:.0}", (x_bounds[0] + x_bounds[1]) / 2.0),
            format!("{:.0}", x_bounds[1]),
        ];
        let y_labels = [
            format!("{:.2}", y_bounds[0]),
            format!("{:.2}", (y_bounds[0] + y_bounds[1]) / 2.0),
            format!("{:.2}", y_bounds[1]),
        ];

        let pause_indicator = if state.paused { " ⏸ PAUSED" } else { "" };
        let uptime = state.uptime_str();
        let sample_rate = state.sample_rate;
        let total_samples = state.total_samples;
        let sensor_count = state.sensors.len();
        let last_error = state.last_error.clone();
        let baud = config.baud;
        let port_name_disp = port_name.clone();

        // Build sidebar rows before the draw closure
        let sidebar_rows: Vec<(String, Color, f64, f64, f64)> = state
            .sensor_order
            .iter()
            .filter_map(|name| {
                state.sensors.get(name).map(|s| {
                    (
                        s.name.clone(),
                        s.color,
                        s.current_value,
                        s.min_value,
                        s.max_value,
                    )
                })
            })
            .collect();

        // Build datasets
        let datasets: Vec<Dataset> = state
            .sensor_order
            .iter()
            .filter_map(|name| state.sensors.get(name))
            .filter(|s| s.has_data())
            .map(|sensor| {
                Dataset::default()
                    .marker(symbols::Marker::Braille)
                    .graph_type(GraphType::Line)
                    .style(Style::default().fg(sensor.color))
                    .data(&sensor.data)
            })
            .collect();

        terminal.draw(|f| {
            let outer = Layout::default()
                .direction(Direction::Vertical)
                .constraints([Constraint::Min(3), Constraint::Length(3)])
                .split(f.area());

            let main_row = Layout::default()
                .direction(Direction::Horizontal)
                .constraints([Constraint::Min(10), Constraint::Length(28)])
                .split(outer[0]);

            match state.active_tab {
                ActiveTab::Chart2D => {
                    let chart_title = format!(
                        " 󰕾 ComChan Plotter  {}  {}  {} sensors{}",
                        port_name_disp, baud, sensor_count, pause_indicator
                    );

                    let chart = Chart::new(datasets)
                        .block(
                            Block::default()
                                .title(Span::styled(
                                    chart_title,
                                    Style::default()
                                        .fg(Color::White)
                                        .add_modifier(Modifier::BOLD),
                                ))
                                .borders(Borders::ALL)
                                .border_style(Style::default().fg(Color::DarkGray)),
                        )
                        .x_axis(
                            Axis::default()
                                .title(Span::styled("Sample", Style::default().fg(Color::Gray)))
                                .style(Style::default().fg(Color::DarkGray))
                                .bounds(x_bounds)
                                .labels(vec![
                                    x_labels[0].as_str(),
                                    x_labels[1].as_str(),
                                    x_labels[2].as_str(),
                                ]),
                        )
                        .y_axis(
                            Axis::default()
                                .title(Span::styled("Value", Style::default().fg(Color::Gray)))
                                .style(Style::default().fg(Color::DarkGray))
                                .bounds(y_bounds)
                                .labels(vec![
                                    y_labels[0].as_str(),
                                    y_labels[1].as_str(),
                                    y_labels[2].as_str(),
                                ]),
                        );

                    f.render_widget(chart, main_row[0]);

                    // ── The Fix: Clear the GPU graphic ──
                    #[cfg(feature = "ratty")]
                    if let Some(graphic) = &mut state.ratty_engines {
                        // By rendering the hardware graphic into an empty 0x0 Area when not in 3D mode,
                        // we force the terminal engine to erase it so it doesn't bleed into the chart.
                        graphic.settings_mut().scale = 0.0;
                        f.render_widget(
                            &*graphic,
                            ratatui::layout::Rect {
                                x: main_row[0].x,
                                y: main_row[0].y,
                                width: 1,
                                height: 1,
                            },
                        );
                    }
                }

                ActiveTab::Wireframe3D => {
                    let pitch_deg = state.sensors.get("Pitch").map_or(0.0, |s| s.current_value);
                    let yaw_deg = state.sensors.get("Yaw").map_or(0.0, |s| s.current_value);
                    let roll_deg = state.sensors.get("Roll").map_or(0.0, |s| s.current_value);

                    let mut _rendered_3d = false;

                    #[cfg(feature = "ratty")]
                    if let Some(main_cube) = &mut state.ratty_engines {
                        let rot = [pitch_deg as f32, yaw_deg as f32, roll_deg as f32];
                        main_cube.settings_mut().scale = 0.25;
                        main_cube.settings_mut().rotation = rot;
                        f.render_widget(&*main_cube, main_row[0]);

                        // Static canvas gnomon — identical to WireframeWidget's axes, no rotation
                        let gnomon_area = {
                            let area = main_row[0];
                            let g_width = 18u16;
                            let g_height = 9u16;
                            ratatui::layout::Rect {
                                x: area.x + area.width.saturating_sub(g_width + 2),
                                y: area.y + area.height.saturating_sub(g_height + 1),
                                width: g_width.min(area.width),
                                height: g_height.min(area.height),
                            }
                        };

                        f.render_widget(ratatui::widgets::Clear, gnomon_area);

                        let gnomon_canvas = ratatui::widgets::canvas::Canvas::default()
                            .block(
                                Block::default()
                                    .borders(Borders::ALL)
                                    .border_style(Style::default().fg(Color::DarkGray)),
                            )
                            .marker(ratatui::symbols::Marker::Braille)
                            .x_bounds([-5.0, 5.0])
                            .y_bounds([-5.0, 5.0])
                            .paint(|ctx| {
                                let p_origin = (0.0, 0.0);
                                let p_x = (4.0, 0.0);
                                let p_y = (0.0, 3.5);
                                let p_z = (-2.8, -2.8);

                                ctx.draw(&ratatui::widgets::canvas::Line {
                                    x1: p_origin.0,
                                    y1: p_origin.1,
                                    x2: p_x.0,
                                    y2: p_x.1,
                                    color: Color::Red,
                                });
                                ctx.draw(&ratatui::widgets::canvas::Line {
                                    x1: p_origin.0,
                                    y1: p_origin.1,
                                    x2: p_y.0,
                                    y2: p_y.1,
                                    color: Color::Yellow,
                                });
                                ctx.draw(&ratatui::widgets::canvas::Line {
                                    x1: p_origin.0,
                                    y1: p_origin.1,
                                    x2: p_z.0,
                                    y2: p_z.1,
                                    color: Color::LightBlue,
                                });

                                ctx.print(
                                    p_x.0 + 0.2,
                                    p_x.1,
                                    ratatui::text::Span::styled(
                                        "X",
                                        Style::default().fg(Color::Red),
                                    ),
                                );
                                ctx.print(
                                    p_y.0,
                                    p_y.1 + 0.2,
                                    ratatui::text::Span::styled(
                                        "Y",
                                        Style::default().fg(Color::Yellow),
                                    ),
                                );
                                ctx.print(
                                    p_z.0 - 0.5,
                                    p_z.1 - 0.5,
                                    ratatui::text::Span::styled(
                                        "Z",
                                        Style::default().fg(Color::LightBlue),
                                    ),
                                );
                            });

                        f.render_widget(gnomon_canvas, gnomon_area);
                        _rendered_3d = true;
                    }

                    if !_rendered_3d {
                        let pitch = pitch_deg.to_radians();
                        let yaw = yaw_deg.to_radians();
                        let roll = roll_deg.to_radians();

                        let (selected_model, displayed_title) = match &config.braille {
                            crate::config::BrailleModel::Cube => (
                                ratatui_wireframe::model::Model::cube(),
                                "Rolling 3D Cube".to_string(),
                            ),
                            crate::config::BrailleModel::Tetrahedron => (
                                ratatui_wireframe::model::Model::tetrahedron(),
                                "Rolling 3D Tetrahedron".to_string(),
                            ),
                            crate::config::BrailleModel::Octahedron => (
                                ratatui_wireframe::model::Model::octahedron(),
                                "Rolling 3D Octahedron".to_string(),
                            ),
                            crate::config::BrailleModel::Custom(path) => {
                                if let Some(ref m) = state.custom_model {
                                    (m, format!("Rolling Custom 3D [{}]", path))
                                } else {
                                    (
                                        ratatui_wireframe::model::Model::cube(),
                                        format!("Error loading [{}], using Cube", path),
                                    )
                                }
                            }
                        };

                        let wireframe = WireframeWidget::new(pitch, yaw, roll)
                            .title(displayed_title)
                            .color(Color::Green)
                            .model(selected_model);

                        f.render_widget(wireframe, main_row[0]);
                    }
                }
            }

            // ── Sidebar (sensor stats) ────────────────────────────────────────
            let sidebar_block = Block::default()
                .title(Span::styled(
                    " Sensors ",
                    Style::default()
                        .fg(Color::Cyan)
                        .add_modifier(Modifier::BOLD),
                ))
                .borders(Borders::ALL)
                .border_style(Style::default().fg(Color::DarkGray));

            let inner = sidebar_block.inner(main_row[1]);
            f.render_widget(sidebar_block, main_row[1]);

            if sidebar_rows.is_empty() {
                let waiting = Paragraph::new("Waiting for\ndata…")
                    .style(Style::default().fg(Color::DarkGray))
                    .alignment(Alignment::Center);
                f.render_widget(waiting, inner);
            } else {
                // Each sensor gets 4 lines: name + cur/min/max + separator
                let mut lines: Vec<Line> = Vec::new();
                for (name, color, cur, min, max) in &sidebar_rows {
                    lines.push(Line::from(vec![Span::styled(
                        format!("● {}", name),
                        Style::default().fg(*color).add_modifier(Modifier::BOLD),
                    )]));
                    lines.push(Line::from(vec![Span::styled(
                        format!("  now: {:.3}", cur),
                        Style::default().fg(Color::White),
                    )]));
                    lines.push(Line::from(vec![Span::styled(
                        format!("  min: {:.3}", min),
                        Style::default().fg(Color::Blue),
                    )]));
                    lines.push(Line::from(vec![Span::styled(
                        format!("  max: {:.3}", max),
                        Style::default().fg(Color::Red),
                    )]));
                    lines.push(Line::from(vec![Span::styled(
                        "  ─────────────",
                        Style::default().fg(Color::DarkGray),
                    )]));
                }
                let para = Paragraph::new(lines).wrap(Wrap { trim: false });
                f.render_widget(para, inner);
            }

            // ── Status bar ────────────────────────────────────────────────────
            let status_bg = if state.paused {
                Color::DarkGray
            } else {
                Color::Reset
            };

            let error_span = if let Some(ref err) = last_error {
                Span::styled(format!(" ⚠ {} ", err), Style::default().fg(Color::Red))
            } else {
                Span::raw("")
            };

            let status_line = Line::from(vec![
                Span::styled(format!(" ⏱ {}", uptime), Style::default().fg(Color::Green)),
                Span::raw("  "),
                Span::styled(
                    format!("󰩙 {} sps", sample_rate),
                    Style::default().fg(Color::Cyan),
                ),
                Span::raw("  "),
                Span::styled(
                    format!("󰆼 {} total", total_samples),
                    Style::default().fg(Color::Yellow),
                ),
                Span::raw("  "),
                error_span,
                Span::styled(
                    format!(" 🖥 {} ", state.terminal_type),
                    Style::default()
                        .fg(Color::Magenta)
                        .add_modifier(Modifier::BOLD),
                ),
                Span::styled(
                    "  [Space] pause  [c] clear  [1/2/Tab] views  [q/Esc] quit [Ctrl + s] Export",
                    Style::default().fg(Color::DarkGray),
                ),
            ]);

            let status_bar = Paragraph::new(status_line)
                .block(
                    Block::default()
                        .borders(Borders::ALL)
                        .border_style(Style::default().fg(Color::DarkGray)),
                )
                .style(Style::default().bg(status_bg));

            f.render_widget(status_bar, outer[1]);
        })?;
    }

    // ── Cleanup ───────────────────────────────────────────────────────────────
    disable_raw_mode()?;
    execute!(terminal.backend_mut(), LeaveAlternateScreen)?;

    if !state.sensors.is_empty() {
        println!("\n{color_green}󰄨 Plotting Summary:{color_reset}");
        for name in &state.sensor_order {
            if let Some(s) = state.sensors.get(name) {
                println!(
                    "   {}: {} pts  min={:.3}  max={:.3}  last={:.3}",
                    s.name,
                    s.data.len(),
                    s.min_value,
                    s.max_value,
                    s.current_value,
                );
            }
        }
        println!(
            "   Total samples: {}  Uptime: {}",
            state.total_samples,
            state.uptime_str()
        );
    }

    Ok(())
}
