// Compiles the Vision Swift shim into a static library and links
// Vision.framework. Mirrors car-inference/build.rs (FoundationModels).
//
// Runs only on Apple Silicon macOS. On every other host we emit
// nothing — the Rust side is cfg-gated to the same target.

use std::env;
use std::path::PathBuf;
use std::process::Command;

/// Emit a soft-skip warning by default, OR a hard build error
/// when `CAR_REQUIRE_APPLE_SHIMS=1` is set. CI / release builds
/// that mustn't ship binaries missing shim functions can flip the
/// switch and get a fail-fast build instead of a quiet skip.
fn shim_skipped(reason: String) {
    if std::env::var("CAR_REQUIRE_APPLE_SHIMS").as_deref() == Ok("1") {
        println!("cargo::error={reason}");
    } else {
        println!("cargo:warning={reason}");
        println!(
            "cargo:warning=Vision shim skipped — downstream consumers of this build will see \
             `visionOcr`/`detect_*`/`classify_image` declared in .d.ts/.pyi but absent at \
             runtime. Set CAR_REQUIRE_APPLE_SHIMS=1 to fail the build instead."
        );
    }
}

fn main() {
    let target = env::var("TARGET").unwrap_or_default();
    // Vision.framework ships on every macOS host since 10.13 — both
    // Apple Silicon and Intel. Gate on the OS only, not the arch.
    let is_macos = target.ends_with("-apple-darwin");
    let is_apple_silicon = target.starts_with("aarch64-");

    // Declare the cfg name so `cargo check` doesn't warn about it.
    println!("cargo::rustc-check-cfg=cfg(car_vision_swift_built)");
    println!("cargo:rerun-if-changed=swift/CarVision.swift");
    println!("cargo:rerun-if-changed=build.rs");

    if !is_macos {
        return;
    }

    let manifest_dir = PathBuf::from(env::var("CARGO_MANIFEST_DIR").unwrap());
    let swift_src = manifest_dir.join("swift").join("CarVision.swift");
    if !swift_src.exists() {
        shim_skipped(format!(
            "Vision Swift shim not found at {}",
            swift_src.display()
        ));
        return;
    }

    let out_dir = PathBuf::from(env::var("OUT_DIR").unwrap());
    let obj_path = out_dir.join("CarVision.o");
    let lib_path = out_dir.join("libCarVision.a");

    // Swift target floor. We use 12.0 so back-deployment shims for
    // Swift Concurrency (`swiftCompatibility51`, `swiftCompatibility56`,
    // `swiftCompatibilityConcurrency`) aren't auto-linked — those
    // dylibs aren't present in the developer toolchain by default
    // and are unnecessary at the Swift target we ship.
    let swift_target = if is_apple_silicon {
        "arm64-apple-macosx15.0"
    } else {
        // 13.0 floor on Intel — drops every Swift back-compat shim
        // (swiftCompatibility{51,56,Concurrency,Packs}) which aren't
        // present in the toolchain by default. macOS 13 shipped Oct
        // 2022; older Intel Macs that need back-deployment can
        // either run via Apple Silicon binaries (Rosetta passthrough
        // is fine) or skip the Vision shim entirely.
        "x86_64-apple-macosx13.0"
    };
    // Keep the Rust-side MACOSX_DEPLOYMENT_TARGET aligned so the
    // final binary doesn't request back-deployment that the Swift
    // object can't honor.
    println!(
        "cargo:rustc-env=MACOSX_DEPLOYMENT_TARGET={}",
        if is_apple_silicon { "15.0" } else { "13.0" }
    );
    let status = Command::new("xcrun")
        .arg("--sdk")
        .arg("macosx")
        .arg("swiftc")
        .arg("-parse-as-library")
        .arg("-O")
        .arg("-target")
        .arg(swift_target)
        .arg("-emit-object")
        .arg("-o")
        .arg(&obj_path)
        .arg(&swift_src)
        .status();

    let status = match status {
        Ok(s) => s,
        Err(e) => {
            shim_skipped(format!(
                "swiftc invocation failed ({e}); Vision backend unavailable. \
                 Install full Xcode (not just Command Line Tools) to enable it."
            ));
            return;
        }
    };
    if !status.success() {
        shim_skipped(format!(
            "swiftc returned non-zero status ({:?}); Vision backend unavailable",
            status.code()
        ));
        return;
    }

    let ar_status = Command::new("ar")
        .arg("crus")
        .arg(&lib_path)
        .arg(&obj_path)
        .status();
    match ar_status {
        Ok(s) if s.success() => {}
        _ => {
            shim_skipped(format!(
                "ar failed to pack {} into {}; Vision backend unavailable",
                obj_path.display(),
                lib_path.display()
            ));
            return;
        }
    }

    println!("cargo:rustc-link-search=native={}", out_dir.display());
    println!("cargo:rustc-link-lib=static=CarVision");

    // Vision is shipped on macOS 10.13+; no need to weak-link.
    println!("cargo:rustc-link-arg=-Wl,-framework,Vision");
    println!("cargo:rustc-link-arg=-Wl,-framework,CoreImage");
    println!("cargo:rustc-link-arg=-Wl,-framework,CoreGraphics");
    println!("cargo:rustc-link-arg=-Wl,-framework,Foundation");

    println!("cargo:rustc-link-arg=-Wl,-rpath,/usr/lib/swift");
    if let Ok(swift_lib) = swift_runtime_path() {
        println!("cargo:rustc-link-search=native={}", swift_lib);
    }
    println!("cargo:rustc-link-lib=dylib=swiftCore");
    println!("cargo:rustc-link-lib=dylib=swift_Concurrency");
    println!("cargo:rustc-link-lib=dylib=swiftFoundation");

    println!("cargo:rustc-cfg=car_vision_swift_built");
}

fn swift_runtime_path() -> Result<String, std::io::Error> {
    let out = Command::new("xcrun").arg("--show-sdk-path").output()?;
    if !out.status.success() {
        return Err(std::io::Error::other("xcrun --show-sdk-path failed"));
    }
    let sdk = String::from_utf8_lossy(&out.stdout).trim().to_string();
    Ok(format!("{}/usr/lib/swift", sdk))
}
