//! settings.rs — Dual-CSS HF Mode
//! Modal settings window with five tabs (Audio / Modem / FEC / PTT / Station),
//! mirroring settings_dlg.py. Edits a working copy of AppConfig; changes apply
//! to the live config only on OK or Apply.

use std::sync::mpsc::{Receiver, TryRecvError};

use egui::{ComboBox, Slider};

use crate::audio;
use crate::config::AppConfig;

#[derive(Clone, Copy, PartialEq, Eq)]
enum Tab {
    Audio,
    Modem,
    Fec,
    Ptt,
    Station,
    Display,
}

/// Outcome of drawing the dialog for one frame.
pub enum SettingsOutcome {
    /// Still open, no change committed.
    Open,
    /// Apply clicked — commit `config` but keep the window open.
    Apply(AppConfig),
    /// OK clicked — commit `config` and close.
    Accept(AppConfig),
    /// Cancel/closed — discard.
    Cancel,
}

/// Result of the background device/port scan.
struct ScanResult {
    input_devices: Vec<(usize, String)>,
    output_devices: Vec<(usize, String)>,
    serial_ports: Vec<String>,
}

pub struct SettingsDialog {
    work: AppConfig, // working copy
    tab: Tab,
    input_devices: Vec<(usize, String)>,
    output_devices: Vec<(usize, String)>,
    serial_ports: Vec<String>,
    flrig_test: Option<String>, // last flrig "Test connection" result
    /// Pending background scan; `Some` until the device/port lists arrive.
    scan_rx: Option<Receiver<ScanResult>>,
    scanning: bool,
}

impl SettingsDialog {
    pub fn new(current: &AppConfig) -> Self {
        // Device + serial enumeration probes every device's default config —
        // blocking ALSA/PipeWire calls that froze the UI for seconds when run
        // synchronously here. Do it on a background thread; the dialog opens
        // instantly and the dropdowns populate when the scan completes.
        let (tx, rx) = std::sync::mpsc::channel();
        std::thread::Builder::new()
            .name("dual_css_dev_scan".into())
            .spawn(move || {
                let (input_devices, output_devices) = audio::input_output_devices();
                let serial_ports = audio::serial_ports();
                // Receiver may be gone if the dialog closed; ignore send errors.
                let _ = tx.send(ScanResult {
                    input_devices,
                    output_devices,
                    serial_ports,
                });
            })
            .expect("spawn device scan");

        Self {
            work: current.clone(),
            tab: Tab::Audio,
            input_devices: Vec::new(),
            output_devices: Vec::new(),
            serial_ports: Vec::new(),
            flrig_test: None,
            scan_rx: Some(rx),
            scanning: true,
        }
    }

    /// Poll the background scan; move results in once ready.
    fn poll_scan(&mut self) {
        if let Some(rx) = &self.scan_rx {
            match rx.try_recv() {
                Ok(result) => {
                    self.input_devices = result.input_devices;
                    self.output_devices = result.output_devices;
                    self.serial_ports = result.serial_ports;
                    self.scanning = false;
                    self.scan_rx = None;
                }
                Err(TryRecvError::Empty) => {} // still scanning
                Err(TryRecvError::Disconnected) => {
                    self.scanning = false;
                    self.scan_rx = None;
                }
            }
        }
    }

    /// Render the modal window; returns the outcome for this frame.
    pub fn show(&mut self, ctx: &egui::Context) -> SettingsOutcome {
        self.poll_scan();
        let mut outcome = SettingsOutcome::Open;
        let mut open = true;

        egui::Window::new("Settings — Dual-CSS HF Mode")
            .collapsible(false)
            .resizable(true)
            .default_width(520.0)
            .open(&mut open)
            .show(ctx, |ui| {
                ui.horizontal(|ui| {
                    ui.selectable_value(&mut self.tab, Tab::Audio, "Audio");
                    ui.selectable_value(&mut self.tab, Tab::Modem, "Modem");
                    ui.selectable_value(&mut self.tab, Tab::Fec, "FEC");
                    ui.selectable_value(&mut self.tab, Tab::Ptt, "PTT");
                    ui.selectable_value(&mut self.tab, Tab::Station, "Station");
                    ui.selectable_value(&mut self.tab, Tab::Display, "Display");
                });
                ui.separator();

                egui::ScrollArea::vertical()
                    .max_height(360.0)
                    .show(ui, |ui| match self.tab {
                        Tab::Audio => self.audio_tab(ui),
                        Tab::Modem => self.modem_tab(ui),
                        Tab::Fec => self.fec_tab(ui),
                        Tab::Ptt => self.ptt_tab(ui),
                        Tab::Station => self.station_tab(ui),
                        Tab::Display => self.display_tab(ui),
                    });

                ui.separator();
                ui.horizontal(|ui| {
                    if ui.button("OK").clicked() {
                        outcome = SettingsOutcome::Accept(self.work.clone());
                    }
                    if ui.button("Cancel").clicked() {
                        outcome = SettingsOutcome::Cancel;
                    }
                    if ui.button("Apply").clicked() {
                        outcome = SettingsOutcome::Apply(self.work.clone());
                    }
                });
            });

        // Window close button ('x') maps to Cancel.
        if !open {
            if let SettingsOutcome::Open = outcome {
                outcome = SettingsOutcome::Cancel;
            }
        }
        outcome
    }

    // ── Audio tab ───────────────────────────────────────────────────────────
    fn audio_tab(&mut self, ui: &mut egui::Ui) {
        if self.scanning {
            ui.horizontal(|ui| {
                ui.spinner();
                ui.label("Scanning audio devices…");
            });
        }
        let a = &mut self.work.audio;

        egui::Grid::new("audio_grid").num_columns(2).show(ui, |ui| {
            ui.label("Input device:");
            device_combo(ui, "in_dev", &mut a.input_device, &self.input_devices);
            ui.end_row();

            ui.label("Output device:");
            device_combo(ui, "out_dev", &mut a.output_device, &self.output_devices);
            ui.end_row();

            ui.label("Sample rate:");
            sample_rate_combo(ui, &mut a.sample_rate);
            ui.end_row();

            ui.label("Buffer size:");
            buffer_combo(ui, &mut a.buffer_size);
            ui.end_row();

            ui.label("Input gain:");
            ui.add(Slider::new(&mut a.input_gain_db, -20.0..=20.0).suffix(" dB"));
            ui.end_row();

            ui.label("Output level:");
            ui.add(
                Slider::new(&mut a.output_level, 0.0..=1.0)
                    .custom_formatter(|v, _| format!("{} %", (v * 100.0).round() as i32)),
            );
            ui.end_row();
        });
    }

    // ── Modem tab ───────────────────────────────────────────────────────────
    fn modem_tab(&mut self, ui: &mut egui::Ui) {
        let m = &mut self.work.modem;
        egui::Grid::new("modem_grid").num_columns(2).show(ui, |ui| {
            ui.label("Spreading factor:");
            ComboBox::from_id_salt("sf")
                .selected_text(format!("SF-{}", m.spreading_factor))
                .show_ui(ui, |ui| {
                    for s in 7..=12u32 {
                        ui.selectable_value(&mut m.spreading_factor, s, format!("SF-{s}"));
                    }
                });
            ui.end_row();

            // Band placement: centre frequency + bandwidth, kept inside the
            // 300–3000 Hz bandpass by set_band. (SF/BW/centre/rate also live
            // in the main window's Quick Settings.)
            let mut bw = m.bandwidth();
            let mut center = m.center_freq();
            let mut band_changed = false;

            ui.label("Bandwidth (preset):");
            ComboBox::from_id_salt("bw_preset")
                .selected_text(format!("{bw} Hz"))
                .show_ui(ui, |ui| {
                    for preset in crate::config::BW_PRESETS {
                        if ui
                            .selectable_value(&mut bw, preset, format!("{preset} Hz"))
                            .changed()
                        {
                            band_changed = true;
                        }
                    }
                });
            ui.end_row();

            ui.label("Bandwidth (fine):");
            if ui
                .add(Slider::new(&mut bw, 300..=2700).suffix(" Hz"))
                .changed()
            {
                band_changed = true;
            }
            ui.end_row();

            ui.label("Center frequency:");
            let half = bw / 2;
            let lo = crate::config::BAND_MIN_HZ + half;
            let hi = (crate::config::BAND_MAX_HZ - (bw - half)).max(lo);
            if ui
                .add(Slider::new(&mut center, lo..=hi).suffix(" Hz"))
                .changed()
            {
                band_changed = true;
            }
            ui.end_row();

            if band_changed {
                m.set_band(center, bw);
            }

            ui.label("Auto RX:");
            ui.checkbox(&mut m.auto_rx, "Detect incoming SF / bandwidth");
            ui.end_row();

            ui.label("L1 preamble:");
            ui.add(Slider::new(&mut m.l1_preamble_symbols, 4..=16).suffix(" sym"));
            ui.end_row();
            ui.label("L2 preamble:");
            ui.add(Slider::new(&mut m.l2_preamble_symbols, 2..=8).suffix(" sym"));
            ui.end_row();
            ui.label("Sync symbols:");
            ui.add(Slider::new(&mut m.sync_symbols, 1..=4).suffix(" sym"));
            ui.end_row();

            ui.label("Interleaver depth:");
            ComboBox::from_id_salt("interleaver")
                .selected_text(format!("depth {}", m.interleaver_depth))
                .show_ui(ui, |ui| {
                    for d in [1u32, 2, 4, 8, 16] {
                        let label = if d == 1 {
                            "depth 1 (off)".to_string()
                        } else {
                            format!("depth {d}")
                        };
                        ui.selectable_value(&mut m.interleaver_depth, d, label);
                    }
                });
            ui.end_row();
        });

        // Live info readout (symbol duration / BW / N), matching the Python label.
        let bw = (m.freq_high as i64 - m.freq_low as i64).max(1) as f64;
        let n = 1u64 << m.spreading_factor;
        let dur = n as f64 / bw * 1000.0;
        ui.separator();
        ui.colored_label(
            egui::Color32::from_rgb(255, 171, 64),
            format!("Symbol duration: {dur:.0} ms  ·  BW: {bw:.0} Hz  ·  N = {n} chips"),
        );
    }

    // ── FEC tab ─────────────────────────────────────────────────────────────
    fn fec_tab(&mut self, ui: &mut egui::Ui) {
        let f = &mut self.work.fec;
        ui.label("FEC type:");
        ui.radio_value(
            &mut f.fec_type,
            "RS".to_string(),
            "Reed-Solomon (recommended)",
        );
        ui.radio_value(&mut f.fec_type, "BCH".to_string(), "BCH (header only)");
        ui.radio_value(&mut f.fec_type, "None".to_string(), "None");
        ui.separator();

        egui::Grid::new("fec_grid").num_columns(2).show(ui, |ui| {
            ui.label("RS code rate:");
            ui.add_enabled_ui(f.fec_type == "RS", |ui| {
                ComboBox::from_id_salt("rs_rate")
                    .selected_text(&f.rs_code_rate)
                    .show_ui(ui, |ui| {
                        for r in ["1/2", "1/3", "2/3"] {
                            ui.selectable_value(&mut f.rs_code_rate, r.to_string(), r);
                        }
                    });
            });
            ui.end_row();

            ui.label("Header protection:");
            ComboBox::from_id_salt("hdr")
                .selected_text(&f.header_protection)
                .show_ui(ui, |ui| {
                    for h in ["RS(15,3)", "RS(7,3)", "BCH(31,6)", "Repeat ×3"] {
                        ui.selectable_value(&mut f.header_protection, h.to_string(), h);
                    }
                });
            ui.end_row();

            ui.label("CRC:");
            ui.checkbox(&mut f.crc_enable, "Enable CRC");
            ui.end_row();

            ui.label("CRC type:");
            ui.add_enabled_ui(f.crc_enable, |ui| {
                ComboBox::from_id_salt("crc")
                    .selected_text(&f.crc_type)
                    .show_ui(ui, |ui| {
                        for c in ["CRC-16", "CRC-32"] {
                            ui.selectable_value(&mut f.crc_type, c.to_string(), c);
                        }
                    });
            });
            ui.end_row();
        });
    }

    // ── PTT tab ─────────────────────────────────────────────────────────────
    fn ptt_tab(&mut self, ui: &mut egui::Ui) {
        // Split disjoint field borrows so the flrig "Test" handler can touch
        // self.flrig_test while `p` borrows self.work.ptt.
        let p = &mut self.work.ptt;
        let flrig_test = &mut self.flrig_test;
        egui::Grid::new("ptt_grid").num_columns(2).show(ui, |ui| {
            ui.label("PTT method:");
            ComboBox::from_id_salt("ptt_method")
                .selected_text(&p.method)
                .show_ui(ui, |ui| {
                    for m in ["RTS", "DTR", "VOX", "CAT/Hamlib", "flrig", "None"] {
                        ui.selectable_value(&mut p.method, m.to_string(), m);
                    }
                });
            ui.end_row();

            let needs_port = matches!(p.method.as_str(), "RTS" | "DTR" | "CAT/Hamlib");
            ui.label("Serial port:");
            ui.add_enabled_ui(needs_port, |ui| {
                ComboBox::from_id_salt("ptt_port")
                    .selected_text(&p.serial_port)
                    .show_ui(ui, |ui| {
                        for port in &self.serial_ports {
                            ui.selectable_value(&mut p.serial_port, port.clone(), port);
                        }
                    });
            });
            ui.end_row();

            ui.label("PTT delay:");
            ui.add(Slider::new(&mut p.ptt_delay_ms, 0..=500).suffix(" ms"));
            ui.end_row();
            ui.label("Hang time:");
            ui.add(Slider::new(&mut p.hang_time_ms, 100..=2000).suffix(" ms"));
            ui.end_row();

            ui.label("VOX level:");
            ui.add_enabled_ui(p.method == "VOX", |ui| {
                ui.add(
                    Slider::new(&mut p.vox_level, 0.0..=1.0)
                        .custom_formatter(|v, _| format!("{} %", (v * 100.0).round() as i32)),
                );
            });
            ui.end_row();

            ui.label("CAT baud rate:");
            ui.add_enabled_ui(p.method == "CAT/Hamlib", |ui| {
                ComboBox::from_id_salt("cat_baud")
                    .selected_text(p.cat_baud.to_string())
                    .show_ui(ui, |ui| {
                        for b in [1200u32, 2400, 4800, 9600, 19200, 38400] {
                            ui.selectable_value(&mut p.cat_baud, b, b.to_string());
                        }
                    });
            });
            ui.end_row();

            // flrig XML-RPC host/port — only relevant for the flrig method.
            let is_flrig = p.method == "flrig";
            ui.label("flrig host:");
            ui.add_enabled_ui(is_flrig, |ui| {
                ui.text_edit_singleline(&mut p.flrig_host);
            });
            ui.end_row();

            ui.label("flrig port:");
            ui.add_enabled_ui(is_flrig, |ui| {
                ui.add(egui::DragValue::new(&mut p.flrig_port).range(1..=65535));
            });
            ui.end_row();

            ui.label("");
            ui.add_enabled_ui(is_flrig, |ui| {
                if ui.button("Test flrig connection").clicked() {
                    let client = crate::flrig::FlrigClient::new(&p.flrig_host, p.flrig_port);
                    *flrig_test = Some(match client.get_info() {
                        Ok(info) => {
                            let info = info.trim();
                            if info.is_empty() {
                                "Connected to flrig".to_string()
                            } else {
                                format!("Connected: {info}")
                            }
                        }
                        Err(e) => e,
                    });
                }
            });
            ui.end_row();

            if let Some(msg) = &flrig_test {
                ui.label("");
                let ok = msg.starts_with("Connected");
                let color = if ok {
                    egui::Color32::from_rgb(0, 230, 118)
                } else {
                    egui::Color32::from_rgb(255, 107, 107)
                };
                ui.colored_label(color, msg);
                ui.end_row();
            }
        });
    }

    // ── Station tab ─────────────────────────────────────────────────────────
    fn station_tab(&mut self, ui: &mut egui::Ui) {
        let s = &mut self.work.station;
        egui::Grid::new("station_grid")
            .num_columns(2)
            .show(ui, |ui| {
                ui.label("Callsign:");
                if ui.text_edit_singleline(&mut s.callsign).changed() {
                    s.callsign = s.callsign.to_uppercase();
                }
                ui.end_row();

                ui.label("Grid square:");
                if ui.text_edit_singleline(&mut s.grid_square).changed() {
                    s.grid_square = s.grid_square.to_uppercase();
                }
                ui.end_row();

                ui.label("Station name:");
                ui.text_edit_singleline(&mut s.name);
                ui.end_row();

                ui.label("Log file:");
                ui.text_edit_singleline(&mut s.log_file);
                ui.end_row();
            });
    }

    // ── Display tab ───────────────────────────────────────────────────────────
    fn display_tab(&mut self, ui: &mut egui::Ui) {
        let d = &mut self.work.display;
        let nyquist = self.work.audio.sample_rate / 2;

        egui::Grid::new("display_grid")
            .num_columns(2)
            .show(ui, |ui| {
                // Waterfall contrast (gain) and noise floor.
                ui.label("Waterfall gain:");
                ui.add(
                    Slider::new(&mut d.waterfall_gain, 0.3..=3.0)
                        .custom_formatter(|v, _| format!("{v:.2}×")),
                );
                ui.end_row();

                ui.label("Noise floor:");
                ui.add(Slider::new(&mut d.waterfall_floor, -140.0..=-40.0).suffix(" dB"));
                ui.end_row();

                // Colormap.
                ui.label("Color scheme:");
                ComboBox::from_id_salt("color_scheme")
                    .selected_text(scheme_label(&d.color_scheme))
                    .show_ui(ui, |ui| {
                        for (key, label) in [
                            ("default", "Default (blue→cyan→yellow)"),
                            ("hot", "Hot (red→yellow→white)"),
                            ("gray", "Grayscale"),
                        ] {
                            ui.selectable_value(&mut d.color_scheme, key.to_string(), label);
                        }
                    });
                ui.end_row();

                // Scroll speed.
                ui.label("Scroll speed:");
                ui.add(Slider::new(&mut d.scroll_speed, 1..=5).suffix(" rows"));
                ui.end_row();

                // FFT size — higher = finer frequency resolution.
                ui.label("Frequency resolution:");
                let bin_hz = self.work.audio.sample_rate as f32 / d.fft_size as f32;
                ComboBox::from_id_salt("fft_size")
                    .selected_text(format!("{} pt  (~{:.1} Hz/bin)", d.fft_size, bin_hz))
                    .show_ui(ui, |ui| {
                        for size in [2048usize, 4096, 8192, 16384, 32768] {
                            let hz = self.work.audio.sample_rate as f32 / size as f32;
                            ui.selectable_value(
                                &mut d.fft_size,
                                size,
                                format!("{size} pt  (~{hz:.1} Hz/bin)"),
                            );
                        }
                    });
                ui.end_row();

                // Displayed band (WSJT-X style full-band view).
                ui.label("Display low:");
                ui.add(
                    Slider::new(&mut d.waterfall_freq_min, 0..=nyquist.saturating_sub(100))
                        .suffix(" Hz"),
                );
                ui.end_row();
                ui.label("Display high:");
                ui.add(Slider::new(&mut d.waterfall_freq_max, 100..=nyquist).suffix(" Hz"));
                ui.end_row();

                // GPU-compute FFT toggle.
                ui.label("GPU FFT:");
                ui.checkbox(&mut d.gpu_fft, "Use GPU compute (wgpu)");
                ui.end_row();
            });

        ui.label(
            egui::RichText::new(
                "GPU FFT runs the transform on the graphics card via wgpu \
                 (Vulkan/Metal/DX12/OpenGL). At these FFT sizes the CPU is \
                 usually just as fast; if the GPU is unavailable it falls back \
                 to the CPU automatically.",
            )
            .small()
            .color(egui::Color32::from_rgb(120, 140, 160)),
        );

        // Keep the display band ordered.
        if d.waterfall_freq_max <= d.waterfall_freq_min {
            d.waterfall_freq_max = (d.waterfall_freq_min + 100).min(nyquist);
        }

        ui.separator();
        ui.colored_label(
            egui::Color32::from_rgb(255, 171, 64),
            format!(
                "Displaying {}–{} Hz  ·  {} pt FFT  ·  {} colormap",
                d.waterfall_freq_min, d.waterfall_freq_max, d.fft_size, d.color_scheme
            ),
        );
    }
}

fn scheme_label(key: &str) -> &'static str {
    match key {
        "hot" => "Hot (red→yellow→white)",
        "gray" | "grey" => "Grayscale",
        _ => "Default (blue→cyan→yellow)",
    }
}

// ── Combo helpers ────────────────────────────────────────────────────────────

fn device_combo(ui: &mut egui::Ui, id: &str, value: &mut i32, devices: &[(usize, String)]) {
    let selected = if *value < 0 {
        "System default".to_string()
    } else {
        devices
            .iter()
            .find(|(i, _)| *i as i32 == *value)
            .map(|(_, n)| n.clone())
            .unwrap_or_else(|| "System default".to_string())
    };
    ComboBox::from_id_salt(id)
        .selected_text(selected)
        .show_ui(ui, |ui| {
            ui.selectable_value(value, -1, "System default");
            for (i, name) in devices {
                ui.selectable_value(value, *i as i32, name);
            }
        });
}

fn sample_rate_combo(ui: &mut egui::Ui, value: &mut u32) {
    ComboBox::from_id_salt("sample_rate")
        .selected_text(format!("{value} Hz"))
        .show_ui(ui, |ui| {
            for sr in [44100u32, 48000, 96000] {
                ui.selectable_value(value, sr, format!("{sr} Hz"));
            }
        });
}

fn buffer_combo(ui: &mut egui::Ui, value: &mut u32) {
    ComboBox::from_id_salt("buffer_size")
        .selected_text(format!("{value} samples"))
        .show_ui(ui, |ui| {
            for b in [512u32, 1024, 2048, 4096] {
                ui.selectable_value(value, b, format!("{b} samples"));
            }
        });
}
