SCHEME     = scheme
JERBOA_HOME ?= $(HOME)/mine/jerboa
LIBDIRS    = --libdirs $(JERBOA_HOME)/lib:./lib:vendor/chez-sqlite/src
JERBUILD   = $(SCHEME) --libdirs $(JERBOA_HOME)/lib --script $(JERBOA_HOME)/jerbuild.ss

# Library paths for FFI shared objects (macOS: dylib, Linux: so)
SQLITE_LIB_DIR := $(shell brew --prefix sqlite 2>/dev/null)/lib
SHIM_DIR       := $(CURDIR)/vendor/chez-sqlite
TUI_SHIM_DIR   := $(CURDIR)/vendor/termbox2
NATIVE_LIB_DIR := $(JERBOA_HOME)/lib
LDPATH         := $(SHIM_DIR):$(TUI_SHIM_DIR):$(SQLITE_LIB_DIR):$(NATIVE_LIB_DIR)

.PHONY: all help build gen run test clean repl binary install tui-shim run-tui jcode-musl linux linux-check linux-local docker purge-stale sqlite-shim android android-clean linux-amd64 linux-arm64

all: help

help:
	@echo "Available targets:"
	@echo "  build        Compile src/ → lib/ and pre-compile imports"
	@echo "  test         Run test suite (test/run.ss)"
	@echo "  run          Start interactive agent REPL"
	@echo "  run-tui      Start TUI mode"
	@echo "  repl         Open a bare Scheme REPL with project libdirs"
	@echo "  gen          Run jerbuild + rebuild sqlite/TUI shims"
	@echo "  binary       Build native binary (macOS)"
	@echo "  install      Install binary to ~/.local/bin"
	@echo "  linux        Build static musl binary via Docker"
	@echo "  linux-local  Build static musl binary on host"
	@echo "  linux-amd64  Cross-compile static jcode-linux-amd64 from macOS"
	@echo "  linux-arm64  Cross-compile static jcode-linux-arm64 from macOS"
	@echo "  linux-check  Fast drift check for musl build path"
	@echo "  clean        Remove compiled artifacts and binaries"
	@echo "  purge-stale  Remove stale .so/.wpo files"
	@echo "  sqlite-shim  Rebuild chez_sqlite_shim.so"
	@echo "  tui-shim     Rebuild jcode_tui_shim"
	@echo "  android      Build Android APK in Termux (android/build/*.apk)"
	@echo "  android-clean  Remove android/build/"

# ── Stale-artifact prevention ───────────────────────────────────────────────
# Delete any compiled .so/.wpo whose source .ss/.sls is newer (or whose
# source no longer exists). This makes "make build" idempotent and immune
# to the class of bug where a stale .so silently linked against an old
# shim causes a runtime "no entry for X" foreign-procedure error.
#
# Always also clear the prebuilt vendor/chez-sqlite artifacts — they pin
# absolute paths to the shim and are cheap to recompile.
purge-stale:
	@find lib -name "*.so" -o -name "*.wpo" 2>/dev/null | while read f; do \
	  src=$$(echo $$f | sed -e 's|^lib/|src/|' -e 's|\.so$$|.ss|' -e 's|\.wpo$$|.ss|'); \
	  sls=$$(echo $$f | sed 's|\.\(so\|wpo\)$$|.sls|'); \
	  if [ ! -f "$$src" ] && [ ! -f "$$sls" ]; then rm -f "$$f"; \
	  elif [ -f "$$src" ] && [ "$$src" -nt "$$f" ]; then rm -f "$$f"; \
	  elif [ -f "$$sls" ] && [ "$$sls" -nt "$$f" ]; then rm -f "$$f"; \
	  fi; \
	done
	@rm -f vendor/chez-sqlite/src/chez-sqlite.so vendor/chez-sqlite/src/chez-sqlite.wpo

# ── chez-sqlite shim build ──────────────────────────────────────────────────
# Rebuild the FFI shim if its C source is newer than the .so. This catches
# the case where vendor/chez-sqlite is updated but the .so isn't refreshed.
sqlite-shim:
	@if [ ! -f vendor/chez-sqlite/chez_sqlite_shim.so ] || \
	    [ vendor/chez-sqlite/chez_sqlite_shim.c -nt vendor/chez-sqlite/chez_sqlite_shim.so ]; then \
	  echo "=== Rebuilding chez_sqlite_shim.so ==="; \
	  cc -shared -fPIC -O2 \
	    -I$(SQLITE_LIB_DIR)/../include -L$(SQLITE_LIB_DIR) \
	    -o vendor/chez-sqlite/chez_sqlite_shim.so \
	    vendor/chez-sqlite/chez_sqlite_shim.c -lsqlite3; \
	fi

gen: purge-stale sqlite-shim
	$(JERBUILD) src lib

build: gen
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) $(LIBDIRS) --compile-imported-libraries --script main.ss < /dev/null

run:
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) $(LIBDIRS) --script main.ss

tui-shim:
	@test -f $(TUI_SHIM_DIR)/jcode_tui_shim.dylib \
	  -o -f $(TUI_SHIM_DIR)/jcode_tui_shim.so || \
	  cc -shared -fPIC -DTB_OPT_ATTR_W=32 \
	    -I$(TUI_SHIM_DIR) \
	    -o $(TUI_SHIM_DIR)/jcode_tui_shim.dylib \
	    src/jcode/ui/jcode_tui_shim.c

run-tui: build tui-shim
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) $(LIBDIRS) --script main.ss --tui

repl:
	$(SCHEME) $(LIBDIRS)

test: build
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) $(LIBDIRS) --script test/run.ss

# --- Static binary targets ---

binary: build
	JERBOA_HOME=$(JERBOA_HOME) \
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) -q --libdirs $(JERBOA_HOME)/lib:./lib:vendor/chez-sqlite/src \
	--script build-binary.ss
	cp vendor/termbox2/jcode_tui_shim.dylib ./jcode_tui_shim.dylib 2>/dev/null || true
	cp vendor/termbox2/jcode_tui_shim.so ./jcode_tui_shim.so 2>/dev/null || true

install: binary
	mkdir -p $(HOME)/.local/bin
	cp jcode $(HOME)/.local/bin/jcode
	cp vendor/termbox2/jcode_tui_shim.dylib $(HOME)/.local/bin/jcode_tui_shim.dylib 2>/dev/null || true
	cp vendor/chez-sqlite/chez_sqlite_shim.so $(PREFIX)/lib/chez_sqlite_shim.so 2>/dev/null || true
	@echo "Installed to ~/.local/bin/jcode"

linux: linux-check docker

# ── Fast local drift check for the musl build path ─────────────────────────
# Runs compile-program on main-binary.ss with the same flags the musl build
# uses, but skips sed patching, the C compile, and the linker. Catches drift
# (new imports, removed stdlib exports, broken except clauses) on the dev's
# laptop in ~10 seconds, before burning 5+ minutes in Docker.
#
# `linux` and `linux-local` both depend on this target so they fail fast.

linux-check: gen
	@echo "=== Running linux-check (fast musl-build drift check) ==="
	DYLD_LIBRARY_PATH=$(LDPATH) LD_LIBRARY_PATH=$(LDPATH) \
	$(SCHEME) -q $(LIBDIRS) --script linux-check.ss

# ── Docker build (canonical static binary, zero runtime deps) ───────────────
# Use `make linux` to build in Docker (canonical, reproducible).
# Use `make linux-local` to build directly on the host (requires
# musl-gcc and a musl-built Chez at ~/chez-musl or JERBOA_MUSL_CHEZ_PREFIX).

docker:
	@echo "=== Building jcode-musl in Docker ==="
	docker build --platform linux/amd64 --build-arg CACHE_BUST=$$(date +%s) -t jcode-builder .
	@id=$$(docker create --platform linux/amd64 jcode-builder) && \
	docker cp $$id:/out/jcode-musl ./jcode-musl && \
	docker rm $$id >/dev/null && \
	chmod +x jcode-musl
	@echo ""
	@ls -lh jcode-musl
	@file jcode-musl

# ─── musl Static Binary ──────────────────────────────────────────────────────
# Build a fully static jcode binary using musl libc.
# Requires: musl-gcc, Chez Scheme built with musl (~/chez-musl),
#           jerboa-native-rs built for x86_64-unknown-linux-musl.

linux-local: linux-check
	@echo "=== Building static jcode with musl ==="
	JERBOA_HOME=$(JERBOA_HOME) ./build-jcode-musl.sh

jcode-musl: linux-local

freebsd: build
	JERBOA_HOME=$(JERBOA_HOME) \
	$(SCHEME) -q --libdirs $(JERBOA_HOME)/lib:./lib:vendor/chez-sqlite/src \
	--script build-binary.ss

# ── Linux musl cross from macOS ─────────────────────────────────────────────
# Requires a cross-built Chez for the target machine (ta6le or tarm64le) at
# $(JERBOA_HOME)/.chez-cross-<machine>, plus the matching musl-cross toolchain
# on PATH (FiloSottile/musl-cross via Homebrew: x86_64-linux-musl-gcc and/or
# aarch64-linux-musl-gcc).
linux-amd64: build
	@echo "=== Cross-compiling jcode-linux-amd64 ==="
	JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=amd64 \
	$(SCHEME) -q --libdirs $(JERBOA_HOME)/lib:./lib:vendor/chez-sqlite/src \
	--script build-jcode-cross.ss

linux-arm64: build
	@echo "=== Cross-compiling jcode-linux-arm64 ==="
	JERBOA_HOME=$(JERBOA_HOME) TARGET_ARCH=arm64 \
	$(SCHEME) -q --libdirs $(JERBOA_HOME)/lib:./lib:vendor/chez-sqlite/src \
	--script build-jcode-cross.ss

clean:
	find lib/jcode -name "*.sls" -delete 2>/dev/null; true
	find . -name "*.so" -delete
	find . -name "*.wpo" -delete
	rm -f jcode jcode-musl jcode-musl.sha256

# ── Android APK (thin client, no embedded binary) ───────────────────────────
# Build from Termux — no Android Studio / Gradle required.
# Requires Termux packages: aapt2 d8 kotlin openjdk-17 apksigner android-tools
# Requires android/android.jar (API 30+) — not checked in.
#
# The APK is a thin GUI client that connects to `jcode serve` over localhost.

android:
	@command -v bash >/dev/null || { echo "bash required"; exit 1; }
	@test -f android/android.jar || { \
	  echo "Missing android/android.jar (API 30+ platform jar)."; \
	  echo "Download one, e.g.:"; \
	  echo "  curl -L -o android/android.jar https://github.com/Sable/android-platforms/raw/master/android-30/android.jar"; \
	  exit 1; }
	@for cmd in aapt2 d8 kotlinc keytool apksigner zipalign; do \
	  command -v $$cmd >/dev/null || { \
	    echo "Missing tool: $$cmd"; \
	    echo "Install with: pkg install aapt2 d8 kotlin openjdk-17 apksigner android-tools"; \
	    exit 1; }; \
	done
	bash android/build-apk.sh
	@echo ""
	@echo "APK(s) in android/build/:"
	@ls -lh android/build/*.apk 2>/dev/null || echo "  (none found — check build-apk.sh output above)"

android-clean:
	rm -rf android/build
