//! ptt.rs — Dual-CSS HF Mode
//! Push-to-talk keying. Supports serial control lines (RTS/DTR) via the
//! `serialport` crate and rig control via flrig's XML-RPC interface.
//! Cross-platform on Windows / macOS / Linux.
//!
//! VOX and None perform no explicit keying: VOX is audio-triggered by the rig,
//! None is a no-op.

use std::time::Duration;

use crate::config::PttConfig;
use crate::flrig::FlrigClient;

enum Backend {
    /// No keying needed (VOX / None).
    None,
    /// Serial RTS or DTR line.
    Serial {
        port: Box<dyn serialport::SerialPort>,
        line: SerialLine,
    },
    /// flrig XML-RPC rig control.
    Flrig(FlrigClient),
}

#[derive(Clone, Copy)]
enum SerialLine {
    Rts,
    Dtr,
}

pub struct Ptt {
    backend: Backend,
}

impl Ptt {
    /// Open the keying backend for the configured method. Errors are returned
    /// as strings for the GUI status bar.
    pub fn open(cfg: &PttConfig) -> Result<Self, String> {
        let backend = match cfg.method.as_str() {
            "RTS" => Backend::Serial {
                port: open_serial(cfg)?,
                line: SerialLine::Rts,
            },
            "DTR" => Backend::Serial {
                port: open_serial(cfg)?,
                line: SerialLine::Dtr,
            },
            "flrig" => {
                let client = FlrigClient::new(&cfg.flrig_host, cfg.flrig_port);
                // Verify flrig is reachable up front so the user gets immediate
                // feedback rather than a silent failure on first TX.
                client.ping()?;
                Backend::Flrig(client)
            }
            // CAT without flrig would need a rig-specific serial protocol; until
            // that exists, fall back to RTS keying on the serial port.
            "CAT" | "CAT/Hamlib" => Backend::Serial {
                port: open_serial(cfg)?,
                line: SerialLine::Rts,
            },
            // VOX / None / anything else: no explicit keying.
            _ => Backend::None,
        };
        Ok(Self { backend })
    }

    /// Assert (true) or release (false) PTT.
    pub fn key(&mut self, on: bool) -> Result<(), String> {
        match &mut self.backend {
            Backend::None => Ok(()),
            Backend::Serial { port, line } => {
                let r = match line {
                    SerialLine::Rts => port.write_request_to_send(on),
                    SerialLine::Dtr => port.write_data_terminal_ready(on),
                };
                r.map_err(|e| format!("PTT keying failed: {e}"))
            }
            Backend::Flrig(client) => client.set_ptt(on),
        }
    }
}

fn open_serial(cfg: &PttConfig) -> Result<Box<dyn serialport::SerialPort>, String> {
    serialport::new(&cfg.serial_port, cfg.cat_baud)
        .timeout(Duration::from_millis(100))
        .open()
        .map_err(|e| format!("PTT serial open failed ({}): {e}", cfg.serial_port))
}
