# Multi-Platform FTP Server - Makefile
# Supports: Linux, macOS, PS3, PS4, PS5
# Standards: MISRA C:2012, CERT C, ISO C11

# Project information
PROJECT := zftpd
VERSION := $(shell grep -E 'define[[:space:]]+RELEASE_VERSION' include/ftp/ftp_config.h | head -n 1 | cut -d'"' -f2)
ifeq ($(strip $(VERSION)),)
VERSION := 0.0.0
endif
ARTIFACT_PREFIX ?= zftpd
HOST_ARCH := $(shell uname -m)
ifeq ($(TARGET),macos)
PLATFORM_TAG := macos-$(HOST_ARCH)
else ifeq ($(TARGET),linux)
PLATFORM_TAG := linux-$(HOST_ARCH)
else
PLATFORM_TAG := $(TARGET)
endif

# Host OS detection (for toolchain/linker compatibility)
HOST_OS := $(shell uname -s)

# Target platform (default detection)
ifeq ($(HOST_OS),Darwin)
TARGET ?= macos
else
TARGET ?= linux
endif
# Normalize target to lowercase so TARGET=PS5/PS4 still matches rules
TARGET := $(shell echo $(TARGET) | tr '[:upper:]' '[:lower:]')

# Build configuration
BUILD_TYPE ?= release
# Valid values: debug, release

PS4_PAYLOAD_SDK ?= $(abspath external/ps4-payload-sdk)
PS4_HOST ?= ps4
PS4_PORT ?= 9021
PS5_PAYLOAD_SDK ?= $(abspath external/ps5-payload-sdk)
PS5_HOST ?= ps5
PS5_PORT ?= 9021

#============================================================================
# PLATFORM DETECTION AND CONFIGURATION
#============================================================================

# Compiler selection
ifeq ($(TARGET),ps3)
    CC := ppu-gcc
    PLATFORM_DEFS := -DPLATFORM_PS3 -DPS3
    PLATFORM_LIBS := -lnet
    PLATFORM_LDFLAGS := 
endif

ifeq ($(TARGET),ps4)
    ORBIS_LLVM_CONFIG := $(LLVM_CONFIG)
    ifeq ($(ORBIS_LLVM_CONFIG),)
        ORBIS_LLVM_CONFIG := $(shell command -v llvm-config-21 2>/dev/null || command -v llvm-config-20 2>/dev/null || command -v llvm-config-19 2>/dev/null || command -v llvm-config-18 2>/dev/null || command -v llvm-config-17 2>/dev/null || command -v llvm-config-16 2>/dev/null || command -v llvm-config-15 2>/dev/null || command -v llvm-config 2>/dev/null)
    endif
    ifeq ($(ORBIS_LLVM_CONFIG),)
        ORBIS_LLVM_CONFIG := $(shell if command -v brew >/dev/null 2>&1; then p=$$(brew --prefix llvm 2>/dev/null); if [ -x "$$p/bin/llvm-config" ]; then echo "$$p/bin/llvm-config"; fi; fi)
    endif
    ifeq ($(ORBIS_LLVM_CONFIG),)
        $(error llvm-config non trovato: installa LLVM (es. brew install llvm) e assicurati che llvm-config sia nel PATH oppure passa LLVM_CONFIG=/percorso/a/llvm-config)
    endif
    export LLVM_CONFIG := $(ORBIS_LLVM_CONFIG)
    include $(PS4_PAYLOAD_SDK)/toolchain/orbis.mk
    PLATFORM_DEFS := -DPLATFORM_PS4 -DPS4
    # SceSysmodule is loaded at runtime with dlopen: the payload SDK does not
    # ship a linkable library for it.
    PLATFORM_LIBS := -lkernel -lpthread -lSceSystemService -lSceUserService
    PLATFORM_LDFLAGS :=
endif

ifeq ($(TARGET),ps5)
    PROSPERO_LLVM_CONFIG := $(LLVM_CONFIG)
    ifeq ($(PROSPERO_LLVM_CONFIG),)
        PROSPERO_LLVM_CONFIG := $(shell command -v llvm-config-21 2>/dev/null || command -v llvm-config-20 2>/dev/null || command -v llvm-config-19 2>/dev/null || command -v llvm-config-18 2>/dev/null || command -v llvm-config-17 2>/dev/null || command -v llvm-config-16 2>/dev/null || command -v llvm-config-15 2>/dev/null || command -v llvm-config 2>/dev/null)
    endif
    ifeq ($(PROSPERO_LLVM_CONFIG),)
        PROSPERO_LLVM_CONFIG := $(shell if command -v brew >/dev/null 2>&1; then p=$$(brew --prefix llvm 2>/dev/null); if [ -x "$$p/bin/llvm-config" ]; then echo "$$p/bin/llvm-config"; fi; fi)
    endif
    ifeq ($(PROSPERO_LLVM_CONFIG),)
        $(error llvm-config non trovato: installa LLVM (es. brew install llvm) e assicurati che llvm-config sia nel PATH oppure passa LLVM_CONFIG=/percorso/a/llvm-config)
    endif
    export LLVM_CONFIG := $(PROSPERO_LLVM_CONFIG)
    include $(PS5_PAYLOAD_SDK)/toolchain/prospero.mk
    PLATFORM_DEFS := -DPLATFORM_PS5 -DPS5 -D__PROSPERO__
    PLATFORM_LIBS := -lkernel -lpthread -lSceNotification
    PLATFORM_LDFLAGS :=
endif

ifeq ($(TARGET),linux)
    CC := gcc
    PLATFORM_DEFS := -DPLATFORM_LINUX -D_GNU_SOURCE
    PLATFORM_LIBS := -lpthread
    ifeq ($(HOST_OS),Linux)
        PLATFORM_LDFLAGS := -Wl,-z,relro -Wl,-z,now
    else
        PLATFORM_LDFLAGS :=
    endif
endif

ifeq ($(TARGET),macos)
    CC := clang
    PLATFORM_DEFS := -DPLATFORM_MACOS -D_DARWIN_C_SOURCE
    PLATFORM_LIBS := -lpthread
    PLATFORM_LDFLAGS :=
endif

# Default to GCC if no target matched
CC ?= gcc
CXX ?= c++
PLATFORM_LIBS ?= -lpthread

# Event loop implementation — kqueue for BSD/macOS/PS4/PS5, epoll for Linux
ifeq ($(TARGET),linux)
    EVENT_LOOP_SRC := src/runtime/event_loop_epoll.c
else
    EVENT_LOOP_SRC := src/runtime/event_loop_kqueue.c
endif

#============================================================================
# COMPILER FLAGS (SAFETY-CRITICAL STANDARDS)
#============================================================================

# C Standard
CFLAGS := -std=c11

# Warning flags (comprehensive)
CFLAGS += -Wall -Wextra -Wpedantic
CFLAGS += -Wformat=2 -Wformat-security
CFLAGS += -Wnull-dereference
CFLAGS += -Wstack-protector
CFLAGS += -Wstrict-overflow=5
CFLAGS += -Warray-bounds
CFLAGS += -Wcast-align
CFLAGS += -Wcast-qual
CFLAGS += -Wconversion
CFLAGS += -Wsign-conversion
CFLAGS += -Wstrict-prototypes
CFLAGS += -Wmissing-prototypes
CFLAGS += -Wredundant-decls
CFLAGS += -Wshadow
CFLAGS += -Wundef
CFLAGS += -Wwrite-strings

# Treat warnings as errors in release builds
ifeq ($(BUILD_TYPE),release)
    CFLAGS += -Werror
endif

# Toolchain compatibility (Clang on macOS uses different warning set)
ifeq ($(HOST_OS),Darwin)
    CFLAGS += -Wno-unknown-warning-option
endif

# Security hardening flags
CFLAGS += -fstack-protector-strong
CFLAGS += -fPIE
CFLAGS += -fno-strict-aliasing

# _FORTIFY_SOURCE is glibc-specific; avoid redef warnings on non-Linux hosts
ifeq ($(HOST_OS),Linux)
    CFLAGS += -D_FORTIFY_SOURCE=2
endif

# Optimization and debug flags
ifeq ($(BUILD_TYPE),debug)
    CFLAGS += -O0 -g3 -DDEBUG -DFTP_DEBUG=1
    CFLAGS += -fsanitize=address,undefined
    LDFLAGS += -fsanitize=address,undefined
else
    CFLAGS += -O2 -g -DNDEBUG -DFTP_DEBUG=0
endif

# Platform-specific flags
CFLAGS += $(PLATFORM_DEFS)

# Avoid symbol interposition when injected into host processes (PS4/PS5 payload).
ifneq ($(filter $(TARGET),ps4 ps5),)
    CFLAGS += -fvisibility=hidden
endif

# PS5: disable aggressive function inlining.  Without this flag the compiler
# inlines every static api_* handler into http_api_handle, creating a massive
# stack frame (~60-80 KB) that overflows the PS5 thread stack (SIGSEGV).
ifeq ($(TARGET),ps5)
    CFLAGS += -fno-inline-functions
endif

# Include directories
CFLAGS += -I./include
CFLAGS += -I./include/ftp -I./include/http -I./include/platform
CFLAGS += -I./include/platform/ps5 -I./include/archive -I./include/runtime

#============================================================================
# LINKER FLAGS
#============================================================================

# Console payloads are loaded by loaders (GoldHEN binloader, elfldr) that
# expect a position-independent executable, no matter which host built them.
ifneq ($(filter $(TARGET),ps4 ps5),)
    LDFLAGS += -pie
else ifeq ($(HOST_OS),Linux)
    LDFLAGS += -pie
endif
LDFLAGS += $(PLATFORM_LDFLAGS)
LIBS := $(PLATFORM_LIBS)

#============================================================================
# SOURCE FILES
#============================================================================

# Source files
SOURCES := src/platform/pal_network.c
SOURCES += src/platform/fileio/basic.c
SOURCES += src/platform/fileio/sendfile.c
SOURCES += src/platform/fileio/copy.c
SOURCES += src/platform/fileio/tree.c
SOURCES += src/platform/fileio/directory.c
SOURCES += src/platform/pal_alloc.c
SOURCES += src/platform/pal_scratch.c
SOURCES += src/platform/pal_notification.c
SOURCES += src/platform/pal_disc.c
SOURCES += src/platform/pal_volume.c
SOURCES += src/platform/pal_dns_filter.c
SOURCES += src/platform/pal_filesystem.c
SOURCES += src/platform/pal_filesystem_psx.c
SOURCES += src/ftp/ftp_path.c
SOURCES += src/ftp/ftp_server.c
SOURCES += src/ftp/ftp_session.c
SOURCES += src/ftp/ftp_protocol.c
SOURCES += src/ftp/commands/control.c
SOURCES += src/ftp/commands/listing.c
SOURCES += src/ftp/commands/transfer.c
SOURCES += src/ftp/commands/filesystem.c
SOURCES += src/ftp/commands/data_connection.c
SOURCES += src/ftp/commands/extensions.c
SOURCES += src/ftp/commands/information.c
SOURCES += src/ftp/commands/transfer_params.c
SOURCES += src/ftp/ftp_buffer_pool.c
SOURCES += src/ftp/ftp_log.c
SOURCES += src/ftp/ftp_crypto.c
SOURCES += src/app/main.c
SOURCES += src/app/instance_control.c
SOURCES += src/platform/pal_resilient_server.c
SOURCES += src/ftp/ftp_instance.c

# PS5 kernel syscall hook is opt-in until its firmware-specific install path
# has been validated on real hardware. The normal payload never loads it.
ENABLE_PS5_NET_FILTER ?= 0
ifeq ($(TARGET),ps5)
    CFLAGS += -DENABLE_PS5_NET_FILTER=$(ENABLE_PS5_NET_FILTER)
endif
ifeq ($(TARGET),ps5)
SOURCES += src/platform/ps5/ps5_fw_offsets.c
ifeq ($(ENABLE_PS5_NET_FILTER),1)
SOURCES += src/platform/ps5/ps5_net_filter.c
endif
endif

#============================================================================
# ZHTTPD (Web File Explorer) — compile-time toggle
# Enabled by default on consoles (PS4/PS5), disabled on PC
#============================================================================

ifneq ($(filter $(TARGET),ps4 ps5),)
    ENABLE_ZHTTPD ?= 1
else
    ENABLE_ZHTTPD ?= 1
endif

# Artifact/build variants (e.g., zhttp)
ifeq ($(ENABLE_ZHTTPD),1)
TEST_BINS += $(BUILD_DIR)/tests/test_http_query
TEST_BINS += $(BUILD_DIR)/tests/test_http_confinement
TEST_BINS += $(BUILD_DIR)/tests/test_notify
    VARIANT_TAG := zhttp
endif

# Build output directories (variant-aware)
BUILD_DIR := build/$(TARGET)/$(BUILD_TYPE)$(if $(VARIANT_TAG),-$(VARIANT_TAG),)
OBJ_DIR := $(BUILD_DIR)/obj
DEP_DIR := $(BUILD_DIR)/dep
BIN_DIR := $(BUILD_DIR)

ARTIFACT_BASE := $(ARTIFACT_PREFIX)-$(PLATFORM_TAG)$(if $(VARIANT_TAG),-$(VARIANT_TAG),)-v$(VERSION)

ifeq ($(TARGET),macos)
OUTPUT_ELF := $(BIN_DIR)/$(ARTIFACT_BASE)
else
OUTPUT_ELF := $(BIN_DIR)/$(ARTIFACT_BASE).elf
endif
OUTPUT_BIN := $(BIN_DIR)/$(ARTIFACT_BASE).bin

OBJCOPY ?= objcopy
STRIP ?= strip

ifeq ($(ENABLE_ZHTTPD),1)
    CFLAGS += -DENABLE_ZHTTPD=1
    CFLAGS += -DENABLE_WEB_UPLOAD=1
    # PKG installation rides along with the web interface; override with =0.
    ENABLE_PKG_INSTALL ?= 1
    CFLAGS += -DENABLE_PKG_INSTALL=$(ENABLE_PKG_INSTALL)
    ifeq ($(TARGET),ps5)
      ifeq ($(ENABLE_PKG_INSTALL),1)
        # The system installer cannot be driven from a payload process, so a
        # small helper program is embedded here and delivered at runtime.  With
        # the installer disabled the helper is neither built nor embedded.
        INSTALL_HELPER_ELF := $(BUILD_DIR)/install_helper.elf
        INSTALL_HELPER_BLOB := $(BUILD_DIR)/generated/install_helper_blob.c
        SOURCES += $(INSTALL_HELPER_BLOB)
        CFLAGS += -DZFTPD_INSTALL_HELPER=1
      endif
      SOURCES += src/http/games/ps5_install_helper.c
    endif
    ENABLE_LIBCURL ?= 1
    SOURCES += $(EVENT_LOOP_SRC)
    SOURCES += src/http/http_server.c
    SOURCES += src/http/http_listener.c
    SOURCES += src/http/http_upload.c
    SOURCES += src/http/http_parser.c
    SOURCES += src/http/http_response.c
    SOURCES += src/http/http_response_stream.c
    SOURCES += src/http/http_json.c
    SOURCES += src/http/http_api.c
    SOURCES += src/http/http_api_common.c
    SOURCES += src/http/http_api_files.c
    SOURCES += src/http/http_api_process.c
    SOURCES += src/http/http_api_system.c src/http/games/common.c src/http/games/psx_install.c src/http/games/psx_appdb.c src/http/games/psx_launch.c src/http/games/admin_api.c src/http/games/metadata_api.c src/http/games/catalog.c
    SOURCES += src/http/http_api_transfer.c
    SOURCES += src/http/http_static.c
    SOURCES += src/http/http_api_archive.c
    SOURCES += src/http/http_api_dump.c
    SOURCES += src/http/http_csrf.c
    WEB_RESOURCE_FILES := $(shell find web -path web/legacy -prune -o -type f -print | sort)
    HTTP_RESOURCES_C := $(BUILD_DIR)/generated/http/http_resources.c
    SOURCES += $(HTTP_RESOURCES_C)
    SOURCES += src/archive/exfat_unpacker.c
    SOURCES += src/archive/pkg_unpacker.c
    SOURCES += src/archive/builtin_unzip.c
SOURCES += src/archive/zip_writer.c
    SOURCES += src/archive/archive_path.c
    SOURCES += src/transfer/transfer_manager.c
    SOURCES += src/transfer/transfer_state.c
    SOURCES += src/transfer/dump_job.c
else
    # Keep the switch visible in every translation unit either way.
    CFLAGS += -DENABLE_ZHTTPD=0
endif

# NFS URL transfers are enabled by default for PS5 zhttp builds. Other targets
# can opt in explicitly with ENABLE_LIBNFS=1 when libnfs is installed.
ifeq ($(ENABLE_ZHTTPD),1)
  ifeq ($(TARGET),ps5)
    ENABLE_LIBNFS ?= 1
  else
    ENABLE_LIBNFS ?= 0
  endif
else
  ENABLE_LIBNFS := 0
endif

#============================================================================
# Enable with ENABLE_MCP=1 
#============================================================================

ifeq ($(ENABLE_MCP),)
    ENABLE_MCP ?= 0
endif

ifeq ($(ENABLE_MCP),1)
    CFLAGS += -DENABLE_MCP=1
    CFLAGS += -I./mcp/include
    CFLAGS += -I./external/sJson-main/src
    # JSON configuration for sJson
    CFLAGS += -DJSON_MAX_DEPTH=32
    CFLAGS += -DJSON_MAX_STRING_LEN=65536
    CFLAGS += -DJSON_MAX_NODES=16384
    SOURCES += mcp/src/mcp_protocol.c
    SOURCES += mcp/src/mcp_server.c
    SOURCES += mcp/src/mcp_handlers.c
    SOURCES += external/sJson-main/src/sJson.c
    SOURCES += $(EVENT_LOOP_SRC)
    # Execution modules
    SOURCES += mcp/src/mcp_execution/payload.c
    SOURCES += mcp/src/mcp_execution/syscall_race.c
    # Hunter modules (Zero-Day detection)
    SOURCES += mcp/src/mcp_hunter/process_monitor.c
    SOURCES += mcp/src/mcp_hunter/vuln_fuzzer.c
    SOURCES += mcp/src/mcp_hunter/exploit_chain.c
    SOURCES += mcp/src/mcp_hunter/jit_compiler.c
    # Additional include path for hunter headers
    CFLAGS += -I./mcp/src/mcp_hunter
    $(info [INFO] MCP kernel analysis module enabled (with Zero-Day Hunter))
endif

#============================================================================
# OPTIONAL LIBRARIES — enable with ENABLE_LIBARCHIVE=1 / ENABLE_LIBCURL=1
#
# When enabled, the Makefile verifies that the required header is actually
# available.  For desktop (gcc/clang) it uses a compiler probe.  For
# PS4/PS5 cross-compilers the probe fails (missing sysroot headers),
# so we fall back to a simple file-existence check on bundled headers.
#============================================================================

# ── libarchive: zhttp only; auto-detect on desktop ─────────────────────
override ENABLE_LIBCURL ?= 0
ifeq ($(ENABLE_ZHTTPD),1)
ifeq ($(TARGET),ps5)
  # Optional PacBrew library for non-ZIP formats; ZIP keeps the builtin reader.
  override ENABLE_LIBARCHIVE ?= 0
  ifeq ($(ENABLE_LIBARCHIVE),1)
    PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
    _HAS_PS5_ARCHIVE := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libarchive && echo 1 || echo 0)
    ifneq ($(_HAS_PS5_ARCHIVE),1)
      $(error PS5 archive extraction requires PacBrew ps5-payload-libarchive)
    endif
    CFLAGS += -DENABLE_LIBARCHIVE=1 $(shell $(PS5_PKG_CONFIG) --cflags libarchive)
    LIBS += $(shell $(PS5_PKG_CONFIG) --static --libs libarchive)
    $(info [INFO] PS5 archive extraction: PacBrew libarchive)
  endif
else ifeq ($(TARGET),ps4)
  # PS4 builds use the builtin ZIP backend.
  override ENABLE_LIBARCHIVE := 0
else
  # Desktop (macOS/Linux): auto-detect unless user explicitly set it to 0
  ifneq ($(ENABLE_LIBARCHIVE),0)
    _HAS_ARCHIVE := 0
    # 1) System installation
    _SYS_ARCHIVE := $(shell echo '\#include <archive.h>' | $(CC) -xc -fsyntax-only - 2>/dev/null && echo 1 || echo 0)
    ifeq ($(_SYS_ARCHIVE),1)
      _HAS_ARCHIVE := 1
      LIBS += -larchive
      $(info [INFO] libarchive: using system installation)
    endif
    # 2) Homebrew keg (libarchive is keg-only on macOS)
    ifeq ($(_HAS_ARCHIVE),0)
      ifeq ($(HOST_OS),Darwin)
        _BREW_ARCHIVE_PREFIX := $(shell brew --prefix libarchive 2>/dev/null)
        ifneq ($(_BREW_ARCHIVE_PREFIX),)
          _HB_ARCHIVE := $(shell echo '\#include <archive.h>' | $(CC) -xc -fsyntax-only -I$(_BREW_ARCHIVE_PREFIX)/include - 2>/dev/null && echo 1 || echo 0)
          ifeq ($(_HB_ARCHIVE),1)
            _HAS_ARCHIVE := 1
            CFLAGS += -I$(_BREW_ARCHIVE_PREFIX)/include
            LDFLAGS += -L$(_BREW_ARCHIVE_PREFIX)/lib
            LIBS += -larchive
            $(info [INFO] libarchive: using Homebrew keg $(_BREW_ARCHIVE_PREFIX))
          endif
        endif
      endif
    endif
    # 3) Bundled headers fallback (external/libarchive-3.8.6)
    ifeq ($(_HAS_ARCHIVE),0)
      _BUNDLED_ARCHIVE_H := $(wildcard external/libarchive-3.8.6/libarchive/archive.h)
      ifneq ($(_BUNDLED_ARCHIVE_H),)
        _HAS_ARCHIVE := 1
        CFLAGS += -I./external/libarchive-3.8.6/libarchive
        ifeq ($(wildcard external/libarchive-3.8.6-compiled/.libs/libarchive.a),)
          LIBS += -larchive
        else
          LIBS += external/libarchive-3.8.6-compiled/.libs/libarchive.a
        endif
        $(info [INFO] libarchive: using bundled headers (external/libarchive-3.8.6))
      endif
    endif
    # 4) Not found — disable
    ifeq ($(_HAS_ARCHIVE),0)
      $(info [INFO] libarchive not found — extraction disabled (install: brew install libarchive))
      override ENABLE_LIBARCHIVE := 0
    else
      override ENABLE_LIBARCHIVE := 1
      CFLAGS += -DENABLE_LIBARCHIVE=1
    endif
  endif
endif
else
  override ENABLE_LIBARCHIVE := 0
endif

# ── libcurl detection ─────────────────────────────────────────────────────
# zftpd deliberately uses external libcurl instead of maintaining an HTTP/TLS
# reimplementation. PS5 consumes the official PacBrew ps5-payload-curl port.
ifeq ($(ENABLE_LIBCURL),1)
  ifeq ($(TARGET),ps5)
    PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
    _HAS_PS5_CURL := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libcurl && echo 1 || echo 0)
    ifneq ($(_HAS_PS5_CURL),1)
      $(error PS5 zhttp requires PacBrew ps5-payload-curl)
    endif
    CFLAGS += -DENABLE_LIBCURL=1 $(shell $(PS5_PKG_CONFIG) --cflags libcurl)
    LIBS += $(shell $(PS5_PKG_CONFIG) --libs libcurl)
    PS5_CA_BUNDLE ?= $(PS5_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt
    ifeq ($(wildcard $(PS5_CA_BUNDLE)),)
      $(error PS5 libcurl CA bundle not found: $(PS5_CA_BUNDLE))
    endif
    GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
    CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
    CURL_CA_BUNDLE_SRC := $(PS5_CA_BUNDLE)
    $(info [INFO] URL downloads: external PacBrew libcurl + embedded CA bundle)
  else ifeq ($(TARGET),ps4)
    # PacBrew ps4-openorbis portlibs (libcurl + mbedTLS + zlib) as unpacked by
    # tools/fetch_pacbrew_ps4.sh.  Only the headers and the static libraries
    # are used: the curl wrapper the package ships emits the OpenOrbis
    # toolchain's own link flags, which do not belong in a payload-SDK link.
    PS4_PACBREW_ROOT ?= $(shell for d in $(abspath external/pacbrew/ps4/openorbis) /opt/pacbrew/ps4/openorbis; do [ -f "$$d/usr/lib/libcurl.a" ] && { printf '%s' "$$d"; break; }; done)

    ifneq ($(strip $(PS4_PACBREW_ROOT)),)
      # The OpenOrbis build reaches errno through musl's __errno_location()
      # and uses fnmatch()/execl(); the payload libc has none of them.
      SOURCES += src/platform/ps4/ps4_pacbrew_compat.c
      CFLAGS += -DENABLE_LIBCURL=1 -DCURL_STATICLIB -I$(PS4_PACBREW_ROOT)/usr/include
      LIBS += -L$(PS4_PACBREW_ROOT)/usr/lib -lcurl -lmbedtls -lmbedx509 -lmbedcrypto -lz -lSceNet
      # Same trust-store contract as PS5: HTTPS verification must not degrade
      # silently, so the Mozilla bundle is embedded at build time.
      PS4_CA_BUNDLE ?= $(PS4_PACBREW_ROOT)/usr/etc/ca-bundle.crt
      ifeq ($(wildcard $(PS4_CA_BUNDLE)),)
        $(error PS4 libcurl CA bundle not found: $(PS4_CA_BUNDLE) — re-run tools/fetch_pacbrew_ps4.sh or set PS4_CA_BUNDLE=<path>)
      endif
      GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
      CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
      CURL_CA_BUNDLE_SRC := $(PS4_CA_BUNDLE)
      $(info [INFO] URL downloads: PacBrew PS4 portlibs ($(PS4_PACBREW_ROOT)) + embedded CA bundle)
    else
    # A toolchain-provided pkg-config entry (or curl-config) still works.
    # Probe the target environment only — a host libcurl would link code built
    # for the wrong OS.
    PS4_PKG_CONFIG ?= $(shell for c in $(PS4_PAYLOAD_SDK)/bin/orbis-pkg-config $(PS4_PAYLOAD_SDK)/bin/openorbis-pkg-config $(OPENORBIS)/bin/openorbis-pkg-config; do [ -x "$$c" ] && { printf '%s' "$$c"; break; }; done)
    PS4_CURL_CONFIG ?= $(shell for c in $(PS4_PAYLOAD_SDK)/bin/orbis-curl-config $(PS4_PAYLOAD_SDK)/bin/curl-config $(PS4_PAYLOAD_SDK)/bin/openorbis-curl-config; do [ -x "$$c" ] && { printf '%s' "$$c"; break; }; done)

    _HAS_PS4_CURL := $(shell if [ -n "$(strip $(PS4_PKG_CONFIG))" ] && $(PS4_PKG_CONFIG) --exists libcurl >/dev/null 2>&1; then echo 1; elif [ -n "$(strip $(PS4_CURL_CONFIG))" ]; then echo 1; fi)

    ifneq ($(_HAS_PS4_CURL),1)
      $(info [INFO] PS4 external libcurl not found — URL downloader disabled)
      $(info [INFO]   run tools/fetch_pacbrew_ps4.sh to install PacBrew ps4-openorbis-portlibs)
      $(info [INFO]   or point PS4_PKG_CONFIG / PS4_CURL_CONFIG at a target config tool)
      override ENABLE_LIBCURL := 0
    else
      ifneq ($(strip $(PS4_PKG_CONFIG)),)
        PS4_CURL_CFLAGS := $(shell $(PS4_PKG_CONFIG) --cflags libcurl)
        PS4_CURL_LIBS := $(shell $(PS4_PKG_CONFIG) --static --libs libcurl)
      else
        PS4_CURL_CFLAGS := $(shell $(PS4_CURL_CONFIG) --cflags)
        PS4_CURL_LIBS := $(shell $(PS4_CURL_CONFIG) --static-libs)
      endif
      CFLAGS += -DENABLE_LIBCURL=1 $(PS4_CURL_CFLAGS)
      LIBS += $(PS4_CURL_LIBS)

      # Same trust-store contract as PS5: the PacBrew curl package ships the
      # Mozilla bundle, and HTTPS verification must not degrade silently.
      PS4_CA_BUNDLE ?= $(PS4_PAYLOAD_SDK)/target/user/homebrew/etc/ca-bundle.crt
      ifeq ($(wildcard $(PS4_CA_BUNDLE)),)
        $(error PS4 libcurl CA bundle not found: $(PS4_CA_BUNDLE) — override with PS4_CA_BUNDLE=<path>)
      endif
      GENERATED_CA_HEADER := $(BUILD_DIR)/generated/zftpd_ca_bundle.h
      CFLAGS += -DZFTPD_EMBEDDED_CA_BUNDLE=1 -I$(BUILD_DIR)/generated
      CURL_CA_BUNDLE_SRC := $(PS4_CA_BUNDLE)
      $(info [INFO] URL downloads: PacBrew PS4 libcurl + embedded CA bundle)
    endif
    endif
  else
    _HAS_CURL := $(shell echo '\#include <curl/curl.h>' | $(CC) -xc -fsyntax-only - 2>/dev/null && echo 1 || echo 0)
    ifneq ($(_HAS_CURL),1)
      $(info [INFO] libcurl headers not found — disabling ENABLE_LIBCURL)
      override ENABLE_LIBCURL := 0
    else
      CFLAGS += -DENABLE_LIBCURL=1
      LIBS += -lcurl
    endif
  endif
endif

ifeq ($(ENABLE_LIBCURL),1)
  SOURCES += src/transfer/backend_curl.c
endif

# Native BitTorrent engine for magnet links. PS5 needs a libtorrent-rasterbar
# port installed into its payload SDK; host libraries must never be linked
# into a console payload.
ENABLE_LIBTORRENT ?= 0
ifeq ($(ENABLE_LIBTORRENT),1)
  ifeq ($(TARGET),ps5)
    PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
    ifneq ($(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libtorrent-rasterbar && echo 1),1)
      $(error PS5 magnet downloads require a PS5 libtorrent-rasterbar port in the payload SDK)
    endif
    TORRENT_CFLAGS := $(shell $(PS5_PKG_CONFIG) --cflags libtorrent-rasterbar)
    TORRENT_LIBS := $(shell $(PS5_PKG_CONFIG) --static --libs libtorrent-rasterbar)
  else
    ifneq ($(shell pkg-config --exists libtorrent-rasterbar 2>/dev/null && echo 1),1)
      $(error ENABLE_LIBTORRENT=1 requires libtorrent-rasterbar development files)
    endif
    TORRENT_CFLAGS := $(shell pkg-config --cflags libtorrent-rasterbar)
    TORRENT_LIBS := $(shell pkg-config --libs libtorrent-rasterbar)
    ifeq ($(HOST_OS),Darwin)
      TORRENT_BOOST_PREFIX := $(shell brew --prefix boost 2>/dev/null)
      TORRENT_CFLAGS += -I$(TORRENT_BOOST_PREFIX)/include
      TORRENT_OPENSSL_PREFIX := $(shell brew --prefix openssl@3 2>/dev/null)
      TORRENT_CFLAGS += -I$(TORRENT_OPENSSL_PREFIX)/include
    endif
  endif
  CFLAGS += -DENABLE_LIBTORRENT=1
  TORRENT_CXXFLAGS := -std=c++17 -Wall -Wextra -Wpedantic -Werror -O2 -fPIE \
    $(PLATFORM_DEFS) -I./include $(TORRENT_CFLAGS)
  ifeq ($(TARGET),ps5)
    TORRENT_CXXFLAGS += -fvisibility=hidden -fno-inline-functions
  endif
  LIBS += $(TORRENT_LIBS)
  TORRENT_OBJECT := $(OBJ_DIR)/transfer/backend_torrent.o
  CXX_LINK := $(CXX)
else
  CXX_LINK := $(CC)
endif

# ── libnfs detection ──────────────────────────────────────────────────────
ifeq ($(ENABLE_LIBNFS),1)
  ifeq ($(TARGET),ps5)
    PS5_PKG_CONFIG ?= $(PS5_PAYLOAD_SDK)/bin/prospero-pkg-config
    _HAS_LIBNFS := $(shell test -x $(PS5_PKG_CONFIG) && $(PS5_PKG_CONFIG) --exists libnfs && echo 1 || echo 0)
    ifneq ($(_HAS_LIBNFS),1)
      $(error PS5 NFS transfers require PacBrew ps5-payload-libnfs)
    endif
    CFLAGS += -DENABLE_LIBNFS=1 $(shell $(PS5_PKG_CONFIG) --cflags libnfs)
    LIBS += $(shell $(PS5_PKG_CONFIG) --libs libnfs)
  else
    _HAS_LIBNFS := $(shell pkg-config --exists libnfs 2>/dev/null && echo 1 || echo 0)
    ifneq ($(_HAS_LIBNFS),1)
      $(error ENABLE_LIBNFS=1 requires libnfs development files)
    endif
    CFLAGS += -DENABLE_LIBNFS=1 $(shell pkg-config --cflags libnfs)
    LIBS += $(shell pkg-config --static --libs libnfs)
  endif
  SOURCES += src/transfer/backend_nfs.c
endif

# Object files (handle both src/ and mcp/src/ paths)
OBJECTS := $(patsubst src/%.c,$(OBJ_DIR)/%.o,$(filter src/%.c,$(SOURCES)))
OBJECTS += $(patsubst mcp/src/%.c,$(OBJ_DIR)/mcp/%.o,$(filter mcp/src/%.c,$(SOURCES)))
OBJECTS += $(patsubst $(BUILD_DIR)/generated/%.c,$(OBJ_DIR)/generated/%.o,$(filter $(BUILD_DIR)/generated/%.c,$(SOURCES)))
OBJECTS += $(TORRENT_OBJECT)

# FFI Object files
FFI_SOURCES := ffi/c_core/pal_ffi.c
FFI_OBJECTS := $(patsubst ffi/%.c,$(OBJ_DIR)/ffi/%.o,$(FFI_SOURCES))

# Object files without main (for unit tests and ffi library)
LIB_OBJECTS := $(filter-out $(OBJ_DIR)/app/main.o,$(OBJECTS))

# Dependency files
DEPENDS := $(patsubst $(OBJ_DIR)/%.o,$(DEP_DIR)/%.d,$(filter-out $(OBJ_DIR)/mcp/%.o,$(OBJECTS)))
DEPENDS += $(patsubst $(OBJ_DIR)/mcp/%.o,$(DEP_DIR)/mcp/%.d,$(filter $(OBJ_DIR)/mcp/%.o,$(OBJECTS)))

#============================================================================
# BUILD TARGETS
#============================================================================

.PHONY: all clean distclean install test help bin deploy deploy-i deploy-nc doctor-ps4
.PHONY: all-platforms release-all debug-all ffi ffi-java ffi-rust ffi-python resources
.PHONY: ps5-hook-blob

# ============================================================================
# PS5 NET FILTER HOOK — Kernel-safe compilation pipeline
#
# The hook functions (src/platform/ps5/ps5_net_filter_hook.c) run in ring-0 (kernel mode)
# and require special compiler flags that differ from the normal build.
#
# Pipeline:
#   1. Compile hook with kernel-safe flags → ps5_net_filter_hook.o
#   2. Extract .text.hook_connect and .text.hook_sendto sections → .bin
#   3. Generate C byte-array header → ps5_net_filter_hook_blob.h
#
# The blob header is included by ps5_net_filter.c to replace the placeholder
# byte arrays (g_hook_connect_code[], g_hook_sendto_code[]).
#
# Run manually before the PS5 build:
#   make ps5-hook-blob
# ============================================================================

ifeq ($(TARGET),ps5)

HOOK_OBJ      := $(OBJ_DIR)/ps5_net_filter_hook.o
HOOK_BIN      := $(OBJ_DIR)/ps5_net_filter_hook.bin
HOOK_BLOB_H   := $(BUILD_DIR)/generated/ps5/ps5_net_filter_hook_blob.h

# Kernel-safe compiler flags (MUST differ from normal CFLAGS)
HOOK_CFLAGS   := \
    -DPS5_HOOK_BUILD \
    -DPLATFORM_PS5 \
    -std=c11 \
    -O2 \
    -fno-stack-protector \
    -mno-red-zone \
    -fPIC \
    -mcmodel=large \
    -fno-plt \
    -fno-common \
    -fno-builtin \
    -fno-exceptions \
    -fomit-frame-pointer \
    -I include/platform/ps5/

ps5-hook-blob: $(HOOK_BLOB_H)
	@echo "  [BLOB] $< generated ($(shell wc -c < $(HOOK_BIN) 2>/dev/null || echo '?') bytes)"

$(HOOK_BLOB_H): $(HOOK_BIN)
	@echo "  [XXD]  $@"
	@mkdir -p $(dir $@)
	@xxd -i $< > $@

$(HOOK_BIN): $(HOOK_OBJ)
	@echo "  [OBJCOPY] $@"
	@$(OBJCOPY) -O binary \
	    --only-section=.text.hook_connect \
	    --only-section=.text.hook_sendto \
	    $< $@

$(HOOK_OBJ): src/platform/ps5/ps5_net_filter_hook.c | $(OBJ_DIR)
	@echo "  [HOOK-CC] $<"
	@$(CC) $(HOOK_CFLAGS) -c $< -o $@

endif # TARGET=ps5

resources: $(HTTP_RESOURCES_C)

$(HTTP_RESOURCES_C): tools/generate_resources.py $(WEB_RESOURCE_FILES)
	@echo "  [GEN] $@"
	@mkdir -p $(dir $@)
	@python3 tools/generate_resources.py > $@

.DEFAULT_GOAL := all

# FFI Shared Library output
ifeq ($(TARGET),macos)
FFI_OUTPUT := $(BIN_DIR)/libzftpd_ffi.dylib
FFI_LDFLAGS := -dynamiclib
else
FFI_OUTPUT := $(BIN_DIR)/libzftpd_ffi.so
FFI_LDFLAGS := -shared
endif

$(BIN_DIR) $(OBJ_DIR) $(DEP_DIR) $(BUILD_DIR)/tests $(OBJ_DIR)/ffi/c_core $(OBJ_DIR)/mcp:
	@mkdir -p $@

ifeq ($(filter $(TARGET),ps4 ps5),)
all: $(OUTPUT_ELF) $(if $(ffi_langs),ffi)
else
all: $(OUTPUT_BIN) $(if $(ffi_langs),ffi)
endif

$(PROJECT): all
	@true

# Link executable
$(OUTPUT_ELF): $(OBJECTS) | $(BIN_DIR)
	@echo "  [LD]  $@"
	@mkdir -p $(BIN_DIR)
	@$(CXX_LINK) $(LDFLAGS) -o $@ $^ $(LIBS)
	@echo "Build complete: $(PROJECT) ($(TARGET), $(BUILD_TYPE))"

# FFI Build Targets
.PHONY: ffi
ffi: $(FFI_OUTPUT)
ifneq ($(findstring java,$(ffi_langs)),)
	@echo "  [FFI] Building Java bindings..."
	@$(MAKE) ffi-java
endif
ifneq ($(findstring rust,$(ffi_langs)),)
	@echo "  [FFI] Building Rust bindings..."
	@$(MAKE) ffi-rust
endif
ifneq ($(findstring python,$(ffi_langs)),)
	@echo "  [FFI] Building Python bindings..."
	@$(MAKE) ffi-python
endif
ifneq ($(findstring go,$(ffi_langs)),)
	@echo "  [FFI] Building Go bindings..."
	@$(MAKE) ffi-go
endif

$(FFI_OUTPUT): $(LIB_OBJECTS) $(FFI_OBJECTS) | $(BIN_DIR)
	@echo "  [LD]  $@ (Shared Library)"
	@mkdir -p $(BIN_DIR)
	@$(CXX_LINK) $(LDFLAGS) $(FFI_LDFLAGS) -fPIC -o $@ $(LIB_OBJECTS) $(FFI_OBJECTS) $(LIBS)
	@echo "FFI C-Core built: $@"

# Build all supported platforms (best-effort: includes only toolchains found on the host).
TARGETS_ALL ?= $(shell \
  echo macos; \
  command -v gcc >/dev/null 2>&1 && echo linux || true; \
  command -v ppu-gcc >/dev/null 2>&1 && echo ps3 || true; \
  [ -d external/ps4-payload-sdk ] && echo ps4 || true; \
  [ -d external/ps5-payload-sdk ] && echo ps5 || true)

# Build matrix toggles for ZHTTPD variant
ZHTTP_VARIANTS ?= 0 1

JAVA_HOME_PATH ?= $(shell /usr/libexec/java_home)

# Java FFI Target
.PHONY: ffi-java
ffi-java: $(FFI_OUTPUT)
	@echo "  [JAVAC] Compiling Java FFI bindings..."
	@mkdir -p $(BIN_DIR)/ffi/java
	@javac -J-Xint -d $(BIN_DIR)/ffi/java ffi/java/src/main/java/org/zftpd/ffi/*.java
	@echo "  [JAVAC] Compiling Java FFI tests..."
	@javac -J-Xint -cp $(BIN_DIR)/ffi/java -d $(BIN_DIR)/ffi/java ffi/java/src/test/java/org/zftpd/ffi/*.java
	@echo "  [CC]    Compiling JNI C wrapper..."
	@$(CC) $(CFLAGS) $(FFI_LDFLAGS) -fPIC \
	    -I"$(JAVA_HOME_PATH)/include" \
	    -I"$(JAVA_HOME_PATH)/include/darwin" \
	    -I"./include" -I"./ffi/c_core" \
	    -o $(BIN_DIR)/libzftpd_ffi_java$(suffix $(FFI_OUTPUT)) \
	    ffi/java/src/main/c/pal_ffi_jni.c \
	    -L$(BIN_DIR) -lzftpd_ffi $(LIBS)
	@echo "  [JAVA]  Running FFI tests..."
	@java -Xint -Djava.library.path=$(BIN_DIR) -cp $(BIN_DIR)/ffi/java org.zftpd.ffi.FfiTests
	@echo "Java FFI bindings built successfully."

# Rust FFI Target
.PHONY: ffi-rust
ffi-rust: $(FFI_OUTPUT)
	@echo "  [CARGO] Building Rust FFI bindings..."
	@cd ffi/rust && cargo build --release
	@echo "  [CARGO] Running Rust FFI tests..."
ifeq ($(TARGET),macos)
	@cd ffi/rust && DYLD_LIBRARY_PATH=../../build/macos/release cargo test
else
	@cd ffi/rust && LD_LIBRARY_PATH=../../build/linux/release cargo test
endif
	@echo "Rust FFI bindings built successfully."

# Python FFI Target
.PHONY: ffi-python
ffi-python: $(FFI_OUTPUT)
	@echo "  [PYTHON] Installing dependencies..."
	@python3 -m pip install -q cffi pytest
	@echo "  [PYTHON] Running Python FFI tests..."
	@cd ffi/python && PYTHONPATH=. pytest tests/
	@echo "Python FFI bindings tested successfully."

# Go FFI Target
.PHONY: ffi-go
ffi-go: $(FFI_OUTPUT)
	@echo "  [GO] Compiling and Testing Go FFI bindings..."
ifeq ($(TARGET),macos)
	@cd ffi/go/zftpd && DYLD_LIBRARY_PATH=../../../build/macos/release go test -v
else
	@cd ffi/go/zftpd && LD_LIBRARY_PATH=../../../build/linux/release go test -v
endif
	@echo "Go FFI bindings tested successfully."

all-platforms: release-all

release-all:
	@set -e; \
	for t in $(TARGETS_ALL); do \
		echo "==> Building $$t (release)"; \
		$(MAKE) TARGET=$$t BUILD_TYPE=release clean all; \
	done

# Build all platforms in release with/without ZHTTPD (produces ELF and BIN where applicable)
release-matrix:
	@set -e; \
	for t in $(TARGETS_ALL); do \
		for z in $(ZHTTP_VARIANTS); do \
			echo "==> Building $$t (release, ENABLE_ZHTTPD=$$z)"; \
			$(MAKE) TARGET=$$t BUILD_TYPE=release ENABLE_ZHTTPD=$$z \
				ENABLE_LIBARCHIVE=$(ENABLE_LIBARCHIVE) \
				ENABLE_LIBCURL=$(ENABLE_LIBCURL) \
				clean all; \
		done; \
	done

debug-all:
	@set -e; \
	for t in $(TARGETS_ALL); do \
		echo "==> Building $$t (debug)"; \
		$(MAKE) TARGET=$$t BUILD_TYPE=debug clean all; \
	done
# Generate the embedded trust store from PacBrew's Mozilla CA bundle (PS4/PS5).
ifneq ($(strip $(CURL_CA_BUNDLE_SRC)),)
$(OBJ_DIR)/transfer/backend_curl.o: $(GENERATED_CA_HEADER)

$(GENERATED_CA_HEADER): $(CURL_CA_BUNDLE_SRC) tools/embed_binary.py
	@echo "  [GEN] $@"
	@python3 tools/embed_binary.py $< $@ zftpd_ca_bundle
endif

# Compile C source files
$(OBJ_DIR)/transfer/backend_torrent.o: src/transfer/backend_torrent.cpp | $(OBJ_DIR) $(DEP_DIR)
	@echo "  [CXX] $<"
	@mkdir -p $(dir $@) $(dir $(DEP_DIR)/transfer/backend_torrent.d)
	@$(CXX) $(TORRENT_CXXFLAGS) -MMD -MP -MF $(DEP_DIR)/transfer/backend_torrent.d -MT $@ -c $< -o $@

$(OBJ_DIR)/%.o: src/%.c | $(OBJ_DIR) $(DEP_DIR)
	@echo "  [CC]  $<"
	@mkdir -p $(dir $@) $(dir $(DEP_DIR)/$*.d)
	@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/$*.d -MT $@ -c $< -o $@

# Compile generated C sources without writing into the source tree.
$(OBJ_DIR)/generated/%.o: $(BUILD_DIR)/generated/%.c | $(OBJ_DIR) $(DEP_DIR)
	@echo "  [CC]  $<"
	@mkdir -p $(dir $@) $(dir $(DEP_DIR)/generated/$*.d)
	@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/generated/$*.d -MT $@ -c $< -o $@

# Compile FFI C source files (with -fPIC for shared library)
$(OBJ_DIR)/ffi/%.o: ffi/%.c | $(OBJ_DIR)/ffi/c_core
	@echo "  [CC]  $< (FFI)"
	@mkdir -p $(dir $@) $(dir $(DEP_DIR)/ffi/$*.d)
	@$(CC) $(CFLAGS) -fPIC -MMD -MP -MF $(DEP_DIR)/ffi/$*.d -MT $@ -c $< -o $@

# Compile MCP C source files
$(OBJ_DIR)/mcp/%.o: mcp/src/%.c | $(OBJ_DIR)/mcp
	@echo "  [CC]  $< (MCP)"
	@mkdir -p $(dir $@) $(dir $(DEP_DIR)/mcp/$*.d)
	@$(CC) $(CFLAGS) -MMD -MP -MF $(DEP_DIR)/mcp/$*.d -MT $@ -c $< -o $@

# Include dependency files
-include $(DEPENDS)

#============================================================================
# UTILITY TARGETS
#============================================================================

# Clean build artifacts
clean:
	@echo "Cleaning build artifacts..."
	@rm -rf $(BUILD_DIR)
	@rm -f src/*.o src/*.d
	@rm -f tests/*.o tests/*.d
	@rm -f $(PROJECT) $(PROJECT).elf $(PROJECT).bin
	@rm -f tests/test_size
	@echo "Clean complete."

# Deep clean (including configuration)
distclean: clean
	@echo "Removing all generated files..."
	@rm -f *~ core
	@rm -rf build
	@echo "Distclean complete."

# Install (simple copy for now)
install: $(OUTPUT_ELF)
	@echo "Installing $(PROJECT)..."
	@install -D -m 0755 $(OUTPUT_ELF) $(DESTDIR)/usr/local/bin/$(PROJECT)
	@echo "Install complete."

# Run static analysis
analyze:
	@echo "Running static analysis..."
	@clang --analyze $(CFLAGS) $(SOURCES)
	@echo "Analysis complete."

# Run tests
TEST_BINS := $(BUILD_DIR)/tests/test_size
TEST_BINS += $(BUILD_DIR)/tests/test_security
TEST_BINS += $(BUILD_DIR)/tests/test_path_security
TEST_BINS += $(BUILD_DIR)/tests/test_buffer_pool
TEST_BINS += $(BUILD_DIR)/tests/test_scratch
TEST_BINS += $(BUILD_DIR)/tests/test_alloc
TEST_BINS += $(BUILD_DIR)/tests/test_mlst_ascii
TEST_BINS += $(BUILD_DIR)/tests/test_list_flag
TEST_BINS += $(BUILD_DIR)/tests/test_instance
TEST_BINS += $(BUILD_DIR)/tests/test_chmod
TEST_BINS += $(BUILD_DIR)/tests/test_copy_atomic
TEST_BINS += $(BUILD_DIR)/tests/test_ftp_commands
TEST_BINS += $(BUILD_DIR)/tests/test_fileio_tree
TEST_BINS += $(BUILD_DIR)/tests/test_fileio_basic
TEST_BINS += $(BUILD_DIR)/tests/test_ps5_fw_offsets
ifeq ($(ENABLE_ZHTTPD),1)
TEST_BINS += $(BUILD_DIR)/tests/test_transfer
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_parallel
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_state
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_queue
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_orphans
ifeq ($(ENABLE_LIBTORRENT),1)
TEST_BINS += $(BUILD_DIR)/tests/test_transfer_torrent
endif
TEST_BINS += $(BUILD_DIR)/tests/test_http_parser
TEST_BINS += $(BUILD_DIR)/tests/test_http_upload
TEST_BINS += $(BUILD_DIR)/tests/test_http_server
TEST_BINS += $(BUILD_DIR)/tests/test_http_response_stream
TEST_BINS += $(BUILD_DIR)/tests/test_http_api_common
TEST_BINS += $(BUILD_DIR)/tests/test_http_files
TEST_BINS += $(BUILD_DIR)/tests/test_http_process
TEST_BINS += $(BUILD_DIR)/tests/test_http_system
TEST_BINS += $(BUILD_DIR)/tests/test_http_games
TEST_BINS += $(BUILD_DIR)/tests/test_archive_path
TEST_BINS += $(BUILD_DIR)/tests/test_builtin_unzip
TEST_BINS += $(BUILD_DIR)/tests/test_zip_writer
TEST_BINS += $(BUILD_DIR)/tests/test_pal_volume
TEST_BINS += $(BUILD_DIR)/tests/test_http_archive
TEST_BINS += $(BUILD_DIR)/tests/test_dump_job
endif

ifeq ($(filter $(TARGET),linux macos),)
test: $(OUTPUT_BIN)
	@echo "Tests skipped for TARGET=$(TARGET)"
else
test: $(OUTPUT_ELF) $(TEST_BINS)
	@echo "Running tests..."
	@set -e; for t in $(TEST_BINS); do ./$$t; done
endif

$(BUILD_DIR)/tests/test_http_query: tests/test_http_query.c $(LIB_OBJECTS) | $(BUILD_DIR)/tests
	@echo "  [CC]  $<"
	@$(CXX_LINK) $(CFLAGS) -DFTP_AUTH_DELAY=0 -DFTP_PORT_ALLOW_FOREIGN_IP=1 -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)

$(BUILD_DIR)/tests/test_transfer_torrent: tests/test_transfer_torrent.cpp $(TORRENT_OBJECT) | $(BUILD_DIR)/tests
	@echo "  [CXX] $<"
	@$(CXX) $(TORRENT_CXXFLAGS) -o $@ $< $(TORRENT_OBJECT) $(LDFLAGS) $(TORRENT_LIBS) -lpthread

$(BUILD_DIR)/tests/%: tests/%.c $(LIB_OBJECTS) | $(BUILD_DIR)/tests
	@echo "  [CC]  $<"
	@$(CXX_LINK) $(CFLAGS) -DFTP_AUTH_DELAY=0 -DFTP_PORT_ALLOW_FOREIGN_IP=1 -o $@ -x c $< -x none $(LIB_OBJECTS) $(LDFLAGS) $(LIBS)

bin: $(OUTPUT_BIN)

$(OUTPUT_BIN): $(OUTPUT_ELF) | $(BIN_DIR)
	@echo "  [STRIP]    $@"
	@command -v $(OBJCOPY) >/dev/null 2>&1 || { echo "error: '$(OBJCOPY)' not found (set OBJCOPY=... or install binutils)"; exit 1; }
ifeq ($(filter $(TARGET),ps4 ps5),)
	@$(OBJCOPY) -O binary $< $@
else
	@$(STRIP) --strip-unneeded -R .comment -R .GCC.command.line $< -o $@
endif

deploy: $(OUTPUT_BIN)
	@if [ "$(TARGET)" = "ps4" ]; then \
		command -v socat >/dev/null 2>&1 || { echo "error: 'socat' non trovato (brew install socat)"; exit 1; }; \
		$(PS4_DEPLOY) -h $(PS4_HOST) -p $(PS4_PORT) $(OUTPUT_BIN); \
	elif [ "$(TARGET)" = "ps5" ]; then \
		$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) $(OUTPUT_BIN); \
	else \
		echo "error: deploy supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
	fi

deploy-i: $(OUTPUT_BIN)
	@if [ "$(TARGET)" = "ps4" ]; then \
		command -v socat >/dev/null 2>&1 || { echo "error: 'socat' non trovato (brew install socat)"; exit 1; }; \
		$(PS4_DEPLOY) -h $(PS4_HOST) -p $(PS4_PORT) -i $(OUTPUT_BIN); \
	elif [ "$(TARGET)" = "ps5" ]; then \
		$(PS5_DEPLOY) -h $(PS5_HOST) -p $(PS5_PORT) -i $(OUTPUT_BIN); \
	else \
		echo "error: deploy-i supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
	fi

deploy-nc: $(OUTPUT_BIN)
	@if [ "$(TARGET)" = "ps4" ]; then \
		command -v nc >/dev/null 2>&1 || { echo "error: 'nc' (netcat) non trovato"; exit 1; }; \
		echo "Sending $(OUTPUT_BIN) to $(PS4_HOST):$(PS4_PORT) via nc..."; \
		nc -w 10 $(PS4_HOST) $(PS4_PORT) < $(OUTPUT_BIN); \
	elif [ "$(TARGET)" = "ps5" ]; then \
		command -v nc >/dev/null 2>&1 || { echo "error: 'nc' (netcat) non trovato"; exit 1; }; \
		echo "Sending $(OUTPUT_BIN) to $(PS5_HOST):$(PS5_PORT) via nc..."; \
		nc -w 10 $(PS5_HOST) $(PS5_PORT) < $(OUTPUT_BIN); \
	else \
		echo "error: deploy-nc supportato solo con TARGET=ps4 o TARGET=ps5"; exit 1; \
	fi

doctor-ps4:
	@echo "TARGET=ps4 prerequisiti (macOS/Homebrew)"
	@echo ""
	@echo "llvm-config:"
	@{ command -v llvm-config >/dev/null 2>&1 && echo "  OK: $$(command -v llvm-config)" || true; }
	@{ [ -x "/opt/homebrew/opt/llvm/bin/llvm-config" ] && echo "  OK: /opt/homebrew/opt/llvm/bin/llvm-config" || true; }
	@echo ""
	@echo "ld.lld:"
	@{ command -v ld.lld >/dev/null 2>&1 && echo "  OK: $$(command -v ld.lld)" || true; }
	@{ command -v brew >/dev/null 2>&1 && p=$$(brew --prefix lld 2>/dev/null || true) && [ -n "$$p" ] && [ -x "$$p/bin/ld.lld" ] && echo "  OK: $$p/bin/ld.lld" || true; }
	@echo ""
	@echo "Se manca qualcosa:"
	@echo "  brew install llvm lld"
	@echo "  export PATH=\"/opt/homebrew/opt/llvm/bin:/opt/homebrew/opt/lld/bin:$$PATH\""

#============================================================================
# HELP
#============================================================================

help:
	@echo "Multi-Platform FTP Server Build System"
	@echo "========================================"
	@echo ""
	@echo "Targets:"
	@echo "  all         - Build the FTP server (default)"
	@echo "  clean       - Remove build artifacts"
	@echo "  distclean   - Deep clean (remove all generated files)"
	@echo "  install     - Install to system (requires root)"
	@echo "  analyze     - Run static analysis (requires clang)"
	@echo "  test        - Run test suite"
	@echo "  help        - Display this help message"
	@echo "  resources   - Generate embedded web resources under build/.../generated"
	@echo ""
	@echo "Variables:"
	@echo "  TARGET            - Target platform (linux, macos, ps3, ps4, ps5)"
	@echo "  BUILD_TYPE        - Build configuration (debug, release)"
	@echo "  ENABLE_LIBARCHIVE - Enable archive extraction (0/1, requires libarchive)"
	@echo "  ENABLE_LIBCURL    - Enable URL downloads (0/1, requires libcurl)"
	@echo "  WEB_DEPLOY_DIR    - Deprecated (web UI is now embedded in binary)"
	@echo ""
	@echo "Examples:"
	@echo "  make                          # Build for Linux (release)"
	@echo "  make TARGET=macos             # Build for macOS"
	@echo "  make TARGET=ps5               # Build for PS5"
	@echo "  make BUILD_TYPE=debug         # Build debug version"
	@echo "  make TARGET=ps4 BUILD_TYPE=debug  # PS4 debug build"
	@echo "  make ENABLE_LIBARCHIVE=1 ENABLE_LIBCURL=1  # With extract + download"
	@echo "  make resources                  # Regenerate embedded web resources"
	@echo ""
	@echo "Current configuration:"
	@echo "  Target:     $(TARGET)"
	@echo "  Build type: $(BUILD_TYPE)"
	@echo "  Compiler:   $(CC)"
	@echo "  libarchive: $(ENABLE_LIBARCHIVE)"
	@echo "  libcurl:    $(ENABLE_LIBCURL)"
	@echo ""

#============================================================================
# COMPILATION DATABASE (for IDE/LSP support)
#============================================================================

compile_commands.json:
	@echo "Generating compilation database..."
	@bear -- make clean all
	@echo "Compilation database generated."

.PHONY: compile_commands.json

#============================================================================
#============================================================================
# WEB DEPLOY — DEPRECATED
#
# All web assets are now embedded directly in the binary via
# http_resources.c (run  make resources  to regenerate).
# The web-deploy target is kept as a no-op for backward compatibility.
#============================================================================

web-deploy:
	@echo "  [WEB]  web-deploy is deprecated — web UI is now embedded in the binary"
	@echo "  [WEB]  Resources are generated automatically under build/.../generated"
	@echo "  [WEB]  Done — $(shell find web -path web/legacy -prune -o \( -name '*.css' -o -name '*.js' \) -print | wc -l | tr -d ' ') files deployed"

#============================================================================
# Flag changes must invalidate object files.
#
# Objects do not record the flags they were built with, so toggling a feature
# (ENABLE_LIBTORRENT, ENABLE_PKG_INSTALL, ...) used to relink stale objects and
# fail with undefined symbols.  The stamp is rewritten only when the flags
# actually change, and every object depends on it, so a real change rebuilds.
#============================================================================

FLAG_STAMP := $(OBJ_DIR)/.build-flags

.PHONY: FORCE
FORCE:

$(FLAG_STAMP): FORCE
	@mkdir -p $(OBJ_DIR)
	@printf '%s\n' '$(CFLAGS)' '$(LDFLAGS)' '$(LIBS)' > $@.tmp
	@if ! cmp -s $@.tmp $@; then \
		echo "  [FLAGS]    build flags changed — objects will be rebuilt"; \
		mv -f $@.tmp $@; \
	fi
	@rm -f $@.tmp

$(OBJECTS) $(LIB_OBJECTS): $(FLAG_STAMP)

#============================================================================
# PS5 installer helper
#============================================================================

ifneq ($(INSTALL_HELPER_ELF),)
$(INSTALL_HELPER_ELF): src/platform/ps5/install_helper/install_helper.c | $(BIN_DIR)
	@echo "  [CC]  $<"
	@$(CXX_LINK) $(CFLAGS) -o $@ $< $(LDFLAGS) $(LIBS)

$(INSTALL_HELPER_BLOB): $(INSTALL_HELPER_ELF) tools/gen_blob.py
	@mkdir -p $(dir $@)
	@python3 tools/gen_blob.py $< install_helper_elf $@

$(OBJ_DIR)/generated/install_helper_blob.o: $(INSTALL_HELPER_BLOB)
endif
