cmake_minimum_required(VERSION 3.24)

project(pennylane_metal LANGUAGES CXX OBJCXX)

set(PENNYLANE_METAL_VERSION "0+unknown")
if(DEFINED SKBUILD_PROJECT_VERSION)
    set(PENNYLANE_METAL_VERSION "${SKBUILD_PROJECT_VERSION}")
endif()

if(NOT APPLE)
    message(FATAL_ERROR "metal.qubit requires macOS 26 and Metal 4")
endif()

set(CMAKE_CXX_STANDARD 20)
set(CMAKE_CXX_STANDARD_REQUIRED ON)
set(CMAKE_OBJCXX_STANDARD 20)
set(CMAKE_OBJCXX_STANDARD_REQUIRED ON)
set(CMAKE_OSX_DEPLOYMENT_TARGET "26.0" CACHE STRING "Minimum macOS version" FORCE)

if(NOT CMAKE_BUILD_TYPE)
    set(CMAKE_BUILD_TYPE Release)
endif()

find_library(METAL_LIBRARY Metal REQUIRED)
find_library(FOUNDATION_LIBRARY Foundation REQUIRED)
find_library(ACCELERATE_LIBRARY Accelerate REQUIRED)
find_package(Python 3.10 COMPONENTS Interpreter Development.Module REQUIRED)
find_package(pybind11 CONFIG REQUIRED)

execute_process(
    COMMAND xcrun --toolchain Metal --find metal
    RESULT_VARIABLE METAL_COMPILER_STATUS
    OUTPUT_QUIET
    ERROR_QUIET
)
execute_process(
    COMMAND xcrun --toolchain Metal --find metallib
    RESULT_VARIABLE METALLIB_COMPILER_STATUS
    OUTPUT_QUIET
    ERROR_QUIET
)
if(NOT METAL_COMPILER_STATUS EQUAL 0 OR NOT METALLIB_COMPILER_STATUS EQUAL 0)
    message(FATAL_ERROR
        "Xcode Metal Toolchain is required. Install it with: "
        "xcodebuild -downloadComponent MetalToolchain"
    )
endif()

set(METAL_SOURCE ${CMAKE_SOURCE_DIR}/cpp/metal/quantum_kernels.metal)
set(METAL_OPCODE_HEADER ${CMAKE_SOURCE_DIR}/cpp/core/QuantumOpcodes.h)
set(METAL_KERNEL_CONFIG_HEADER
    ${CMAKE_SOURCE_DIR}/cpp/core/MetalKernelConfig.h)
set(METAL_AIR ${CMAKE_BINARY_DIR}/quantum_kernels.air)
set(METAL_LIBRARY_OUTPUT ${CMAKE_BINARY_DIR}/pennylane_metal.metallib)

add_custom_command(
    OUTPUT ${METAL_LIBRARY_OUTPUT}
    COMMAND xcrun --toolchain Metal -sdk macosx metal
        -std=metal4.0
        -mmacosx-version-min=26.0
        -O3
        -c ${METAL_SOURCE}
        -o ${METAL_AIR}
    COMMAND xcrun --toolchain Metal -sdk macosx metallib
        ${METAL_AIR}
        -o ${METAL_LIBRARY_OUTPUT}
    DEPENDS ${METAL_SOURCE} ${METAL_OPCODE_HEADER} ${METAL_KERNEL_CONFIG_HEADER}
    COMMENT "Compiling Metal 4 quantum kernels"
    VERBATIM
)

add_custom_target(metal_shaders ALL DEPENDS ${METAL_LIBRARY_OUTPUT})

add_library(pennylane_metal_core STATIC
    cpp/metal/MetalContext.mm
    cpp/core/SimulationResources.cpp
    cpp/core/SpectralMath.cpp
    cpp/core/MetalBatchWorkspace.cpp
    cpp/core/MetalScratchBufferPool.cpp
    cpp/core/MetalProgramBuffers.mm
    cpp/core/MetalSimulator.mm
    cpp/core/MetalDensitySimulator.mm
)

target_include_directories(pennylane_metal_core PUBLIC ${CMAKE_SOURCE_DIR}/cpp)
target_link_libraries(pennylane_metal_core PUBLIC
    ${METAL_LIBRARY}
    ${FOUNDATION_LIBRARY}
    ${ACCELERATE_LIBRARY}
)
target_compile_options(pennylane_metal_core PRIVATE
    -fobjc-arc
    -Wall
    -Wextra
    -Wpedantic
)
target_compile_definitions(pennylane_metal_core PRIVATE
    ACCELERATE_NEW_LAPACK
    PENNYLANE_METAL_SHADER_PATH="${METAL_LIBRARY_OUTPUT}"
)
add_dependencies(pennylane_metal_core metal_shaders)

pybind11_add_module(_core cpp/bindings/bindings.cpp)
target_link_libraries(_core PRIVATE pennylane_metal_core)
target_compile_definitions(_core PRIVATE
    PENNYLANE_METAL_VERSION="${PENNYLANE_METAL_VERSION}"
    PENNYLANE_METAL_SHADER_PATH="${METAL_LIBRARY_OUTPUT}"
)

add_custom_command(TARGET _core POST_BUILD
    COMMAND ${CMAKE_COMMAND} -E copy_if_different
        ${METAL_LIBRARY_OUTPUT}
        $<TARGET_FILE_DIR:_core>/pennylane_metal.metallib
    COMMENT "Copying Metal 4 shader library"
)

install(TARGETS _core LIBRARY DESTINATION pennylane_metal)
install(FILES ${METAL_LIBRARY_OUTPUT} DESTINATION pennylane_metal)
