//! Barcode / QR code detection via `VNDetectBarcodesRequest`.
//!
//! Returns one [`BarcodeObservation`] per decoded code with the
//! payload string, the symbology (e.g. `"VNBarcodeSymbologyQR"`,
//! `"VNBarcodeSymbologyEAN13"`), confidence, and normalized
//! bounding box. Codes whose symbology is detected but whose
//! payload can't be decoded are skipped — the framework returns
//! a nil `payloadStringValue` for those.

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};

#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct BarcodeObservation {
    pub payload: String,
    /// Apple's symbology raw value, e.g.
    /// `"VNBarcodeSymbologyQR"`, `"VNBarcodeSymbologyEAN13"`,
    /// `"VNBarcodeSymbologyAztec"`.
    pub symbology: String,
    pub confidence: f32,
    pub x: f64,
    pub y: f64,
    pub w: f64,
    pub h: f64,
}

/// Detect all barcodes / QR codes in the image at `path`.
pub fn detect(path: impl AsRef<Path>) -> Result<Vec<BarcodeObservation>> {
    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_barcodes(
                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!("barcode 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
}
