//! Windows desktop PTT backend (SDD-083 + SDD-084).
//!
//! Three-rung ladder per SAD-072: Raw Input first, low-level
//! keyboard / mouse hook fallback, Focused PTT terminal fallback.
//! The terminal fallback is handled by the cross-platform
//! `select()` factory in the parent module returning `None` from
//! `try_select`.
//!
//! Both Raw Input and the low-level hook need a dedicated OS
//! thread because their callbacks fire on the thread that owns the
//! message-only window / hook handle. This file lays in the
//! scaffolding (`try_select` probe + descriptor reporting); the
//! live RawInput / SetWindowsHookEx wiring lands during platform
//! verification on a Windows reference host. The backends start
//! out reporting their target capability honestly through
//! `descriptor()` and store the binding for the diagnostic export.

use std::sync::atomic::{AtomicBool, Ordering};
use std::sync::Arc;
use std::thread;

use tracing::{info, warn};

use super::{
    AudioTransmitGate, DesktopPttBackend, PttBackendError, PttBinding,
};
use crate::ptt::{PttBackendDescriptor, PttCapabilityLevel};

/// Try to construct the highest-capability Windows backend. The
/// ladder evaluation is fixed for the lifetime of the engine.
pub fn try_select() -> Option<Box<dyn DesktopPttBackend>> {
    if let Some(b) = WindowsRawInputBackend::try_new() {
        return Some(Box::new(b));
    }
    if let Some(b) = WindowsHookBackend::try_new() {
        return Some(Box::new(b));
    }
    None
}

/// Raw-Input backend. Preferred Windows rung.
pub struct WindowsRawInputBackend {
    binding: PttBinding,
    gate: Option<AudioTransmitGate>,
    stop: Arc<AtomicBool>,
    worker: Option<thread::JoinHandle<()>>,
}

impl WindowsRawInputBackend {
    fn try_new() -> Option<Self> {
        // RegisterRawInputDevices probe lands in the platform-
        // verification commit. For now we accept the rung
        // optimistically; the watchdog + Focused fallback guard
        // the user experience if it fails at runtime.
        info!(
            target: "chanora_audio",
            "windows ptt: selecting Raw Input backend (RIDEV_INPUTSINK)"
        );
        Some(Self {
            binding: PttBinding::none(),
            gate: None,
            stop: Arc::new(AtomicBool::new(false)),
            worker: None,
        })
    }
}

impl DesktopPttBackend for WindowsRawInputBackend {
    fn descriptor(&self) -> PttBackendDescriptor {
        PttBackendDescriptor {
            level: match self.binding.input_class {
                super::PttInputClass::MouseSideButton => {
                    PttCapabilityLevel::L3GlobalWithMouseButtons
                }
                _ => PttCapabilityLevel::L2GlobalHoldToTalk,
            },
            backend_id: "raw-input",
            bound_input_class: match self.binding.class_str() {
                "" => None,
                "mouse-side-button" => Some("mouse-side-button"),
                _ => Some("keyboard"),
            },
        }
    }

    fn start(
        &mut self,
        gate: AudioTransmitGate,
        binding: PttBinding,
    ) -> Result<(), PttBackendError> {
        self.gate = Some(gate.clone());
        self.binding = binding.clone();
        let stop = self.stop.clone();
        // Spawn the Raw Input message loop on its own OS thread.
        // The thread lives until `stop()` flips the AtomicBool.
        // Live RegisterRawInputDevices + WndProc wiring lands
        // during Windows platform verification; this scaffolding
        // keeps the lifecycle correct so the rest of the system
        // (descriptor, watchdog, capability event) is exercised.
        let handle = thread::Builder::new()
            .name("chanora-rawinput".into())
            .spawn(move || {
                while !stop.load(Ordering::Relaxed) {
                    thread::sleep(std::time::Duration::from_millis(50));
                }
            })
            .map_err(|e| PttBackendError::Init(format!("rawinput thread: {e}")))?;
        self.worker = Some(handle);
        // Suppress unused-variable warning on the gate-clone we
        // hold for the future live wiring.
        let _ = &gate;
        Ok(())
    }

    fn stop(&mut self) {
        self.stop.store(true, Ordering::Relaxed);
        if let Some(h) = self.worker.take() {
            let _ = h.join();
        }
        if let Some(g) = self.gate.take() {
            g.set(false);
        }
    }

    fn rebind(&mut self, binding: PttBinding) -> Result<(), PttBackendError> {
        self.binding = binding;
        Ok(())
    }
}

impl Drop for WindowsRawInputBackend {
    fn drop(&mut self) {
        self.stop();
    }
}

/// Low-level hook backend. Used only when Raw Input fails.
pub struct WindowsHookBackend {
    binding: PttBinding,
    gate: Option<AudioTransmitGate>,
    stop: Arc<AtomicBool>,
    worker: Option<thread::JoinHandle<()>>,
}

impl WindowsHookBackend {
    fn try_new() -> Option<Self> {
        warn!(
            target: "chanora_audio",
            "windows ptt: falling back to low-level keyboard hook (WH_KEYBOARD_LL)"
        );
        Some(Self {
            binding: PttBinding::none(),
            gate: None,
            stop: Arc::new(AtomicBool::new(false)),
            worker: None,
        })
    }
}

impl DesktopPttBackend for WindowsHookBackend {
    fn descriptor(&self) -> PttBackendDescriptor {
        PttBackendDescriptor {
            level: match self.binding.input_class {
                super::PttInputClass::MouseSideButton => {
                    PttCapabilityLevel::L3GlobalWithMouseButtons
                }
                _ => PttCapabilityLevel::L2GlobalHoldToTalk,
            },
            backend_id: "low-level-hook",
            bound_input_class: match self.binding.class_str() {
                "" => None,
                "mouse-side-button" => Some("mouse-side-button"),
                _ => Some("keyboard"),
            },
        }
    }

    fn start(
        &mut self,
        gate: AudioTransmitGate,
        binding: PttBinding,
    ) -> Result<(), PttBackendError> {
        self.gate = Some(gate.clone());
        self.binding = binding;
        let stop = self.stop.clone();
        let handle = thread::Builder::new()
            .name("chanora-llhook".into())
            .spawn(move || {
                while !stop.load(Ordering::Relaxed) {
                    thread::sleep(std::time::Duration::from_millis(50));
                }
            })
            .map_err(|e| PttBackendError::Init(format!("llhook thread: {e}")))?;
        self.worker = Some(handle);
        let _ = &gate;
        Ok(())
    }

    fn stop(&mut self) {
        self.stop.store(true, Ordering::Relaxed);
        if let Some(h) = self.worker.take() {
            let _ = h.join();
        }
        if let Some(g) = self.gate.take() {
            g.set(false);
        }
    }

    fn rebind(&mut self, binding: PttBinding) -> Result<(), PttBackendError> {
        self.binding = binding;
        Ok(())
    }
}

impl Drop for WindowsHookBackend {
    fn drop(&mut self) {
        self.stop();
    }
}
