.DEFAULT_GOAL := help
INTEGRATION ?= true
export INTEGRATION

listen ?= :6060
index_dir ?= ./tmp/indexer

TEST_OPTIONS = -race -timeout 30s
LOCK_FILE := ./tmp/.build.lock
LOCK_DIR := $(dir $(LOCK_FILE))
LIB_DIR = $(PWD)/libindexer/

GDK_GITLAB_URL ?= http://localhost:3000

help: ## Display available commands
	@echo "Available targets:"
	@awk -F':' '/^[a-zA-Z0-9_-]+:/ { \
		target = $$1; \
		has_help = match($$0, /##[^#].*$$/); \
		if (has_help) { \
			help = substr($$0, RSTART + 2, RLENGTH - 2); \
		} else { \
			help = ""; \
		} \
		printf "\033[36m%-20s\033[0m %s\n", target, help; \
	}' $(MAKEFILE_LIST) | sort
.PHONY: help

go-generate: ## Generate code
	@echo "Go Generate"
	go generate -tags=with_kuzu ./...
	@echo "[Generation complete]"
.PHONY: go-generate

test: go-generate ## Run tests
	@echo "Executing tests with INTEGRATION set to '$(INTEGRATION)'"
	set -o pipefail; go test ./... ${TEST_OPTIONS} 2>&1 | grep -v "has malformed LC_DYSYMTAB"
	@echo "[Done]"
.PHONY: test

cover: ## Run tests with coverage
	@echo "Executing tests with INTEGRATION set to '$(INTEGRATION)'"
	set -o pipefail; go test ./... ${TEST_OPTIONS} -cover -coverprofile=tmp/test.coverage 2>&1 | grep -v "has malformed LC_DYSYMTAB"
	gcov2lcov -infile=tmp/test.coverage -outfile=tmp/coverage.xml
	@echo "[Done]"
.PHONY: cover

bench: ## Run benchmarks
	go test -bench=. -benchmem -run ^$$ -benchtime=1s gitlab.com/gitlab-org/gitlab-zoekt-indexer/internal/file_cleaner
	@echo "[Done]"
.PHONY: bench

# Lock mechanism to prevent concurrent builds
acquire-lock:
	@echo "Acquiring build lock"
	@[ -d "$(LOCK_DIR)" ] || mkdir -p "$(LOCK_DIR)"
	@if [ -f "$(LOCK_FILE)" ] && kill -0 `cat "$(LOCK_FILE)"` 2>/dev/null; then \
		echo "Build in progress, waiting for it to complete..."; \
		while [ -f "$(LOCK_FILE)" ] && kill -0 `cat "$(LOCK_FILE)"` 2>/dev/null; do sleep 1; done; \
	fi
	@echo $$ > "$(LOCK_FILE)"

release-lock:
	$(release_lock)

build: acquire-lock ## Build indexer
	$(call not_recommended_warning,$@,build-unified)
	@trap '$(release_lock)' EXIT; \
	go build $(VERSION_FLAGS) -o bin/gitlab-zoekt-indexer ./cmd/gitlab-zoekt-indexer/
.PHONY: build

build_web: ## Build webserver (deprecated)
	$(call deprecated_warning,$@,build-web)
	@$(MAKE) build-web
.PHONY: build_web

build-web: acquire-lock ## Build webserver
	$(call not_recommended_warning,$@,build-unified)
	@trap '$(release_lock)' EXIT; \
	go build $(VERSION_FLAGS) -o bin/gitlab-zoekt-webserver ./cmd/gitlab-zoekt-webserver/
.PHONY: build-web

build_unified: ## Build unified binary (deprecated)
	$(call deprecated_warning,$@,build-unified)
	@$(MAKE) build-unified
.PHONY: build_unified

build-unified: acquire-lock ## Build unified binary
	@trap '$(release_lock)' EXIT; \
	CGO_ENABLED=0 go build $(VERSION_FLAGS) -o bin/gitlab-zoekt ./cmd/gitlab-zoekt/
.PHONY: build-unified

build-unified-with-kuzu: go-generate acquire-lock ## Build unified binary with kuzu
	@trap '$(release_lock)' EXIT; \
	CGO_LDFLAGS="-L$(LIB_DIR)/lib" CGO_CFLAGS="-I$(LIB_DIR)/include" go build -tags=with_kuzu $(VERSION_FLAGS) -o bin/gitlab-zoekt ./cmd/gitlab-zoekt/
 .PHONY: build-unified-with-kuzu

build-release: ## Build release binaries for multiple platforms
	mkdir -p build/
	@echo "Building Linux amd64 binary..."
	CGO_ENABLED=0 GOOS=linux GOARCH=amd64 go build $(VERSION_FLAGS) -o build/gitlab-zoekt-linux-amd64 ./cmd/gitlab-zoekt/
	@echo "Building Darwin amd64 binary..."
	CGO_ENABLED=0 GOOS=darwin GOARCH=amd64 go build $(VERSION_FLAGS) -o build/gitlab-zoekt-darwin-amd64 ./cmd/gitlab-zoekt/
	@echo "Generating checksums..."
	cd build && sha256sum gitlab-zoekt-linux-amd64 > gitlab-zoekt-linux-amd64.sha256
	cd build && sha256sum gitlab-zoekt-darwin-amd64 > gitlab-zoekt-darwin-amd64.sha256
	@echo "Release binaries built successfully!"
.PHONY: build-release

all: ## Build all binaries
	$(MAKE) -f $(MAKEFILE_LIST) build && $(MAKE) -f $(MAKEFILE_LIST) build-web && $(MAKE) -f $(MAKEFILE_LIST) build-unified
.PHONY: all

run: build ## Run indexer
	./bin/gitlab-zoekt-indexer -index_dir $(index_dir) -listen $(listen)
.PHONY: run

run-unified-indexer: build-unified ## Run unified indexer
	./bin/gitlab-zoekt indexer -index_dir $(index_dir) -listen $(listen)
.PHONY: run-unified-indexer

run-unified-web: build-unified ## Run unified webserver
	./bin/gitlab-zoekt webserver -index_dir $(index_dir) -listen $(listen)
.PHONY: run-unified-web

gdk: check-gdk-dir check-foreman ## Run all GDK development services via foreman
	@echo "Starting GDK services (development env) using foreman..."
	foreman start -f Procfile.gdk_development
.PHONY: gdk

gdk-indexer-1: check-gdk-dir build-unified ## Run indexer-1 with GDK
	@echo "Running indexer-1 in GDK mode. GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt indexer\
		-index_dir '$(GDK_DIR)/zoekt-data/development/index' \
		-listen :6080 \
		-gitlab_url '$(GDK_GITLAB_URL)' \
		-self_url 'http://localhost:6080' \
		-search_url 'http://localhost:6090' \
		-secret_path '$(GDK_DIR)/gitlab/.gitlab_shell_secret'
.PHONY: gdk-indexer-1

gdk-webserver-1: check-gdk-dir build-unified ## Run webserver-1 with GDK
	@echo "Running webserver-1 in GDK mode. GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt webserver \
		-index_dir '$(GDK_DIR)/zoekt-data/development/index' \
		-rpc \
		-listen :6090 \
		-secret_path '$(GDK_DIR)/gitlab/.gitlab_shell_secret'
.PHONY: gdk-webserver-1

gdk-indexer-2: check-gdk-dir build-unified ## Run indexer-2 with GDK
	@echo "Running indexer-2 in GDK mode. GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt indexer \
		-index_dir '$(GDK_DIR)/zoekt-data/development/index-2' \
		-listen :6081 \
		-gitlab_url '$(GDK_GITLAB_URL)' \
		-self_url 'http://localhost:6081' \
		-search_url 'http://localhost:6091' \
		-secret_path '$(GDK_DIR)/gitlab/.gitlab_shell_secret'
.PHONY: gdk-indexer-2

gdk-webserver-2: check-gdk-dir build-unified ## Run webserver-2 with GDK
	@echo "Running webserver-2 in GDK mode. GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt webserver \
		-index_dir '$(GDK_DIR)/zoekt-data/development/index-2' \
		-rpc \
		-listen :6091 \
		-secret_path '$(GDK_DIR)/gitlab/.gitlab_shell_secret'
.PHONY: gdk-webserver-2

gdk-test: check-gdk-dir check-foreman ## Run all GDK services (test env) via foreman
	@echo "Starting GDK services (test env) using foreman..."
	foreman start -f Procfile.gdk_test
.PHONY: gdk-test

gdk-test-indexer: check-gdk-dir build-unified ## Run indexer in GDK test environment
	@echo "Running indexer in GDK mode (test env). GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt indexer \
		-index_dir '$(GDK_DIR)/zoekt-data/test/index' \
		-listen :6060
.PHONY: gdk-test-indexer

gdk-test-webserver: check-gdk-dir build-unified ## Run indexer in GDK test environment
	@echo "Running webserver in GDK mode (test env). GDK_DIR is '$(GDK_DIR)'"
	./bin/gitlab-zoekt webserver \
		-index_dir '$(GDK_DIR)/zoekt-data/test/index' \
		-rpc \
		-listen :6070
.PHONY: gdk-test-webserver

watch-run: check-watchexec ## Watch for changes and run indexer
	watchexec -c -n -r -e go -- make run
.PHONY: watch-run

watch-web: check-watchexec ## Watch for changes and run webserver
	watchexec -c -n -r -e go -- make run-unified-web
.PHONY: watch-web

watch-gdk: check-watchexec ## Watch for changes and run GDK
	watchexec -c -n -r -e go -- make gdk
.PHONY: watch-gdk

watch-test: check-watchexec ## Watch for changes and run tests
	watchexec -c -n -e go -- make test
.PHONY: watch-test

watch-cover: check-watchexec ## Watch for changes and run coverage
	watchexec -c -n -e go -- make cover
.PHONY: watch-cover

check-gdk-dir: ## Check GDK directory exists
ifndef GDK_DIR
	$(error GDK_DIR must be set)
endif
.PHONY: check-gdk-dir

check-watchexec: ## Check watchexec is installed
ifeq (, $(shell which watchexec))
	$(error "No watchexec installed, consider running brew install watchexec")
endif
.PHONY: check-watchexec

check-foreman: ## Check foreman is installed
ifeq (, $(shell which foreman))
	$(error "No foreman installed, consider running gem install foreman")
endif
.PHONY: check-foreman

golangci-lint:
	CGO_LDFLAGS="-L$(LIB_DIR)/lib" CGO_CFLAGS="-I$(LIB_DIR)/include" golangci-lint run -v
.PHONY: golangci-lint

shadow-lint:
	CGO_LDFLAGS="-L$(LIB_DIR)/lib" CGO_CFLAGS="-I$(LIB_DIR)/include" shadow ./...
.PHONY: shadow-lint

##### =====> Internals <===== #####

# Display deprecation warning
# Usage: $(call deprecated_warning,target_name[,replacement_target])
define deprecated_warning
   @printf "\033[31mWARNING: 'make $(1)' is deprecated and will be removed in a future release.$(if $(2), Please use 'make $(2)' instead.)\033[0m\n"
endef

# Display not recommended warning
# Usage: $(call not_recommended_warning,target_name,recommended_target)
define not_recommended_warning
   @printf "\033[33mNOTE: 'make $(1)' is not recommended. Please consider using 'make $(2)' instead if possible.\033[0m\n"
endef

define release_lock
if [ -f "$(LOCK_FILE)" ]; then echo "Releasing build lock"; rm -f "$(LOCK_FILE)"; fi
endef

DATE             := $(shell date -u '+%Y.%m.%d')
VERSION          := $(shell git describe --tags --always --dirty=".dev")
BUILD_TIME       := $(shell date -u '+%Y-%m-%d %H:%M UTC')
VERSION_FLAGS    := -ldflags='-X "main.Version=$(DATE)-$(VERSION)" -X "main.BuildTime=$(BUILD_TIME)"'
