MOXIE    ?= $(CURDIR)/../moxie/moxie
MOXIEJS  ?= $(CURDIR)/../moxie/moxiejs
# An inherited MOXIEROOT is ignored on purpose: `?=` cannot tell an exported
# stale root from an intended one, and a stale root silently compiles a
# different tree's src/ and pkg/ (that is how `make build-app-wasm` failed
# with "'err' shadows declaration in enclosing scope" from an old checkout,
# while the same build with the repo root succeeds). Pass it on the command
# line to build against another tree: make MOXIEROOT=/path/to/moxie ...
ifeq ($(origin MOXIEROOT),environment)
MOXIEROOT := $(CURDIR)/../moxie
endif
MOXIEROOT ?= $(CURDIR)/../moxie
JSRUNTIME ?= $(CURDIR)/../moxie/jsruntime
ISKRA    ?= $(HOME)/.local/bin/moxie-iskra
# relay-proxy builds from source with the tree compiler. The earlier workaround
# of using an installed moxie (its cached bitcode "avoided too much recursion
# during _start()") was a symptom of a compiler bug: taking the address of a
# frame local stored a stack pointer into the receiver's arena. Fixed in
# _mxc_stage4 (markAddrEscaping mirrors the legacy compiler's Alloc.Heap), and
# a fresh build now produces the same 430KB module the installed compiler did.
MOXIE_RELAY_PROXY ?= $(MOXIE)

# Moxie closures do not capture: a closure receives what it needs through its
# parameters, and the legacy compiler rejects a capturing one. stage4 enforces
# the same rule by default. This app predates that enforcement and registers
# hundreds of handlers that close over the enclosing frame (286 sites in
# web/wasm/app alone), so every build here opts out explicitly. Porting them to
# explicit parameters or to holder types with the callback as an interface is
# tracked with the app, not the compiler; until it lands, the debt is declared
# here rather than silently relying on stage4's old acceptance.
CAPTURES ?= -allow-closure-captures

# === Relay (native) ===
build-relay:
	MOXIEROOT=$(MOXIEROOT) $(MOXIE) build $(CAPTURES) -o smesh .

# === Frontend (WASM) ===
build-app-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/app.wasm ./web/wasm/app/

build-relay-proxy-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE_RELAY_PROXY) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/relay-proxy.wasm ./web/wasm/relay-proxy/

build-store-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/store.wasm ./web/wasm/store/

build-verify-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/verify.wasm ./web/wasm/verify/

build-profile-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/profile.wasm ./web/wasm/profile/

build-feed-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/feed.wasm ./web/wasm/feed/

build-mls-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/mls.wasm ./web/wasm/mls/


build-notif-wasm:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/notif.wasm ./web/wasm/notif/

build-sw:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/\$$sw/sw.wasm ./web/wasm/sw/

# === Signer WASM (page-embedded worker) ===
# The web app uses signer.wasm directly via signer-wasm-host.mjs.
# No browser extension is required for normal operation.
build-signer:
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -o $(CURDIR)/web/static/signer.wasm ./web/wasm/signer/

# Extension build (optional, not part of default build).
build-ext-firefox: build-signer
	cp $(CURDIR)/web/static/signer.wasm $(CURDIR)/web/ext/bg/signer.wasm
	bash scripts/pack-ext.sh firefox
	cp dist/smesh-signer-firefox.xpi web/static/smesh-signer.xpi

build-ext: build-ext-firefox

# === All ===
# Direct messaging (MLS/Marmot) is parked again: the wasm worker panics in
# Moxie's codec while generating its first KeyPackage (an invalid type
# descriptor, kind=0), so its worker is not built or served and the app has no
# /msg route. The mls code, host plumbing and worker all stay in the tree;
# re-enable by restoring the sidebar entry, the router cases, the mlsWorkerTypes
# branch and build-mls-wasm in `build`/`dev` (see docs/relay-test-coverage.md).
build: build-relay build-app-wasm build-relay-proxy-wasm build-store-wasm build-verify-wasm build-profile-wasm build-feed-wasm build-notif-wasm build-signer build-sw

# === Test ===
build-test-relay:
	MOXIEROOT=$(MOXIEROOT) $(MOXIE) build $(CAPTURES) -o smesh-test .

# Unit tests: every package carrying a *_test.mx, run by the compiler's own test
# harness. Nothing ran these before - the files sat in the tree, and the ones
# that had stopped compiling or passing were invisible until they were run.
#
# UNIT_SKIP names the packages whose tests are known not to build or pass yet.
# Each one is recorded in docs/relay-test-coverage.md with its reason: declared
# debt, not silence.
#
# pkg/crawler came off the list once stage4 rejected `push` on a non-slice
# (the same error legacy gives) instead of emitting IR for it; its test helper
# pushed onto a *tag.S instead of onto its T slice, which is what the old
# "IR-emit bug" report was.
#
# web/common/marmot came off the list once its closure compiled: the mls
# test-support files shadowed the named `err` return from nested blocks, and
# the test's `wantN int` is a type this compiler no longer defines (stage4
# emits a ptr where the fmt interface slot needs {i64,ptr}, and clang refuses
# the module). Both are fixed in place; the test is int32 and uses explicit
# return after Fatal, and 6 tests pass.
#
# pkg/nostr/ws came off the list once stage4 boxed a concrete operand
# (syscall.Errno sentinels, errors.Const) into the interface representation
# before comparing it against an error value, the way legacy already did
# through go/ssa. The loopback WebSocket tests now connect and pass.
#
# pkg/lol came off the list once stage4 stopped printing a phantom
# `%!(NOVERB)` after every formatted value: its 9 tests pass.
UNIT_SKIP ?=
test-unit:
	@set -e ; for f in $$( { [ -f main_test.mx ] && echo main_test.mx ; find pkg web -name '*_test.mx' ; } | sort ) ; do \
		d=$$(dirname $$f) ; \
		case " $(UNIT_SKIP) " in *" $$d "*) echo "=== unit: $$d (skipped: see docs/relay-test-coverage.md)" ; continue ;; esac ; \
		echo "=== unit: $$d" ; \
		MOXIEROOT=$(MOXIEROOT) $(MOXIE) test $(CAPTURES) ./$$d || exit 1 ; \
	done

test-signer: build-signer
	node web/_test/signertest/run.mjs web/ext/bg/signer.wasm

# Unit coverage: the same package loop with the harness's own report. `moxie
# test -cover` writes the site table, starts the instrumented test binary and
# prints the per-file table itself, so nothing external joins the halves.
unit-cover:
	@set -e ; for f in $$( { [ -f main_test.mx ] && echo main_test.mx ; find pkg web -name '*_test.mx' ; } | sort ) ; do \
		d=$$(dirname $$f) ; \
		case " $(UNIT_SKIP) " in *" $$d "*) echo "=== unit-cover: $$d (skipped: see docs/relay-test-coverage.md)" ; continue ;; esac ; \
		echo "=== unit-cover: $$d" ; \
		MOXIEROOT=$(MOXIEROOT) $(MOXIE) test $(CAPTURES) -cover ./$$d || exit 1 ; \
	done

test: test-unit build-test-relay test-signer
	python3 -m pytest test/ -v --tb=short -x

# === Coverage ===
#
# `make cover` measures the integration suite's line coverage of this module:
# the relay is built with -cover, which makes the compiler emit a coverage hit
# per statement and write <output>.cover_sites.json (id -> pkg.Func@file:line);
# MOXIE_COVER_OUT makes the instrumented runtime append its counters to one file
# when a domain exits, which the test fixture's SIGTERM triggers; mxcover joins
# the table to the counters and prints the per-file table plus every statement
# that never ran.
#
# The file is appended to, not rewritten, because each test starts a fresh relay
# and the relay is a spawn tree - the store, the ingest workers, the proxy, the
# blossom and broadcast workers each run in a forked domain with a private copy
# of the counter table - so every process and domain contributes its own dump.
# The target deletes the file first, so one run's report is one run's coverage.
#
# The report is the work list. A coverage run rebuilds the whole closure (a
# cached package would have no hits to count) and the instrumented relay is
# slower, so this is not part of `make test`.
#
# test_relay_proxy_high_event_volume runs with a widened post-EOSE window. The
# old poll treated the gap after EOSE as "quiet" and finished 2s later, so a
# slow relay whose first event lands after EOSE read 0 events - which is what
# every `smesh-test` run did, and what the stock relay did in about 1 isolated
# run in 4. The test now waits for the first event up to PROXY_VOLUME_WINDOW_MS,
# so the coverage run widens it (the worker lingers 25s for those stragglers).
COVER_BIN    := $(CURDIR)/smesh-test
COVER_SITES  := $(COVER_BIN).cover_sites.json
COVER_DIR    := $(CURDIR)/tmp
COVER_COUNTS := $(COVER_DIR)/cover.counts
COVER_TOOL   := $(COVER_DIR)/mxcover
# Site names are "module/path/file.mx:line" for both compilers, so the report
# selects this module out of the link set by the module path from moxie.mod.
COVER_PREFIX := $(shell sed -n 's/^module[[:space:]]*//p' $(CURDIR)/moxie.mod)
cover:
	@mkdir -p $(COVER_DIR)
	MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build -cover $(CAPTURES) -o $(COVER_BIN) .
	rm -f $(COVER_COUNTS)
	@rc=0 ; SMESH_RELAY_BIN=$(COVER_BIN) MOXIE_COVER_OUT=$(COVER_COUNTS) \
	 PROXY_VOLUME_WINDOW_MS=180000 \
	 python3 -m pytest test/ -q --tb=short || rc=$$? ; \
	 MOXIEROOT=$(MOXIEROOT) $(MOXIE) build -o $(COVER_TOOL) $(MOXIEROOT)/cmd/mxcover || rc=$$? ; \
	 $(COVER_TOOL) $(COVER_SITES) $(COVER_COUNTS) $(COVER_PREFIX) || rc=$$? ; \
	 exit $$rc

test-smoke: build-test-relay
	python3 -m pytest test/test_smoke.py -v --tb=short

# Allocation instrumentation: the app wasm is rebuilt with -tags alloc_trace
# and -print-allocs, which makes src/runtime publish the __moxie_* counters the
# worker host reads in its __mem_query reply. The pattern is anchored to the
# entry package: the compiler names a function by its package path, and the app
# is the root package of its own module, so it is "main.<func>" - the same name
# the legacy compiler reports.
#
# The instrumented module is scratch: the stock app.wasm is put back whether
# the build or the tests fail, so this never leaves an instrumented artifact or
# a stray side file in the tree. An instrumented build recompiles everything
# serially (site ids are numbered in emit order), so it is slower than
# build-app-wasm.
APP_WASM := $(CURDIR)/web/static/app.wasm
test-memory: build-test-relay
	@if [ -f "$(APP_WASM)" ]; then cp "$(APP_WASM)" "$(APP_WASM).stock"; fi ; \
	 MOXIEROOT=$(MOXIEROOT) GOWORK=off $(MOXIE) build $(CAPTURES) -target wasm -tags alloc_trace \
		-print-allocs '^main\.' -o $(APP_WASM) ./web/wasm/app/ ; \
	 rc=$$? ; \
	 rm -f "$(APP_WASM).alloc_sites.json" ; \
	 if [ $$rc -eq 0 ]; then \
		python3 -m pytest test/test_memory_profile.py -v --tb=short -m memory ; rc=$$? ; \
	 fi ; \
	 if [ -f "$(APP_WASM).stock" ]; then mv "$(APP_WASM).stock" "$(APP_WASM)"; fi ; \
	 exit $$rc

# === Dev (frontend only, relay already running) ===
dev: build-app-wasm build-relay-proxy-wasm build-store-wasm build-verify-wasm build-profile-wasm build-feed-wasm build-notif-wasm build-sw

.PHONY: build-relay build-app-wasm build-relay-proxy-wasm build-store-wasm build-verify-wasm build-profile-wasm build-feed-wasm build-mls-wasm build-notif-wasm build-sw build-signer build-ext-firefox build-ext-chrome build-ext build dev build-test-relay test-signer test-unit test cover test-smoke test-memory unit-cover
