cmake_minimum_required(VERSION 3.25)

# This tells cmake we have goodies in the /cmake folder
list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/cmake")
include (PamplejuceVersion)

# Modern concise way to add dependencies to your project
include (CPM)
add_compile_options(-w)
# Configures universal binaries and decides which version of macOS to support
include(PamplejuceMacOS)

# Couple tweaks that IMO should be JUCE defaults
include(JUCEDefaults)

# Worry not, JUCE's PRODUCT_NAME can have spaces (and is what DAWs will display)
# You can also just have it be the same thing as PROJECT_NAME
# You may want to append the major version on the end of this (and PROJECT_NAME) ala    JUCE_PLUGINHOST_VST3=1:
#   set(PROJECT_NAME "MyPlugin_v${MAJOR_VERSION}")
# Doing so enables major versions to show up in IDEs and DAWs as separate plugin    JUCE_PLUGINHOST_VST3=
# allowing you to change parameters and behavior without breaking existing user projects
set(PROJECT_NAME "bitKlavier${MAJOR_VERSION}")
set(PRODUCT_NAME "bitKlavier${MAJOR_VERSION}")

# Change me! Used for the MacOS bundle name and Installers
set(COMPANY_NAME "Many Arrows Music")

# Change me! Used for the MacOS bundle identifier (and signing)
set(BUNDLE_ID "com.manyarrowsmusic.bitklavier5")

# Change me! Set the plugin formats you want built
# Valid choices: AAX Unity VST VST3 AU AUv3 Standalone
set(FORMATS Standalone AU VST3 AUv3)

# For simplicity, the name of the CMake project is also the name of the target
project(${PROJECT_NAME} VERSION ${CURRENT_VERSION})

# JUCE is setup as a submodule in the /JUCE folder
# Locally, you must run `git submodule update --init --recursive` once
# and later `git submodule update --remote --merge` to keep it up to date
# On Github Actions, this is done as a part of actions/checkout

# Apply local patches to JUCE that haven't been accepted upstream yet.
# Each patch is idempotent: if already applied, it is skipped silently.
#
# CI note: GitHub Actions may flag the JUCE submodule directory as an "unsafe
# repository" (git safe.directory) because the runner user doesn't own it.
# We add it to safe.directory so git apply works inside the submodule.
if(APPLE)
    execute_process(
        COMMAND git config --global --add safe.directory "${CMAKE_CURRENT_SOURCE_DIR}/JUCE"
        RESULT_VARIABLE SAFE_DIR_RESULT
        OUTPUT_QUIET
        ERROR_QUIET
    )

    set(JUCE_PATCHES
        juce_opengl_mac_mouse_forwarding  # forward mouseDown/Up/Dragged + rightMouseDragged through NSOpenGLView
    )
    foreach(PATCH_NAME ${JUCE_PATCHES})
        set(PATCH_FILE "${CMAKE_CURRENT_SOURCE_DIR}/patches/${PATCH_NAME}.patch")
        execute_process(
            COMMAND git apply --check "${PATCH_FILE}"
            WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/JUCE"
            RESULT_VARIABLE PATCH_CHECK_RESULT
            OUTPUT_QUIET
            ERROR_VARIABLE PATCH_CHECK_ERROR
        )
        if(PATCH_CHECK_RESULT EQUAL 0)
            message(STATUS "Applying JUCE patch: ${PATCH_NAME}")
            execute_process(
                COMMAND git apply "${PATCH_FILE}"
                WORKING_DIRECTORY "${CMAKE_CURRENT_SOURCE_DIR}/JUCE"
                RESULT_VARIABLE PATCH_RESULT
                ERROR_VARIABLE PATCH_ERROR
            )
            if(NOT PATCH_RESULT EQUAL 0)
                message(FATAL_ERROR "Failed to apply JUCE patch '${PATCH_NAME}': ${PATCH_ERROR}")
            endif()
        else()
            if(PATCH_CHECK_ERROR MATCHES "unsafe repository|dubious ownership")
                message(FATAL_ERROR "git safe.directory not set for JUCE submodule — patch '${PATCH_NAME}' could not be checked: ${PATCH_CHECK_ERROR}")
            else()
                message(STATUS "JUCE patch already applied (skipping): ${PATCH_NAME}")
            endif()
        endif()
    endforeach()
endif()

add_subdirectory(JUCE)
# Link to any other modules you added (with juce_add_module) here!
message("-- Adding juce modules")
juce_add_module(modules/melatonin_audio_sparklines)

# Add any other modules you want modules here, before the juce_add_plugin call
message("-- Adding external libraries" )
add_subdirectory(third_party/chowdsp_utils)
add_subdirectory(third_party/sfzq/SFZPlayer)
add_subdirectory(third_party/freeverb3)
add_subdirectory(third_party/chuck-cmake)

# Add CLAP format

# Add any other modules you want modules here, before the juce_add_plugin call
# juce_add_module(modules/my_module)

# This adds the melatonin inspector module

# See `docs/CMake API.md` in the JUCE repo for all config options
juce_add_plugin("${PROJECT_NAME}"
    # Icons for the standalone app
    ICON_BIG "${CMAKE_CURRENT_SOURCE_DIR}/packaging/icon.png"

    # Change me!
    COMPANY_NAME "${COMPANY_NAME}"
    BUNDLE_ID "${BUNDLE_ID}"

    # On MacOS, plugin is copied to /Users/yourname/Library/Audio/Plug-Ins/
    COPY_PLUGIN_AFTER_BUILD TRUE

    # Change me!
    # A four-character plugin id
    # First character MUST be uppercase for AU formats
    PLUGIN_MANUFACTURER_CODE Bitk

    # Change me!
    # A unique four-character plugin id
    # Note: this must have at least one upper-case character
    PLUGIN_CODE B202
    FORMATS "${FORMATS}"
    IS_SYNTH TRUE
    NEEDS_MIDI_INPUT TRUE
    NEEDS_WEB_BROWSER TRUE

    # The name of your final executable
    # This is how it's listed in the DAW
    # This can be different from PROJECT_NAME and can have spaces!
    # You might want to use v${MAJOR_VERSION} here once you go to v2...
    PRODUCT_NAME "${PRODUCT_NAME}"
    PLIST_TO_MERGE "<plist version=\"1.0\"><dict><key>NSBluetoothAlwaysUsageDescription</key><string>bitKlavier uses Bluetooth to connect to Bluetooth MIDI devices.</string></dict></plist>")

target_link_libraries("${PROJECT_NAME}" PRIVATE
        "-framework MetalPerformanceShaders"
        "-framework MetalPerformanceShadersGraph"
        "-framework Metal"
)

# This lets us use our code in both the JUCE targets and our Test target
# Without running into ODR violations
add_library(SharedCode INTERFACE
        source/interface/components/peak_meter_viewer.h
)
# Enable fast math, C++20 and a few other target defaults
include(SharedCodeDefaults)


# Manually list all .h and .cpp files for the plugin
# If you are like me, you'll use globs for your sanity.
# Just ensure you employ CONFIGURE_DEPENDS so the build system picks up changes
# If you want to appease the CMake gods and avoid globs, manually add files like so:
# set(SourceFiles Source/PluginEditor.h Source/PluginProcessor.h Source/PluginEditor.cpp Source/PluginProcessor.cpp)
file(GLOB_RECURSE SourceFiles CONFIGURE_DEPENDS "${CMAKE_CURRENT_SOURCE_DIR}/source/*.cpp" "${CMAKE_CURRENT_SOURCE_DIR}/source/*.h")
target_sources(SharedCode INTERFACE ${SourceFiles})
# --- Fail build if juce::ParameterID strings contain '_' (Ninja-safe) ---

# Write list of files at *configure time* (avoid huge fragile Ninja command lines)
set(PARAMID_SCAN_LIST "${CMAKE_BINARY_DIR}/check_parameterid_files.txt")
file(WRITE "${PARAMID_SCAN_LIST}" "")
foreach(f IN LISTS SourceFiles)
    file(APPEND "${PARAMID_SCAN_LIST}" "${f}\n")
endforeach()

add_custom_target(check_parameterid_underscores ALL
        COMMAND ${CMAKE_COMMAND}
        -DINPUT_LIST_FILE=${PARAMID_SCAN_LIST}
        -P ${CMAKE_CURRENT_SOURCE_DIR}/mycmake/CheckParameterIDUnderscores.cmake
        VERBATIM
)

message(STATUS "ParameterID underscore scan list: ${PARAMID_SCAN_LIST}")

if(NOT EXISTS "${PARAMID_SCAN_LIST}")
    message(FATAL_ERROR "Failed to create scan list at configure time: ${PARAMID_SCAN_LIST}")
endif()

add_dependencies("${PROJECT_NAME}" check_parameterid_underscores)

# Adds a BinaryData target for embedding assets into the binary
include(Assets)
include(cmake/Sanitizers.cmake)
# MacOS only: Cleans up folder and target organization on Xcode.
include(XcodePrettify)
target_include_directories(SharedCode
    INTERFACE
        source/common

        #synthesis includes
        source/synthesis/sound_engine
        source/synthesis
        source/synthesis/framework
        source/synthesis/framework/Processors
        source/synthesis/framework/MTSESP
        source/synthesis/framework/ParamHolders
        source/synthesis/framework/Synthesiser
        #interface includes
        source/interface
        source/interface/components
        source/interface/components/BKitems
        source/interface/components/Cable
        source/interface/components/opengl
        source/interface/sections
        source/interface/Preparations
        source/interface/ParameterView
        source/interface/look_and_feel
        source/modulation
)

#we were previously linking against the entire tracktion_engine and including
# via git submodules. this was a source of many headaches and we only use a small
# portion of the library so i moved the useful files here and removed the submodule
target_include_directories(SharedCode
        INTERFACE
        third_party/tracktion_engine)


# This is where you can set preprocessor definitions for JUCE and your plugin
target_compile_definitions(SharedCode
    INTERFACE
        #JUCE_USE_CUSTOM_PLUGIN_STANDALONE_ENTRYPOINT=1

        JUCE_USE_CUSTOM_PLUGIN_STANDALONE_APP=1
    # JUCE_WEB_BROWSER and JUCE_USE_CURL off by default
    JUCE_WEB_BROWSER=1  # If you set this to 1, add `NEEDS_WEB_BROWSER TRUE` to the `juce_add_plugin` call
    JUCE_USE_CURL=0     # If you set this to 1, add `NEEDS_CURL TRUE` to the `juce_add_plugin` call
    JUCE_VST3_CAN_REPLACE_VST2=0
        JUCE_OPENGL3=1
        JUCE_PLUGINHOST_VST3=1
    # Uncomment if you are paying for a an Indie/Pro license or releasing under GPLv3
    # JUCE_DISPLAY_SPLASH_SCREEN=0
        #JUCE_ENABLE_REPAINT_DEBUGGING=1
    # lets the app known if we're Debug or Release
    CMAKE_BUILD_TYPE="${CMAKE_BUILD_TYPE}"
    VERSION="${CURRENT_VERSION}"
        JUCE_MODULE_AVAILABLE_chowdsp_plugin_state=1
    # JucePlugin_Name is for some reason doesn't use the nicer PRODUCT_NAME
    PRODUCT_NAME_WITHOUT_VERSION="bitKlavier5"
)


# ─────────────────────────────────────────────────────────────────────────────
# MTS-ESP master / publishing support (optional, default ON)
#
# Vendored ODDSound SDK lives in third_party/MTS-ESP/. The SDK wrapper
# (libMTSMaster.cpp) dynamically loads the runtime libMTS library at startup and
# degrades to no-ops if it isn't installed, so enabling this is safe even on
# machines without MTS-ESP installed. When OFF, the SDK is not compiled and all
# bitKlavier MTS code (added in later stages) compiles to no-ops.
#
# See docs/MTS-ESP-Master-Spec.md for the full design.
# ─────────────────────────────────────────────────────────────────────────────
option(BITKLAVIER_ENABLE_MTS_ESP "Build with MTS-ESP master/publishing support" ON)

if(BITKLAVIER_ENABLE_MTS_ESP)
    message(STATUS "MTS-ESP master support: ENABLED")
    target_sources(SharedCode INTERFACE
        "${CMAKE_CURRENT_SOURCE_DIR}/third_party/MTS-ESP/libMTSMaster.cpp"
        "${CMAKE_CURRENT_SOURCE_DIR}/third_party/MTS-ESP/libMTSClient.cpp")
    target_include_directories(SharedCode INTERFACE
        "${CMAKE_CURRENT_SOURCE_DIR}/third_party/MTS-ESP")
    target_compile_definitions(SharedCode INTERFACE BITKLAVIER_ENABLE_MTS_ESP=1)
else()
    message(STATUS "MTS-ESP master support: DISABLED")
    target_compile_definitions(SharedCode INTERFACE BITKLAVIER_ENABLE_MTS_ESP=0)
endif()


# Link to any other modules you added (with juce_add_module) here!
# Usually JUCE modules must have PRIVATE visibility
# See https://github.com/juce-framework/JUCE/blob/master/docs/CMake%20API.md#juce_add_module
# However, with bitKlavier2, you'll link modules to SharedCode with INTERFACE visibility
# This allows the JUCE plugin targets and the Tests target to link against it
set(CHOWDSP_DEPENDENCIES
        chowdsp::chowdsp_plugin_base
        chowdsp::chowdsp_plugin_state
        chowdsp::chowdsp_sources
        chowdsp::chowdsp_plugin_utils
        chowdsp::chowdsp_gui
        chowdsp::chowdsp_dsp_utils
        chowdsp::chowdsp_data_structures
)
set(JUCE_DEPENDENCIES
        juce_audio_utils
        juce_audio_processors
        juce_dsp
        juce_gui_basics
        juce_gui_extra
        juce_opengl
        juce::juce_recommended_config_flags
        juce::juce_recommended_warning_flags

)
# Enable LTO only for Release-ish builds
if (CMAKE_BUILD_TYPE MATCHES "Release|RelWithDebInfo|MinSizeRel")
    list(APPEND JUCE_DEPENDENCIES juce::juce_recommended_lto_flags)
endif()
set(CMAKE_INTERPROCEDURAL_OPTIMIZATION_DEBUG OFF)
if (CMAKE_BUILD_TYPE STREQUAL "Debug")
    add_compile_options(-O0 -g3 -fno-omit-frame-pointer)
    add_link_options(-O0)
endif()
target_link_libraries(SharedCode
    INTERFACE
    Assets

    ${CHOWDSP_DEPENDENCIES}
    ${JUCE_DEPENDENCIES}
    melatonin_audio_sparklines
    sfzero
    freeverb3
    chuck
)


## --- Fail build if juce::ParameterID strings contain '_' ---
#set(PARAMID_UNDERSCORE_REGEX
#        "juce::ParameterID[ \t\r\n]*\\{[ \t\r\n]*\"[^\"]*_[^\"]*\"[ \t\r\n]*,[ \t\r\n]*[0-9]+[ \t\r\n]*\\}"
#)
#
#add_custom_target(check_parameterid_underscores ALL
#        COMMAND ${CMAKE_COMMAND}
#        -DINPUT_FILES="$<JOIN:${SourceFiles},;>"
#        -DREGEX="${PARAMID_UNDERSCORE_REGEX}"
#        -P "${CMAKE_CURRENT_SOURCE_DIR}/cmake/CheckParameterIDUnderscores.cmake"
#        VERBATIM
#)
#
## Ensure it runs before the plugin target builds
#add_dependencies("${PROJECT_NAME}" check_parameterid_underscores)
include (FetchContent)

#FetchContent_Declare (melatonin_perfetto
#        GIT_REPOSITORY https://github.com/sudara/melatonin_perfetto.git
#        GIT_TAG origin/main)
#
#FetchContent_MakeAvailable (melatonin_perfetto)
# Link the JUCE plugin targets our SharedCode target
target_link_libraries("${PROJECT_NAME}" PRIVATE
        SharedCode

        #Melatonin::Perfetto
)

#add_custom_target(graphviz ALL
#        COMMAND ${CMAKE_COMMAND} "--graphviz=foo.dot" .
#        COMMAND dot -Tpng foo.dot -o foo.png
#        WORKING_DIRECTORY "${CMAKE_BINARY_DIR}"
#)
# IPP support, comment out to disable
include(PamplejuceIPP)

## Everything related to the tests target
include(Tests)
#
# A separate target keeps the Tests target fast!
include(Benchmarks)
#
# Pass some config to GA (like our PRODUCT_NAME)
include(GitHubENV)
if(APPLE)
    set(STANDALONE_TGT "${PROJECT_NAME}_Standalone")

    if(TARGET "${STANDALONE_TGT}")
        add_custom_command(TARGET "${STANDALONE_TGT}" POST_BUILD
                COMMAND /bin/sh -c
                "if [ \"$<CONFIG>\" = \"Debug\" ]; then \
           build_dir=\"$<TARGET_PROPERTY:${STANDALONE_TGT},BINARY_DIR>\"; \
           artefacts_dir=\"$build_dir/${PROJECT_NAME}_artefacts\"; \
           juce_codegen_dir=\"$artefacts_dir/JuceLibraryCode\"; \
           if [ ! -d \"$juce_codegen_dir\" ]; then \
             echo \"ERROR: JUCE codegen dir not found: $juce_codegen_dir\"; \
             exit 1; \
           fi; \
           base_ent=\"$(find \"$juce_codegen_dir\" -maxdepth 2 -name '*Standalone*.entitlements' -print -quit)\"; \
           if [ -z \"$base_ent\" ]; then \
             echo \"ERROR: Could not find Standalone entitlements under $juce_codegen_dir\"; \
             echo \"       Available entitlements:\"; \
             find \"$juce_codegen_dir\" -maxdepth 2 -name '*.entitlements' -print || true; \
             exit 1; \
           fi; \
           debug_ent=\"$artefacts_dir/JuceLibraryCode/${STANDALONE_TGT}.debug.entitlements\"; \
           cp \"$base_ent\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.get-task-allow bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.get-task-allow true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.disable-library-validation bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.disable-library-validation true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.allow-unsigned-executable-memory bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.allow-unsigned-executable-memory true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.allow-jit bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.allow-jit true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.allow-dyld-environment-variables bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.allow-dyld-environment-variables true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.network.client bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.network.client true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.network.server bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.network.server true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.device.audio-input bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.device.audio-input true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.device.usb bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.device.usb true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.device.bluetooth bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.device.bluetooth true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.personal-information.location bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.personal-information.location true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.automation.apple-events bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.automation.apple-events true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.disable-executable-page-protection bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.disable-executable-page-protection true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.cs.debugger bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.cs.debugger true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.files.user-selected.read-write bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.files.user-selected.read-write true\" \"$debug_ent\"; \
           /usr/libexec/PlistBuddy -c \"Add :com.apple.security.files.downloads.read-write bool true\" \"$debug_ent\" 2>/dev/null || \
           /usr/libexec/PlistBuddy -c \"Set :com.apple.security.files.downloads.read-write true\" \"$debug_ent\"; \
           /usr/bin/codesign --force --deep --sign - \
             --entitlements \"$debug_ent\" \
             \"$<TARGET_BUNDLE_DIR:${STANDALONE_TGT}>\"; \
           echo \"Ad-hoc signed $<TARGET_BUNDLE_DIR:${STANDALONE_TGT}> with entitlements:\"; \
           codesign -d --entitlements - \"$<TARGET_BUNDLE_DIR:${STANDALONE_TGT}>\"; \
         fi"
                VERBATIM
                COMMENT "Debug ad-hoc codesign ${STANDALONE_TGT} with JUCE entitlements + get-task-allow"
        )
    endif()
endif()
