//! Face rectangle detection via `VNDetectFaceRectanglesRequest`.
//!
//! Returns one [`FaceObservation`] per detected face — bounding box
//! + Vision's confidence score. Identity / landmark detection is
//! intentionally NOT exposed: face recognition has privacy
//! implications that should be a per-bundle agent-capability
//! decision (`vision-detect-faces` ≠ `vision-recognize-faces`),
//! not a default.
//!
//! Use case: agents that need to know "is there a face here" before
//! running heavier downstream pipelines (vision LLM, framing for
//! a thumbnail, etc.).

use std::path::Path;

#[cfg(all(target_os = "macos", car_vision_swift_built))]
use std::ffi::CString;
#[cfg(all(target_os = "macos", car_vision_swift_built))]
use std::os::raw::c_char;
#[cfg(all(target_os = "macos", car_vision_swift_built))]
use std::ptr;

use serde::{Deserialize, Serialize};

use crate::{Result, VisionError};

/// One face detection. Coordinates normalized to `[0, 1]` in
/// Vision's bottom-left origin space.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct FaceObservation {
    pub confidence: f32,
    pub x: f64,
    pub y: f64,
    pub w: f64,
    pub h: f64,
}

/// Detect face rectangles in the image at `path`. The image is
/// loaded via `CGImageSourceCreateWithURL`, which handles every
/// common format (PNG/JPEG/HEIC/TIFF/etc).
///
/// Returns an empty vec when no faces are present. Errors only on
/// image-load failure or framework error — "no faces" is not an
/// error.
pub fn detect(path: impl AsRef<Path>) -> Result<Vec<FaceObservation>> {
    let path = path.as_ref();

    #[cfg(not(all(target_os = "macos", car_vision_swift_built)))]
    {
        let _ = path;
        Err(VisionError::PlatformUnsupported {
            reason: "Vision framework requires aarch64-apple-darwin with the Swift shim built",
        })
    }

    #[cfg(all(target_os = "macos", car_vision_swift_built))]
    {
        let path_c = CString::new(path.to_string_lossy().as_bytes())
            .map_err(|e| VisionError::InvalidInput(format!("path has interior NUL: {e}")))?;

        let mut out_json: *mut c_char = ptr::null_mut();
        let mut out_err: *mut c_char = ptr::null_mut();

        let rc = unsafe {
            crate::ffi::car_vision_detect_faces(
                path_c.as_ptr(),
                &mut out_json as *mut *mut c_char,
                &mut out_err as *mut *mut c_char,
            )
        };

        if rc != 0 {
            return Err(VisionError::RequestFailed(consume_swift_string(out_err)));
        }

        let json = consume_swift_string(out_json);
        serde_json::from_str(&json).map_err(|e| {
            VisionError::MalformedResponse(format!("face JSON parse: {e}; payload: {json}"))
        })
    }
}

#[cfg(all(target_os = "macos", car_vision_swift_built))]
fn consume_swift_string(ptr: *mut c_char) -> String {
    if ptr.is_null() {
        return String::new();
    }
    let s = unsafe { std::ffi::CStr::from_ptr(ptr) }
        .to_string_lossy()
        .into_owned();
    unsafe { crate::ffi::car_vision_free_string(ptr) };
    s
}
