# ═══════════════════════════════════════════════════════════════════
# wasm/Makefile — three-tier Lumbda to WebAssembly
# ═══════════════════════════════════════════════════════════════════
#
# Builds:
#   python/  — Pyodide loader + lumbda.py (CPython-in-WASM)
#   c/       — Emscripten build of c/ (tree-walker + bytecode VM, no JIT)
#   asm/     — Hand-written WAT compiled to wasm (parallel "asm" tier)
#   app/     — Single-page app shell (HTML + CodeMirror + JS glue)
#
# Targets:
#   make build        all three tiers + SPA bundle in dist/
#   make test         unit (node) + integration (node, cross-tier diff)
#   make test-fn      functional (playwright headless browser)
#   make serve        local dev server on :8080
#   make deploy       copy dist/ -> ../www/playground/
#   make clean

EMSDK_DIR  := $(HOME)/git/emsdk
EMSDK_ENV  := $(EMSDK_DIR)/emsdk_env.sh
EMCC       := $(EMSDK_DIR)/upstream/emscripten/emcc
WAT2WASM   := $(HOME)/git/wabt/bin/wat2wasm
WASM_VALIDATE := $(HOME)/git/wabt/bin/wasm-validate

REPO_ROOT  := $(abspath ..)
C_SRC_DIR  := $(REPO_ROOT)/c
PY_SRC     := $(REPO_ROOT)/lumbda.py
STDLIB     := $(REPO_ROOT)/stdlib.lsp

DIST       := dist

# Pyodide pinned version
PYODIDE_VER := 0.27.2
PYODIDE_CDN := https://cdn.jsdelivr.net/pyodide/v$(PYODIDE_VER)/full

# ─── Top-level ─────────────────────────────────────────────────────

.PHONY: all build test test-unit test-integration test-fn serve deploy clean \
        python c asm app dist-bundle check-tools

all: build

build: check-tools python c asm app dist-bundle

check-tools:
	@test -x $(EMCC) || { echo "emcc missing at $(EMCC)"; exit 1; }
	@test -x $(WAT2WASM) || { echo "wat2wasm missing at $(WAT2WASM)"; exit 1; }
	@command -v node >/dev/null || { echo "node missing"; exit 1; }

# ─── Python tier (Pyodide) ─────────────────────────────────────────
#
# Pyodide loads CPython into WASM. We ship a small JS loader that:
#   1. Imports Pyodide from CDN (pinned version)
#   2. Drops lumbda.py + stdlib.lsp into Pyodide's virtual FS
#   3. Exposes evalLisp(src) -> string

python: $(DIST)/python/lumbda-py.js $(DIST)/python/lumbda.py $(DIST)/python/stdlib.lsp

$(DIST)/python/lumbda-py.js: python/lumbda-py.js
	@mkdir -p $(@D)
	@cp $< $@

$(DIST)/python/lumbda.py: $(PY_SRC)
	@mkdir -p $(@D)
	@cp $< $@

$(DIST)/python/stdlib.lsp: $(STDLIB)
	@mkdir -p $(@D)
	@cp $< $@

# ─── C tier (Emscripten) ───────────────────────────────────────────
#
# Build the C tier under emcc. Drops:
#   * jit.c    — x86_64 machine-code emitter, no WASM equivalent
#   * gc.c     — Boehm-specific tracing (replaced with malloc-no-free)
#
# Keeps tree-walker + bytecode VM + reader + printer + portal.
#
# -DLUMBDA_WASM gates conditional code paths in main.c / eval.c.

C_SOURCES := $(addprefix $(C_SRC_DIR)/, \
	reader.c printer.c eval.c vm.c types.c builtins.c bignum.c portal.c gc.c)

# jit.c is included but g_jit_enabled stays false at runtime so emit paths
# are never executed. mmap PROT_EXEC would fail in WASM anyway; harmless.
C_JIT_STUB := c/jit-stub.c

C_CFLAGS := -O2 -DLUMBDA_WASM -std=c11 -D_POSIX_C_SOURCE=200809L -D_GNU_SOURCE \
            -I$(C_SRC_DIR) -Wno-everything
C_LDFLAGS := -s WASM=1 -s MODULARIZE=1 -s EXPORT_ES6=1 \
             -s EXPORT_NAME=createLumbdaC \
             -s EXPORTED_FUNCTIONS='["_lumbda_wasm_init","_lumbda_wasm_eval","_lumbda_wasm_free_result","_malloc","_free"]' \
             -s EXPORTED_RUNTIME_METHODS='["cwrap","ccall","UTF8ToString","stringToUTF8","lengthBytesUTF8"]' \
             -s ALLOW_MEMORY_GROWTH=1 -s INITIAL_MEMORY=32MB \
             -s STACK_SIZE=8MB \
             -s ENVIRONMENT=web,worker,node \
             -s SINGLE_FILE=0

c: $(DIST)/c/lumbda-c.js $(DIST)/c/lumbda-c.wasm $(DIST)/c/lumbda-c.loader.js

$(DIST)/c/lumbda-c.js $(DIST)/c/lumbda-c.wasm: $(C_SOURCES) $(C_JIT_STUB) c/lumbda_wasm_entry.c
	@mkdir -p $(@D)
	EMSDK=$(EMSDK_DIR) EMSDK_NODE=$(EMSDK_DIR)/node/22.16.0_64bit/bin/node \
	PATH=$(EMSDK_DIR)/node/22.16.0_64bit/bin:$(EMSDK_DIR)/upstream/emscripten:$$PATH \
	$(EMCC) $(C_CFLAGS) $(C_SOURCES) $(C_JIT_STUB) c/lumbda_wasm_entry.c \
		$(C_LDFLAGS) -o $(DIST)/c/lumbda-c.js

$(DIST)/c/lumbda-c.loader.js: c/lumbda-c.loader.js
	@mkdir -p $(@D)
	@cp $< $@

# ─── Asm tier (hand-written WAT) ───────────────────────────────────
#
# asm/lumbda.wat is a Lisp interpreter written directly in WebAssembly
# Text format. Stack-machine assembly, no libc, raw linear memory.
# Parallel implementation to asm/lumbda.s (x86_64). Same tier semantics.

asm: $(DIST)/asm/lumbda-asm.wasm $(DIST)/asm/lumbda-asm.loader.js

$(DIST)/asm/lumbda-asm.wasm: asm/lumbda.wat
	@mkdir -p $(@D)
	$(WAT2WASM) $< -o $@
	$(WASM_VALIDATE) $@

$(DIST)/asm/lumbda-asm.loader.js: asm/lumbda-asm.loader.js
	@mkdir -p $(@D)
	@cp $< $@

# ─── SPA shell ─────────────────────────────────────────────────────

APP_SRC := $(wildcard app/*.html app/*.css app/*.js app/*.mjs app/demos/*.lsp)

app: $(DIST)/index.html $(DIST)/style.css $(DIST)/app.js $(DIST)/runner.js $(DIST)/worker.mjs \
     $(DIST)/lumbda-logo-green.png $(DIST)/fonts/chunkfive/chunkfive-regular-webfont.woff2 \
     $(DIST)/fonts/chunkfive/chunkfive-regular-webfont.woff

$(DIST)/%: app/%
	@mkdir -p $(@D)
	@cp $< $@

dist-bundle: app
	@mkdir -p $(DIST)/demos
	@cp app/demos/*.lsp $(DIST)/demos/ 2>/dev/null || true

# ─── Tests ─────────────────────────────────────────────────────────

test: test-unit test-integration

test-unit: build
	@echo "── wasm unit tests ──"
	node tests/unit.mjs

test-integration: build
	@echo "── wasm integration tests (cross-tier diff) ──"
	node tests/integration.mjs

test-fn: build
	@echo "── wasm functional tests (headless browser) ──"
	@test -d node_modules/playwright || { \
		echo "SKIP: node_modules/playwright not present (symlink or npm install)"; exit 0; }
	node tests/functional.mjs

# ─── Dev server ────────────────────────────────────────────────────

serve: build
	@echo "Serving $(DIST) on http://localhost:8080"
	@cd $(DIST) && python3 -m http.server 8080

# ─── Deploy ────────────────────────────────────────────────────────

deploy: build
	@mkdir -p $(REPO_ROOT)/www/playground
	@cp -r $(DIST)/* $(REPO_ROOT)/www/playground/
	@echo "Deployed to www/playground/"

# ─── Clean ─────────────────────────────────────────────────────────

clean:
	rm -rf $(DIST)
