# =============================================================================
# go-doc — documentation & audience feedback platform
#
#   make help                  list every target
#   make dev                   run infrastructure + backend + frontend together
#   make test                  run unit, integration and BDD suites
#   make build                 embed the React bundle into the portable Go binary
#   make docker                build the all-in-one image
#   make docker-push           build and push the image to the private registry
#   make docker-run            run the image on :8080
#   make helm-deploy-development  deploy to development
#   make helm-deploy-production   deploy to production
# =============================================================================

SHELL        := /bin/bash
.SHELLFLAGS  := -eu -o pipefail -c

GO           ?= go
NPM          ?= npm
COMPOSE      ?= docker compose
HELM         ?= helm

ROOT         := $(shell pwd)
WORKSPACE    ?= $(dir $(ROOT))
BACKEND_DIR  := $(ROOT)/backend
FRONTEND_DIR := $(ROOT)/frontend
DATA_DIR     := $(ROOT)/data
WEBUI_DIST   := $(BACKEND_DIR)/webui/dist

BINARY       := $(ROOT)/bin/godoc
BACKEND_ADDR ?= 0.0.0.0:8080
HOST_ADDR    ?= 127.0.0.1:8080
VITE_PORT    ?= 5173

# Docker/Registry
IMAGE           ?= go-doc
TAG             ?= latest
REGISTRY_IMAGE  ?= nexus.urpi.be/docker-hosted/go-doc

# Helm
HELM_CHART      := $(ROOT)/helm/go-doc
HELM_RELEASE    ?= go-doc
HELM_NAMESPACE  ?= go-doc
KUBECONFIG      ?= $(HOME)/.kube/config
GO_LOOSE_CA_SECRET ?= local-ca

# Go Loose credentials passed via environment or .env
GO_LOOSE_SET = $(if $(GO_LOOSE_CLIENT_ID),--set-string goLoose.clientID=$(GO_LOOSE_CLIENT_ID)) \
	$(if $(GO_LOOSE_CLIENT_SECRET),--set-string goLoose.clientSecret=$(GO_LOOSE_CLIENT_SECRET)) \
	$(if $(GO_LOOSE_CA_SECRET),--set-string goLoose.ca.existingSecret=$(GO_LOOSE_CA_SECRET))

# Parent directory that holds the go-* projects to document. Override with
# WORKSPACE=/path/to/projects when the projects live elsewhere.
DOCS_WORKSPACE ?= $(patsubst %/,%,$(WORKSPACE))
DOCGEN_INTERVAL ?= 5s

DOCS_COUNT    ?= 120
COMMENTS_PER_DOC ?= 25
SEED_WORKERS ?= 8

GO_SOURCES  := $(shell find $(BACKEND_DIR) -name '*.go' -type f 2>/dev/null)
FRONTEND_SOURCES := $(shell find $(FRONTEND_DIR)/src -type f 2>/dev/null)

.DEFAULT_GOAL := help
.PHONY: help infra-up infra-down infra-logs backend-run frontend-run dev test \
        test-unit test-integration test-bdd test-race build build-frontend \
        build-backend fmt vet tidy seed seed-perf clean distclean run docker-build \
        docker docker-push docker-run helm-lint helm-deploy-development \
        helm-deploy-production helm-down-development helm-down-production \
        docs-sync docs-watch docs-dry-run docs-list

## ---------------------------------------------------------------------------
## Help
## ---------------------------------------------------------------------------
help: ## Show this help
	@printf '\n\033[1mgo-doc\033[0m — available targets\n\n'
	@grep -hE '^[a-zA-Z_-]+:.*?## .*$$' $(MAKEFILE_LIST) \
		| sort \
		| awk 'BEGIN {FS = ":.*?## "}; {printf "  \033[36m%-18s\033[0m %s\n", $$1, $$2}'
	@printf '\n'

## ---------------------------------------------------------------------------
## Infrastructure
## ---------------------------------------------------------------------------
infra-up: ## Start local development containers detached
	@if $(COMPOSE) version >/dev/null 2>&1; then \
		$(COMPOSE) up -d --remove-orphans; \
	else \
		echo "error: '$(COMPOSE)' is unavailable. Install Docker Compose v2." >&2; \
		exit 1; \
	fi

infra-down: ## Tear down infrastructure and remove volumes
	@if $(COMPOSE) version >/dev/null 2>&1; then \
		$(COMPOSE) down --volumes --remove-orphans; \
	else \
		echo "docker compose unavailable; nothing to tear down"; \
	fi

infra-logs: ## Tail infrastructure logs
	$(COMPOSE) logs -f

docker-build: ## Build the container images
	$(COMPOSE) build

## ---------------------------------------------------------------------------
## Run
## ---------------------------------------------------------------------------
backend-run: ## Run the Go API natively on 0.0.0.0:8080
	@mkdir -p $(DATA_DIR)
	@echo "→ backend  http://$(HOST_ADDR)"
	@cd $(BACKEND_DIR) && GO_DOC_DATA_DIR=$(DATA_DIR) $(GO) run main.go -addr $(BACKEND_ADDR)

frontend-run: ## Run the Vite dev server on port 5173
	@echo "→ frontend http://$(VITE_PORT)"
	@cd $(FRONTEND_DIR) && $(NPM) run dev -- --port $(VITE_PORT)

dev: ## Run backend, frontend and infrastructure concurrently
	@$(MAKE) --no-print-directory infra-up
	@echo "→ backend  http://$(HOST_ADDR)"
	@echo "→ frontend http://$(VITE_PORT)  (Ctrl-C stops everything)"
	@trap 'echo; echo "→ stopping dev stack"; kill 0' EXIT INT TERM; \
	( cd $(BACKEND_DIR) && GO_DOC_DATA_DIR=$(DATA_DIR) $(GO) run main.go -addr $(BACKEND_ADDR) ) & \
	( cd $(FRONTEND_DIR) && $(NPM) run dev -- --port $(VITE_PORT) ) & \
	wait

run: build ## Build the single-file binary and run it
	@echo "→ serving SPA + API on http://$(HOST_ADDR)"
	@$(BINARY) -addr $(BACKEND_ADDR) -data $(DATA_DIR)

## ---------------------------------------------------------------------------
## Test
## ---------------------------------------------------------------------------
test: fmt vet ## Run every suite: unit, integration, BDD, race
	@echo "→ go test ./... -count=1"
	@cd $(BACKEND_DIR) && $(GO) test ./... -count=1
	@echo "→ race detector on the storage engine and API"
	@cd $(BACKEND_DIR) && $(GO) test ./storage/... ./tests/... -race -count=1
	@echo "✓ all suites passed"

test-unit: ## Run storage engine unit tests only
	@cd $(BACKEND_DIR) && $(GO) test ./storage/... -count=1 -v

test-integration: ## Run end-to-end API tests against the real filesystem
	@cd $(BACKEND_DIR) && $(GO) test ./tests/ -run Integration -count=1 -v

test-bdd: ## Run the BDD feature suites
	@cd $(BACKEND_DIR) && $(GO) test ./tests/bdd/... -count=1 -v

test-race: ## Run all tests under the race detector
	@cd $(BACKEND_DIR) && $(GO) test ./... -race -count=1

## ---------------------------------------------------------------------------
## Build
## ---------------------------------------------------------------------------
build: build-frontend build-backend ## Build the SPA and embed it in the Go binary
	@echo "✓ $(BINARY) serves both the SPA and /api"

build-frontend: $(FRONTEND_SOURCES) ## Compile the React bundle into frontend/dist
	@cd $(FRONTEND_DIR) && $(NPM) install --no-audit --no-fund
	@cd $(FRONTEND_DIR) && $(NPM) run build

build-backend: $(GO_SOURCES) ## Stage assets into webui/dist and compile the binary
	@test -f $(FRONTEND_DIR)/dist/index.html || { \
		echo "error: frontend/dist/index.html missing — run 'make build-frontend' first" >&2; \
		exit 1; \
	}
	@mkdir -p $(WEBUI_DIST) $(dir $(BINARY))
	@rm -rf $(WEBUI_DIST)
	@mkdir -p $(WEBUI_DIST)
	@cp -r $(FRONTEND_DIR)/dist/. $(WEBUI_DIST)/
	@echo "→ embedded assets: $$(find $(WEBUI_DIST) -type f | wc -l) files"
	@cd $(BACKEND_DIR) && CGO_ENABLED=0 $(GO) build -trimpath -ldflags '-s -w' -o $(BINARY) .
	@echo "✓ built $(BINARY) ($$(du -h $(BINARY) | cut -f1))"

## ---------------------------------------------------------------------------
## Quality
## ---------------------------------------------------------------------------
fmt: ## gofmt the backend
	@cd $(BACKEND_DIR) && $(GO) fmt ./... >/dev/null
	@echo "✓ gofmt"

vet: ## go vet the backend
	@cd $(BACKEND_DIR) && $(GO) vet ./...
	@echo "✓ go vet"

tidy: ## Tidy go.mod
	@cd $(BACKEND_DIR) && $(GO) mod tidy

## ---------------------------------------------------------------------------
## Documentation generation
## ---------------------------------------------------------------------------
## Every go-* project under WORKSPACE is analysed and imported into data/docs.
## Generated pages are wrapped in <!-- go-doc:generated ---> markers, so a
## re-sync replaces only the machine-written part and keeps anything you wrote
## outside the markers. Pass DOCGEN_ARGS to reach the CLI directly, e.g.
##   make docs-sync DOCGEN_ARGS="-apps go-tell -force"

docs-sync: ## Analyse every go-* project under WORKSPACE and import its docs (DOCGEN_ARGS)
	@mkdir -p $(DATA_DIR)/docs
	@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
		-root $(DOCS_WORKSPACE) -data $(DATA_DIR) $(DOCGEN_ARGS)

docs-watch: ## Keep importing documentation as projects change (Ctrl-C to stop)
	@mkdir -p $(DATA_DIR)/docs
	@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
		-root $(DOCS_WORKSPACE) -data $(DATA_DIR) -watch -interval $(DOCGEN_INTERVAL) $(DOCGEN_ARGS)

docs-dry-run: ## Analyse and render without writing anything
	@cd $(BACKEND_DIR) && $(GO) run ./scripts/docgen \
		-root $(DOCS_WORKSPACE) -data $(DATA_DIR) -dry-run $(DOCGEN_ARGS)

docs-list: ## List the go-* projects that a sync would import
	@printf 'workspace: %s\n\n' '$(DOCS_WORKSPACE)'
	@ls -1 $(DOCS_WORKSPACE) 2>/dev/null | grep '^go-' | grep -v '^go-doc$$' | sed 's/^/  /'

## ---------------------------------------------------------------------------
## Data
## ---------------------------------------------------------------------------
seed: ## Seed 120 generated documents + thousands of comments (DOCS_COUNT, COMMENTS_PER_DOC)
	@cd $(BACKEND_DIR) && $(GO) run ./scripts/seed_perf_data.go \
		-data $(DATA_DIR) -docs $(DOCS_COUNT) -comments $(COMMENTS_PER_DOC) -workers $(SEED_WORKERS)

seed-perf: seed ## Alias for seed

## ---------------------------------------------------------------------------
## Clean
## ---------------------------------------------------------------------------
clean: ## Remove build artefacts
	@rm -rf $(ROOT)/bin $(FRONTEND_DIR)/dist
	@echo "✓ cleaned build artefacts (backend/webui/dist left intact)"

distclean: clean ## Also restore the placeholder frontend bundle
	@rm -rf $(WEBUI_DIST)
	@mkdir -p $(WEBUI_DIST)
	@cp $(BACKEND_DIR)/webui/dist.placeholder/index.html $(WEBUI_DIST)/index.html
	@echo "✓ webui/dist reset to the placeholder bundle"

## ---------------------------------------------------------------------------
## Docker
## ---------------------------------------------------------------------------
docker: ## Build the all-in-one image
	docker build -t $(IMAGE):$(TAG) -f $(BACKEND_DIR)/Dockerfile .

docker-push:  ## Build and push the image to the private registry
	docker tag $(IMAGE):$(TAG) $(REGISTRY_IMAGE):$(TAG)
	docker push $(REGISTRY_IMAGE):$(TAG)

docker-run: ## Run the image on :8080
	docker run --rm -p 8080:8080 --env-file $(ROOT)/.env $(IMAGE):$(TAG)

## ---------------------------------------------------------------------------
## Helm
## ---------------------------------------------------------------------------
helm-lint: ## Lint the helm chart
	$(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-dev.yaml
	$(HELM) lint $(HELM_CHART) -f $(HELM_CHART)/values-prd.yaml
	$(HELM) template $(HELM_RELEASE) $(HELM_CHART) \
		-f $(HELM_CHART)/values-dev.yaml >/dev/null
	$(HELM) template $(HELM_RELEASE) $(HELM_CHART) \
		-f $(HELM_CHART)/values-prd.yaml \
		--set-string goLoose.clientID=lint --set-string goLoose.clientSecret=lint >/dev/null

# Go Loose credentials are never stored in the values files. Pass them on the
# command line, from a secret store, or with --set-file:
#   make helm-deploy-production \
#     --set goLoose.clientID=$(GO_LOOSE_CLIENT_ID) \
#     --set goLoose.clientSecret=$(GO_LOOSE_CLIENT_SECRET) \
#     --set goLoose.ca.existingSecret=local-ca
helm-deploy-development: ## Deploy to development
	KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
		--namespace $(HELM_NAMESPACE)-development --create-namespace \
		-f $(HELM_CHART)/values-dev.yaml \
		--rollback-on-failure --wait --timeout 10m

helm-deploy-production: ## Deploy to production
	set -a; [ ! -f $(ROOT)/.env ] || . $(ROOT)/.env; set +a; \
	KUBECONFIG="$(KUBECONFIG)" $(HELM) upgrade --install $(HELM_RELEASE) $(HELM_CHART) \
		--namespace go-insane-prd --create-namespace \
		-f $(HELM_CHART)/values-prd.yaml \
		$(GO_LOOSE_SET) \
		--rollback-on-failure --wait --timeout 10m

helm-down-development: ## Tear down development release
	KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-development

helm-down-production: ## Tear down production release
	KUBECONFIG="$(KUBECONFIG)" $(HELM) uninstall $(HELM_RELEASE) -n $(HELM_NAMESPACE)-production