# pxl8 - Makefile
#
# Usage:
#   make                Build everything (debug)
#   make release        Build everything (release)
#   make run            Build and run (CART=script.fnl)
#   make demo3d-server  Build demo3d server only
#   make client         Build pxl8 only
#   make install        Install to ~/.local/bin (release)
#   make profile        Profile with perf + flamegraph (Linux)
#   make ase            Aseprite tools (ASE_CMD=package|clean)
#   make clean          Remove build artifacts
#   make clean-all      Remove build artifacts and dependencies
#   make clean-deps     Remove only dependencies
#   make clean-cache    Clear ccache
#   make update         Fetch/update all dependencies
#   make vendor-sdl     Vendor SDL3 from source
#   make help           Show available targets
#
# Variables:
#   MODE=release          Build in release mode (default: debug)
#   CART=demo             Cart/script to run (default: demo)
#   PROFILE_DURATION=30   Perf recording seconds (default: 30)
#   ASE_CMD=              Aseprite subcommand (package, clean, ...)

export PATH := $(PATH):/usr/bin:/bin

# -- Colors -------------------------------------------------------------------

GREEN  := \033[1;38;2;184;187;38m
YELLOW := \033[1;38;2;250;189;47m
RED    := \033[1;38;2;251;73;52m
CYAN   := \033[1;38;2;131;165;152m
NC     := \033[0m

INFO  = @printf '$(GREEN)[%s INFO]$(NC) %s\n' "$$(date +%H:%M:%S)"
WARN  = @printf '$(YELLOW)[%s WARN]$(NC) %s\n' "$$(date +%H:%M:%S)"
ERROR = @printf '$(RED)[%s ERROR]$(NC) %s\n' "$$(date +%H:%M:%S)"
CC_   = @printf '$(CYAN)[%s CC]$(NC) %s\n' "$$(date +%H:%M:%S)"
SHD_  = @printf '$(CYAN)[%s SHADER]$(NC) %s\n' "$$(date +%H:%M:%S)"

# -- Platform detection -------------------------------------------------------

UNAME := $(shell uname)
PLATFORM := posix

ifneq (,$(findstring MINGW,$(UNAME)))
  PLATFORM := windows
endif
ifneq (,$(findstring MSYS,$(UNAME)))
  PLATFORM := windows
endif
ifneq (,$(findstring CYGWIN,$(UNAME)))
  PLATFORM := windows
endif

# -- Toolchain ----------------------------------------------------------------

CC       := clang
CCACHE   := $(shell command -v ccache 2>/dev/null)
ifdef CCACHE
  CC := ccache $(CC)
endif

NPROC := $(shell nproc 2>/dev/null || sysctl -n hw.ncpu 2>/dev/null || echo 4)

# -- Directories --------------------------------------------------------------

BUILDDIR  = .build/$(MODE)
OBJDIR    = $(BUILDDIR)/obj
BINDIR    = bin/$(MODE)
SHADERDIR = $(BUILDDIR)/shaders/cpu

# -- Mode (debug/release) ----------------------------------------------------

MODE ?= debug

ifeq ($(MODE),release)
  CFLAGS  = -std=c23 -Wall -Wextra -Wno-missing-braces \
            -O3 -flto -ffast-math -funroll-loops \
            -fno-unwind-tables -fno-asynchronous-unwind-tables
  LDFLAGS = -flto
  STRIP   = true
else
  CFLAGS  = -std=c23 -Wall -Wextra -Wno-missing-braces -g -O1 -DDEBUG
  LDFLAGS =
  STRIP   = false
endif

DEP_CFLAGS = -O3 -funroll-loops -MMD -MP
CFLAGS    += -MMD -MP

# -- Linker -------------------------------------------------------------------

ifeq ($(PLATFORM),windows)
  LDFLAGS += -fuse-ld=lld
else
  MOLD := $(shell command -v mold 2>/dev/null)
  ifdef MOLD
    LDFLAGS += -fuse-ld=mold
  endif
endif

ifeq ($(PLATFORM),windows)
  LIBS     = -lws2_32
  LDFLAGS += -Wl,/SUBSYSTEM:CONSOLE
  PXL8_DEF = $(BUILDDIR)/pxl8.def
  EXE_EXT  = .exe
else ifeq ($(UNAME),Darwin)
  LIBS     = -lm
  export MACOSX_DEPLOYMENT_TARGET := $(shell sw_vers -productVersion | cut -d '.' -f 1)
else
  LIBS     = -lm
  LDFLAGS += -rdynamic
  LIBS    += -ldl
endif

EXE_EXT ?=

# -- SDL3 detection -----------------------------------------------------------

ifeq ($(PLATFORM),windows)
  # Windows: always vendor SDL3, ship SDL3.dll alongside the .exe
  SDL3_BUILD_DIR := $(firstword $(wildcard lib/SDL/build/Release lib/SDL/build/Debug))
  ifneq ($(SDL3_BUILD_DIR),)
    SDL3_CFLAGS = -Ilib/SDL/include
    SDL3_LIBS   = -L$(SDL3_BUILD_DIR) -lSDL3
    HAS_SDL3    = 1
  endif
else
  # Unix: check vendored build, then system
  ifneq (,$(wildcard lib/SDL/build/libSDL3.*))
    SDL3_CFLAGS = -Ilib/SDL/include
    SDL3_LIBS   = -Llib/SDL/build -lSDL3 -Wl,-rpath,$(CURDIR)/lib/SDL/build
    HAS_SDL3    = 1
  else
    SDL3_CFLAGS := $(shell pkg-config --cflags sdl3 2>/dev/null)
    SDL3_LIBS   := $(shell pkg-config --libs sdl3 2>/dev/null)
    ifneq ($(SDL3_LIBS),)
      HAS_SDL3 = 1
    endif
  endif
endif

CFLAGS += $(SDL3_CFLAGS)
LIBS   += $(SDL3_LIBS)

# -- Linenoise ----------------------------------------------------------------

ifeq ($(PLATFORM),windows)
  LINENOISE_DIR    = lib/linenoise-win32
  LINENOISE_SRCS   = $(LINENOISE_DIR)/linenoise.c $(LINENOISE_DIR)/stringbuf.c $(LINENOISE_DIR)/utf8.c
  CFLAGS          += -DPXL8_WIN32_LINENOISE -D_CRT_SECURE_NO_WARNINGS
else
  LINENOISE_DIR    = lib/linenoise
  LINENOISE_SRCS   = $(LINENOISE_DIR)/linenoise.c
endif

# -- LuaJIT ------------------------------------------------------------------

LUAJIT_LIB = lib/luajit/src/libluajit.a

ifeq ($(PLATFORM),windows)
  LUAJIT_BUILD = $(MAKE) -C lib/luajit -j$(NPROC) \
    CC="$(firstword $(CC))" HOST_CC="$(firstword $(CC))" \
    TARGET_SYS=Windows BUILDMODE=static \
    MINILUA_LIBS= TARGET_XLIBS= Q=
else
  LUAJIT_BUILD = $(MAKE) -C lib/luajit -j$(NPROC) \
    CC="$(firstword $(CC))" HOST_CC="$(firstword $(CC))" Q= > /dev/null 2>&1
endif

# -- Include paths ------------------------------------------------------------

INCLUDES = \
  -Isrc/asset -Isrc/core -Isrc/gfx -Isrc/gui -Isrc/platform \
  -Isrc/math -Isrc/net -Isrc/procgen -Isrc/script -Isrc/shader \
  -Isrc/sfx \
  -I$(LINENOISE_DIR) -Ilib/luajit/src -Ilib/miniz -I.build/shaders/c

DEMO3D_INCLUDES = \
  -Idemo3d/client/bsp \
  -Idemo3d/client/world \
  -Idemo3d/client/sim \
  -Idemo3d/client/net

# -- Source files -------------------------------------------------------------

LIB_SRCS = $(LINENOISE_SRCS) lib/miniz/miniz.c

PXL8_SRCS = \
  src/asset/pxl8_ase.c \
  src/asset/pxl8_cart.c \
  src/asset/pxl8_save.c \
  src/core/pxl8.c \
  src/core/pxl8_bytes.c \
  src/core/pxl8_io.c \
  src/core/pxl8_log.c \
  src/core/pxl8_replay.c \
  src/core/pxl8_rng.c \
  src/gfx/pxl8_camera3d.c \
  src/gfx/pxl8_anim.c \
  src/gfx/pxl8_atlas.c \
  src/gfx/pxl8_blit.c \
  src/gfx/pxl8_colormap.c \
  src/gfx/pxl8_dither.c \
  src/gfx/pxl8_blit3d.c \
  src/gfx/pxl8_shader_registry.c \
  src/gfx/pxl8_shader_runtime.c \
  src/gfx/pxl8_font.c \
  src/gfx/pxl8_gfx.c \
  src/gfx/pxl8_glows.c \
  src/gfx/pxl8_lightmap.c \
  src/gfx/pxl8_lights.c \
  src/gfx/pxl8_mesh.c \
  src/gfx/pxl8_palette.c \
  src/gfx/pxl8_particles.c \
  src/gfx/pxl8_tilemap.c \
  src/gfx/pxl8_tilesheet.c \
  src/gfx/pxl8_transition.c \
  src/gui/pxl8_gui.c \
  src/platform/pxl8_platform_sdl3.c \
  src/platform/pxl8_thread_sdl3.c \
  src/math/pxl8_math.c \
  src/math/pxl8_noise.c \
  src/net/pxl8_net.c \
  src/procgen/pxl8_graph.c \
  src/script/pxl8_repl.c \
  src/script/pxl8_script.c \
  src/sfx/pxl8_sfx.c

DEMO3D_SRCS = \
  demo3d/client/bsp/demo3d_bsp.c \
  demo3d/client/bsp/demo3d_bsp_render.c \
  demo3d/client/world/demo3d_world.c \
  demo3d/client/world/demo3d_chunk.c \
  demo3d/client/world/demo3d_chunk_cache.c \
  demo3d/client/world/demo3d_entity.c \
  demo3d/client/sim/demo3d_sim.c \
  demo3d/client/net/demo3d_protocol.c \
  demo3d/client/net/demo3d_net.c \
  demo3d/client/demo3d.c

ifeq ($(HAS_SDL3),1)
  PXL8_SRCS += src/platform/pxl8_io_sdl3.c src/platform/pxl8_mem_sdl3.c
else
  PXL8_SRCS += src/platform/pxl8_mem.c
endif

# -- Object files -------------------------------------------------------------

LIB_OBJS    = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(LIB_SRCS)))
PXL8_OBJS   = $(patsubst %.c,$(OBJDIR)/%.o,$(notdir $(PXL8_SRCS)))
DEMO3D_OBJS = $(patsubst %.c,$(OBJDIR)/demo3d_%.o,$(notdir $(DEMO3D_SRCS)))
ALL_OBJS    = $(LIB_OBJS) $(PXL8_OBJS)

# -- Shader objects -----------------------------------------------------------

SHADER_SRCS = $(wildcard src/gfx/shaders/cpu/*.c)
SHADER_OBJS = $(patsubst src/gfx/shaders/cpu/%.c,$(SHADERDIR)/obj/%.o,$(SHADER_SRCS))
SHADER_INCLUDES = -Isrc/core -Isrc/gfx -Isrc/math

# -- Targets ------------------------------------------------------------------

CLIENT  = $(BINDIR)/pxl8$(EXE_EXT)
SERVER  = $(BINDIR)/demo3d-server$(EXE_EXT)
DEMO3D  = $(BINDIR)/demo3d$(EXE_EXT)

CART             ?= demo
PROFILE_DURATION ?= 30
ASE_CMD          ?=

.PHONY: all release client server demo3d demo3d-server run run-demo3d \
        clean clean-all clean-deps clean-cache \
        update vendor-sdl deps luajit install ase profile help

all: client demo3d demo3d-server

release:
	$(MAKE) MODE=release all

# -- Dependencies -------------------------------------------------------------

DEPS_OK = lib/luajit/src/luajit.c lib/miniz/miniz.c lib/fennel/fennel.lua $(LINENOISE_DIR)/linenoise.c

deps: $(DEPS_OK)

lib/luajit/src/luajit.c:
	$(INFO) "Fetching LuaJIT"
	@rm -rf lib/luajit
	@git clone --quiet --branch v2.1 https://github.com/LuaJIT/LuaJIT.git lib/luajit

lib/linenoise/linenoise.c:
	@mkdir -p lib/linenoise
	$(INFO) "Fetching linenoise"
	@curl -sL -o lib/linenoise/linenoise.c https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.c
	@curl -sL -o lib/linenoise/linenoise.h https://raw.githubusercontent.com/antirez/linenoise/master/linenoise.h

lib/linenoise-win32/linenoise.c:
	$(INFO) "Fetching linenoise-win32"
	@rm -rf lib/linenoise-win32
	@git clone --quiet --depth 1 https://github.com/msteveb/linenoise.git lib/linenoise-win32

lib/miniz/miniz.c:
	@mkdir -p lib/miniz
	$(INFO) "Fetching miniz"
	@curl -sL -o /tmp/miniz.zip "https://github.com/richgel999/miniz/releases/download/3.1.0/miniz-3.1.0.zip"
	@unzip -qjo /tmp/miniz.zip miniz.c miniz.h -d lib/miniz/
	@rm -f /tmp/miniz.zip

lib/fennel/fennel.lua:
	@mkdir -p lib/fennel
	$(INFO) "Fetching Fennel"
	@curl -sL -o lib/fennel/fennel.lua "https://fennel-lang.org/downloads/fennel-1.6.1.lua"

update:
	@rm -f lib/linenoise/linenoise.c lib/miniz/miniz.c lib/fennel/fennel.lua
	@rm -rf lib/luajit lib/linenoise-win32
	$(MAKE) deps

vendor-sdl:
	$(INFO) "Fetching SDL3"
	@if [ -d lib/SDL/.git ]; then cd lib/SDL && git pull --quiet origin main; \
	 else rm -rf lib/SDL && git clone --quiet https://github.com/libsdl-org/SDL.git lib/SDL; fi
	$(INFO) "Building SDL3"
	@mkdir -p lib/SDL/build && cd lib/SDL/build && \
	 cmake .. -DCMAKE_BUILD_TYPE=Release && \
	 cmake --build . --config Release --parallel $(NPROC)
	$(INFO) "Built SDL3"

# -- LuaJIT build ------------------------------------------------------------

luajit: $(LUAJIT_LIB)

$(LUAJIT_LIB): lib/luajit/src/luajit.c
	$(INFO) "Building LuaJIT"
	@$(LUAJIT_BUILD)
	$(INFO) "Built LuaJIT"

# -- Server (Rust) ------------------------------------------------------------

server: demo3d-server

demo3d-server: $(SERVER)

$(SERVER): $(wildcard demo3d/server/src/*.rs demo3d/server/Cargo.toml)
	@mkdir -p $(BINDIR)
	$(INFO) "Building demo3d-server ($(MODE) mode)"
ifeq ($(MODE),release)
	@cd demo3d/server && PATH="$$(echo $$PATH | tr ':' '\n' | grep -v '/usr/bin' | tr '\n' ':')" cargo build --release --quiet
	@cp demo3d/server/target/release/demo3d-server$(EXE_EXT) $(SERVER)
else
	@cd demo3d/server && PATH="$$(echo $$PATH | tr ':' '\n' | grep -v '/usr/bin' | tr '\n' ':')" cargo build --quiet
	@cp demo3d/server/target/debug/demo3d-server$(EXE_EXT) $(SERVER)
endif
	$(INFO) "Built demo3d-server"

# -- Client (C23) -------------------------------------------------------------

client: deps $(CLIENT)

$(CLIENT): $(ALL_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB)
	@mkdir -p $(BINDIR)
	$(INFO) "Linking pxl8"
ifeq ($(PLATFORM),windows)
	@(echo EXPORTS && llvm-nm --defined-only $(ALL_OBJS) $(SHADER_OBJS) | grep ' T pxl8_\| T SDL' | awk '{print $$3}') > $(PXL8_DEF)
	@MSYS_NO_PATHCONV=1 $(CC) $(LDFLAGS) $(ALL_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB) $(LIBS) -Wl,/DEF:$(PXL8_DEF) -o $@
	@cp -u $(SDL3_BUILD_DIR)/SDL3.dll $(BINDIR)/
else
	@$(CC) $(LDFLAGS) $(ALL_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB) $(LIBS) -o $@
endif
ifeq ($(STRIP),true)
	@llvm-strip $@
endif
	$(INFO) "Built pxl8 -> $@"

# -- Demo3D (game client) ----------------------------------------------------

demo3d: deps $(DEMO3D)

$(DEMO3D): $(ALL_OBJS) $(DEMO3D_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB)
	@mkdir -p $(BINDIR)
	$(INFO) "Linking demo3d"
ifeq ($(PLATFORM),windows)
	@(echo EXPORTS && llvm-nm --defined-only $(ALL_OBJS) $(DEMO3D_OBJS) $(SHADER_OBJS) | grep ' T pxl8_\| T demo3d_\| T SDL' | awk '{print $$3}') > $(BUILDDIR)/demo3d.def
	@MSYS_NO_PATHCONV=1 $(CC) $(LDFLAGS) $(ALL_OBJS) $(DEMO3D_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB) $(LIBS) -Wl,/DEF:$(BUILDDIR)/demo3d.def -o $@
	@cp -u $(SDL3_BUILD_DIR)/SDL3.dll $(BINDIR)/
else
	@$(CC) $(LDFLAGS) $(ALL_OBJS) $(DEMO3D_OBJS) $(SHADER_OBJS) $(LUAJIT_LIB) $(LIBS) -o $@
endif
ifeq ($(STRIP),true)
	@llvm-strip $@
endif
	$(INFO) "Built demo3d -> $@"

run-demo3d: demo3d demo3d-server
	@$(BINDIR)/demo3d-server$(EXE_EXT) & SERVER_PID=$$!; \
	 sleep 0.5; \
	 $(BINDIR)/demo3d$(EXE_EXT) demo3d $(EXTRA_ARGS) || true; \
	 kill $$SERVER_PID 2>/dev/null; wait $$SERVER_PID 2>/dev/null || true

# -- Compile rules ------------------------------------------------------------

# Lib sources (dep flags, no warnings)
$(OBJDIR)/linenoise.o: $(LINENOISE_DIR)/linenoise.c | $(OBJDIR)
	$(CC_) "$<"
	@$(CC) -c $(DEP_CFLAGS) $(INCLUDES) $< -o $@

$(OBJDIR)/stringbuf.o: $(LINENOISE_DIR)/stringbuf.c | $(OBJDIR)
	$(CC_) "$<"
	@$(CC) -c $(DEP_CFLAGS) $(INCLUDES) $< -o $@

$(OBJDIR)/utf8.o: $(LINENOISE_DIR)/utf8.c | $(OBJDIR)
	$(CC_) "$<"
	@$(CC) -c $(DEP_CFLAGS) $(INCLUDES) $< -o $@

$(OBJDIR)/miniz.o: lib/miniz/miniz.c | $(OBJDIR)
	$(CC_) "$<"
	@$(CC) -c $(DEP_CFLAGS) $(INCLUDES) $< -o $@

# Pattern rules for pxl8 sources by directory
define PXL8_COMPILE_RULE
$(OBJDIR)/%.o: $(1)/%.c | $(OBJDIR)
	@printf '$(CYAN)[%s CC]$(NC) %s\n' "$$$$(date +%H:%M:%S)" "$$<"
	@$$(CC) -c $$(CFLAGS) $$(INCLUDES) $$< -o $$@
endef

$(foreach dir,src/asset src/core src/gfx src/gui src/platform src/math src/net src/procgen src/script src/shader src/sfx,$(eval $(call PXL8_COMPILE_RULE,$(dir))))

define DEMO3D_COMPILE_RULE
$(OBJDIR)/demo3d_%.o: $(1)/%.c | $(OBJDIR)
	@printf '$(CYAN)[%s CC]$(NC) %s\n' "$$$$(date +%H:%M:%S)" "$$<"
	@$$(CC) -c $$(CFLAGS) $$(INCLUDES) $$(DEMO3D_INCLUDES) $$< -o $$@
endef

$(foreach dir,demo3d/client demo3d/client/bsp demo3d/client/world demo3d/client/sim demo3d/client/net,$(eval $(call DEMO3D_COMPILE_RULE,$(dir))))

# pxl8_script.o depends on embedded Lua/Fennel sources
LUA_SRCS = $(wildcard src/lua/*.lua src/lua/pxl8/*.lua) lib/fennel/fennel.lua
$(OBJDIR)/pxl8_script.o: $(LUA_SRCS)

# Shader objects
$(SHADERDIR)/obj/%.o: src/gfx/shaders/cpu/%.c | $(SHADERDIR)/obj
	$(SHD_) "$<"
	@$(CC) -c -O2 $(SHADER_INCLUDES) $< -o $@

# -- Directory creation -------------------------------------------------------

$(OBJDIR) $(SHADERDIR)/obj:
	@mkdir -p $@

# -- Gitignore bookkeeping ---------------------------------------------------

$(shell mkdir -p bin .build lib 2>/dev/null)
$(shell [ -f bin/.gitignore ] || echo '*' > bin/.gitignore)
$(shell [ -f .build/.gitignore ] || echo '*' > .build/.gitignore)
$(shell [ -f lib/.gitignore ] || echo '*' > lib/.gitignore)

# -- Run ----------------------------------------------------------------------

run: client
	@$(BINDIR)/pxl8$(EXE_EXT) $(CART) $(EXTRA_ARGS)

# -- Clean --------------------------------------------------------------------

clean:
	rm -rf .build/debug .build/release bin/debug bin/release .build/shaders
	@if [ -d demo3d/server ]; then cd demo3d/server && cargo clean 2>/dev/null; fi || true

clean-all: clean
	rm -rf lib

clean-deps:
	rm -rf lib

clean-cache:
	@if command -v ccache >/dev/null 2>&1; then ccache -C; else echo "ccache not found"; fi

# -- Install ------------------------------------------------------------------

install:
	$(MAKE) MODE=release client
	@mkdir -p $(HOME)/.local/bin
	@cp bin/release/pxl8$(EXE_EXT) $(HOME)/.local/bin/pxl8$(EXE_EXT)
	@chmod +x $(HOME)/.local/bin/pxl8$(EXE_EXT)
	$(INFO) "Installed pxl8 to $(HOME)/.local/bin/pxl8$(EXE_EXT)"

# -- Aseprite tools -----------------------------------------------------------

ase:
	@bash tools/aseprite/pxl8-ase.sh $(ASE_CMD)

# -- Profile (Linux) ----------------------------------------------------------

profile: client
	@if [ "$$(uname)" != "Linux" ]; then printf '$(RED)[ERROR]$(NC) %s\n' "Profiling requires Linux + perf"; exit 1; fi
	@if ! command -v perf >/dev/null 2>&1; then printf '$(RED)[ERROR]$(NC) %s\n' "perf not found"; exit 1; fi
	@if [ ! -d lib/FlameGraph ]; then \
	  echo "[INFO] Fetching FlameGraph"; \
	  git clone --quiet https://github.com/brendangregg/FlameGraph.git lib/FlameGraph; \
	fi
	@mkdir -p .build/$(MODE)/profile; \
	 TIMESTAMP=$$(date +%Y%m%d_%H%M%S); \
	 PDIR=.build/$(MODE)/profile; \
	 echo "[INFO] Starting server..."; \
	 $(BINDIR)/demo3d-server$(EXE_EXT) & SERVER_PID=$$!; \
	 sleep 0.5; \
	 echo "[INFO] Profiling for $(PROFILE_DURATION)s (Ctrl+C to stop early)..."; \
	 perf record -F 99 -g --call-graph dwarf -o $$PDIR/perf_$$TIMESTAMP.data -- \
	   timeout $(PROFILE_DURATION)s $(BINDIR)/pxl8$(EXE_EXT) $(CART) 2>/dev/null || true; \
	 kill $$SERVER_PID 2>/dev/null; wait $$SERVER_PID 2>/dev/null || true; \
	 echo "[INFO] Generating flamegraph..."; \
	 perf script -i $$PDIR/perf_$$TIMESTAMP.data > $$PDIR/perf_$$TIMESTAMP.perf; \
	 lib/FlameGraph/stackcollapse-perf.pl $$PDIR/perf_$$TIMESTAMP.perf > $$PDIR/perf_$$TIMESTAMP.folded; \
	 lib/FlameGraph/flamegraph.pl --cp --colors orange --title "pxl8 profile" \
	   $$PDIR/perf_$$TIMESTAMP.folded > $$PDIR/flamegraph_$$TIMESTAMP.svg; \
	 rm -f $$PDIR/perf_$$TIMESTAMP.data $$PDIR/perf_$$TIMESTAMP.perf $$PDIR/perf_$$TIMESTAMP.folded; \
	 echo "[INFO] Flamegraph: $$PDIR/flamegraph_$$TIMESTAMP.svg"; \
	 command -v xdg-open >/dev/null 2>&1 && xdg-open $$PDIR/flamegraph_$$TIMESTAMP.svg 2>/dev/null &

# -- Help ---------------------------------------------------------------------

help:
	@echo "pxl8 - framework build system"
	@echo ""
	@echo "TARGETS:"
	@echo "  make                Build everything (debug)"
	@echo "  make release        Build everything (release)"
	@echo "  make run            Build and run      (CART=\"demo --repl\")"
	@echo "  make client         Build pxl8 only"
	@echo "  make demo3d-server  Build demo3d client"
	@echo "  make server         Build demo3d-server"
	@echo "  make install        Install to ~/.local/bin (release)"
	@echo "  make profile        Profile with perf  (PROFILE_DURATION=30, Linux only)"
	@echo "  make ase            Aseprite tools      (ASE_CMD=package|clean)"
	@echo "  make clean          Remove build artifacts"
	@echo "  make clean-all      Remove artifacts + dependencies"
	@echo "  make clean-deps     Remove only dependencies"
	@echo "  make clean-cache    Clear ccache"
	@echo "  make update         Fetch/update all dependencies"
	@echo "  make vendor-sdl     Vendor SDL3 from source"
	@echo ""
	@echo "VARIABLES:"
	@echo "  MODE=release        Build mode (default: debug)"
	@echo "  CART=demo           Cart or script to run"

# -- Auto-generated header dependencies --------------------------------------

-include $(ALL_OBJS:.o=.d)
-include $(SHADER_OBJS:.o=.d)

# -- compile_commands.json ----------------------------------------------------

compile_commands.json: $(LIB_SRCS) $(PXL8_SRCS) $(DEMO3D_SRCS)
	$(INFO) "Generating compile_commands.json"
	@echo '[' > $@.tmp
	@first=true; \
	for f in $(LIB_SRCS); do \
	  if $$first; then first=false; else printf ',\n' >> $@.tmp; fi; \
	  printf '  {"directory":"%s","command":"clang -c %s %s","file":"%s"}' \
	    "$(CURDIR)" "$(DEP_CFLAGS) $(INCLUDES)" "$$f" "$$f" >> $@.tmp; \
	done; \
	for f in $(PXL8_SRCS); do \
	  if $$first; then first=false; else printf ',\n' >> $@.tmp; fi; \
	  printf '  {"directory":"%s","command":"clang -c %s %s","file":"%s"}' \
	    "$(CURDIR)" "$(CFLAGS) $(INCLUDES)" "$$f" "$$f" >> $@.tmp; \
	done; \
	for f in $(DEMO3D_SRCS); do \
	  if $$first; then first=false; else printf ',\n' >> $@.tmp; fi; \
	  printf '  {"directory":"%s","command":"clang -c %s %s %s","file":"%s"}' \
	    "$(CURDIR)" "$(CFLAGS) $(INCLUDES)" "$(DEMO3D_INCLUDES)" "$$f" "$$f" >> $@.tmp; \
	done
	@printf '\n]\n' >> $@.tmp
	@mv $@.tmp $@
