CLI_ARGS := $(wordlist 2,$(words $(MAKECMDGOALS)),$(MAKECMDGOALS))
$(eval $(sort $(subst :,\:,$(CLI_ARGS))):;@:)

PRIMARY_GOAL := $(firstword $(MAKECMDGOALS))
ifeq ($(PRIMARY_GOAL),)
    PRIMARY_GOAL := help
endif

# Round 704 (NEEDS 76). `-include`, not `include`, and the difference is the
# whole reason `make up` had never worked from a fresh clone.
#  
# `docker/.env` is gitignored, so a clone does not have one. A hard `include`
# fails at PARSE time — "No rule to make target 'docker/.env'" — so EVERY
# target dies, including any target whose job is to create the file. The
# instructions in `docker/.env.example` could not be followed by running
# anything in this Makefile; a developer had to know to copy it by hand, and
# `make init` below could never have run.
#  
# The argument for the hard include is KEPT, in the place it was actually
# about. `docker/.env.example` says: "Making it `-include` would let `make
# prod-deploy` run with IMAGE and STACK_NAME empty, pushing and deploying under
# names nobody chose. A build that stops is better than a build that guesses."
# That is correct, and it is about the production targets — so the guard now
# sits on those three (see `require-prod-env`) rather than on every target in
# the file. Nothing deploys under a guessed name; `make up` runs.
-include docker/.env

# Current user ID and group ID except MacOS where it conflicts with Docker abilities
ifeq ($(shell uname), Darwin)
    export UID=1000
    export GID=1000
else
    export UID=$(shell id -u)
    export GID=$(shell id -g)
endif

# Falls back for DEV ONLY, so `make up` has a stable project name on a fresh
# clone. STACK_NAME itself is left genuinely unset, so `require-prod-env`
# below still names it — a default here would have made the deploy guard
# list a variable it could never be missing.
export COMPOSE_PROJECT_NAME=$(or $(STACK_NAME),nexhire)
DOCKER_COMPOSE_DEV := docker compose -f docker/compose.yml -f docker/dev/compose.yml
DOCKER_COMPOSE_TEST := docker compose -f docker/compose.yml -f docker/test/compose.yml

#
# Development
#

ifeq ($(PRIMARY_GOAL),init)
init: ## Create the env files the compose stacks require, from their examples.
	@for pair in docker/.env docker/dev/.env docker/test/.env; do \
		if [ -f "$$pair" ]; then \
			echo "  exists  $$pair"; \
		else \
			cp "$$pair.example" "$$pair" && echo "  created $$pair"; \
		fi; \
	done
	@echo "Ready. 'make up' next."
endif

ifeq ($(PRIMARY_GOAL),build)
build: ## Build docker images.
	$(DOCKER_COMPOSE_DEV) build $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),up)
up: ## Up the dev environment.
	$(DOCKER_COMPOSE_DEV) up -d --remove-orphans
endif

ifeq ($(PRIMARY_GOAL),down)
down: ## Down the dev environment.
	$(DOCKER_COMPOSE_DEV) down --remove-orphans
endif

ifeq ($(PRIMARY_GOAL),stop)
stop: ## Stop the dev environment.
	$(DOCKER_COMPOSE_DEV) stop
endif

ifeq ($(PRIMARY_GOAL),clear)
clear: ## Remove development docker containers and volumes.
	$(DOCKER_COMPOSE_DEV) down --volumes --remove-orphans
endif

ifeq ($(PRIMARY_GOAL),shell)
shell: ## Get into container shell.
	$(DOCKER_COMPOSE_DEV) exec app /bin/bash
endif

#
# Tools
#

ifeq ($(PRIMARY_GOAL),yii)
yii: ## Execute Yii command.
	$(DOCKER_COMPOSE_DEV) run --rm app ./yii $(CLI_ARGS)
.PHONY: yii
endif

ifeq ($(PRIMARY_GOAL),composer)
composer: ## Run Composer.
	$(DOCKER_COMPOSE_DEV) run --rm app composer $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),rector)
rector: ## Run Rector.
	$(DOCKER_COMPOSE_DEV) run --rm app ./vendor/bin/rector $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),cs-fix)
cs-fix: ## Run PHP CS Fixer.
	$(DOCKER_COMPOSE_DEV) run --rm app ./vendor/bin/php-cs-fixer fix --config=.php-cs-fixer.php --diff
endif

#
# Tests and analysis
#

ifeq ($(PRIMARY_GOAL),test)
test: ## Run tests.
	$(DOCKER_COMPOSE_TEST) run --rm app ./vendor/bin/codecept run $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),test-coverage)
test-coverage: ## Run tests with coverage.
	$(DOCKER_COMPOSE_TEST) run --rm app ./vendor/bin/codecept run --coverage --coverage-html --disable-coverage-php
endif

ifeq ($(PRIMARY_GOAL),codecept)
codecept: ## Run Codeception.
	$(DOCKER_COMPOSE_TEST) run --rm app ./vendor/bin/codecept $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),psalm)
psalm: ## Run Psalm.
	$(DOCKER_COMPOSE_DEV) run --rm app ./vendor/bin/psalm $(CLI_ARGS)
endif

ifeq ($(PRIMARY_GOAL),composer-dependency-analyser)
composer-dependency-analyser: ## Run Composer Dependency Analyser.
	$(DOCKER_COMPOSE_DEV) run --rm app ./vendor/bin/composer-dependency-analyser --config=composer-dependency-analyser.php $(CLI_ARGS)
endif

#
# Production
#

# **Round 704 (NEEDS 76).** The guard `docker/.env.example` argued for, moved from
# every target in the file to the three it was about. A deploy still refuses to
# run under a name nobody chose — it just no longer takes `make up` down with
# it. Defined outside an `ifeq` because a prerequisite must exist to be one.
#  
# Each name is expanded by MAKE, not by the shell. The first version of this
# guard looped over the names in shell and read `$$IMAGE`, which is always
# empty: these come from an `-include`d file and make does not export them. It
# refused correctly on a fresh clone and would have refused exactly as
# confidently on a configured one — **a guard that always refuses looks like a
# guard that works**, and only testing the PASSING case showed it.
require-prod-env: ## Refuse a production target when its names are unset.
	@missing=""; \
	[ -n "$(IMAGE)" ]      || missing="$$missing IMAGE"; \
	[ -n "$(IMAGE_TAG)" ]  || missing="$$missing IMAGE_TAG"; \
	[ -n "$(STACK_NAME)" ] || missing="$$missing STACK_NAME"; \
	[ -n "$(PROD_SSH)" ]   || missing="$$missing PROD_SSH"; \
	if [ -n "$$missing" ]; then \
		echo "Refusing: these are unset:$$missing"; \
		echo "They come from docker/.env — run 'make init', then set them."; \
		echo "A build that stops is better than a build that guesses."; \
		exit 1; \
	fi
.PHONY: require-prod-env

ifeq ($(PRIMARY_GOAL),prod-build)
prod-build: require-prod-env ## Build an image.
	docker build --file docker/Dockerfile --target prod --pull -t ${IMAGE}:${IMAGE_TAG} .
endif

ifeq ($(PRIMARY_GOAL),prod-push)
prod-push: require-prod-env ## Push image to repository.
	docker push ${IMAGE}:${IMAGE_TAG}
endif

ifeq ($(PRIMARY_GOAL),prod-deploy)
# Round 915 (Cham's Q41 (a), NEEDS 101): the release's migrations run as a
# one-off job BEFORE the stack is deployed, and a failed migration stops the
# release before any new container takes traffic. The steps live in
# bin/prod-deploy.sh, where they can be read and tested; the recipe that was
# here could never have run (its lines had no separators, and make expanded
# `$(grep …)` as a variable).
prod-deploy: require-prod-env ## Deploy to production: migrations first, then the stack.
	IMAGE='$(IMAGE)' IMAGE_TAG='$(IMAGE_TAG)' STACK_NAME='$(STACK_NAME)' PROD_SSH='$(PROD_SSH)' \
		DB_PASSWORD_SECRET='$(DB_PASSWORD_SECRET)' NEXHIRE_SECRET_CIPHER_KEY_SECRET='$(NEXHIRE_SECRET_CIPHER_KEY_SECRET)' \
		bin/prod-deploy.sh
endif

#
# Other
#

ifeq ($(PRIMARY_GOAL),help)
# **Round 704:** the trailing `{ blank = 0 }`. A section heading is a `# Word`
# line IMMEDIATELY after a bare `#`, and `blank` was never cleared by anything
# else — so the first prose line after any `#` separator, however far below,
# was printed as a heading. It went unnoticed because no comment block in this
# file had ever been longer than a line, and `docker/.env` is pulled into
# MAKEFILE_LIST by the include above, so its comments were rendered too.
help: ## This help.
	@awk 'BEGIN { printf "\nUsage:\n  make \033[36m<target>\033[0m\n" } \
	/^#$$/ { blank = 1; next } \
	blank && /^# [a-zA-Z]/ { printf "\n\033[1m%s\033[0m\n", substr($$0, 3); blank = 0; next } \
	/^[a-zA-Z_-]+:([^=]|$$)/ { \
		split($$0, parts, "##"); \
		target = parts[1]; sub(/:.*/, "", target); \
		desc = parts[2]; \
		gsub(/^[[:space:]]+|[[:space:]]+$$/, "", desc); \
		printf "  \033[36m%-25s\033[0m %s\n", target, desc; \
		blank = 0; \
	} \
	{ blank = 0 }' $(MAKEFILE_LIST)
endif
