-include config.mk

BUILD ?= build/linux
VERSION ?= $(shell tr -d '\r\n' < VERSION)
PACKAGE_ARCH ?= $(shell uname -m)
PACKAGE_STAGE ?= $(CURDIR)/$(BUILD)/package-root
PACKAGE_DIR ?= $(CURDIR)/dist/linux-$(PACKAGE_ARCH)

PREFIX ?= /usr/local
DPIDIR ?= $(PREFIX)/lib/dillo/dpi/dat
DAT_SCHEME_DIR ?= $(PREFIX)/share/datgpt/scheme
DESTDIR ?=
CC ?= cc
AR ?= ar
RANLIB ?= ranlib
OBJCOPY ?= objcopy
INSTALL ?= install
CFLAGS ?= -O2 -std=c99 -Wall -Wextra -Wpedantic
HOST_OS := $(shell uname -s 2>/dev/null)
ifneq (,$(findstring CYGWIN,$(HOST_OS)))
TEST_LDFLAGS += -Wl,--stack,8388608
endif
CPPFLAGS ?=
LDFLAGS ?=
SODIUM_LIBS ?= -lsodium
CURL_CFLAGS ?= $(shell pkg-config --cflags libcurl 2>/dev/null)
CURL_LIBS ?= $(shell pkg-config --libs libcurl 2>/dev/null || echo -lcurl)
ENABLE_SDL2 ?= 0
FFMPEG_MEDIA_AVAILABLE := $(shell pkg-config --exists 'libavcodec >= 59' 'libavformat >= 59' 'libavutil >= 57' 'libswresample >= 4' 'libswscale >= 6' 2>/dev/null && echo 1 || echo 0)
ENABLE_CABLE_MEDIA ?= 0
ifeq ($(ENABLE_CABLE_MEDIA),1)
ifneq ($(FFMPEG_MEDIA_AVAILABLE),1)
$(error Cable media needs FFmpeg 5.0+ development files: libavcodec>=59, libavformat>=59, libavutil>=57, libswresample>=4, libswscale>=6)
endif
CABLE_MEDIA_CFLAGS = -DCABLE_HAVE_FFMPEG $(shell pkg-config --cflags libavcodec libavformat libavutil libswresample libswscale)
CABLE_MEDIA_LIBS = $(shell pkg-config --libs libavformat libavcodec libavutil libswresample libswscale)
endif
# Node and Cabal modules are used only by optional interoperability tests.
NODE ?= node
CABAL_JS_MODULE ?= cabal-core
CABAL_CLIENT_MODULE ?= cabal-client

CPPFLAGS += -I. -Iinc -Ilibcess/include -DSLEEP_SALT -DCESSPOOL_HAVE_SODIUM $(CURL_CFLAGS)
CPPFLAGS += -DDAT_BUILD_DIR=\"$(BUILD)\"
# Root-level NETWORK_PROTOCOL is the Cesspool peer identity and discovery
# namespace. Hypercore-compatible protocol identifiers belong in test builds.
NETWORK_PROTOCOL := $(shell tr -d '\r\n' < NETWORK_PROTOCOL)
CPPFLAGS += -DDAT_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\"
CPPFLAGS += -DCESSPOOL_NETWORK_PROTOCOL=\"$(NETWORK_PROTOCOL)\"
CPPFLAGS += -DCESSPOOL_CONFIG_DIRECTORY=\"dat-gpt\"
ifeq ($(NETWORK_PROTOCOL),hypercore)
CPPFLAGS += -DDAT_LEGACY_HYPERCORE
endif
LIBS = $(SODIUM_LIBS) $(CURL_LIBS) -lm -ldl
LIB_SOURCES = src/base32768.c src/cable.c src/cable_feed.c src/dat.c src/dat_browse.c src/dat_dns.c src/dat_fetch.c src/dat_walk.c src/dat_wire.c src/dat_session.c \
	src/sleep.c src/sleep_tree.c src/sleep_bitfield.c \
	src/sleep_proto.c src/sleep_crypto.c src/sleep_writer.c \
	src/sleep_feed.c src/sleep_proof.c src/sleep_replication.c src/sleep_utp.c
LIB_SOURCES += src/dat_s7.c src/dat_scheme.c src/s7.c
S7_INSTALL = $(INSTALL) -m 755 $(BUILD)/ddat-scheme "$(DESTDIR)$(PREFIX)/bin/ddat-scheme"
S7_SCHEME_INSTALL = $(INSTALL) -d "$(DESTDIR)$(DAT_SCHEME_DIR)" && \
	$(INSTALL) -m 644 scheme/dat-init.scm scheme/dat-record.scm scheme/dat-wire.scm scheme/dat-json.scm scheme/cable-room.scm "$(DESTDIR)$(DAT_SCHEME_DIR)/"
S7_UNINSTALL = rm -f "$(DESTDIR)$(PREFIX)/bin/ddat-scheme" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-init.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-record.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-wire.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/dat-json.scm" "$(DESTDIR)$(DAT_SCHEME_DIR)/cable-room.scm"
LIB_OBJECTS = $(LIB_SOURCES:src/%.c=$(BUILD)/%.o) $(BUILD)/sleep_verify_lib.o
LIBCESS_OBJECTS = $(BUILD)/libcess/cess_net.o $(BUILD)/libcess/cesspool.o $(BUILD)/libcess/cess_identity.o
PUBLIC_HEADERS = $(wildcard inc/*.h)
TESTS = $(BUILD)/test_tree $(BUILD)/test_bitfield $(BUILD)/test_base32768 $(BUILD)/test_cable $(BUILD)/test_proto $(BUILD)/test_wire \
	$(BUILD)/test_archive $(BUILD)/test_session $(BUILD)/test_dns $(BUILD)/test_utp \
	$(BUILD)/test_discovery $(BUILD)/test_replication $(BUILD)/test_writer $(BUILD)/test_paths
ifeq ($(ENABLE_SDL2),1)
GUI_TESTS = $(BUILD)/test_version_bar $(BUILD)/test_gui_p2p
CABLE_LIVE_TEST = $(BUILD)/test_cable_thang
endif

.PHONY: all test install uninstall clean distclean package package-linux
.PHONY: test-cabal-interop
.PHONY: test-cable-live
.PHONY: FORCE
all: $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/sleep-verify $(BUILD)/dat $(BUILD)/ddat $(BUILD)/cable $(BUILD)/cesspool-bootstrap $(BUILD)/dat.dpi
all: $(BUILD)/ddat-scheme
ifeq ($(ENABLE_SDL2),1)
all: $(BUILD)/dat-thang $(BUILD)/cable-thang
endif

$(BUILD):
	mkdir -p $(BUILD)

# A protocol identity changes wire bytes and must never reuse objects built for
# another value (including make command-line overrides used for compatibility).
$(BUILD)/.network-protocol: FORCE NETWORK_PROTOCOL | $(BUILD)
	@protocol="$(NETWORK_PROTOCOL)"; \
	if test ! -f $@ || test "$$(cat $@)" != "$$protocol"; then \
		printf '%s\n' "$$protocol" > $@.tmp; mv $@.tmp $@; \
	fi

$(BUILD)/%.o: src/%.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@

$(BUILD)/libcess:
	mkdir -p $@

$(BUILD)/libcess/%.o: libcess/src/%.c libcess/include/cesspool.h libcess/include/cess_net.h libcess/include/cess_compat.h NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/libcess
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@

$(BUILD)/libcess.a: $(LIBCESS_OBJECTS)
	$(AR) rcs $@ $(LIBCESS_OBJECTS)
	$(RANLIB) $@

# S7 is an upstream single-file interpreter with noisy legacy diagnostics.
# Suppression applies only to that vendored translation unit; our host stays
# under the normal project warning flags.
$(BUILD)/s7.o: src/s7.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)
	$(CC) $(CPPFLAGS) $(CFLAGS) -w -MMD -MP -c $< -o $@

$(BUILD)/sleep_verify_lib.o: src/sleep_verify.c $(BUILD)/.network-protocol | $(BUILD)
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -DSLEEP_VERIFY_NO_MAIN -c $< -o $@

-include $(LIB_OBJECTS:$(BUILD)/%.o=$(BUILD)/%.d)

$(BUILD)/libdatgpt.a: $(LIB_OBJECTS) $(BUILD)/.network-protocol
	$(AR) rcs $@ $(LIB_OBJECTS)
	$(RANLIB) $@

# The DPI uses S7's reader for dat.scm, treating archive metadata as data and
# never evaluating archive-supplied Scheme forms.
DPI_LIB_OBJECTS = $(patsubst $(BUILD)/%.o,$(BUILD)/dpi/%.o,$(filter-out $(BUILD)/dat_s7.o $(BUILD)/dat_scheme.o $(BUILD)/s7.o,$(LIB_OBJECTS)))
$(BUILD)/dpi:
	mkdir -p $@

$(BUILD)/dpi/%.o: src/%.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/dpi
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -c $< -o $@

$(BUILD)/dpi/sleep_verify_lib.o: src/sleep_verify.c NETWORK_PROTOCOL $(BUILD)/.network-protocol | $(BUILD)/dpi
	$(CC) $(CPPFLAGS) $(CFLAGS) -MMD -MP -DSLEEP_VERIFY_NO_MAIN -c $< -o $@

$(BUILD)/dpi_s7.o: $(BUILD)/s7.o | $(BUILD)
	$(OBJCOPY) --redefine-sym main=dat_dpi_s7_standalone_main $< $@

-include $(DPI_LIB_OBJECTS:$(BUILD)/dpi/%.o=$(BUILD)/dpi/%.d) $(LIBCESS_OBJECTS:$(BUILD)/libcess/%.o=$(BUILD)/libcess/%.d)

$(BUILD)/libdatgpt-dpi.a: $(DPI_LIB_OBJECTS) $(BUILD)/.network-protocol
	$(AR) rcs $@ $(DPI_LIB_OBJECTS)
	$(RANLIB) $@

$(BUILD)/sleep-verify: src/sleep_verify.c $(BUILD)/libdatgpt.a $(BUILD)/.network-protocol
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/sleep_verify.c $(BUILD)/libdatgpt.a $(LIBS) -o $@

$(BUILD)/dat: src/dat_cli.c NETWORK_PROTOCOL $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/.network-protocol
	$(CC) $(CPPFLAGS) -DDAT_CLI_NAME=\"dat\" $(CFLAGS) $(LDFLAGS) src/dat_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@

$(BUILD)/ddat: src/dat_cli.c NETWORK_PROTOCOL $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(BUILD)/.network-protocol
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/dat_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@

$(BUILD)/cable: src/cable_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@

$(BUILD)/cesspool-bootstrap: libcess/tools/cesspool-bootstrap.c $(BUILD)/libcess.a
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libcess.a $(SODIUM_LIBS) -o $@

$(BUILD)/ddat-scheme: src/dat_scheme_cli.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a
	$(CC) $(CPPFLAGS) -DDAT_SCHEME_DIR=\"$(DAT_SCHEME_DIR)\" $(CFLAGS) $(LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@

SDL2_CFLAGS ?= $(shell pkg-config --cflags sdl2)
SDL2_LIBS ?= $(shell pkg-config --libs sdl2)
SDL2_TTF_CFLAGS ?= $(shell pkg-config --cflags SDL2_ttf)
SDL2_TTF_LIBS ?= $(shell pkg-config --libs SDL2_ttf)
DAT_FONT_PATH ?= $(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf
DAT_UNIFONT_PATH ?= $(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf
DAT_SPEAKER_PATH ?= $(PREFIX)/share/datgpt/icons/datthang-speaker.bmp
ifeq ($(ENABLE_SDL2),1)
GUI_CPPFLAGS = -D_POSIX_C_SOURCE=200809L -D_XOPEN_SOURCE=700 -DDAT_FONT_PATH=\"$(DAT_FONT_PATH)\" -DDAT_UNIFONT_PATH=\"$(DAT_UNIFONT_PATH)\" -DDAT_SPEAKER_PATH=\"$(DAT_SPEAKER_PATH)\"
GUI_INSTALL = rm -f "$(DESTDIR)$(PREFIX)/bin/ddat-gui" && \
	$(INSTALL) -d "$(DESTDIR)$(PREFIX)/share/datgpt/fonts" "$(DESTDIR)$(PREFIX)/share/datgpt/icons" && \
	$(INSTALL) -m 644 assets/fonts/MorePerfectDOSVGA.ttf "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf" && \
	$(INSTALL) -m 644 assets/fonts/Unifont-18.0.01.otf "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf" && \
	$(INSTALL) -m 644 assets/fonts/Unifont-OFL-1.1.txt "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-OFL-1.1.txt" && \
	$(INSTALL) -m 644 assets/datthang-speaker.bmp "$(DESTDIR)$(PREFIX)/share/datgpt/icons/datthang-speaker.bmp" && \
	$(INSTALL) -m 755 $(BUILD)/dat-thang "$(DESTDIR)$(PREFIX)/bin/dat-thang" && \
	$(INSTALL) -m 644 docs/dat-thang.1 "$(DESTDIR)$(PREFIX)/share/man/man1/dat-thang.1" && \
	$(INSTALL) -m 755 $(BUILD)/cable-thang "$(DESTDIR)$(PREFIX)/bin/cable-thang" && \
	$(INSTALL) -m 644 docs/cable-thang.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cable-thang.1" && \
	$(INSTALL) -m 644 docs/ddat-gui.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/ddat-gui.md"
else
GUI_INSTALL = :
endif
$(BUILD)/dat-thang: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp | $(BUILD)
	$(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@

$(BUILD)/cable_media.o: src/cable_media.c inc/cable_media.h | $(BUILD)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(CABLE_MEDIA_CFLAGS) -MMD -MP -c $< -o $@

$(BUILD)/cable_video.o: src/cable_video.c inc/cable_video.h | $(BUILD)
	$(CC) $(CPPFLAGS) $(SDL2_CFLAGS) $(CFLAGS) $(CABLE_MEDIA_CFLAGS) -MMD -MP -c $< -o $@

$(BUILD)/cable-thang: src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp | $(BUILD)
	$(CC) $(CPPFLAGS) -DDAT_THANG_VERSION=\"v$(VERSION)\" $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@

.PHONY: gui
gui: $(BUILD)/dat-thang

# Native Dillo DPI plugin; dpid supplies an authenticated socket on stdin/stdout.
$(BUILD)/dat.dpi: src/dat_dpi.c src/dat_dpi_icons.h NETWORK_PROTOCOL $(BUILD)/libdatgpt-dpi.a $(BUILD)/libcess.a $(BUILD)/dpi_s7.o $(BUILD)/.network-protocol
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) src/dat_dpi.c $(BUILD)/libdatgpt-dpi.a $(BUILD)/libcess.a $(BUILD)/dpi_s7.o $(LIBS) -o $@

$(BUILD)/test_%: tests/test_%.c $(BUILD)/libdatgpt.a $(BUILD)/libcess.a
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) -o $@

$(BUILD)/test_dpi_socket: tests/test_dpi_socket.c | $(BUILD)
	$(CC) $(CPPFLAGS) $(CFLAGS) $(LDFLAGS) $< -o $@

$(BUILD)/test_gui_p2p: tests/test_gui_p2p.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp
	$(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@

$(BUILD)/test_version_bar: tests/test_version_bar.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp
	$(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@

$(BUILD)/test_cable_thang: tests/test_cable_thang.c src/ddat_gui.c $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a assets/fonts/MorePerfectDOSVGA.ttf assets/datthang-speaker.bmp
	$(CC) $(CPPFLAGS) $(GUI_CPPFLAGS) $(CFLAGS) $(SDL2_CFLAGS) $(SDL2_TTF_CFLAGS) $(LDFLAGS) $(TEST_LDFLAGS) $< $(BUILD)/cable_media.o $(BUILD)/cable_video.o $(BUILD)/libdatgpt.a $(BUILD)/libcess.a $(LIBS) $(SDL2_TTF_LIBS) $(SDL2_LIBS) $(CABLE_MEDIA_LIBS) -lm -o $@

$(TESTS): $(PUBLIC_HEADERS)

test: all $(TESTS) $(GUI_TESTS) $(BUILD)/test_dpi_socket
	@echo "TEST Cable address CLI"
	@$(BUILD)/cable address cabal://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | grep -q '^cable://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$$'
	@$(BUILD)/cable address hyper://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa | grep -q '^cable://aaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaa$$'
	@echo "TEST Cabal JS/Cable side-by-side room and user identity"
	@if command -v "$(NODE)" >/dev/null 2>&1 && CABAL_JS_MODULE="$(CABAL_JS_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_JS_MODULE)' >/dev/null 2>&1; then \
		CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABAL_BIN="$(CURDIR)/$(BUILD)/cable" "$(NODE)" tests/interop/cabal/cabal_cable_side_by_side.js; \
	else echo "SKIP Cabal JS/Cable interop (run make test-cabal-interop on a host with Node and cabal-core installed)"; fi
	@if command -v "$(NODE)" >/dev/null 2>&1 && CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_CLIENT_MODULE)' >/dev/null 2>&1; then \
		CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" tests/interop/cabal/cabal_cli_features.js; \
	else echo "SKIP cabal-cli multi-peer workflows (run make test-cabal-interop on a host with Node and cabal-client installed)"; fi
	@if test "$(ENABLE_SDL2)" = 1 && command -v "$(NODE)" >/dev/null 2>&1 && CABAL_JS_MODULE="$(CABAL_JS_MODULE)" "$(NODE)" -e 'require(process.env.CABAL_JS_MODULE)' >/dev/null 2>&1; then \
		CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cable_js_c_live.js $(BUILD)/test_cable_thang; \
	else echo "SKIP C SDL/Cabal JS live test (use make test-cable-live ENABLE_SDL2=1 with Node and cabal-core installed)"; fi
	@echo "TEST bundled S7 JSON/S-expression adapter"
	@json_output=$$($(BUILD)/ddat-scheme tests/test-json.scm 2>&1); \
	printf '%s\n' "$$json_output"; \
	printf '%s\n' "$$json_output" | grep -q '^S7 JSON/S-expression adapter: PASS$$'; \
	! printf '%s\n' "$$json_output" | grep -q '^;'
	@if command -v "$(NODE)" >/dev/null 2>&1; then "$(NODE)" tests/interop/cabal/cable_sexpr_boundary.js; fi
	@set -e; for test_program in $(TESTS); do echo "TEST $$test_program"; $$test_program; done
	@set -e; for test_program in $(GUI_TESTS); do echo "TEST $$test_program"; SDL_VIDEODRIVER=dummy SDL_AUDIODRIVER=dummy $$test_program; done
	@echo "TEST current Dillo DPIP helper protocol"
	@sh tests/test_dpi.sh $(BUILD)/dat.dpi $(BUILD)/dat $(BUILD)/test_dpi_socket
	@set -e; cli_root=$$(mktemp -d /tmp/datgpt-cli.XXXXXX); mkdir -p "$$cli_root/home"; \
		echo "TEST dat doctor local network primitives"; \
		$(BUILD)/ddat doctor | grep -q '^cesspool: UDP socket bound on 127.0.0.1:'; \
		$(BUILD)/ddat doctor 0000000000000000000000000000000000000000000000000000000000000000 | grep -q '^address: dat://'; \
		$(BUILD)/ddat --help | grep -q '^ddat '; \
		echo "TEST dat create/sync/log and dat:// address"; \
		HOME="$$cli_root/home" $(BUILD)/dat create "$$cli_root/archive" --title "hello" --description "archive test" > "$$cli_root/url"; \
		grep -q '^dat://' "$$cli_root/url"; \
		cp "$$cli_root/url" "$$cli_root/original-url"; \
		HOME="$$cli_root/home" $(BUILD)/dat dns set archive.example.test "$$cli_root/archive" > "$$cli_root/dns-set"; \
		grep -q '^DAT-DNS /.well-known/dat:$$' "$$cli_root/dns-set"; \
		HOME="$$cli_root/home" $(BUILD)/dat resolve archive.example.test | grep -q '^dat://'; \
		HOME="$$cli_root/home" $(BUILD)/dat dns list | grep -q '^archive.example.test[[:space:]]'; \
		HOME="$$cli_root/home" $(BUILD)/dat dns remove archive.example.test; \
		mkdir -p "$$cli_root/empty-home"; \
		cp "$$cli_root/archive/.dat/metadata.key" "$$cli_root/metadata-key-before-readonly-sync"; \
		if HOME="$$cli_root/empty-home" $(BUILD)/dat sync "$$cli_root/archive" >/dev/null 2>&1; then exit 1; fi; \
		cmp "$$cli_root/metadata-key-before-readonly-sync" "$$cli_root/archive/.dat/metadata.key"; \
		grep -q '(title "hello")' "$$cli_root/archive/dat.scm"; \
		grep -q '(description "archive test")' "$$cli_root/archive/dat.scm"; \
		grep -q '(url "dat://' "$$cli_root/archive/dat.scm"; \
		$(BUILD)/dat log "$$cli_root/archive" | grep -q dat.scm; \
		test ! -e "$$cli_root/archive/dat.json"; \
		HOME="$$cli_root/home" $(BUILD)/dat create "$$cli_root/empty" --yes > /dev/null; \
		test -f "$$cli_root/empty/dat.scm"; \
		test ! -e "$$cli_root/empty/dat.json"; \
		mkdir -p "$$cli_root/scheme-created"; \
		$(BUILD)/ddat-scheme -e "(dat-close (dat-create \"$$cli_root/scheme-created\"))" >/dev/null; \
		grep -q '(title "scheme-created")' "$$cli_root/scheme-created/dat.scm"; \
		$(BUILD)/dat log "$$cli_root/scheme-created" | grep -q dat.scm; \
		echo hello > "$$cli_root/archive/hello.txt"; \
		echo hidden > "$$cli_root/archive/.hidden"; \
		echo '*.skip' > "$$cli_root/archive/.datignore"; \
		echo ignored > "$$cli_root/archive/ignored.skip"; \
		HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \
		$(BUILD)/dat status "$$cli_root/archive" | sed -n 's/^Archive: //p' > "$$cli_root/updated-url"; \
		cmp "$$cli_root/original-url" "$$cli_root/updated-url"; \
		$(BUILD)/dat status "$$cli_root/archive" | grep -q 'Metadata entries:'; \
		$(BUILD)/dat url --compact '' "$$cli_root/archive" | grep -q '^dat://'; \
		$(BUILD)/dat files "$$cli_root/archive" | grep -q hello.txt; \
		$(BUILD)/dat verify "$$cli_root/archive" >/dev/null; \
		$(BUILD)/dat log "$$cli_root/archive" | grep -q hello.txt; \
		! $(BUILD)/dat log "$$cli_root/archive" | grep -qE '\.(hidden|datignore)|ignored\.skip'; \
		old_version=$$($(BUILD)/dat log "$$cli_root/archive" | awk '/hello.txt/{print $$1; exit}'); \
		echo newer > "$$cli_root/archive/hello.txt"; \
		HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \
		test "$$($(BUILD)/dat cat "$$old_version" /hello.txt "$$cli_root/archive")" = hello; \
		$(BUILD)/dat url "$$old_version" /hello.txt "$$cli_root/archive" | \
			grep -Eq "^dat://[0-9a-f]{64}\\+$$old_version/hello.txt$$"; \
		$(BUILD)/dat checkout "$$old_version" "$$cli_root/snapshot" "$$cli_root/archive"; \
		test "$$(cat "$$cli_root/snapshot/hello.txt")" = hello; \
		versions_before=$$($(BUILD)/dat log "$$cli_root/archive" | wc -l); \
		HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \
		test "$$($(BUILD)/dat log "$$cli_root/archive" | wc -l)" = "$$versions_before"; \
		$(BUILD)/dat files "$$old_version" "$$cli_root/archive" | grep -q hello.txt; \
		rm "$$cli_root/archive/hello.txt"; \
		HOME="$$cli_root/home" $(BUILD)/dat sync "$$cli_root/archive" > /dev/null; \
		$(BUILD)/dat log "$$cli_root/archive" | grep -q 'del .*hello.txt'
	@echo "TEST historical .dat fixture"
	@$(BUILD)/sleep-verify .dat
	@echo "TEST dat/ddat side-by-side archive compatibility"
	@sh tests/test_cli_parity.sh $(BUILD)/dat $(BUILD)/ddat

test-cabal-interop: $(BUILD)/cable
	CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABAL_BIN="$(CURDIR)/$(BUILD)/cable" "$(NODE)" tests/interop/cabal/cabal_cable_side_by_side.js
	CABAL_CLIENT_MODULE="$(CABAL_CLIENT_MODULE)" "$(NODE)" tests/interop/cabal/cabal_cli_features.js
	CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cabal_core_hyperswarm_live.js

ifeq ($(ENABLE_SDL2),1)
test-cable-live: $(BUILD)/test_cable_thang
	CABAL_JS_MODULE="$(CABAL_JS_MODULE)" CABLE_CABAL_CORE="$(CABAL_JS_MODULE)" "$(NODE)" tests/interop/cabal/cable_js_c_live.js $(BUILD)/test_cable_thang
else
test-cable-live:
	@echo "test-cable-live requires ENABLE_SDL2=1, SDL2, Node.js, and Cabal Core" >&2
	@exit 2
endif

install: all
	$(INSTALL) -d "$(DESTDIR)$(PREFIX)/lib" "$(DESTDIR)$(PREFIX)/include/datgpt" "$(DESTDIR)$(PREFIX)/include/libcess" "$(DESTDIR)$(PREFIX)/bin" "$(DESTDIR)$(PREFIX)/share/man/man1" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt" "$(DESTDIR)$(DPIDIR)"
	$(INSTALL) -m 644 $(BUILD)/libdatgpt.a "$(DESTDIR)$(PREFIX)/lib/libdatgpt.a"
	$(INSTALL) -m 644 $(BUILD)/libcess.a "$(DESTDIR)$(PREFIX)/lib/libcess.a"
	$(INSTALL) -m 644 $(PUBLIC_HEADERS) "$(DESTDIR)$(PREFIX)/include/datgpt/"
	$(INSTALL) -m 644 libcess/include/cesspool.h libcess/include/cess_net.h libcess/include/cess_compat.h "$(DESTDIR)$(PREFIX)/include/libcess/"
	$(INSTALL) -m 755 $(BUILD)/sleep-verify "$(DESTDIR)$(PREFIX)/bin/sleep-verify"
	$(INSTALL) -m 755 $(BUILD)/ddat "$(DESTDIR)$(PREFIX)/bin/ddat"
	$(INSTALL) -m 755 $(BUILD)/cable "$(DESTDIR)$(PREFIX)/bin/cable"
	$(INSTALL) -m 755 $(BUILD)/cesspool-bootstrap "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap"
	$(S7_INSTALL)
	$(GUI_INSTALL)
	$(S7_SCHEME_INSTALL)
	$(INSTALL) -m 755 $(BUILD)/dat.dpi "$(DESTDIR)$(DPIDIR)/dat.dpi"
	$(INSTALL) -m 644 docs/dat.1 "$(DESTDIR)$(PREFIX)/share/man/man1/dat.1"
	$(INSTALL) -m 644 docs/cable.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cable.1"
	$(INSTALL) -m 644 docs/cesspool-bootstrap.1 "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1"
	$(INSTALL) -m 644 docs/libcess.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/libcess.md"
	$(INSTALL) -m 644 docs/key-management.md docs/cable-interop.md docs/ddat-gui.md docs/dillo.md "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/"

# Build an install-tree archive suitable for Linux distribution and testing.
# System libraries remain explicit runtime dependencies; this is not an AppImage.
package: package-linux

package-linux: all
	rm -rf "$(PACKAGE_STAGE)"
	$(MAKE) BUILD="$(BUILD)" DESTDIR="$(PACKAGE_STAGE)" PREFIX=/usr install
	mkdir -p "$(PACKAGE_DIR)"
	tar -czf "$(PACKAGE_DIR)/dat-gpt-$(VERSION)-linux-$(PACKAGE_ARCH).tar.gz" -C "$(PACKAGE_STAGE)" .

uninstall:
	rm -f "$(DESTDIR)$(PREFIX)/lib/libdatgpt.a" "$(DESTDIR)$(PREFIX)/lib/libcess.a" "$(DESTDIR)$(PREFIX)/include/libcess/cesspool.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_net.h" "$(DESTDIR)$(PREFIX)/include/libcess/cess_compat.h" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/libcess.md" "$(DESTDIR)$(PREFIX)/bin/sleep-verify" "$(DESTDIR)$(PREFIX)/bin/ddat" "$(DESTDIR)$(PREFIX)/bin/cable" "$(DESTDIR)$(PREFIX)/bin/cesspool-bootstrap" "$(DESTDIR)$(PREFIX)/bin/ddat-scheme" "$(DESTDIR)$(PREFIX)/bin/ddat-gui" "$(DESTDIR)$(PREFIX)/bin/dat-thang" "$(DESTDIR)$(PREFIX)/bin/cable-thang" "$(DESTDIR)$(DPIDIR)/dat.dpi" "$(DESTDIR)$(PREFIX)/share/datgpt/cable_hyperswarm.js" "$(DESTDIR)$(PREFIX)/share/datgpt/cable_bootstrap.js" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/MorePerfectDOSVGA.ttf" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-18.0.01.otf" "$(DESTDIR)$(PREFIX)/share/datgpt/fonts/Unifont-OFL-1.1.txt" "$(DESTDIR)$(PREFIX)/share/datgpt/icons/datthang-speaker.bmp" "$(DESTDIR)$(PREFIX)/share/man/man1/dat.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cable.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cesspool-bootstrap.1" "$(DESTDIR)$(PREFIX)/share/man/man1/dat-thang.1" "$(DESTDIR)$(PREFIX)/share/man/man1/cable-thang.1" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/key-management.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/cable-interop.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/ddat-gui.md" "$(DESTDIR)$(PREFIX)/share/doc/dat-gpt/dillo.md"
	$(S7_UNINSTALL)
	@for header in $(notdir $(PUBLIC_HEADERS)); do rm -f "$(DESTDIR)$(PREFIX)/include/datgpt/$$header"; done

clean:
	@if test -d "$(BUILD)"; then find "$(BUILD)" -type f \( -name '*.o' -o -name '*.d' -o -name '*.a' -o -name '*.so' -o -name '*.so.*' -o -name '*.dll' -o -name '*.exe' \) -delete; fi
	rm -f "$(BUILD)/.network-protocol" "$(BUILD)/.network-protocol.tmp" "$(BUILD)/libdatgpt.a" "$(BUILD)/libdatgpt-dpi.a" "$(BUILD)/libcess.a" "$(BUILD)/dat" "$(BUILD)/ddat" "$(BUILD)/cable" "$(BUILD)/cesspool-bootstrap" "$(BUILD)/sleep-verify" "$(BUILD)/ddat-scheme" "$(BUILD)/dat.dpi" "$(BUILD)/dat-thang" "$(BUILD)/cable-thang" "$(BUILD)/test_"* "$(BUILD)/test-*"
	rm -rf "$(BUILD)/dpi" "$(BUILD)/libcess"
	$(MAKE) -C libcess clean

distclean: clean
	rm -f config.mk config.h config.log
