.PHONY: build web go go-embed linux dev dev-server dev-backend dev-web setup deploy-web \
        render-k8s render-k8s-stdout k8s-dry-run \
        vulncheck gosec pnpm-audit \
        test test-race vet fmt fmt-check tsc \
        tools docs verify \
        version version-patch version-minor version-major

# ── Build ─────────────────────────────────────────────────────────────────────
CMD    := ./cmd/dune-admin
PKG    := ./...
GO     := go
PREFIX ?= /usr/local
COGNIT_TARGET := $(if $(wildcard cmd/dune-admin),./cmd/dune-admin,.)

# On Windows, Make defaults to cmd.exe which can't run POSIX recipes.
# Force bash from Git for Windows so all targets work from any terminal.
ifeq ($(OS),Windows_NT)
SHELL     := cmd.exe
.SHELLFLAGS := /C
BIN       := bin/dune-admin.exe
LOCAL_BIN := dune-admin.exe
else
BIN       := bin/dune-admin
LOCAL_BIN := dune-admin
endif

ifeq ($(OS),Windows_NT)
VERSION    ?= $(shell type VERSION 2>NUL || echo dev)
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>NUL || echo unknown)
BUILD_TIME ?= $(shell powershell -NoProfile -Command "[DateTime]::UtcNow.ToString('yyyy-MM-ddTHH:mm:ssZ')")
else
VERSION    ?= $(shell cat VERSION 2>/dev/null || git describe --tags --always --dirty 2>/dev/null || echo "dev")
GIT_COMMIT ?= $(shell git rev-parse --short HEAD 2>/dev/null || echo "unknown")
BUILD_TIME ?= $(shell date -u '+%Y-%m-%dT%H:%M:%SZ')
endif
LDFLAGS    := -ldflags "-s -w -X main.AppVersion=$(VERSION) -X main.GitCommit=$(GIT_COMMIT) -X main.BuildTime=$(BUILD_TIME)"

# Build frontend + backend binary with embedded SPA.
build: web go-embed

# Build backend binary only (no embedded frontend).
go:
ifeq ($(OS),Windows_NT)
	@if not exist bin mkdir bin
	$(GO) build -trimpath $(LDFLAGS) -o $(BIN) $(CMD)
	@copy /Y "bin\dune-admin.exe" "$(LOCAL_BIN)" >NUL
else
	@mkdir -p bin
	$(GO) build -trimpath $(LDFLAGS) -o $(BIN) $(CMD)
	install -m 0755 $(BIN) ./$(LOCAL_BIN)
endif

# Build backend binary with embedded frontend (requires make web first).
go-embed:
ifeq ($(OS),Windows_NT)
	@if not exist bin mkdir bin
	$(GO) build -trimpath $(LDFLAGS) -tags embed -o $(BIN) $(CMD)
	@copy /Y "bin\dune-admin.exe" "$(LOCAL_BIN)" >NUL
else
	@mkdir -p bin
	$(GO) build -trimpath $(LDFLAGS) -tags embed -o $(BIN) $(CMD)
	install -m 0755 $(BIN) ./dune-admin
endif

# Install the binary system-wide (Linux/macOS only).
install: go
ifeq ($(OS),Windows_NT)
	@echo "Use 'make go' to build. Copy $(BIN) to your desired location manually."
else
	install -d $(DESTDIR)$(PREFIX)/bin
	install -m 0755 $(BIN) $(DESTDIR)$(PREFIX)/bin/dune-admin
endif

linux:
	GOOS=linux GOARCH=amd64 $(GO) build -trimpath $(LDFLAGS) -o dune-admin-linux $(CMD)

dev-server:
	go run $(CMD)

dev-backend:
ifeq ($(OS),Windows_NT)
	go tool github.com/air-verse/air -build.cmd "go build -o ./tmp/dune-admin.exe ./cmd/dune-admin" -build.bin "./tmp/dune-admin.exe" -build.full_bin "./tmp/dune-admin.exe"
else
	go tool github.com/air-verse/air
endif

dev-web:
	cd web && pnpm dev

dev:
ifeq ($(OS),Windows_NT)
	@start "dune-admin-backend" /MIN $(MAKE) dev-backend
	-@cd web && node node_modules\vite\bin\vite.js
	-@taskkill /F /FI "WINDOWTITLE eq dune-admin-backend*" >NUL 2>&1
	-@taskkill /F /IM dune-admin.exe >NUL 2>&1
else
	@set -e; \
	AIR_PID=; VITE_PID=; \
	cleanup() { \
		trap - EXIT INT TERM; \
		[ -n "$$AIR_PID" ] && kill $$AIR_PID 2>/dev/null || true; \
		[ -n "$$VITE_PID" ] && kill $$VITE_PID 2>/dev/null || true; \
		[ -n "$$AIR_PID" ] && wait $$AIR_PID 2>/dev/null || true; \
		[ -n "$$VITE_PID" ] && wait $$VITE_PID 2>/dev/null || true; \
	}; \
	trap 'cleanup' EXIT INT TERM; \
	$(MAKE) dev-backend & AIR_PID=$$!; \
	$(MAKE) dev-web & VITE_PID=$$!; \
	set +e; \
	while kill -0 $$AIR_PID 2>/dev/null && kill -0 $$VITE_PID 2>/dev/null; do \
		sleep 1; \
	done; \
	if ! kill -0 $$AIR_PID 2>/dev/null; then \
		wait $$AIR_PID; status=$$?; \
		kill $$VITE_PID 2>/dev/null || true; \
		wait $$VITE_PID 2>/dev/null || true; \
	else \
		wait $$VITE_PID; status=$$?; \
		kill $$AIR_PID 2>/dev/null || true; \
		wait $$AIR_PID 2>/dev/null || true; \
	fi; \
	exit $$status
endif

setup:
	go run $(CMD) -setup

# ── Web ───────────────────────────────────────────────────────────────────────

web:
	cd web && pnpm install --frozen-lockfile && pnpm build
ifeq ($(OS),Windows_NT)
	@if exist cmd\dune-admin\dist rmdir /S /Q cmd\dune-admin\dist
	@xcopy /E /I /Q web\dist cmd\dune-admin\dist >NUL
else
	rm -rf cmd/dune-admin/dist
	cp -r web/dist cmd/dune-admin/dist
endif

deploy-web:
	cd web && pnpm install --frozen-lockfile && pnpm build && wrangler pages deploy dist --project-name dune-admin

render-k8s:
	go run $(CMD) -render-k8s deploy/k8s/dune-admin.rendered.yaml

render-k8s-stdout:
	go run $(CMD) -render-k8s -

k8s-dry-run:
	@$(MAKE) render-k8s-stdout | kubectl apply --dry-run=client -f -

# ── Test ──────────────────────────────────────────────────────────────────────

test:
	go test $(PKG)

test-race:
	go test -race $(PKG)

# ── Quality ───────────────────────────────────────────────────────────────────

vet:
	go vet $(PKG)

fmt:
	go fmt $(PKG)
	gofmt -s -w .

fmt-check:
ifeq ($(OS),Windows_NT)
	@powershell -NoProfile -Command "if (gofmt -l .) { Write-Host 'Code is not formatted. Run make fmt'; exit 1 }"
else
	@test -z "$$(gofmt -l .)" || (echo "Code is not formatted. Run 'make fmt'" && exit 1)
endif

vulncheck:
	go tool golang.org/x/vuln/cmd/govulncheck $(PKG)

gocognit:
	@echo "Running code complexity analysis with gocognit..."
ifeq ($(OS),Windows_NT)
	@$(GO) tool github.com/uudashr/gocognit/cmd/gocognit -over 15 -ignore "_test|node_modules" $(COGNIT_TARGET) \
		> %TEMP%\gocognit-out.txt 2>&1 || (exit /b 0)
	@powershell -NoProfile -Command "\
		$$ignore = (Get-Content .gocognit-ignore | Where-Object { $$_ -notmatch '^\s*#' -and $$_.Trim() } | ForEach-Object { ($$_ -split '\s+')[0] }); \
		$$lines = Get-Content $$env:TEMP\gocognit-out.txt -ErrorAction SilentlyContinue | Where-Object { $$line = $$_; -not ($$ignore | Where-Object { $$line -like \"*$$_*\" }) }; \
		if ($$lines) { $$lines | Write-Host; exit 1 }"
else
	@$(GO) tool github.com/uudashr/gocognit/cmd/gocognit -over 15 -ignore "_test|node_modules" $(COGNIT_TARGET) \
		> /tmp/gocognit-out.txt 2>&1 || true; \
	grep -v '^#' .gocognit-ignore | awk '{print $$1}' > /tmp/gocognit-ignore.txt; \
	grep -v -F -f /tmp/gocognit-ignore.txt /tmp/gocognit-out.txt > /tmp/gocognit-new.txt || true; \
	if [ -s /tmp/gocognit-new.txt ]; then cat /tmp/gocognit-new.txt; exit 1; fi
endif

gosec:
	go tool github.com/securego/gosec/v2/cmd/gosec -severity high -confidence high $(PKG)

pnpm-audit:
	cd web && pnpm audit --audit-level=high

tsc:
	cd web && pnpm typecheck

lint:
	@$(MAKE) lint-go
	@$(MAKE) lint-md

lint-go:
	@$(GO) tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint run

lint-md:
	@npx -y markdownlint-cli2 --fix "**/*.md"

verify:
	@$(MAKE) fmt-check
	@$(MAKE) vet
	@$(MAKE) test-race
	@$(MAKE) vulncheck
	@$(MAKE) lint
	@$(MAKE) gocognit
	@echo "All verification checks passed!"

# ── Docs ──────────────────────────────────────────────────────────────────────

docs:
	$(GO) tool github.com/swaggo/swag/cmd/swag init -g cmd/dune-admin/main.go -o docs

# ── Tools ─────────────────────────────────────────────────────────────────────

tools:
	@echo "Caching dev tools (versions pinned in go.mod)..."
	@$(GO) tool github.com/golangci/golangci-lint/v2/cmd/golangci-lint --version || true
	@$(GO) tool github.com/air-verse/air -v || true
	@$(GO) tool golang.org/x/vuln/cmd/govulncheck -version || true
	@$(GO) tool github.com/uudashr/gocognit/cmd/gocognit -version || true
	@$(GO) tool github.com/securego/gosec/v2/cmd/gosec --version || true
	@echo "Done!"

# Print current version.
version:
	@echo $(VERSION)

# Setup git hooks
hooks:
	@git config core.hooksPath .githooks
	@echo "Git hooks configured!"

# Bump patch version (1.0.0 → 1.0.1), commit, tag, and push — triggers release workflow.
version-patch:
	@V=$$(cat VERSION); \
	MAJOR=$$(echo $$V | cut -d. -f1); \
	MINOR=$$(echo $$V | cut -d. -f2); \
	PATCH=$$(echo $$V | cut -d. -f3); \
	NEW="$$MAJOR.$$MINOR.$$((PATCH + 1))"; \
	printf "Push tag v$$NEW to origin? [y/N] "; read ans; [ "$$ans" = "y" ] || { echo "Aborted."; exit 1; }; \
	echo $$NEW > VERSION; \
	git add VERSION && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
	git push && git push origin "v$$NEW"; \
	echo "Bumped $$V -> $$NEW (tagged and pushed v$$NEW)"

# Bump minor version (1.0.0 → 1.1.0), commit, tag, and push — triggers release workflow.
version-minor:
	@V=$$(cat VERSION); \
	MAJOR=$$(echo $$V | cut -d. -f1); \
	MINOR=$$(echo $$V | cut -d. -f2); \
	NEW="$$MAJOR.$$((MINOR + 1)).0"; \
	printf "Push tag v$$NEW to origin? [y/N] "; read ans; [ "$$ans" = "y" ] || { echo "Aborted."; exit 1; }; \
	echo $$NEW > VERSION; \
	git add VERSION && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
	git push && git push origin "v$$NEW"; \
	echo "Bumped $$V -> $$NEW (tagged and pushed v$$NEW)"

# Bump major version (1.0.0 → 2.0.0), commit, tag, and push — triggers release workflow.
version-major:
	@V=$$(cat VERSION); \
	MAJOR=$$(echo $$V | cut -d. -f1); \
	NEW="$$((MAJOR + 1)).0.0"; \
	printf "Push tag v$$NEW to origin? [y/N] "; read ans; [ "$$ans" = "y" ] || { echo "Aborted."; exit 1; }; \
	echo $$NEW > VERSION; \
	git add VERSION && git commit -m "chore: bump version to $$NEW" && git tag "v$$NEW"; \
	git push && git push origin "v$$NEW"; \
	echo "Bumped $$V -> $$NEW (tagged and pushed v$$NEW)"
