# desklock — one entry point for a three-component repo.
#
#   firmware/   ESP-IDF application for the device. No tests, no release flow.
#   gateway/    Python service on :8600. The only component with a test suite.
#   sim/        a static page that mimics the device face in a browser.
#
# This lives at the root and the components have no Makefiles of their own, so
# `make test` means the same thing wherever you are standing. A per-component
# Makefile makes it mean "some of the tests" depending on your working
# directory, which is the `git -C` failure in another costume (D-27).
#
# Paths resolve here rather than in callers (D-10). Two of them bite:
#
#   ESP-IDF is invisible until export.sh is sourced, so `idf.py` is
#   "command not found" for anyone who forgets — the same class of failure as
#   the four tool-resolution misses recorded in D-24. The firmware targets
#   source it themselves.
#
#   `python3` on this host is 3.8, which cannot parse the gateway's sources.
#   PYTHON names 3.12 explicitly and is overridable for other machines.

PYTHON     ?= python3.12
IDF_EXPORT ?= $(HOME)/esp-idf/export.sh
GATEWAY    := $(CURDIR)/gateway
VENV       := $(GATEWAY)/.venv

.DEFAULT_GOAL := help

.PHONY: help
help: ## Show this help
	@grep -hE '^[a-z][a-z0-9_-]*:.*?## ' $(MAKEFILE_LIST) \
	  | awk 'BEGIN{FS=":.*?## "}{printf "  \033[36m%-16s\033[0m %s\n", $$1, $$2}'

# --- the three D-27 required targets -----------------------------------------

.PHONY: test
test: test-gateway ## Run every component's tests that exist
	@echo "  --   firmware: no test suite (undetermined, not passing)"
	@echo "  --   sim: a static page, nothing to test"

.PHONY: lint
lint: lint-gateway ## Lint every component that has a linter

# --- gateway ------------------------------------------------------------------

.PHONY: setup
setup: ## Gateway venv + dev deps + prove it works (firmware needs export.sh — see below)
	cd $(GATEWAY) && $(PYTHON) -m venv .venv && .venv/bin/pip install -e ".[dev]"
	@# This covers the gateway half only, deliberately. The firmware half needs
	@# `source ~/esp-idf/export.sh` in every shell (see the firmware gotchas
	@# above); a Makefile recipe runs in its own subshell, so it cannot leave
	@# that sourced in the caller's shell. A `setup` that appeared to prepare
	@# firmware and silently left `idf.py` unresolved would be worse than one
	@# that says plainly it does not touch that half — hence `build-firmware`
	@# sources export.sh itself, per target, instead.
	@#
	@# Exit 0 from `pip install` is not evidence (D-24) — pip reports success
	@# even when the result is unusable (e.g. a dependency that resolved but
	@# doesn't actually import, or a stale .venv left over from a different
	@# Python). Prove the environment works instead of trusting the install
	@# step: `--collect-only` imports every test module and therefore every
	@# src module each one pulls in (T-47). It runs zero tests, so it stays
	@# cheap, and it also confirms ruff/mypy landed in .venv/bin — the venv
	@# is the only place either binary exists (see gateway gotchas above);
	@# `--version` is enough to prove each resolves and runs.
	cd $(GATEWAY) && .venv/bin/python -m pytest tests/ --collect-only -q
	cd $(GATEWAY) && .venv/bin/ruff --version >/dev/null
	cd $(GATEWAY) && .venv/bin/mypy --version >/dev/null

.PHONY: setup-speech
setup-speech: ## Additionally install faster-whisper and piper
	cd $(GATEWAY) && .venv/bin/pip install -e ".[dev,speech]"

.PHONY: run
run: ## Run the gateway on :8600 with reload
	cd $(GATEWAY) && .venv/bin/uvicorn desklock_gateway.main:app --host 0.0.0.0 --port 8600 --reload

.PHONY: test-gateway
test-gateway: ## Gateway pytest suite
	@cd $(GATEWAY) && .venv/bin/pytest

.PHONY: lint-gateway
lint-gateway: ## ruff check and format --check over the gateway
	cd $(GATEWAY) && .venv/bin/ruff check src tests && .venv/bin/ruff format --check src tests

.PHONY: typecheck
typecheck: ## mypy over the gateway sources
	cd $(GATEWAY) && .venv/bin/mypy src

# --- firmware -----------------------------------------------------------------
#
# Each target sources export.sh in its own shell. That is deliberate: make runs
# every recipe line in a fresh shell, so exporting in one target would not carry
# to the next, and a caller who sources it by hand still works because sourcing
# twice is harmless.

.PHONY: build-firmware
build-firmware: ## Build the ESP-IDF firmware (sources export.sh for you)
	@test -f $(IDF_EXPORT) || { echo "FAIL — no ESP-IDF at $(IDF_EXPORT); set IDF_EXPORT=<path>/export.sh"; exit 69; }
	. $(IDF_EXPORT) && cd firmware && idf.py build

.PHONY: flash
flash: ## Flash and monitor the device (PORT=/dev/ttyACM0 by default)
	@test -f $(IDF_EXPORT) || { echo "FAIL — no ESP-IDF at $(IDF_EXPORT); set IDF_EXPORT=<path>/export.sh"; exit 69; }
	. $(IDF_EXPORT) && cd firmware && idf.py -p $(or $(PORT),/dev/ttyACM0) flash monitor

# --- sim ----------------------------------------------------------------------

.PHONY: serve-sim
serve-sim: ## Serve the face simulator on :8601
	cd sim/face && $(PYTHON) -m http.server 8601

# --- housekeeping -------------------------------------------------------------

.PHONY: clean
clean: ## Remove the gateway venv and caches
	rm -rf $(VENV) $(GATEWAY)/.pytest_cache $(GATEWAY)/.ruff_cache $(GATEWAY)/.mypy_cache
	find $(GATEWAY) -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

# git hands a hook a non-login shell, which never sees ~/.local/bin — where
# gitleaks lands. Without this the scan reports "not installed" on every push,
# which is a check that fails open (D-24).
export PATH := $(HOME)/.local/bin:/usr/local/bin:$(PATH)

.PHONY: secrets
secrets: ## Scan the commits about to be pushed for credentials
	@ci/secrets.sh

# The call surface is identical in every repo; what it runs is not.
#
# `secrets` runs first, deliberately: it is the only failure here that cannot be
# undone by fixing it afterwards. A failed lint costs another commit; a pushed
# credential is cached and indexed whether or not it is later deleted.
#
# Some of these fail today, and are left wired anyway. The state was measured
# once and written down in T-56 rather than being worked around here — a gate
# quietly narrowed to what already passes is a gate that reports success for
# doing nothing, which is the failure this workspace keeps rediscovering.
.PHONY: pre-push
pre-push: secrets lint typecheck test ## Everything the pre-push hook runs
