SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c
.DEFAULT_GOAL := help
.DELETE_ON_ERROR:

PROJECT          := latchd
BINARY           := latchd
PREFIX           ?= /usr
DESTDIR          ?=
BINDIR           := $(DESTDIR)$(PREFIX)/bin
CONFDIR          := $(DESTDIR)/etc/$(PROJECT)
SYSTEMD_DIR      := $(DESTDIR)/usr/lib/systemd/system
BUILD_DIR        ?= build
DIST_DIR         ?= dist
CARGO_TARGET_DIR ?= target
CARGO            ?= cargo
GO               ?= go
TARGET           ?=
TAG              ?=

VERSION ?= $(shell sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)
GIT_COMMIT ?= $(shell git rev-parse --short=12 HEAD 2>/dev/null || printf unknown)
SOURCE_DATE_EPOCH ?= $(shell git log -1 --format=%ct 2>/dev/null || date +%s)
TARGET_TRIPLE ?= $(shell rustc -vV 2>/dev/null | sed -n 's/^host: //p' || printf unknown)

TARGET_ARGS := $(if $(TARGET),--target $(TARGET),)
RELEASE_DIR := $(CARGO_TARGET_DIR)/$(if $(TARGET),$(TARGET)/)release
PACKAGE_NAME := $(PROJECT)-$(VERSION)-$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))
PACKAGE_FILE := $(DIST_DIR)/$(PACKAGE_NAME).tar.gz
PACKAGE_STAGE := $(DIST_DIR)/stage/$(PACKAGE_NAME)
CARGO_ENV := LATCHD_VERSION=$(VERSION) LATCHD_COMMIT=$(GIT_COMMIT) LATCHD_BUILD_TIME=$(SOURCE_DATE_EPOCH) SOURCE_DATE_EPOCH=$(SOURCE_DATE_EPOCH)

COLOR ?= 1
ifeq ($(COLOR),1)
RESET := \033[0m
DIM := \033[2m
CYAN := \033[36m
GREEN := \033[32m
YELLOW := \033[33m
RED := \033[31m
else
RESET :=
DIM :=
CYAN :=
GREEN :=
YELLOW :=
RED :=
endif

.PHONY: all deps build release-build test check fmt lint doc audit docker-test \
        integration-test system-e2e system-e2e-strict \
        package release release-check version install uninstall clean run-preview \
        legacy-build legacy-check enable update help

all: release-build ## Build the optimized release binary

version: ## Print the package, commit, and reproducibility metadata
	@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
		'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'

deps: ## Check native dependencies needed for builds and tests
	@command -v $(CARGO) >/dev/null || { echo 'cargo (Rust 1.88+) is required'; exit 1; }
	@command -v rustfmt >/dev/null || { echo 'rustfmt is required'; exit 1; }
	@command -v pkg-config >/dev/null || { echo 'pkg-config is required'; exit 1; }
	@pkg-config --exists pam || { echo 'libpam development files are required'; exit 1; }
	@printf '$(GREEN)dependencies ok$(RESET)\n'

build: release-check ## Build and copy the optimized binary to build/
	@mkdir -p '$(BUILD_DIR)'
	@printf '$(CYAN)building$(RESET) %s %s\n' '$(VERSION)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))'
	@env $(CARGO_ENV) $(CARGO) build --release --locked $(TARGET_ARGS)
	@install -m755 '$(RELEASE_DIR)/$(BINARY)' '$(BUILD_DIR)/$(BINARY)'
	@'$(BUILD_DIR)/$(BINARY)' --version

release-build: build ## Alias for the optimized release build

fmt: ## Check Rust formatting
	@$(CARGO) fmt --all -- --check

test: ## Run all Rust tests
	@$(CARGO) test --locked --workspace --all-targets

lint: ## Run Clippy with warnings denied
	@$(CARGO) clippy --locked --workspace --all-targets --all-features -- -D warnings

doc: ## Build rustdoc with warnings denied
	@RUSTDOCFLAGS='-D warnings' $(CARGO) doc --locked --workspace --no-deps

audit: ## Run cargo-audit when installed
	@command -v cargo-audit >/dev/null || { echo 'cargo-audit is required; install with cargo install cargo-audit --locked'; exit 1; }
	@cargo audit

check: fmt test lint doc ## Run the complete local verification gate

docker-test: ## Run the pinned Docker verification suite
	@./scripts/test-docker.sh

integration-test: build ## Exercise safe, non-authenticating CLI paths
	@./scripts/test-cli.sh '$(BUILD_DIR)/$(BINARY)'

system-e2e: ## Probe PAM, VT, X11, Wayland, and logind without requiring privileges
	@./scripts/test-system-e2e.sh '$(BUILD_DIR)/$(BINARY)'

system-e2e-strict: ## Require all real login-manager capabilities (prepared VM only)
	@./scripts/test-system-e2e.sh --strict '$(BUILD_DIR)/$(BINARY)'

release-check: ## Validate a release version and optional tag
	@[[ '$(VERSION)' =~ ^[0-9]+\.[0-9]+\.[0-9]+([-.][0-9A-Za-z.-]+)?$$ ]] || { echo 'invalid VERSION: $(VERSION)'; exit 1; }
	@cargo_version="$$(sed -n 's/^version = "\([^"]*\)"/\1/p' Cargo.toml | head -1)"; \
	[[ "$$cargo_version" == '$(VERSION)' ]] || { echo "Cargo version $$cargo_version does not match $(VERSION)"; exit 1; }
	@if [[ -n '$(TAG)' ]]; then \
		tag_version="$$(printf %s '$(TAG)' | sed 's/^v//')"; \
		[[ "$$tag_version" == '$(VERSION)' ]] || { echo "tag $(TAG) does not match $(VERSION)"; exit 1; }; \
	fi

package: release-check build ## Create a deterministic install archive and checksum
	@rm -rf '$(PACKAGE_STAGE)'
	@mkdir -p '$(PACKAGE_STAGE)/usr/bin' '$(PACKAGE_STAGE)/usr/lib/systemd/system' \
		'$(PACKAGE_STAGE)/etc/pam.d' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wms' '$(PACKAGE_STAGE)/etc/$(PROJECT)/wayland'
	@install -m755 '$(BUILD_DIR)/$(BINARY)' '$(PACKAGE_STAGE)/usr/bin/$(BINARY)'
	@install -m644 extra/config.toml '$(PACKAGE_STAGE)/etc/$(PROJECT)/config.toml'
	@install -m644 extra/latchd.pam '$(PACKAGE_STAGE)/etc/pam.d/$(PROJECT)'
	@install -m644 extra/latchd.service '$(PACKAGE_STAGE)/usr/lib/systemd/system/$(PROJECT).service'
	@install -m644 README.md LICENSE CHANGELOG.md '$(PACKAGE_STAGE)/'
	@printf 'version=%s\ncommit=%s\nepoch=%s\ntarget=%s\n' \
		'$(VERSION)' '$(GIT_COMMIT)' '$(SOURCE_DATE_EPOCH)' '$(if $(TARGET),$(TARGET),$(TARGET_TRIPLE))' \
		> '$(PACKAGE_STAGE)/BUILD-METADATA'
	@mkdir -p '$(DIST_DIR)'
	@tar --sort=name --numeric-owner --owner=0 --group=0 --mtime='@$(SOURCE_DATE_EPOCH)' \
		-C '$(DIST_DIR)/stage' -czf '$(PACKAGE_FILE)' '$(PACKAGE_NAME)'
	@(cd '$(DIST_DIR)' && sha256sum '$(notdir $(PACKAGE_FILE))' > '$(notdir $(PACKAGE_FILE)).sha256')
	@(cd '$(DIST_DIR)' && sha256sum --check '$(notdir $(PACKAGE_FILE)).sha256')
	@checksum="$$(sha256sum '$(PACKAGE_FILE)' | cut -d' ' -f1)"; \
		printf 'package=%s\nsha256=%s\nversion=%s\n' \
			'$(PACKAGE_FILE)' "$$checksum" '$(VERSION)' > '$(DIST_DIR)/release.env'
	@printf '$(GREEN)created$(RESET) %s\n' '$(PACKAGE_FILE)'

release: check audit integration-test system-e2e docker-test package ## Run all gates and produce release artifacts

install: build ## Install the binary, config, PAM policy, and systemd unit
	@install -Dm755 '$(BUILD_DIR)/$(BINARY)' '$(BINDIR)/$(BINARY)'
	@install -Dm644 extra/config.toml '$(CONFDIR)/config.toml'
	@install -Dm644 extra/latchd.pam '$(DESTDIR)/etc/pam.d/$(PROJECT)'
	@install -Dm644 extra/latchd.service '$(SYSTEMD_DIR)/$(PROJECT).service'
	@install -d '$(CONFDIR)/wms' '$(CONFDIR)/wayland'

uninstall: ## Remove files installed by make install
	@rm -f '$(BINDIR)/$(BINARY)' '$(DESTDIR)/etc/pam.d/$(PROJECT)' '$(SYSTEMD_DIR)/$(PROJECT).service'
	@printf '$(YELLOW)configuration at %s was preserved; remove it manually if no longer needed$(RESET)\n' '$(CONFDIR)'

clean: ## Remove generated build, release, and Cargo artifacts
	@rm -rf '$(BUILD_DIR)' '$(DIST_DIR)'
	@$(CARGO) clean

run-preview: build ## Run the safe non-authenticating preview UI
	@'$(BUILD_DIR)/$(BINARY)' --preview --no-log

legacy-build: ## Build the quarantined Go reference only when explicitly requested
	@printf '$(YELLOW)legacy Go is reference-only and excluded from release builds$(RESET)\n'
	@command -v $(GO) >/dev/null || { echo 'go is required for legacy-build'; exit 1; }
	@mkdir -p '$(BUILD_DIR)'
	@cd legacy/go && $(GO) build -trimpath -o '../../$(BUILD_DIR)/$(BINARY)-go' .

legacy-check: ## Format-check and test the quarantined Go reference
	@command -v $(GO) >/dev/null || { echo 'go is required for legacy-check'; exit 1; }
	@command -v gofmt >/dev/null || { echo 'gofmt is required for legacy-check'; exit 1; }
	@test -z "$$(find legacy/go -type f -name '*.go' -print0 | xargs -0 gofmt -l)" || { echo 'legacy Go files need gofmt'; exit 1; }
	@cd legacy/go && $(GO) test ./...

enable: ## Enable the systemd service after host validation
	@sudo systemctl enable $(PROJECT).service

update: ## Rebuild and reinstall the current checkout without changing git state
	@sudo $(MAKE) install

help: ## Show available targets
	@printf '$(CYAN)$(PROJECT)$(RESET) $(VERSION) — production build targets\n\n'
	@awk -F ':|##' '/^[a-zA-Z0-9_-]+:.*##/ { printf "  %-18s %s\n", $$1, $$NF }' '$(MAKEFILE_LIST)' | sort
