//! Hardware video-encode capability detection (Task 7).
//!
//! Probes Windows Media Foundation for a HARDWARE H.264 encoder MFT at startup.
//! The result is cached and advertised to the server in `AgentStatus.supports_h264`
//! so the server can negotiate the codec (see `StartStream.video_codec`).
//!
//! Detection is intentionally cheap and side-effect-free: it only ENUMERATES the
//! available encoder MFTs (it does not create or initialize one). A `true` result
//! means a hardware H.264 encoder was advertised by the OS; it does NOT guarantee
//! the encoder will successfully initialize at stream time — the H.264 encoder
//! still falls back to raw on any init/feed failure.
//!
//! On non-Windows targets, or if MF is unavailable, this reports `false`.

use std::sync::OnceLock;

/// Cached capability result. Detection runs at most once per process.
static SUPPORTS_H264: OnceLock<bool> = OnceLock::new();

/// Return whether this machine has a hardware H.264 encoder, detecting once and
/// caching the result. Safe to call repeatedly and from any thread.
pub fn supports_hardware_h264() -> bool {
    *SUPPORTS_H264.get_or_init(detect_hardware_h264)
}

/// Run the actual detection. Separated so the cached accessor stays trivial.
fn detect_hardware_h264() -> bool {
    let supported = detect_inner();
    if supported {
        tracing::info!("Hardware H.264 encoder detected (Media Foundation)");
    } else {
        tracing::info!("No hardware H.264 encoder detected; raw+Zstd only");
    }
    supported
}

#[cfg(windows)]
fn detect_inner() -> bool {
    // Enumerate hardware H.264 encoder MFTs. This is a read-only probe; it does
    // not init D3D, COM apartments persistently, or create the encoder.
    match unsafe { enumerate_hardware_h264() } {
        Ok(found) => found,
        Err(e) => {
            tracing::warn!("H.264 capability probe failed: {e:#}; assuming no HW encoder");
            false
        }
    }
}

#[cfg(not(windows))]
fn detect_inner() -> bool {
    false
}

#[cfg(windows)]
unsafe fn enumerate_hardware_h264() -> anyhow::Result<bool> {
    use windows::Win32::Media::MediaFoundation::{
        MFMediaType_Video, MFTEnumEx, MFVideoFormat_H264, MFT_CATEGORY_VIDEO_ENCODER,
        MFT_ENUM_FLAG_HARDWARE, MFT_ENUM_FLAG_SORTANDFILTER, MFT_ENUM_FLAG_TRANSCODE_ONLY,
        MFT_REGISTER_TYPE_INFO,
    };

    // We only specify the OUTPUT type (H.264); input is left unconstrained so the
    // probe matches encoders regardless of their preferred input subtype.
    let output_type = MFT_REGISTER_TYPE_INFO {
        guidMajorType: MFMediaType_Video,
        guidSubtype: MFVideoFormat_H264,
    };

    let mut activate_ptr: *mut Option<windows::Win32::Media::MediaFoundation::IMFActivate> =
        std::ptr::null_mut();
    let mut count: u32 = 0;

    // MFTEnumEx does not itself require MFStartup for a pure enumeration, but we
    // guard with a Result so any HRESULT failure degrades to "no HW encoder".
    MFTEnumEx(
        MFT_CATEGORY_VIDEO_ENCODER,
        MFT_ENUM_FLAG_HARDWARE | MFT_ENUM_FLAG_SORTANDFILTER | MFT_ENUM_FLAG_TRANSCODE_ONLY,
        None, // input type: any
        Some(&output_type as *const _),
        &mut activate_ptr,
        &mut count,
    )?;

    // Release every returned IMFActivate, then free the array CoTaskMemAlloc'd by MF.
    let found = count > 0;
    if !activate_ptr.is_null() {
        let slice = std::slice::from_raw_parts_mut(activate_ptr, count as usize);
        for entry in slice.iter_mut() {
            // Dropping the Option<IMFActivate> releases the COM reference.
            entry.take();
        }
        windows::Win32::System::Com::CoTaskMemFree(Some(activate_ptr as *const _));
    }

    Ok(found)
}
