#!/usr/bin/env bash
#
# build-occt.sh — Build OpenCascade Technology (OCCT) as shared libraries
# for macOS arm64, with only the modules needed for STEP parsing + tessellation.
#
# Shared (dynamic) linking is used for LGPL 2.1 compliance — users can
# replace the bundled dylibs with a modified OCCT build.
#
# Usage:
#   ./scripts/build-occt.sh
#
# Prerequisites:
#   - Xcode Command Line Tools (clang, cmake)
#   - Internet connection (downloads OCCT source)
#
# Output:
#   Vendor/OCCT/lib/    — Shared libraries (.dylib files)
#   Vendor/OCCT/include/ — Header files
#

set -euo pipefail

# ─── Configuration ───────────────────────────────────────────────────────────

OCCT_VERSION="7.9.3"
OCCT_ARCHIVE="opencascade-${OCCT_VERSION}.tar.gz"
OCCT_URL="https://git.dev.opencascade.org/gitweb/?p=occt.git;a=snapshot;h=refs/tags/V${OCCT_VERSION//./_};sf=tgz"
OCCT_DIR="opencascade-${OCCT_VERSION}"

# H-4: supply-chain integrity check. SHA-256 of the GitHub release archive
# https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V7_9_3.tar.gz
# (48,586,815 bytes), verified by two independent downloads on 2026-06-03.
# (Updated 7.8.1 -> 7.9.3 for IGES-reader stability fixes.)
# GitHub release-tag archives are byte-stable; the git.dev snapshot is not, so
# only the GitHub path is checksum-verified. Override/clear if you change source.
OCCT_SHA256="${OCCT_SHA256:-5ecf094ec6b12d5413dfb851d8c3590c354058aee556e32e408bdfbf8c357d57}"

SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
PROJECT_DIR="$(dirname "$SCRIPT_DIR")"
BUILD_DIR="${PROJECT_DIR}/.build/occt"
INSTALL_DIR="${PROJECT_DIR}/Vendor/OCCT"

MACOS_DEPLOYMENT_TARGET="15.0"
ARCH="arm64"

# ─── Functions ───────────────────────────────────────────────────────────────

log() { echo "==> $*"; }
err() { echo "ERROR: $*" >&2; exit 1; }

check_prerequisites() {
    command -v cmake >/dev/null 2>&1 || err "cmake not found. Install with: brew install cmake"
    command -v clang >/dev/null 2>&1 || err "clang not found. Install Xcode Command Line Tools."
}

download_occt() {
    if [[ -d "${BUILD_DIR}/${OCCT_DIR}" ]]; then
        log "OCCT source already exists, skipping download"
        return
    fi

    mkdir -p "${BUILD_DIR}"
    cd "${BUILD_DIR}"

    local occt_source=""
    if [[ ! -f "${OCCT_ARCHIVE}" ]]; then
        # H-4: prefer the GitHub release archive — its bytes are stable and can
        # be checksum-verified. The official git.dev snapshot is generated on
        # the fly (not byte-stable), so it is only an unverified fallback.
        local MIRROR_URL="https://github.com/Open-Cascade-SAS/OCCT/archive/refs/tags/V${OCCT_VERSION//./_}.tar.gz"
        log "Downloading OCCT ${OCCT_VERSION} from GitHub (checksum-verified)..."
        if curl -fSL -o "${OCCT_ARCHIVE}" "${MIRROR_URL}"; then
            occt_source="github"
        else
            log "GitHub download failed, trying official OCCT snapshot (cannot be checksum-verified)..."
            curl -fSL -o "${OCCT_ARCHIVE}" "${OCCT_URL}" \
                || err "Failed to download OCCT. Please download manually to ${BUILD_DIR}/${OCCT_ARCHIVE}"
            occt_source="gitweb"
        fi
    else
        occt_source="cached"
    fi

    # H-4: verify integrity. The pinned hash is the GitHub release archive's.
    if [[ -n "${OCCT_SHA256}" ]]; then
        if [[ "${occt_source}" == "github" ]]; then
            log "Verifying OCCT archive checksum..."
            echo "${OCCT_SHA256}  ${OCCT_ARCHIVE}" | shasum -a 256 -c - \
                || err "OCCT archive checksum mismatch — refusing to extract."
        elif [[ "${occt_source}" == "cached" ]]; then
            if echo "${OCCT_SHA256}  ${OCCT_ARCHIVE}" | shasum -a 256 -c - >/dev/null 2>&1; then
                log "Cached OCCT archive matches pinned checksum."
            else
                log "⚠️  Cached OCCT archive does not match the pinned checksum (may be from the non-GitHub source) — proceeding unverified."
            fi
        else
            log "⚠️  Used non-GitHub fallback source — checksum NOT verified (snapshot archives are not byte-stable)."
        fi
    fi

    log "Extracting..."
    tar xzf "${OCCT_ARCHIVE}"

    # Handle different archive directory naming (GitHub: OCCT-7_9_3;
    # git.dev snapshot: occt-V7_9_3). Scope the globs to OCCT source dirs and
    # tolerate non-matching globs under `set -euo pipefail` (the trailing
    # `|| true` prevents a failed glob from aborting the whole script).
    if [[ ! -d "${OCCT_DIR}" ]]; then
        EXTRACTED=$(ls -d OCCT-* occt-* 2>/dev/null | grep -v '\.tar' | head -1 || true)
        if [[ -n "${EXTRACTED}" && -d "${EXTRACTED}" ]]; then
            mv "${EXTRACTED}" "${OCCT_DIR}"
        else
            err "Could not find extracted OCCT directory"
        fi
    fi
}

build_occt() {
    local src_dir="${BUILD_DIR}/${OCCT_DIR}"
    local build_dir="${BUILD_DIR}/build"

    mkdir -p "${build_dir}"
    cd "${build_dir}"

    log "Configuring OCCT (minimal modules for STEP support)..."

    cmake "${src_dir}" \
        -DCMAKE_POLICY_VERSION_MINIMUM=3.5 \
        -DCMAKE_BUILD_TYPE=Release \
        -DCMAKE_INSTALL_PREFIX="${INSTALL_DIR}" \
        -DCMAKE_OSX_ARCHITECTURES="${ARCH}" \
        -DCMAKE_OSX_DEPLOYMENT_TARGET="${MACOS_DEPLOYMENT_TARGET}" \
        \
        -DBUILD_LIBRARY_TYPE=Shared \
        -DCMAKE_INSTALL_NAME_DIR=@rpath \
        \
        -DBUILD_MODULE_FoundationClasses=ON \
        -DBUILD_MODULE_ModelingData=ON \
        -DBUILD_MODULE_ModelingAlgorithms=ON \
        -DBUILD_MODULE_DataExchange=ON \
        -DBUILD_MODULE_Visualization=OFF \
        -DBUILD_MODULE_ApplicationFramework=ON \
        -DBUILD_MODULE_Draw=OFF \
        \
        -DBUILD_DOC_Overview=OFF \
        -DBUILD_SAMPLES_MFC=OFF \
        -DBUILD_SAMPLES_QT=OFF \
        -DBUILD_Inspector=OFF \
        \
        -DUSE_FREETYPE=OFF \
        -DUSE_FREEIMAGE=OFF \
        -DUSE_OPENVR=OFF \
        -DUSE_FFMPEG=OFF \
        -DUSE_TBB=OFF \
        -DUSE_VTK=OFF \
        -DUSE_RAPIDJSON=OFF \
        -DUSE_DRACO=OFF \
        -DUSE_OPENGL=OFF \
        -DUSE_GLES2=OFF \
        -DUSE_D3D=OFF \
        \
        -DCMAKE_C_FLAGS="-fPIC" \
        -DCMAKE_CXX_FLAGS="-fPIC -std=c++17" \
        2>&1

    log "Building OCCT (this may take 10-20 minutes)..."
    cmake --build . --config Release --parallel "$(sysctl -n hw.ncpu)" 2>&1

    log "Installing to ${INSTALL_DIR}..."
    cmake --install . 2>&1
}

verify_installation() {
    log "Verifying installation..."

    # Check for essential shared libraries
    local required_libs=(
        "libTKDESTEP.dylib"
        "libTKDE.dylib"
        "libTKXSBase.dylib"
        "libTKMesh.dylib"
        "libTKBRep.dylib"
        "libTKTopAlgo.dylib"
        "libTKGeomBase.dylib"
        "libTKGeomAlgo.dylib"
        "libTKG3d.dylib"
        "libTKG2d.dylib"
        "libTKMath.dylib"
        "libTKernel.dylib"
        "libTKXCAF.dylib"
        "libTKVCAF.dylib"
        "libTKLCAF.dylib"
        "libTKCAF.dylib"
        "libTKCDF.dylib"
        "libTKShHealing.dylib"
    )

    local missing=0
    for lib in "${required_libs[@]}"; do
        if [[ ! -f "${INSTALL_DIR}/lib/${lib}" ]]; then
            echo "  MISSING: ${lib}"
            missing=$((missing + 1))
        fi
    done

    if [[ ${missing} -gt 0 ]]; then
        log "WARNING: ${missing} expected libraries not found. Build may still work with different naming."
        log "Listing available libraries:"
        find "${INSTALL_DIR}" -name "*.dylib" | head -30
    else
        log "All required shared libraries found!"
    fi

    # Verify install names use @rpath
    local sample_dylib
    sample_dylib=$(find "${INSTALL_DIR}/lib" -name "libTKernel*.dylib" -maxdepth 1 | head -1)
    if [[ -n "${sample_dylib}" ]]; then
        local install_name
        install_name=$(otool -D "${sample_dylib}" | tail -1)
        if [[ "${install_name}" == *"@rpath"* ]]; then
            log "Install names correctly use @rpath"
        else
            log "WARNING: Install name '${install_name}' does not use @rpath"
        fi
    fi

    # Show total size
    local total_size
    total_size=$(du -sh "${INSTALL_DIR}/lib" 2>/dev/null | cut -f1)
    log "Library size: ${total_size}"
}

print_xcode_config() {
    log ""
    log "═══════════════════════════════════════════════════════════"
    log "  OCCT build complete! (shared libraries for LGPL compliance)"
    log "═══════════════════════════════════════════════════════════"
    log ""
    log "Xcode build settings (already configured in project):"
    log ""
    log "  HEADER_SEARCH_PATHS:"
    log "    \$(PROJECT_DIR)/Vendor/OCCT/include/opencascade"
    log ""
    log "  LIBRARY_SEARCH_PATHS:"
    log "    \$(PROJECT_DIR)/Vendor/OCCT/lib"
    log ""
    log "  OTHER_LDFLAGS: (same -l flags work for both static and shared)"
    log "    -lTKDESTEP -lTKDE -lTKXSBase ..."
    log ""
    log "  The 'Copy OCCT Dylibs' build phase embeds .dylib files"
    log "  into the app bundle's Frameworks directory at build time."
    log ""
}

# ─── Main ────────────────────────────────────────────────────────────────────

main() {
    log "Building OCCT ${OCCT_VERSION} for macOS ${ARCH}"
    log "Install directory: ${INSTALL_DIR}"
    echo ""

    check_prerequisites
    download_occt
    build_occt
    verify_installation
    print_xcode_config
}

main "$@"
