//! Build script for cobre-solver.
//!
//! This script:
//! 1. Checks that the `HiGHS` git submodule is initialized at `crates/cobre-solver/vendor/HiGHS/`
//! 2. Builds `HiGHS` from source using the `cmake` crate (static library)
//! 3. Compiles the thin C wrapper (`csrc/highs_wrapper.c`) via `cc`
//! 4. Links the built `HiGHS` static library and the C++ standard library

// Build scripts routinely use expect/panic for unrecoverable configuration
// errors. Allow these lints here since there is no caller to propagate errors to.
#![allow(clippy::expect_used, clippy::panic, clippy::manual_assert)]

use std::env;
use std::path::PathBuf;

fn main() {
    println!("cargo:rerun-if-changed=csrc/highs_wrapper.c");
    println!("cargo:rerun-if-changed=csrc/highs_wrapper.h");
    println!("cargo:rerun-if-changed=csrc/highs_wrapper_cpp.cpp");

    let manifest_dir = PathBuf::from(
        env::var("CARGO_MANIFEST_DIR").expect("CARGO_MANIFEST_DIR must be set by Cargo"),
    );
    let highs_src = manifest_dir.join("vendor/HiGHS");

    if !highs_src.join("CMakeLists.txt").exists() {
        panic!(
            "HiGHS source not found at crates/cobre-solver/vendor/HiGHS/. \
             Run: git submodule update --init --recursive"
        );
    }

    eprintln!("cobre-solver: building HiGHS from {}", highs_src.display());

    let target_os = env::var("CARGO_CFG_TARGET_OS").unwrap_or_default();
    let target_env = env::var("CARGO_CFG_TARGET_ENV").unwrap_or_default();

    // Always build HiGHS in Release mode regardless of the Rust profile.
    // HiGHS is a solver library — an unoptimized build is ~10x slower and
    // would produce misleading results even during development.
    let mut cmake_config = cmake::Config::new(&highs_src);
    cmake_config
        .define("CMAKE_BUILD_TYPE", "Release")
        .define("BUILD_SHARED_LIBS", "OFF")
        .define("HIGHS_NO_DEFAULT_THREADS", "ON")
        .define("BUILD_TESTING", "OFF")
        .define("BUILD_EXAMPLES", "OFF")
        // Ensure HighsInt is 32-bit (matching the i32 types in our FFI bindings).
        // The _Static_assert in highs_wrapper.c catches this at compile time,
        // but setting the flag here prevents any mismatch from the cmake build.
        .define("HIGHSINT64", "OFF")
        // Disable zlib in HiGHS. HiGHS uses zlib only for reading compressed
        // .mps.gz/.lp.gz files via Highs_readModel(). Cobre constructs all LPs
        // programmatically via the C API and never uses file-based model I/O.
        // Disabling zlib eliminates a system dependency that causes
        // cross-compilation failures in the Python wheel CI.
        .define("CMAKE_DISABLE_FIND_PACKAGE_ZLIB", "ON");

    // On MSVC, use static CRT to avoid requiring vcruntime140.dll in the wheel.
    if target_env == "msvc" {
        cmake_config.define("CMAKE_MSVC_RUNTIME_LIBRARY", "MultiThreaded");
        cmake_config.cflag("/MT");
        cmake_config.cxxflag("/MT");
    }

    let highs_dst = cmake_config.build();

    eprintln!(
        "cobre-solver: HiGHS cmake output at {}",
        highs_dst.display()
    );

    println!(
        "cargo:rustc-link-search=native={}",
        highs_dst.join("lib").display()
    );
    println!(
        "cargo:rustc-link-search=native={}",
        highs_dst.join("lib64").display()
    );

    // MSVC cmake may place libraries in a configuration subdirectory.
    if target_env == "msvc" {
        println!(
            "cargo:rustc-link-search=native={}",
            highs_dst.join("lib/Release").display()
        );
    }

    println!("cargo:rustc-link-lib=static=highs");

    // Link the C++ standard library.
    // MSVC links it automatically — no explicit directive needed.
    if target_env != "msvc" {
        if target_os == "macos" {
            println!("cargo:rustc-link-lib=c++");
        } else {
            println!("cargo:rustc-link-lib=stdc++");
        }
    }

    let highs_include = highs_dst.join("include");
    let highs_include_highs = highs_dst.join("include/highs");

    eprintln!(
        "cobre-solver: compiling C wrapper with include paths: {}, {}",
        highs_include.display(),
        highs_include_highs.display()
    );

    let mut build = cc::Build::new();
    build
        .file("csrc/highs_wrapper.c")
        .include("csrc")
        .warnings(true)
        .extra_warnings(true);

    // Treat HiGHS headers as system includes so that their warnings
    // (unused-parameter on setHotStart/freezeBasis/unfreezeBasis inlines) do
    // not surface while we keep full warning coverage on our own wrappers.
    // MSVC lacks -isystem; fall back to -I and accept the vendor noise there.
    add_system_or_include(&mut build, target_env == "msvc", &highs_include);
    add_system_or_include(&mut build, target_env == "msvc", &highs_include_highs);

    // MSVC: link against static CRT (`/MT`) to match the static-CRT HiGHS
    // build above. Without this the cc-compiled wrappers default to the
    // dynamic CRT (`/MD`) and the linker rejects the mixed runtime objects
    // (`LNK2038: 'RuntimeLibrary' mismatch MT_StaticRelease vs
    // MD_DynamicRelease`). cargo-dist Windows builds happen to set
    // `+crt-static` via `RUSTFLAGS`, which the cc crate honours, but the
    // PyO3/maturin Python wheel build does not — so an explicit override
    // is required for the wheel to link.
    if target_env == "msvc" {
        build.static_crt(true);
    }

    // GCC/Clang-specific warning suppression.
    if target_env != "msvc" {
        build.flag("-Wno-unused-function");
    }

    build.compile("highs_wrapper");

    // C++ shim: implements cobre_highs_set_basis_non_alien which constructs a
    // HighsBasis (C++ type) with alien = false and calls
    // Highs::setBasis(const HighsBasis&) directly, bypassing the alien-path LU
    // factorisation that the HiGHS C API always triggers.  Compiled as a
    // separate object with C++17 so that the plain-C wrapper above is
    // unaffected.
    let mut build_cpp = cc::Build::new();
    build_cpp
        .file("csrc/highs_wrapper_cpp.cpp")
        .cpp(true)
        .include("csrc")
        .warnings(true)
        .extra_warnings(true);

    add_system_or_include(&mut build_cpp, target_env == "msvc", &highs_include);
    add_system_or_include(&mut build_cpp, target_env == "msvc", &highs_include_highs);

    build_cpp.flag_if_supported("-std=c++17");

    // Mirror the MSVC CRT setting used for the HiGHS cmake build above:
    // static CRT (`/MT`) so that the linker accepts the C++ wrapper
    // alongside HiGHS's C++ objects (which were also compiled `/MT`).
    if target_env == "msvc" {
        build_cpp.flag("/std:c++17");
        build_cpp.static_crt(true);
    } else {
        build_cpp.flag("-Wno-unused-function");
    }

    build_cpp.compile("highs_wrapper_cpp");
}

/// Add an include path, preferring `-isystem` on GCC/Clang so warnings from
/// third-party headers are suppressed while warnings on our own wrappers stay
/// active.  MSVC does not accept `-isystem`, so fall back to a regular
/// `.include()` there.
fn add_system_or_include(build: &mut cc::Build, is_msvc: bool, path: &std::path::Path) {
    if is_msvc {
        build.include(path);
    } else {
        build.flag("-isystem");
        build.flag(path.to_str().expect("HiGHS include path must be UTF-8"));
    }
}
