# =============================================================================
# Map Overlay DLL Build System
# =============================================================================
# Each map overlay is compiled as a shared library (.dll/.so) that exports
# its g_MapOverlayHeader_<name> symbol. This mirrors PSX behavior where
# each map is a separate overlay loaded at runtime.
#
# Maps can also be distributed as standalone DLLs for mod support.
# =============================================================================

set(DECOMP_DIR "${CMAKE_SOURCE_DIR}/..")
set(MAP_SRC_DIR "${DECOMP_DIR}/src/maps")

# Include paths (same as main executable)
set(MAP_INCLUDE_DIRS
    "${CMAKE_SOURCE_DIR}/include"
    "${CMAKE_SOURCE_DIR}/include/psyq_compat"
    "${CMAKE_SOURCE_DIR}/src"
    "${DECOMP_DIR}/include"
    "${DECOMP_DIR}/include/decomp"
    "${CMAKE_SOURCE_DIR}/PsyCross/include/psx"
    "${CMAKE_SOURCE_DIR}/PsyCross/include"
)

# Common compile definitions for all map overlays
set(MAP_COMMON_DEFS
    VER_USA
    SH_PC_PORT
    SKIP_ASM
)
if(SH_STATIC_MAPS)
    # Statically linked overlay (iOS and anything else that cannot dlopen).
    list(APPEND MAP_COMMON_DEFS SH_MAP_STATIC)
else()
    list(APPEND MAP_COMMON_DEFS SH_MAP_DLL)
endif()

# =============================================================================
# Function to create a map overlay DLL target
# =============================================================================
function(add_map_overlay MAP_NAME MAP_DEFINE)
    set(MAP_DIR "${MAP_SRC_DIR}/${MAP_NAME}")

    # Skip if directory doesn't exist
    if(NOT EXISTS "${MAP_DIR}")
        message(STATUS "Map ${MAP_NAME}: source directory not found, skipping")
        return()
    endif()

    # Collect all .c files in the map directory
    file(GLOB MAP_SOURCES "${MAP_DIR}/*.c")
    if(NOT MAP_SOURCES)
        message(STATUS "Map ${MAP_NAME}: no source files, skipping")
        return()
    endif()

    # If the map has extracted-data file (cutscene voice cmds, timers,
    # camera waypoints from VIN/MAP*.BIN), include it. The file is
    # generated by pc_port/tools/extract_map_data.py.
    set(EXTRACTED_DATA "${CMAKE_SOURCE_DIR}/build_gen/extracted_data/${MAP_NAME}_extracted_data.c")
    if(EXISTS "${EXTRACTED_DATA}")
        list(APPEND MAP_SOURCES "${EXTRACTED_DATA}")
    endif()

    # Create the overlay target. Static overlays are archives that get symbol
    # prefixed below and force-loaded into the exe; DLL overlays are dlopen'd.
    if(SH_STATIC_MAPS)
        add_library(${MAP_NAME} STATIC ${MAP_SOURCES})
    else()
        add_library(${MAP_NAME} SHARED ${MAP_SOURCES})
    endif()

    target_compile_definitions(${MAP_NAME} PRIVATE
        ${MAP_COMMON_DEFS}
        ${MAP_DEFINE}
        SH_MAP_NAME=${MAP_NAME}
        "static_assert=_Static_assert"
    )

    target_include_directories(${MAP_NAME} PRIVATE ${MAP_INCLUDE_DIRS})

    # Force-include the DLL fixup header (provides <stdio.h> for PC debug
    # logging in shared map headers).
    target_compile_options(${MAP_NAME} PRIVATE
        -include "${CMAKE_SOURCE_DIR}/include/map_dll_fixups.h"
    )

    # Same warning suppressions as the main exe (../CMakeLists.txt). The decomp
    # has many un-finalized signatures (e.g. q3_12* vs q19_12*, void(*)() vs
    # typed fn ptrs); GCC 14 promotes -Wincompatible-pointer-types and friends
    # to hard errors by default, which breaks the map DLL build even though the
    # exe (which carries these flags) compiles the same sources fine. Without
    # this, no map except the built-in map0_s00 builds, so the first map
    # transition crashes on a NULL overlay header. No-op on GCC 13 (warnings).
    if(NOT MSVC)
        target_compile_options(${MAP_NAME} PRIVATE
            -Wno-incompatible-pointer-types
            -Wno-discarded-qualifiers
            -Wno-builtin-declaration-mismatch
            -Wno-int-to-pointer-cast
            -Wno-pointer-to-int-cast
            -Wno-sign-compare
            -Wno-missing-braces
            -Wno-unused-variable
            -Wno-unused-function
        )
    endif()

    # GCC 14 promoted these C warnings to default errors; the Linux map-DLL
    # build (GCC 14) needs them demoted like the exe (pc_port/CMakeLists.txt).
    # GCC 13 (Windows/MinGW) errors on -Wno-error=<unknown-flag>, and two of
    # these are GCC-14-only, so gate the block on GCC >= 14 (no-op on the
    # Windows toolchain — the -Wno- forms above already silence them there).
    if(CMAKE_C_COMPILER_ID STREQUAL "GNU" AND CMAKE_C_COMPILER_VERSION VERSION_GREATER_EQUAL 14)
        target_compile_options(${MAP_NAME} PRIVATE
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=implicit-function-declaration>
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=implicit-int>
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=int-conversion>
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=incompatible-pointer-types>
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=declaration-missing-parameter-type>
            $<$<COMPILE_LANGUAGE:C>:-Wno-error=return-mismatch>
        )
    endif()

    if(SH_STATIC_MAPS)
        # Every overlay carries its own copy of the shared AI/particle/player
        # code it #includes, so linking all 43 into one binary collides on 500+
        # globals. Rename this overlay's DEFINED symbols to <map>_<sym> right
        # after the archive is built; undefined references are untouched, so map
        # code still binds to the single engine copy. This is PSX overlay
        # semantics — a private copy of the shared code per overlay — expressed
        # at link time instead of by swapping a module in and out.
        add_custom_command(TARGET ${MAP_NAME} POST_BUILD
            COMMAND ${Python3_EXECUTABLE}
                    "${CMAKE_SOURCE_DIR}/tools/prefix_map_syms.py"
                    --nm "${SH_LLVM_NM}"
                    --objcopy "${SH_LLVM_OBJCOPY}"
                    --archive "$<TARGET_FILE:${MAP_NAME}>"
                    --map-name "${MAP_NAME}"
                    --sym-prefix "${SH_SYM_PREFIX}"
            COMMENT "Prefixing ${MAP_NAME} overlay symbols"
            VERBATIM
        )
        # The exe force-loads these (see ../CMakeLists.txt): a map's event
        # tables and constructor-patched anim_info objects are reached only
        # through the header's pointers, so lazy archive semantics would drop
        # them.
        set_property(GLOBAL APPEND PROPERTY SH_STATIC_MAP_TARGETS ${MAP_NAME})
        message(STATUS "Map ${MAP_NAME}: static overlay target created")
    elseif(WIN32)
        # Link only against the main executable's import library. The executable
        # exports both game code and the one initialized PsyCross instance.
        # Linking psycross_static here as well creates a second copy of PsyCross
        # in every map DLL and, for the modern-mesh renderer, also pulls render
        # objects whose Pc_Modern* callbacks cannot resolve from the archive.
        # The import library already exports those callbacks, so it is both the
        # complete link surface and the required single-instance architecture.
        target_link_libraries(${MAP_NAME} PRIVATE SilentHillPC)

        target_link_options(${MAP_NAME} PRIVATE
            # WINDOWS_EXPORT_ALL_SYMBOLS (set below) is *supposed* to emit a
            # generated .def listing every global, but on this CMake/MinGW
            # combo it silently does nothing for the map DLLs — the resulting
            # build.ninja link rule has neither `--export-all-symbols` nor a
            # generated def file, and DllLoader_GetSymbol fails with
            # "GetProcAddress error 127" looking up g_MapOverlayHeader_<name>
            # at runtime. Falling back to the global ld flag bypasses CMake's
            # auto-export machinery and exports every TU-global in the DLL.
            # Same flag the main exe uses; identical behavior to the old
            # working state. */
            "LINKER:--export-all-symbols"
        )
    else()
        # Linux/macOS: a shared object cannot link against an executable, so
        # we do NOT link SilentHillPC here. Game-code symbols (g_SysWork, the
        # decompiled functions, etc.) are left undefined in the .so and
        # resolved at dlopen() time from the main executable, which is built
        # with ENABLE_EXPORTS (adds -rdynamic so its symbols populate the
        # global dynamic symbol table). ELF shared objects export all global
        # symbols by default, so g_MapOverlayHeader_<name> is visible to dlsym
        # without extra flags.
        # Build ordering only (the exe must exist to dlopen the map); there is
        # no link-time dependency on the executable.
        add_dependencies(${MAP_NAME} SilentHillPC)
        if(APPLE)
            # ld64 (unlike GNU ld) errors on undefined symbols at link time.
            # Game-code symbols are resolved from the main exe at dlopen, so
            # allow them to stay undefined in the dylib. (macOS two-level
            # namespaces already bind each dylib's OWN defined symbols locally,
            # so the ELF interposition problem handled below does not arise.)
            # PsyCross is linked statically here (built PIC) because macOS is
            # untested for the shared-instance arrangement used on Linux below.
            target_link_libraries(${MAP_NAME} PRIVATE psycross_static)
            target_link_options(${MAP_NAME} PRIVATE "-Wl,-undefined,dynamic_lookup")
        else()
            # Linux/ELF: do NOT statically link psycross_static into the map .so.
            #
            # PsyCross holds singleton runtime state — the glad GL function
            # pointer table, the GTE register bank (gteRegs), the split/vertex
            # buffers (g_splitIndex, g_vertexIndex), activeDrawEnv/DispEnv and
            # drawsync_callback. That state is initialized ONCE, in the copy of
            # PsyCross linked into the main exe (which loads GL, populates glad,
            # etc.). If each map .so links its OWN psycross_static copy, the .so
            # gets a SECOND, uninitialized instance of all that state; combined
            # with -Bsymbolic (below) the map's PsyCross calls bind to that dead
            # local copy. Map code that drives rendering itself then operates on
            # a NULL glad table and a desynced GTE/vertex bank — e.g.
            # MapEvent_MapItemTake -> DrawSync -> GL via NULL pointer (segfault
            # when picking up cafe items), and map-emitted geometry transformed
            # through the wrong gteRegs (the persistent additive screen-garbage).
            # Leaving PsyCross undefined here makes every PsyCross symbol resolve
            # at dlopen to the exe's single, initialized instance. (The exe links
            # psycross_static with --whole-archive so -rdynamic exports the full
            # public PsyCross API — see ../CMakeLists.txt.)
            #
            # -Bsymbolic: bind THIS .so's own global definitions to its local
            # copies instead of letting them be interposed by the main exe.
            # map0_s00 is compiled INTO the exe, and -rdynamic publishes its
            # globals (MAP_EVENTS, MAP_POINTS, the per-map func_800D* code,
            # extracted-data tables) into the global dynamic scope. Every other
            # map defines those SAME names; under ELF's default global-scope
            # binding, map0_s01.so's `MAP_EVENTS` (and its overlay header's
            # .mapEvents pointer) resolve to map0_s00's copy at dlopen — so the
            # cafe (map0_s01) iterated the ALLEY's event table and re-fired its
            # TriggerType_None "load cafe overlay" event every frame, reloading
            # forever. Windows PE binds per-module, so this only bites ELF.
            # -Bsymbolic restores per-module binding for symbols the .so
            # defines; undefined symbols (game state AND PsyCross) are
            # unaffected and still import from the exe. dlsym of
            # g_MapOverlayHeader_<name> still works.
            target_link_options(${MAP_NAME} PRIVATE "-Wl,-Bsymbolic")
        endif()
    endif()

    if(NOT SH_STATIC_MAPS)
    set_target_properties(${MAP_NAME} PROPERTIES
        PREFIX ""                        # No "lib" prefix
        OUTPUT_NAME "${MAP_NAME}"        # maps/map0_s01.dll | maps/map0_s01.so
        RUNTIME_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/maps"
        LIBRARY_OUTPUT_DIRECTORY "${CMAKE_BINARY_DIR}/maps"
    )
    endif()
    if(WIN32 AND NOT SH_STATIC_MAPS)
        # Export the g_MapOverlayHeader_<name> symbol from the DLL.
        # WINDOWS_EXPORT_ALL_SYMBOLS would auto-generate a .def file on
        # MinGW/MSVC in theory; in practice we use --export-all-symbols
        # above (see comment) since the auto-def path silently no-ops here.
        # (No-op on ELF, where shared objects export globals by default.)
        set_target_properties(${MAP_NAME} PROPERTIES WINDOWS_EXPORT_ALL_SYMBOLS ON)
    endif()

    if(NOT SH_STATIC_MAPS)
        message(STATUS "Map ${MAP_NAME}: DLL target created")
    endif()
endfunction()

# =============================================================================
# Register all map overlays
# =============================================================================
# map0_s00 is compiled into the main executable, not as a DLL.
# All other maps are built as DLLs.
#
# Issue 1 (non-constant initializer with func_800706E4): FIXED.
# The 7 affected anim_info.c files now use a #define-shim + constructor
# patch under SH_PC_PORT to bind func_800706E4 at DLL load time.
# Maps re-enabled: map2_s00, map2_s02, map4_s02, map5_s01, map6_s00,
# map6_s02, map6_s04.
#
# Issue 2 (cross-map shared data sharedData_800CB094_3_s01): FIXED.
# Symbol now defined once in pc_port/src/stubs/data_stubs.c and exported
# from main exe; map7_s01/02 local definitions are #ifndef'd out for PC.
# Maps re-enabled: map3_s01, map3_s03, map3_s04, map3_s05.
#
# All 42 maps now build as DLLs.

# Global chara pool pseudo-map (docs/Global_Chara_Pool.md): every portable
# monster's AI compiled with NO map define (generic variants). Loaded once at
# boot by pc_chara_pool.c with its own handle; never routed through
# MapOverlay_Load.
add_map_overlay(chara_global CHARA_GLOBAL)

add_map_overlay(map0_s01 MAP0_S01)
add_map_overlay(map0_s02 MAP0_S02)
add_map_overlay(map1_s00 MAP1_S00)
add_map_overlay(map1_s01 MAP1_S01)
add_map_overlay(map1_s02 MAP1_S02)
add_map_overlay(map1_s03 MAP1_S03)
add_map_overlay(map1_s04 MAP1_S04)
add_map_overlay(map1_s05 MAP1_S05)
add_map_overlay(map1_s06 MAP1_S06)
add_map_overlay(map2_s00 MAP2_S00)
add_map_overlay(map2_s01 MAP2_S01)
add_map_overlay(map2_s02 MAP2_S02)
add_map_overlay(map2_s03 MAP2_S03)
add_map_overlay(map2_s04 MAP2_S04)
add_map_overlay(map3_s00 MAP3_S00)
add_map_overlay(map3_s01 MAP3_S01)
add_map_overlay(map3_s02 MAP3_S02)
add_map_overlay(map3_s03 MAP3_S03)
add_map_overlay(map3_s04 MAP3_S04)
add_map_overlay(map3_s05 MAP3_S05)
add_map_overlay(map3_s06 MAP3_S06)
add_map_overlay(map4_s00 MAP4_S00)
add_map_overlay(map4_s01 MAP4_S01)
add_map_overlay(map4_s02 MAP4_S02)
add_map_overlay(map4_s03 MAP4_S03)
add_map_overlay(map4_s04 MAP4_S04)
add_map_overlay(map4_s05 MAP4_S05)
add_map_overlay(map4_s06 MAP4_S06)
add_map_overlay(map5_s00 MAP5_S00)
add_map_overlay(map5_s01 MAP5_S01)
add_map_overlay(map5_s02 MAP5_S02)
add_map_overlay(map5_s03 MAP5_S03)
add_map_overlay(map6_s00 MAP6_S00)
add_map_overlay(map6_s01 MAP6_S01)
add_map_overlay(map6_s02 MAP6_S02)
add_map_overlay(map6_s03 MAP6_S03)
add_map_overlay(map6_s04 MAP6_S04)
add_map_overlay(map6_s05 MAP6_S05)
add_map_overlay(map7_s00 MAP7_S00)
add_map_overlay(map7_s01 MAP7_S01)
add_map_overlay(map7_s02 MAP7_S02)
add_map_overlay(map7_s03 MAP7_S03)
