cmake_minimum_required(VERSION 3.20)
project(ksOSKernel LANGUAGES ASM C)
set(KERNEL_MODULE_NAME "Kernel")

# Where to emit compile_commands.json for SourceKit-LSP. Empty: Kernel/
# (ancestor of SWIFT_SOURCES). Override: -DKERNEL_COMPILE_COMMANDS_DIR=/path
# or env KERNEL_COMPILE_COMMANDS_DIR.
set(KERNEL_COMPILE_COMMANDS_DIR "" CACHE PATH
    "Directory for Swift compile_commands.json (SourceKit-LSP)")

# --- Locate Swift toolchain with Embedded Swift stdlib ---
# Priority: cmake var > env var > auto-detect
if(NOT SWIFT_TOOLCHAIN AND DEFINED ENV{SWIFT_TOOLCHAIN})
    set(SWIFT_TOOLCHAIN "$ENV{SWIFT_TOOLCHAIN}")
endif()

if(SWIFT_TOOLCHAIN)
    set(SWIFTC             "${SWIFT_TOOLCHAIN}/usr/bin/swiftc")
    set(SWIFT_RESOURCE_DIR "${SWIFT_TOOLCHAIN}/usr/lib/swift")
elseif(CMAKE_HOST_SYSTEM_NAME STREQUAL "Darwin")
    # Scan for a swift.org toolchain that ships the embedded stdlib.
    # APPLE is false here (target is Generic), so we check the HOST OS.
    file(GLOB _tc_candidates
        "$ENV{HOME}/Library/Developer/Toolchains/*.xctoolchain"
        "/Library/Developer/Toolchains/*.xctoolchain"
    )
    foreach(_tc ${_tc_candidates})
        if(EXISTS "${_tc}/usr/lib/swift/embedded")
            set(SWIFTC             "${_tc}/usr/bin/swiftc")
            set(SWIFT_RESOURCE_DIR "${_tc}/usr/lib/swift")
            break()
        endif()
    endforeach()
else()
    # Linux: find swiftc in PATH
    find_program(_SWIFTC_EXE swiftc)
    if(_SWIFTC_EXE)
        get_filename_component(_SWIFTC_BIN "${_SWIFTC_EXE}" DIRECTORY)
        get_filename_component(_SWIFTC_USR "${_SWIFTC_BIN}" DIRECTORY)
        
        if(EXISTS "${_SWIFTC_USR}/lib/swift/embedded")
            set(SWIFTC             "${_SWIFTC_EXE}")
            set(SWIFT_RESOURCE_DIR "${_SWIFTC_USR}/lib/swift")
        elseif(EXISTS "${_SWIFTC_USR}/lib/swift/lib/swift/embedded")
            set(SWIFTC             "${_SWIFTC_EXE}")
            set(SWIFT_RESOURCE_DIR "${_SWIFTC_USR}/lib/swift/lib/swift")
        endif()
    endif()
endif()

if(NOT SWIFTC OR NOT EXISTS "${SWIFT_RESOURCE_DIR}/embedded")
    message(FATAL_ERROR
        "Swift toolchain with Embedded Swift not found.\n"
        "Install a swift.org toolchain and pass -DSWIFT_TOOLCHAIN=<root> "
        "or set the SWIFT_TOOLCHAIN env var.")
endif()

message(STATUS "Swift: ${SWIFTC}")
message(STATUS "Swift resource dir: ${SWIFT_RESOURCE_DIR}")

# Hint for SourceKit: VSCode must use this toolchain (same as compile_commands),
# not Xcode's default; otherwise: "Loading the standard library failed" with Embedded.
get_filename_component(_SWIFT_IDE_PATH "${SWIFTC}" DIRECTORY)
set(_ide_hint
"# If SourceKit reports \"Loading the standard library failed\", set the Swift
# extension's swift.path to the directory on the line below and restart
# \"Swift: Restart SourceKit LSP\". (Embedded Swift — must match this toolchain, not Xcode.)
# Regenerated on each CMake configure.
")
string(APPEND _ide_hint "${_SWIFT_IDE_PATH}\n")
file(WRITE "${CMAKE_CURRENT_SOURCE_DIR}/../ide-swift-toolchain.txt" "${_ide_hint}")

# --- Build ---
add_compile_options(-ffreestanding -nostdlib -O0 -g)

set(LINKER_SCRIPT ${CMAKE_CURRENT_SOURCE_DIR}/linker.ld)
add_link_options(
    -fuse-ld=lld
    -nostdlib
    -static
    -Wl,-T,${LINKER_SCRIPT}
)

set(SWIFT_SOURCES 
    ${CMAKE_CURRENT_SOURCE_DIR}/Source/Kernel.swift
    ${CMAKE_CURRENT_SOURCE_DIR}/Source/Arch/DTB.swift
    ${CMAKE_CURRENT_SOURCE_DIR}/Source/IO/UART.swift
    ${CMAKE_CURRENT_SOURCE_DIR}/Source/Arch/ExceptionContext.swift
    ${CMAKE_CURRENT_SOURCE_DIR}/Source/OS/panic.swift # idfk why but sourcekit-lsp dont see Panic.swift but see panic.swift perfectly
)
set(SWIFT_OBJ     ${CMAKE_CURRENT_BINARY_DIR}/kernel_swift.o)

add_custom_command(
    OUTPUT ${SWIFT_OBJ}
    COMMAND ${SWIFTC}
        -target aarch64-none-none-elf
        -enable-experimental-feature Embedded
        -parse-as-library
        -wmo
        -O
        -Xcc -fno-stack-protector
        -Xcc -I${CMAKE_CURRENT_SOURCE_DIR}/../Common
        -import-bridging-header ${CMAKE_CURRENT_SOURCE_DIR}/Source/Support/BridgingHeader.h
        -resource-dir ${SWIFT_RESOURCE_DIR}
        -c ${SWIFT_SOURCES}
        -o ${SWIFT_OBJ}
    COMMAND ${LLVM_OBJCOPY} --remove-section=.swift_modhash ${SWIFT_OBJ}
    DEPENDS ${SWIFT_SOURCES}
    COMMENT "Compiling Swift kernel"
)

set_source_files_properties(${SWIFT_OBJ} PROPERTIES
    EXTERNAL_OBJECT TRUE
    GENERATED TRUE
)

add_executable(kernel.elf Source/Arch/entry.S Source/Support/stubs.c Source/Arch/vectors.S ${SWIFT_OBJ})

add_custom_command(TARGET kernel.elf POST_BUILD
    COMMAND ${LLVM_OBJCOPY} -O binary kernel.elf kernel.bin
)

# --- SourceKit-LSP: generate compile_commands.json for Swift (Dynamic) ---
set(_BRIDGING_HEADER "${CMAKE_CURRENT_SOURCE_DIR}/Source/Support/BridgingHeader.h")

set(SWIFT_ARGS 
    "\"${SWIFTC}\""
    "\"-target\"" "\"aarch64-none-none-elf\""
    "\"-enable-experimental-feature\"" "\"Embedded\""
    "\"-module-name\"" "\"${KERNEL_MODULE_NAME}\""
    "\"-parse-as-library\""
    "\"-wmo\""
    "\"-import-bridging-header\"" "\"${_BRIDGING_HEADER}\""
    "\"-Xcc\"" "\"-I${CMAKE_CURRENT_SOURCE_DIR}/../Common\""
    "\"-Xcc\"" "\"-fno-stack-protector\""
    "\"-resource-dir\"" "\"${SWIFT_RESOURCE_DIR}\""
)
foreach(_src IN LISTS SWIFT_SOURCES)
    list(APPEND SWIFT_ARGS "\"${_src}\"")
endforeach()

string(JOIN ", " SWIFT_ARGS_JSON ${SWIFT_ARGS})

set(COMPDB_ENTRIES "")
list(LENGTH SWIFT_SOURCES _src_count)
math(EXPR _last_idx "${_src_count} - 1")
set(_idx 0)

foreach(_src IN LISTS SWIFT_SOURCES)
    set(_entry "  {\n    \"file\": \"${_src}\",\n    \"directory\": \"${CMAKE_CURRENT_BINARY_DIR}\",\n    \"arguments\": [${SWIFT_ARGS_JSON}]\n  }")
    
    if(_idx LESS _last_idx)
        string(APPEND _entry ",\n")
    else()
        string(APPEND _entry "\n")
    endif()
    
    string(APPEND COMPDB_ENTRIES "${_entry}")
    math(EXPR _idx "${_idx} + 1")
endforeach()

# Precedence: CMake -D > env > Kernel/ (ancestor of all SWIFT_SOURCES).
if(NOT KERNEL_COMPILE_COMMANDS_DIR STREQUAL "")
    set(COMPDB_OUTPUT_DIR "${KERNEL_COMPILE_COMMANDS_DIR}")
elseif(DEFINED ENV{KERNEL_COMPILE_COMMANDS_DIR} AND NOT "$ENV{KERNEL_COMPILE_COMMANDS_DIR}" STREQUAL "")
    set(COMPDB_OUTPUT_DIR "$ENV{KERNEL_COMPILE_COMMANDS_DIR}")
else()
    set(COMPDB_OUTPUT_DIR "${CMAKE_CURRENT_SOURCE_DIR}")
endif()

# Same JSON twice: (1) optional Kernel/ or KERNEL_COMPILE_COMMANDS_DIR, (2) repo
# root. SourceKit/Cursor/VSCode Swift extension resolve compile_commands at the
# workspace root first; a DB only under Kernel/ often yields no language service
# and broken cross-file resolution (WMO) even though the build works.
set(KERNEL_COMPDB_CONTENT "[\n${COMPDB_ENTRIES}]\n")
file(GENERATE OUTPUT "${COMPDB_OUTPUT_DIR}/compile_commands.json"
    CONTENT "${KERNEL_COMPDB_CONTENT}"
)
set(_REPO_COMPDB "${CMAKE_CURRENT_SOURCE_DIR}/../compile_commands.json")
file(GENERATE OUTPUT "${_REPO_COMPDB}"
    CONTENT "${KERNEL_COMPDB_CONTENT}"
)