// SPDX-FileCopyrightText: 2026 Stan Grams <sjg@haxx.space>
//
// SPDX-License-Identifier: GPL-2.0-or-later

use serde::{Deserialize, Serialize};
use uuid::Uuid;

use crate::radio::freq::Freq;
use crate::rig::{RigControl, RigInfo, RigRxStatus, RigStatus, RigStatusProvider, RigTxStatus};

/// Decoder enable/disable flags grouped for cleaner state management.
///
/// Flattened into `RigState` and `RigSnapshot` so the JSON wire format is
/// unchanged (backward compatible with existing clients).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DecoderConfig {
    #[serde(default)]
    pub aprs_decode_enabled: bool,
    #[serde(default)]
    pub hf_aprs_decode_enabled: bool,
    #[serde(default)]
    pub cw_decode_enabled: bool,
    #[serde(default)]
    pub ft8_decode_enabled: bool,
    #[serde(default)]
    pub ft4_decode_enabled: bool,
    #[serde(default)]
    pub ft2_decode_enabled: bool,
    #[serde(default)]
    pub wspr_decode_enabled: bool,
    #[serde(default)]
    pub lrpt_decode_enabled: bool,
    #[serde(default)]
    pub wefax_decode_enabled: bool,
    #[serde(default)]
    pub recorder_enabled: bool,
}

/// Decoder reset sequence counters for invalidating decoder windows.
///
/// Each counter is incremented when the corresponding decoder is reset
/// (e.g. frequency change, explicit reset command). Decoder tasks compare
/// against a cached value to detect resets without being fully disabled.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Default)]
pub struct DecoderResetSeqs {
    #[serde(default, skip_serializing)]
    pub aprs_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub hf_aprs_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub cw_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub ft8_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub ft4_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub ft2_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub wspr_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub lrpt_decode_reset_seq: u64,
    #[serde(default, skip_serializing)]
    pub wefax_decode_reset_seq: u64,
}

/// Simple transceiver state representation held by the rig task.
#[derive(Debug, Clone, Serialize, PartialEq)]
pub struct RigState {
    #[serde(skip_deserializing)]
    pub rig_info: Option<RigInfo>,
    pub status: RigStatus,
    pub initialized: bool,
    #[serde(skip_serializing, skip_deserializing)]
    pub control: RigControl,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_callsign: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_version: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_build_date: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_latitude: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_longitude: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pskreporter_status: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub aprs_is_status: Option<String>,
    /// Decoder enable/disable flags.
    #[serde(flatten)]
    pub decoders: DecoderConfig,
    #[serde(default)]
    pub cw_auto: bool,
    #[serde(default)]
    pub cw_wpm: u32,
    #[serde(default)]
    pub cw_tone_hz: u32,
    /// Filter state for backends that support runtime filter adjustment.
    /// Skipped in serde; flows into RigSnapshot via snapshot().
    #[serde(skip)]
    pub filter: Option<RigFilterState>,
    /// Latest spectrum frame from SDR backends.
    /// Skipped in serde (not part of persistent state); flows into RigSnapshot on demand.
    #[serde(skip)]
    pub spectrum: Option<SpectrumData>,
    /// Latest virtual-channel RDS data from SDR backends.
    /// Skipped in serde (not part of persistent state); flows into RigSnapshot on demand.
    #[serde(skip)]
    pub vchan_rds: Option<Vec<VchanRdsEntry>>,
    /// Decoder reset sequence counters.
    #[serde(flatten)]
    pub reset_seqs: DecoderResetSeqs,
}

/// Mode supported by the rig.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq, Eq)]
pub enum RigMode {
    LSB,
    USB,
    CW,
    CWR,
    AM,
    /// Synchronous AM (Stereo AM) — carrier-locked stereo demodulation.
    SAM,
    WFM,
    FM,
    AIS,
    VDES,
    DIG,
    PKT,
    Other(String),
}

impl Default for RigStatus {
    fn default() -> Self {
        Self {
            freq: Freq { hz: 144_300_000 }, // 2m calling frequency
            mode: RigMode::USB,
            tx_en: false,
            vfo: None,
            tx: Some(RigTxStatus {
                power: None,
                limit: None,
                swr: None,
                alc: None,
            }),
            rx: Some(RigRxStatus { sig: None }),
            lock: Some(false),
        }
    }
}

impl Default for RigControl {
    fn default() -> Self {
        Self {
            rpt_offset_hz: None,
            ctcss_hz: None,
            dcs_code: None,
            lock: Some(false),
            clar_hz: None,
            clar_on: None,
            enabled: Some(false),
        }
    }
}

impl RigStatusProvider for RigState {
    fn status(&self) -> RigStatus {
        self.status.clone()
    }
}

impl RigState {
    /// Create uninitialized state with common defaults (client-side).
    pub fn new_uninitialized() -> Self {
        Self {
            rig_info: None,
            status: RigStatus::default(),
            initialized: false,
            control: RigControl::default(),
            server_callsign: None,
            server_version: None,
            server_build_date: None,
            server_latitude: None,
            server_longitude: None,
            pskreporter_status: None,
            aprs_is_status: None,
            decoders: DecoderConfig::default(),
            cw_auto: true,
            cw_wpm: 15,
            cw_tone_hz: 700,
            filter: None,
            spectrum: None,
            vchan_rds: None,
            reset_seqs: DecoderResetSeqs::default(),
        }
    }

    /// Create state with server metadata and initial freq/mode (server-side).
    pub fn new_with_metadata(
        callsign: Option<String>,
        version: Option<String>,
        build_date: Option<String>,
        latitude: Option<f64>,
        longitude: Option<f64>,
        initial_freq_hz: u64,
        initial_mode: RigMode,
    ) -> Self {
        let mut state = Self::new_uninitialized();
        state.server_callsign = callsign;
        state.server_version = version;
        state.server_build_date = build_date;
        state.server_latitude = latitude;
        state.server_longitude = longitude;
        state.status.freq = Freq {
            hz: initial_freq_hz,
        };
        state.apply_mode(initial_mode);
        state
    }

    /// Convert snapshot to full state (remote client).
    pub fn from_snapshot(snapshot: RigSnapshot) -> Self {
        let lock = snapshot.status.lock;
        Self {
            rig_info: Some(snapshot.info),
            status: snapshot.status,
            initialized: snapshot.initialized,
            control: RigControl {
                rpt_offset_hz: None,
                ctcss_hz: None,
                dcs_code: None,
                lock,
                clar_hz: None,
                clar_on: None,
                enabled: snapshot.enabled,
            },
            server_callsign: snapshot.server_callsign,
            server_version: snapshot.server_version,
            server_build_date: snapshot.server_build_date,
            server_latitude: snapshot.server_latitude,
            server_longitude: snapshot.server_longitude,
            pskreporter_status: snapshot.pskreporter_status,
            aprs_is_status: snapshot.aprs_is_status,
            decoders: snapshot.decoders,
            cw_auto: snapshot.cw_auto,
            cw_wpm: snapshot.cw_wpm,
            cw_tone_hz: snapshot.cw_tone_hz,
            filter: snapshot.filter,
            spectrum: None, // spectrum flows through /api/spectrum, not persistent state
            vchan_rds: None, // vchan RDS flows through /api/spectrum, not persistent state
            reset_seqs: DecoderResetSeqs::default(),
        }
    }

    pub fn band_name(&self) -> Option<String> {
        self.rig_info.as_ref().and_then(|info| {
            self.status
                .freq
                .band_name(&info.capabilities.supported_bands)
        })
    }

    /// Produce an immutable snapshot suitable for sharing with clients.
    pub fn snapshot(&self) -> Option<RigSnapshot> {
        let info = self.rig_info.clone()?;
        Some(RigSnapshot {
            info,
            status: self.status.clone(),
            band: self.band_name(),
            enabled: self.control.enabled,
            initialized: self.initialized,
            server_callsign: self.server_callsign.clone(),
            server_version: self.server_version.clone(),
            server_build_date: self.server_build_date.clone(),
            server_latitude: self.server_latitude,
            server_longitude: self.server_longitude,
            pskreporter_status: self.pskreporter_status.clone(),
            aprs_is_status: self.aprs_is_status.clone(),
            decoders: self.decoders.clone(),
            cw_auto: self.cw_auto,
            cw_wpm: self.cw_wpm,
            cw_tone_hz: self.cw_tone_hz,
            filter: self.filter.clone(),
            spectrum: self.spectrum.clone(),
            vchan_rds: self.vchan_rds.clone(),
        })
    }

    /// Apply a frequency change into the state.
    pub fn apply_freq(&mut self, freq: crate::radio::freq::Freq) {
        self.status.freq = freq;
    }

    /// Apply a mode change into the state.
    pub fn apply_mode(&mut self, mode: RigMode) {
        let cw_mode = matches!(mode, RigMode::CW | RigMode::CWR);
        self.status.mode = mode;
        if cw_mode {
            self.decoders.cw_decode_enabled = true;
        }
    }

    /// Apply a PTT change, resetting meters on TX off.
    pub fn apply_ptt(&mut self, ptt: bool) {
        self.status.tx_en = ptt;
        self.status.lock = self.control.lock;
        if !ptt {
            if let Some(tx) = self.status.tx.as_mut() {
                tx.power = Some(0);
                tx.swr = Some(0.0);
            }
        }
    }
}

/// Current filter/DSP state for backends that support runtime filter adjustment.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RigFilterState {
    pub bandwidth_hz: u32,
    pub cw_center_hz: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_gain_db: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_lna_gain_db: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_agc_enabled: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_squelch_enabled: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_squelch_threshold_db: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_nb_enabled: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub sdr_nb_threshold: Option<f64>,
    #[serde(default = "default_wfm_deemphasis_us")]
    pub wfm_deemphasis_us: u32,
    #[serde(default = "default_wfm_stereo")]
    pub wfm_stereo: bool,
    #[serde(default)]
    pub wfm_stereo_detected: bool,
    #[serde(default = "default_wfm_denoise_level")]
    pub wfm_denoise: WfmDenoiseLevel,
    /// Co-Channel Interference level (0–100 scale).
    #[serde(default)]
    pub wfm_cci: u8,
    /// Adjacent Channel Interference level (0–100 scale).
    #[serde(default)]
    pub wfm_aci: u8,
    /// SAM stereo width (0.0 = mono, 1.0 = full stereo).
    #[serde(default = "default_sam_stereo_width")]
    pub sam_stereo_width: f32,
    /// SAM carrier synchronization enabled.
    #[serde(default = "default_sam_carrier_sync")]
    pub sam_carrier_sync: bool,
}

#[derive(Debug, Clone, Copy, Serialize, Deserialize, PartialEq, Eq)]
#[serde(rename_all = "lowercase")]
pub enum WfmDenoiseLevel {
    Off,
    Auto,
    Low,
    Medium,
    High,
}

fn default_wfm_deemphasis_us() -> u32 {
    75
}

fn default_wfm_stereo() -> bool {
    true
}

fn default_sam_stereo_width() -> f32 {
    1.0
}

fn default_sam_carrier_sync() -> bool {
    true
}

fn default_wfm_denoise_level() -> WfmDenoiseLevel {
    WfmDenoiseLevel::Auto
}

/// Spectrum data from SDR backends (FFT magnitude over the full capture bandwidth).
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct SpectrumData {
    /// FFT magnitude bins in dBFS, FFT-shifted so DC (centre frequency) is at index N/2.
    pub bins: Vec<f32>,
    /// Centre frequency of the SDR capture in Hz.
    pub center_hz: u64,
    /// SDR capture sample rate in Hz; the displayed span is ±sample_rate/2.
    pub sample_rate: u32,
    /// Decoded Radio Data System state, when available for WFM.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rds: Option<RdsData>,
}

/// Live RDS metadata decoded from a WFM broadcast.
#[derive(Debug, Clone, Default, Serialize, Deserialize, PartialEq)]
pub struct RdsData {
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pi: Option<u16>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub program_service: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub radio_text: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub program_type_name_long: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pty: Option<u8>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pty_name: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub traffic_program: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub traffic_announcement: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub music: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub stereo: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub artificial_head: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub compressed: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub dynamic_pty: Option<bool>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub alternative_frequencies_hz: Option<Vec<u32>>,
}

/// RDS metadata snapshot for a virtual channel.
///
/// `PartialEq` intentionally ignores `signal_db` so that rapidly-changing
/// signal levels do not cause the main state snapshot to diff on every poll
/// cycle (signal_db flows through the spectrum SSE instead).
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct VchanRdsEntry {
    /// Virtual channel UUID.
    pub id: Uuid,
    /// Latest RDS data, if decoded.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub rds: Option<RdsData>,
    /// Channel signal level in dBFS.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub signal_db: Option<f32>,
}

impl PartialEq for VchanRdsEntry {
    fn eq(&self, other: &Self) -> bool {
        self.id == other.id && self.rds == other.rds
    }
}

/// Read-only projection of state shared with clients.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct RigSnapshot {
    pub info: RigInfo,
    pub status: RigStatus,
    pub band: Option<String>,
    pub enabled: Option<bool>,
    pub initialized: bool,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_callsign: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_version: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_build_date: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_latitude: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub server_longitude: Option<f64>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub pskreporter_status: Option<String>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub aprs_is_status: Option<String>,
    /// Decoder enable/disable flags.
    #[serde(flatten)]
    pub decoders: DecoderConfig,
    #[serde(default)]
    pub cw_auto: bool,
    #[serde(default)]
    pub cw_wpm: u32,
    #[serde(default)]
    pub cw_tone_hz: u32,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub filter: Option<RigFilterState>,
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub spectrum: Option<SpectrumData>,
    /// Per-virtual-channel RDS snapshots, when available.
    #[serde(default, skip_serializing_if = "Option::is_none")]
    pub vchan_rds: Option<Vec<VchanRdsEntry>>,
}
