//! Apple Vision.framework bindings for the Common Agent Runtime.
//!
//! Currently exposes one capability: [`ocr::recognize`] for on-device
//! text recognition via `VNRecognizeTextRequest`. More request types
//! are added as callers need them.
//!
//! The primary implementation lives in a small Swift shim
//! (`swift/CarVision.swift`) compiled by `build.rs`. Cfg-gated to
//! `target_os = "macos"` (both Apple Silicon and Intel). The Swift shim
//! only compiles when an Apple Swift toolchain is reachable; `build.rs`
//! sets `car_vision_swift_built` when the compile succeeded so the FFI is
//! gated on a strict superset of host availability.
//!
//! When that shim isn't compiled in (every non-macOS host, and macOS
//! builds without the Swift toolchain), [`ocr::recognize`] falls back to
//! the cross-platform [`ocr_tesseract`] backend — the `tesseract` CLI if
//! it's installed on `PATH` — so OCR works off macOS without a feature
//! flag. The other request types (faces, barcodes, classify) remain
//! Apple-only and return [`VisionError::PlatformUnsupported`] elsewhere.

use serde::{Deserialize, Serialize};
use thiserror::Error;

pub mod barcodes;
pub mod classify;
pub mod faces;
pub mod ocr;
/// Cross-platform OCR fallback (Tesseract CLI). Compiled on every
/// platform so it stays type-checked, but only *used* when the Apple
/// Vision shim isn't built; allow dead_code on Apple+shim builds where
/// the dispatch never reaches it.
#[cfg_attr(all(target_os = "macos", car_vision_swift_built), allow(dead_code))]
pub mod ocr_tesseract;

#[derive(Debug, Error)]
pub enum VisionError {
    #[error("Vision framework is not available on this platform: {reason}")]
    PlatformUnsupported { reason: &'static str },

    #[error("invalid input: {0}")]
    InvalidInput(String),

    #[error("vision request failed: {0}")]
    RequestFailed(String),

    #[error("response from Swift shim was malformed: {0}")]
    MalformedResponse(String),
}

pub type Result<T> = std::result::Result<T, VisionError>;

/// One bounding-box-tagged piece of recognized content.
///
/// Coordinates are normalized to `[0, 1]` in Vision's coordinate
/// space — origin at the bottom-left, x grows right, y grows up.
/// Convert as needed for top-left UI conventions.
#[derive(Debug, Clone, Serialize, Deserialize, PartialEq)]
pub struct Observation {
    pub text: String,
    pub confidence: f32,
    pub x: f64,
    pub y: f64,
    pub w: f64,
    pub h: f64,
}

/// Returns true when OCR is reachable in this build: the Apple Vision
/// framework on macOS (when the Swift shim compiled), or the
/// cross-platform Tesseract CLI fallback elsewhere when `tesseract` is
/// installed on `PATH`. False when neither is available.
///
/// Note this tracks the OCR path specifically — the Apple-only request
/// types (faces, barcodes, classify) are reachable only on the macOS
/// shim build regardless of this result.
pub fn is_available() -> bool {
    #[cfg(all(target_os = "macos", car_vision_swift_built))]
    {
        unsafe { crate::ffi::car_vision_is_available() != 0 }
    }
    #[cfg(not(all(target_os = "macos", car_vision_swift_built)))]
    {
        crate::ocr_tesseract::is_available()
    }
}

#[cfg(all(target_os = "macos", car_vision_swift_built))]
mod ffi {
    use std::os::raw::{c_char, c_int};
    extern "C" {
        pub fn car_vision_is_available() -> c_int;
        pub fn car_vision_free_string(ptr: *mut c_char);
        pub fn car_vision_ocr(
            image_path: *const c_char,
            config_json: *const c_char,
            out_json: *mut *mut c_char,
            out_err: *mut *mut c_char,
        ) -> c_int;
        pub fn car_vision_detect_faces(
            image_path: *const c_char,
            out_json: *mut *mut c_char,
            out_err: *mut *mut c_char,
        ) -> c_int;
        pub fn car_vision_detect_barcodes(
            image_path: *const c_char,
            out_json: *mut *mut c_char,
            out_err: *mut *mut c_char,
        ) -> c_int;
        pub fn car_vision_classify_image(
            image_path: *const c_char,
            top_k: i32,
            out_json: *mut *mut c_char,
            out_err: *mut *mut c_char,
        ) -> c_int;
    }
}
