# ONNX Runtime acquisition for the ML note detector (Basic Pitch).
#
# Mirrors the NAM_AVAILABLE / RTNEURAL_AVAILABLE conditional pattern: if ONNX
# Runtime can be obtained, ONNXRUNTIME_AVAILABLE is set ON and the audio addon
# compiles with SLOPSMITH_ONNX_SUPPORT=1; otherwise the build still succeeds and
# the engine falls back to the YIN PitchDetector / ChordScorer (Constitution VII).
#
# Delivery: a pinned, prebuilt CPU release fetched at configure time with a
# pinned URL + SHA-256 (Constitution V — reproducible builds). For fully offline
# / pre-seeded builds, set -DSLOPSMITH_ONNXRUNTIME_ROOT=/path/to/extracted/onnxruntime
# (the directory containing include/ and lib/) and the fetch is skipped.
#
# Exports (cache/parent scope):
#   ONNXRUNTIME_AVAILABLE     ON/OFF
#   ONNXRUNTIME_INCLUDE_DIR   header directory
#   ONNXRUNTIME_IMPORT_LIB    library to link against
#   ONNXRUNTIME_RUNTIME_LIB   shared lib that must sit next to slopsmith_audio.node

# Plain variable, not a cache entry: the version is pinned in lock-step with
# the per-asset SHA-256 table below, so it must always be the value in this
# file. A CACHE STRING would keep a stale version from a reused build dir
# (a branch on a different version), making the hash check fail and silently
# disable ONNX.
set(ONNXRUNTIME_VERSION "1.20.1")

# --- Resolve the prebuilt asset for this OS/arch --------------------------
set(_ort_base "https://github.com/microsoft/onnxruntime/releases/download/v${ONNXRUNTIME_VERSION}")
set(_ort_ok ON)

# Only x64 / arm64 prebuilt assets exist. An unrecognised processor must
# disable ONNX (clean YIN fallback) — never default to an x64 asset, which
# would only fail later at link/load on e.g. Windows ARM64 or 32-bit ARM.
if(CMAKE_SYSTEM_NAME STREQUAL "Windows")
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "AMD64|x86_64|x64")
        set(_ort_asset "onnxruntime-win-x64-${ONNXRUNTIME_VERSION}")
        set(_ort_ext "zip")
        set(_ort_sha "78d447051e48bd2e1e778bba378bec4ece11191c9e538cf7b2c4a4565e8f5581")
        set(_ort_import "onnxruntime.lib")
        set(_ort_runtime "onnxruntime.dll")
        set(_ort_providers "onnxruntime_providers_shared.dll")
    else()
        set(_ort_ok OFF)
        message(STATUS "ONNX Runtime: unsupported Windows arch '${CMAKE_SYSTEM_PROCESSOR}' — ML note detection disabled")
    endif()
elseif(CMAKE_SYSTEM_NAME STREQUAL "Darwin")
    set(_ort_ext "tgz")
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "arm64|aarch64")
        set(_ort_asset "onnxruntime-osx-arm64-${ONNXRUNTIME_VERSION}")
        set(_ort_sha "b678fc3c2354c771fea4fba420edeccfba205140088334df801e7fc40e83a57a")
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64")
        set(_ort_asset "onnxruntime-osx-x86_64-${ONNXRUNTIME_VERSION}")
        set(_ort_sha "0f73006813af2a1a5d1723ed7dfb694fc629d15037124081bb61b7bf7d99fc78")
    else()
        set(_ort_ok OFF)
        message(STATUS "ONNX Runtime: unsupported macOS arch '${CMAKE_SYSTEM_PROCESSOR}' — ML note detection disabled")
    endif()
    set(_ort_import "libonnxruntime.dylib")
    set(_ort_runtime "libonnxruntime.${ONNXRUNTIME_VERSION}.dylib")
    set(_ort_providers "libonnxruntime_providers_shared.dylib")
elseif(CMAKE_SYSTEM_NAME STREQUAL "Linux")
    set(_ort_ext "tgz")
    if(CMAKE_SYSTEM_PROCESSOR MATCHES "aarch64|arm64")
        set(_ort_asset "onnxruntime-linux-aarch64-${ONNXRUNTIME_VERSION}")
        set(_ort_sha "ae4fedbdc8c18d688c01306b4b50c63de3445cdf2dbd720e01a2fa3810b8106a")
    elseif(CMAKE_SYSTEM_PROCESSOR MATCHES "x86_64|AMD64|x64")
        set(_ort_asset "onnxruntime-linux-x64-${ONNXRUNTIME_VERSION}")
        set(_ort_sha "67db4dc1561f1e3fd42e619575c82c601ef89849afc7ea85a003abbac1a1a105")
    else()
        set(_ort_ok OFF)
        message(STATUS "ONNX Runtime: unsupported Linux arch '${CMAKE_SYSTEM_PROCESSOR}' — ML note detection disabled")
    endif()
    set(_ort_import "libonnxruntime.so")
    # SONAME is libonnxruntime.so.1 — that exact name must sit next to the addon.
    set(_ort_runtime "libonnxruntime.so.1")
    set(_ort_providers "libonnxruntime_providers_shared.so")
else()
    set(_ort_ok OFF)
    message(STATUS "ONNX Runtime: unsupported platform '${CMAKE_SYSTEM_NAME}' — ML note detection disabled")
endif()

# --- Obtain the runtime: explicit root override, or pinned fetch ----------
set(_ort_root "")

if(_ort_ok AND DEFINED SLOPSMITH_ONNXRUNTIME_ROOT)
    if(EXISTS "${SLOPSMITH_ONNXRUNTIME_ROOT}/include/onnxruntime_cxx_api.h")
        set(_ort_root "${SLOPSMITH_ONNXRUNTIME_ROOT}")
        message(STATUS "ONNX Runtime: using prepopulated root ${_ort_root}")
    else()
        message(WARNING "SLOPSMITH_ONNXRUNTIME_ROOT='${SLOPSMITH_ONNXRUNTIME_ROOT}' "
                        "has no include/onnxruntime_cxx_api.h — ignoring")
    endif()
endif()

# Download + extract the prebuilt archive. Every failure mode here is SOFT —
# offline machine, download error, hash mismatch, bad layout all just disable
# ML detection (YIN fallback). Configure must never abort on this, so we use
# file(DOWNLOAD) with a status check rather than FetchContent, which raises a
# FATAL_ERROR when the archive can't be fetched.
if(_ort_ok AND _ort_root STREQUAL "")
    set(_ort_url     "${_ort_base}/${_ort_asset}.${_ort_ext}")
    set(_ort_archive "${CMAKE_BINARY_DIR}/_deps/${_ort_asset}.${_ort_ext}")
    set(_ort_extract "${CMAKE_BINARY_DIR}/_deps/onnxruntime")
    set(_ort_header  "${_ort_extract}/${_ort_asset}/include/onnxruntime_cxx_api.h")

    # On a clean build tree _deps does not exist yet — create it so the
    # file(DOWNLOAD) below can open its destination file instead of failing
    # and silently disabling ONNX support on every fresh configure.
    file(MAKE_DIRECTORY "${CMAKE_BINARY_DIR}/_deps")

    # Download — cached: a prior configure's archive with a matching hash is
    # reused, so reconfigure / clean-tree rebuilds don't re-fetch.
    set(_ort_have OFF)
    if(EXISTS "${_ort_archive}")
        file(SHA256 "${_ort_archive}" _ort_got)
        if(_ort_got STREQUAL "${_ort_sha}")
            set(_ort_have ON)
        endif()
    endif()
    if(NOT _ort_have)
        message(STATUS "ONNX Runtime: downloading ${_ort_asset}.${_ort_ext}")
        # Bounded timeouts so a network stall can't hang cmake configure
        # indefinitely — the status check below then soft-falls to YIN.
        file(DOWNLOAD "${_ort_url}" "${_ort_archive}" STATUS _ort_dlst
             TLS_VERIFY ON INACTIVITY_TIMEOUT 30 TIMEOUT 600)
        list(GET _ort_dlst 0 _ort_dlcode)
        if(_ort_dlcode EQUAL 0 AND EXISTS "${_ort_archive}")
            file(SHA256 "${_ort_archive}" _ort_got)
            if(_ort_got STREQUAL "${_ort_sha}")
                set(_ort_have ON)
            else()
                message(WARNING "ONNX Runtime: SHA-256 mismatch on ${_ort_asset} "
                                "— ML note detection disabled (YIN fallback)")
            endif()
        else()
            list(GET _ort_dlst 1 _ort_dlmsg)
            message(STATUS "ONNX Runtime: download failed (${_ort_dlmsg}) "
                           "— ML note detection disabled (YIN fallback)")
        endif()
    endif()

    # Extract — only when the archive is good and not already unpacked.
    if(_ort_have AND NOT EXISTS "${_ort_header}")
        file(REMOVE_RECURSE "${_ort_extract}")
        file(MAKE_DIRECTORY "${_ort_extract}")
        file(ARCHIVE_EXTRACT INPUT "${_ort_archive}" DESTINATION "${_ort_extract}")
    endif()

    if(_ort_have AND EXISTS "${_ort_header}")
        set(_ort_root "${_ort_extract}/${_ort_asset}")
        message(STATUS "ONNX Runtime ${ONNXRUNTIME_VERSION}: ready (${_ort_asset})")
    elseif(_ort_have)
        message(WARNING "ONNX Runtime: unexpected archive layout "
                        "— ML note detection disabled (YIN fallback)")
    endif()
endif()

# --- Publish results -------------------------------------------------------
# Verify the link + runtime libraries actually exist before declaring ONNX
# available. A prepopulated SLOPSMITH_ONNXRUNTIME_ROOT (or an unexpected
# archive layout) could carry the headers but miss/rename the libs — without
# this check configure would pass and the build would only fail later at link
# or the post-build copy, defeating the intended soft-fall to YIN.
set(_ort_import_path    "${_ort_root}/lib/${_ort_import}")
set(_ort_runtime_path   "${_ort_root}/lib/${_ort_runtime}")
# The shared-provider stub library ONNX Runtime dlopen()s alongside the main
# runtime. The CPU EP works without it, but staging it avoids a session-
# creation failure on builds/versions where it is consulted — see below.
set(_ort_providers_path "${_ort_root}/lib/${_ort_providers}")
if(_ort_ok AND NOT _ort_root STREQUAL ""
   AND NOT (EXISTS "${_ort_import_path}" AND EXISTS "${_ort_runtime_path}"))
    message(WARNING "ONNX Runtime: headers found but lib '${_ort_import}' / "
                    "'${_ort_runtime}' missing under ${_ort_root}/lib "
                    "— ML note detection disabled (YIN fallback)")
    set(_ort_root "")
endif()

if(_ort_ok AND NOT _ort_root STREQUAL "")
    # FORCE so a reconfigure always reflects the current probe result rather
    # than a stale entry from an earlier configure.
    set(ONNXRUNTIME_AVAILABLE   ON                       CACHE INTERNAL "" FORCE)
    set(ONNXRUNTIME_INCLUDE_DIR "${_ort_root}/include"   CACHE INTERNAL "" FORCE)
    set(ONNXRUNTIME_IMPORT_LIB  "${_ort_import_path}"    CACHE INTERNAL "" FORCE)
    set(ONNXRUNTIME_RUNTIME_LIB "${_ort_runtime_path}"   CACHE INTERNAL "" FORCE)
    # Optional — present in the standard releases but staged only if it exists,
    # so an unexpected layout never fails the build.
    if(EXISTS "${_ort_providers_path}")
        set(ONNXRUNTIME_PROVIDERS_LIB "${_ort_providers_path}" CACHE INTERNAL "" FORCE)
    else()
        unset(ONNXRUNTIME_PROVIDERS_LIB CACHE)
    endif()
    message(STATUS "ONNX Runtime available — ML note detection enabled")
else()
    set(ONNXRUNTIME_AVAILABLE OFF CACHE INTERNAL "" FORCE)
    # Clear any stale paths a previous successful configure cached, so a
    # later reconfigure that loses ONNX can't leave downstream steps
    # pointed at a runtime that is no longer there.
    unset(ONNXRUNTIME_INCLUDE_DIR CACHE)
    unset(ONNXRUNTIME_IMPORT_LIB CACHE)
    unset(ONNXRUNTIME_RUNTIME_LIB CACHE)
    unset(ONNXRUNTIME_PROVIDERS_LIB CACHE)
    message(STATUS "ONNX Runtime not available — ML note detection disabled (YIN fallback)")
endif()
