# SPDX-FileCopyrightText: 2026 Doug Slater <https://slater.dev>
# SPDX-License-Identifier: AGPL-3.0-only

CORE := chow
CLI  := chow_cli
UI   := chow_ui
SERVER := chow_server

NODE_MODULES := $(UI)/node_modules

BIN ?= $(HOME)/.local/bin

R2 := r2:chow

ASSET_TTL := 31536000

TARGET_PAIRS := aarch64-macos:aarch64-apple-darwin \
                x86_64-macos:x86_64-apple-darwin \
                aarch64-linux-static:aarch64-unknown-linux-musl \
                x86_64-linux-static:x86_64-unknown-linux-musl \
                x86_64-windows:x86_64-pc-windows-gnu
TARGETS      := $(foreach p,$(TARGET_PAIRS),$(word 1,$(subst :, ,$(p))))
RUST_TARGETS := $(foreach p,$(TARGET_PAIRS),$(word 2,$(subst :, ,$(p))))

HELPER_CRATE := $(CLI)/native/chow-sqlite
HELPER       := $(CLI)/priv/chow-sqlite
HELPER_SRC   := $(HELPER_CRATE)/Cargo.toml $(HELPER_CRATE)/Cargo.lock $(wildcard $(HELPER_CRATE)/src/*.rs)

OTP_VERSION   := 28.3.2
ZBAR_VERSION  := 0.11.0
ZBAR_SHA      := cb46d4c1d3df2e56999d233f0a453c187ce8d553ef513ed792534d40a17bae59
ZBAR          := $(UI)/src/chow_ui/zbar.mjs
GLEAM_VERSION := 1.14.0
QUESO_VERSION := 0.3.0
ZIG_VERSION   := 0.16.0

ARCH     := $(shell uname -m | sed 's/arm64/aarch64/')
SYS      := $(shell uname -s)
BUNDLE   := $(UI)/src-tauri/target/release/bundle/macos/chow.app
APP      := /Applications/chow.app
LSREGISTER := /System/Library/Frameworks/CoreServices.framework/Frameworks/LaunchServices.framework/Support/lsregister
ANDROID_SDK := /opt/homebrew/share/android-commandlinetools
ANDROID_NDK := $(lastword $(wildcard $(ANDROID_SDK)/ndk/*))
MOBILE_ENV  := ANDROID_HOME=$(ANDROID_SDK) NDK_HOME=$(ANDROID_NDK)
ADB         := $(ANDROID_SDK)/platform-tools/adb
APK         := $(UI)/src-tauri/gen/android/app/build/outputs/apk/universal/debug/app-universal-debug.apk
IOS_APP     := $(UI)/src-tauri/gen/apple/build/arm64-sim/chow.app
IOS_ID      := dev.slater.chow
PKG         := dev.slater.chow.debug
ACTIVITY    := dev.slater.chow.MainActivity
TRIPLE   := $(ARCH)-$(shell echo $(SYS) | sed 's/Darwin/apple-darwin/;s/Linux/unknown-linux-musl/')
ZIG_DIR  := zig-$(ARCH)-$(shell echo $(SYS) | sed 's/Darwin/macos/;s/Linux/linux/')-$(ZIG_VERSION)
OTP_ARCH := $(shell uname -m | sed 's/aarch64/arm64/;s/x86_64/x64/')
OPT      := $(HOME)/.local/opt
OTP_BIN  := $(OPT)/otp/bin

ifeq ($(SYS),Darwin)
OTP_URL := https://github.com/erlef/otp_builds/releases/download/OTP-$(OTP_VERSION)/OTP-$(OTP_VERSION)-macos-$(shell uname -m).tar.gz
else
OTP_URL := https://github.com/gleam-community/erlang-linux-builds/releases/download/OTP-$(OTP_VERSION)/erlang-$(OTP_VERSION)-$(OTP_ARCH).tar.gz
endif

VERSION  := $(shell sed -n 's/^version = "\(.*\)"/\1/p' $(CLI)/gleam.toml)
HOST_BIN := dist/chow-$(VERSION)-$(ARCH)-$(shell echo $(SYS) | sed 's/Darwin/macos/;s/Linux/linux-static/')
SMOKE    := build/smoke

.PHONY: all app bundle android ios android-init ios-init android-build ios-build android-install android-device ios-install ios-device serve serve-web dist deploy snapshot server-image container deps build check test test-live vendor fmt fmt-check clean run install uninstall release toolchain tools smoke helper-test check-cli-sync check-food

all: check test

deps:
	cd $(CORE) && gleam deps download
	cd $(CLI)  && gleam deps download
	cd $(UI)   && gleam deps download
	cd $(SERVER) && gleam deps download

$(HELPER): $(HELPER_SRC)
	cd $(HELPER_CRATE) && cargo build --release --locked
	mkdir -p $(CLI)/priv
	cp $(HELPER_CRATE)/target/release/chow-sqlite $@

helper-test:
	cd $(HELPER_CRATE) && cargo test --locked

build: $(HELPER)
	cd $(CORE) && gleam build --target erlang
	cd $(CLI)  && gleam build --target erlang

check: fmt-check
	cd $(CORE) && gleam build --target erlang --warnings-as-errors
	cd $(CORE) && gleam build --target javascript --warnings-as-errors
	cd $(CLI)  && gleam build --target erlang --warnings-as-errors
	cd $(UI)   && gleam build --target javascript --warnings-as-errors
	cd $(SERVER) && gleam build --warnings-as-errors

test: $(NODE_MODULES) $(HELPER) helper-test
	cd $(CORE) && gleam test --target erlang
	cd $(CORE) && gleam test --target javascript
	cd $(CLI)  && gleam test --target erlang
	cd $(UI)   && gleam test --target javascript
	cd $(SERVER) && NODE_OPTIONS=--disable-warning=ExperimentalWarning gleam test
	cd $(SERVER) && gleam build
	node scripts/check-server.mjs
	./scripts/check-cli-sync.sh
	node scripts/check-scan.mjs
	node scripts/check-append.mjs
	node scripts/check-bookmarklet.mjs
	node scripts/check-scope.mjs
	node scripts/check-account.mjs
	node scripts/check-android-account.mjs
	node scripts/check-capabilities.mjs
	node scripts/check-folders.mjs
	node scripts/check-store.mjs
	node scripts/check-tree.mjs
	node scripts/check-zip.mjs
	node scripts/check-merge.mjs
	node scripts/check-chunks.mjs
	node scripts/check-trouble.mjs
	node scripts/check-flags.mjs

check-food: $(NODE_MODULES)
	cd $(UI) && gleam build --target javascript
	cd $(SERVER) && gleam build
	node scripts/check-food.mjs

server-image:
	docker build -f $(SERVER)/Dockerfile -t chow-server .

SERVER_PORT ?= 8080
SERVER_URL  := http://localhost:$(SERVER_PORT)/
BROWSE      := $(if $(filter Darwin,$(SYS)),open,xdg-open)

container: server-image
	docker rm -f chow >/dev/null 2>&1 || true
	@(for _ in $$(seq 1 60); do \
	  curl -fsS $(SERVER_URL)health >/dev/null 2>&1 && exec $(BROWSE) $(SERVER_URL); \
	  sleep 0.5; \
	done; echo "chow did not answer at $(SERVER_URL)") &
	docker run --rm --name chow -p $(SERVER_PORT):8080 -v chow:/data chow-server

check-cli-sync:
	./scripts/check-cli-sync.sh

test-live:
	cd $(CLI) && gleam run -m chow_cli/live_check

vendor:
	curl -sfL -o $(ZBAR) \
	  https://cdn.jsdelivr.net/npm/@undecaf/zbar-wasm@$(ZBAR_VERSION)/dist/inlined/main.mjs
	echo "$(ZBAR_SHA)  $(ZBAR)" | shasum -a 256 -c -

fmt:
	cd $(CORE) && gleam format
	cd $(CLI)  && gleam format
	cd $(UI)   && gleam format
	cd $(SERVER) && gleam format

fmt-check:
	cd $(CORE) && gleam format --check
	cd $(CLI)  && gleam format --check
	cd $(UI)   && gleam format --check
	cd $(SERVER) && gleam format --check

app: dist
	@echo "note: macOS sends chow:// links to the installed $(APP), not this build."
	@echo "      for the MyFitnessPal bookmarklet, run make bundle instead."
	cd $(UI)/src-tauri && cargo tauri dev

bundle: dist
	cd $(UI)/src-tauri && cargo tauri build --bundles app
ifeq ($(SYS),Darwin)
	rm -rf $(APP)
	ditto $(BUNDLE) $(APP)
	$(LSREGISTER) -f $(APP)
	@echo "installed $(APP), which answers chow:// links"
	open $(APP)
else
	open $(BUNDLE)
endif

android-init:
	cd $(UI)/src-tauri && $(MOBILE_ENV) cargo tauri android init

ios-init:
	cd $(UI)/src-tauri && cargo tauri ios init

android: dist
	cd $(UI)/src-tauri && $(MOBILE_ENV) cargo tauri android dev

ios: dist
	cd $(UI)/src-tauri && cargo tauri ios dev

android-build: dist
	cd $(UI)/src-tauri && $(MOBILE_ENV) cargo tauri android build --debug --target aarch64

ios-build: dist
	rm -rf $(dir $(IOS_APP))
	cd $(UI)/src-tauri && cargo tauri ios build --debug --target aarch64-sim

android-install: android-build
	$(ADB) -e install -r $(APK)
	@echo "installed on the emulator"

ios-install: ios-build
	xcrun simctl boot booted 2>/dev/null || true
	xcrun simctl install booted $(IOS_APP)
	xcrun simctl launch booted $(IOS_ID)
	@echo "chow running on the booted simulator"

ios-device: dist
	@set -e; \
	test -n "$$APPLE_TEAM_ID" || { echo "APPLE_TEAM_ID is unset; source ~/.env.chow"; exit 1; }; \
	rm -rf $(UI)/src-tauri/gen/apple/build/arm64; \
	( cd $(UI)/src-tauri && APPLE_DEVELOPMENT_TEAM="$$APPLE_TEAM_ID" \
	  cargo tauri ios build --debug --target aarch64 ); \
	app=$$(find $(UI)/src-tauri/gen/apple/build/arm64 -maxdepth 1 -name '*.ipa' -o -maxdepth 1 -name '*.app' | head -1); \
	device=$$(xcrun devicectl list devices 2>/dev/null | awk 'match($$0, /[0-9A-F]{8}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{4}-[0-9A-F]{12}/) { print substr($$0, RSTART, RLENGTH); exit }'); \
	test -n "$$device" || { echo "no paired iOS device"; exit 1; }; \
	xcrun devicectl device install app --device "$$device" "$$app"; \
	xcrun devicectl device process launch --device "$$device" $(IOS_ID); \
	echo "chow running on $$device"

android-device: android-build
	@set -e; \
	device=$$($(ADB) devices | awk '$$2 == "device" && $$1 !~ /^emulator-/ { print $$1; exit }'); \
	test -n "$$device" || { echo "no device; plug one in or pair it with adb"; exit 1; }; \
	$(ADB) -s "$$device" install -r $(APK); \
	$(ADB) -s "$$device" shell am start -n $(PKG)/$(ACTIVITY); \
	echo "chow running on $$device"

serve: dist
	@echo "chow at http://localhost:8000"
	@(sleep 1 && open http://localhost:8000) &
	cd $(UI)/dist && python3 -m http.server 8000

serve-web:
	@echo "landing page at http://localhost:8001"
	@(sleep 1 && open http://localhost:8001) &
	python3 -m http.server 8001 -d web

$(NODE_MODULES): $(UI)/package-lock.json
	cd $(UI) && npm ci
	@touch $@

dist: $(NODE_MODULES)
	cd $(UI) && npx vite build

deploy: $(NODE_MODULES)
	cd $(UI) && npx vite build --base=/app/
	mkdir -p build
	ls $(UI)/dist/assets > build/assets.txt
	rclone copy $(UI)/dist/assets/ $(R2)/app/assets/ --s3-no-check-bucket --ignore-existing \
	  --header-upload "Cache-Control: public, max-age=$(ASSET_TTL), immutable"
	rclone copy web/ $(R2)/ --s3-no-check-bucket
	rclone copyto $(UI)/dist/index.html $(R2)/app/index.html --s3-no-check-bucket \
	  --header-upload "Cache-Control: no-cache"
	rclone delete $(R2)/app/assets/ --s3-no-check-bucket --exclude-from build/assets.txt
	@echo "live at https://chow.fit/app"

snapshot:
	mkdir -p build
	git archive --format=zip --prefix=chow/ -o build/chow.zip HEAD -- \
	  CHANGELOG.md LICENSE Makefile README.md chow chow_cli chow_ui chow_server scripts \
	  ':(exclude,glob)**/.gitignore'
	rclone copyto build/chow.zip $(R2)/chow.zip --s3-no-check-bucket \
	  --header-upload "Cache-Control: no-cache"
	@echo "snapshot at https://chow.fit/chow.zip"

run: $(HELPER)
	cd $(CLI) && gleam run -- $(ARGS)

install: check test
	rm -rf $(CLI)/build/queso
	cd $(CLI) && queso build
	mkdir -p $(BIN)
	cp $(CLI)/build/queso/chow_cli-* $(BIN)/chow
	chmod +x $(BIN)/chow
	@echo "installed $(BIN)/chow"

uninstall:
	rm -f $(BIN)/chow

release: check test
	rm -rf $(CLI)/build/queso dist
	mkdir -p dist
	for pair in $(TARGET_PAIRS); do \
	  target=$${pair%%:*}; triple=$${pair#*:}; \
	  case $$triple in *windows*) exe=chow-sqlite.exe;; *) exe=chow-sqlite;; esac; \
	  (cd $(HELPER_CRATE) && cargo zigbuild --release --locked --target $$triple) || exit 1; \
	  rm -f $(HELPER) $(HELPER).exe; \
	  cp $(HELPER_CRATE)/target/$$triple/release/$$exe $(CLI)/priv/$$exe || exit 1; \
	  (cd $(CLI) && queso build --target $$target) || exit 1; \
	  for built in $(CLI)/build/queso/chow_cli-*-$$target*; do \
	    cp "$$built" "dist/$$(basename "$$built" | sed 's/^chow_cli-/chow-/')"; \
	  done; \
	done
	rm -f $(HELPER) $(HELPER).exe
	@ls dist | sed 's/^/built dist\//'

toolchain:
	rm -rf $(OPT)/otp $(BIN)/erl $(BIN)/erlc $(BIN)/escript $(BIN)/rebar3
	mkdir -p $(BIN) $(OPT)/otp
	curl -fsSL $(OTP_URL) | tar xz -C $(OPT)/otp
	test ! -f $(OPT)/otp/Install || (cd $(OPT)/otp && ./Install -minimal $(OPT)/otp)
	curl -fsSL https://github.com/gleam-lang/gleam/releases/download/v$(GLEAM_VERSION)/gleam-v$(GLEAM_VERSION)-$(TRIPLE).tar.gz | tar xz -C $(BIN)
	@echo "put on PATH: $(OTP_BIN) and $(BIN)"

tools:
	mkdir -p $(BIN) $(OPT)
	curl -fsSL https://github.com/jtdowney/queso/releases/download/v$(QUESO_VERSION)/queso-$(TRIPLE).tar.gz | tar xz -C $(BIN)
	curl -fsSL https://ziglang.org/download/$(ZIG_VERSION)/$(ZIG_DIR).tar.xz | tar xJ -C $(OPT)
	ln -sf $(OPT)/$(ZIG_DIR)/zig $(BIN)/zig
	command -v cargo >/dev/null || curl --proto '=https' --tlsv1.2 -sSf https://sh.rustup.rs | sh -s -- -y --no-modify-path
	PATH="$(HOME)/.cargo/bin:$$PATH" cargo install --locked cargo-zigbuild
	PATH="$(HOME)/.cargo/bin:$$PATH" rustup target add $(RUST_TARGETS)

smoke:
	chmod +x $(HOST_BIN)
	rm -rf $(SMOKE)
	mkdir -p $(SMOKE)
	cp $(CLI)/test/data/foods.chow $(SMOKE)/
	printf 'include foods.chow\n2026-09-14 08:20  mfp/63651213856109  1.5 x 170 g\n' > $(SMOKE)/main.chow
	cd $(SMOKE) && $(CURDIR)/$(HOST_BIN) ls 2026-09-14 | grep -q '225 kcal'
	$(CURDIR)/$(HOST_BIN) barcode 3017620422003 | grep -q 'Nutella'
	cd $(SMOKE) && $(CURDIR)/$(HOST_BIN) add </dev/null 2>&1 | grep -q 'Which food'
	cd $(SMOKE) && $(CURDIR)/$(HOST_BIN) add nosuchfood </dev/null 2>&1 | grep -q 'No food matching nosuchfood'
	mkdir -p $(SMOKE)/data/chow/food
	cp $(UI)/test/fixture/fdc.bin $(SMOKE)/data/chow/food/fdc-2026-01-01-aaaaaaaaaaaa.bin
	cp $(UI)/test/fixture/off.bin $(SMOKE)/data/chow/food/off-2026-01-01-aaaaaaaaaaaa.bin
	env -u LOCALAPPDATA XDG_DATA_HOME=$(CURDIR)/$(SMOKE)/data $(CURDIR)/$(HOST_BIN) search apple | grep -q 'fdc/1001'
	env -u LOCALAPPDATA XDG_DATA_HOME=$(CURDIR)/$(SMOKE)/data $(CURDIR)/$(HOST_BIN) barcode 3017620422003 | grep -q 'off/3017620422003'
	@echo "smoke passed $(HOST_BIN)"

clean:
	rm -rf $(CORE)/build $(CLI)/build $(UI)/build $(SERVER)/build $(UI)/dist dist build
	rm -rf $(HELPER_CRATE)/target $(HELPER) $(HELPER).exe
