########################################
## Functions

# Check that a required environment variable is defined
define check_env_var
	@if [ -z "$($(1))" ]; then echo "Error: $(1) env var is not defined" >&2; exit 1; fi
endef

########################################
## Configuration

# Current OCaml version
OCAML_VERSION = "4.14.2"

# machine word size
WORD_SIZE = "64"

# Default profile
ifeq ($(DUNE_PROFILE),)
DUNE_PROFILE := dev
endif

########################################
## Branch and versioning
# Default branch name
# This is used for versioning and release purposes when building docker or debian
# For example when BRANCH_NAME=fix-branch:
# Target version : 3.1.2-alpha-fix-branch-bullseye-devnet
BRANCH_NAME ?= $(shell git rev-parse --abbrev-ref HEAD)

ifeq ($(OPAMSWITCH)$(IN_NIX_SHELL)$(CI)$(BUILDKITE),)
# Sometimes opam replaces these env variables in shell with
# an explicit mention of a particular switch (dereferenced from the value)
OPAM_SWITCH_PREFIX := $(PWD)/_opam
OCAML_TOPLEVEL_PATH := $(OPAM_SWITCH_PREFIX)/lib/toplevel
PATH := $(OPAM_SWITCH_PREFIX)/bin:$(PATH)
endif

# Temp directory
TMPDIR ?= /tmp

# Genesis dir
GENESIS_DIR := $(TMPDIR)/coda_cache_dir

# Coverage directory
COVERAGE_DIR=_coverage

########################################
## Handy variables

# Distribution codename, to be used in Docker builds
CODENAME ?= $(shell lsb_release -cs)

# Network for the aggregate local build targets (debian-build-all / docker-build-all).
# devnet or mainnet.
NETWORK ?= devnet

# Set LOAD_ONLY=1 for purely local docker builds: images are loaded into the
# local docker daemon only and NOT pushed to the (o1labs) registry. Leave unset
# to preserve the historical push behaviour of build_docker_image.
ifeq ($(LOAD_ONLY),1)
DOCKER_LOAD_ONLY_ARG := --load-only
else
DOCKER_LOAD_ONLY_ARG :=
endif

# Individual docker-build-* targets build from scratch, preserving their
# historical behaviour. docker-build-all overrides this to empty: it builds
# mina-base, archive and daemon back-to-back off the SAME base-deps layer, so
# --no-cache would re-run that layer's apt update/upgrade and gcloud SDK
# download three times in one invocation.
DOCKER_NO_CACHE ?= --no-cache

# This commit hash
GITHASH := $(shell git rev-parse --short=8 HEAD)
GITLONGHASH := $(shell git rev-parse HEAD)

# Unique signature of libp2p code tree
LIBP2P_HELPER_SIG := $(shell cd src/app/libp2p_helper ; find . -type f -print0  | xargs -0 sha1sum | sort | sha1sum | cut -f 1 -d ' ')

# Database for archive
PG_USER ?= mina
PG_PW 	?= minaminamina
PG_DB 	?= archive
PG_HOST	?= localhost
PG_PORT	?= 5432

########################################
.PHONY: help
help: ## Display this help information
	@grep -E '^[a-zA-Z0-9_-]+:.*?## .*$$' $(MAKEFILE_LIST) | sort | awk 'BEGIN {FS = ":.*?## "}; {printf "\033[36m%-30s\033[0m %s\n", $$1, $$2}'


########################################
.PHONY: add-o1labs-opam-repo
add-o1labs-opam-repo:
	opam repository add --yes --all --set-default o1-labs https://github.com/o1-labs/opam-repository.git

.PHONY: prepare
prepare: add-o1labs-opam-repo
	@echo "Preparing the environment and installing dependencies..."
	@# Check Go installation and version
	@command -v go >/dev/null 2>&1 || { echo >&2 "Error: Go is not installed. Please install Go before continuing. You can use gvm to install the appropriate Go environment."; exit 1; }
	@GO_VERSION=$$(go version | awk '{print $$3}' | sed 's/go//'); \
	GO_MOD_PATH="src/app/libp2p_helper/src/go.mod"; \
	REQUIRED_GO_VERSION=$$(grep -E "^go [0-9]+\.[0-9]" $$GO_MOD_PATH | awk '{print $$2}'); \
	if ! printf '%s\n%s\n' "$$REQUIRED_GO_VERSION" "$$GO_VERSION" | sort -V | head -n1 | grep -q "^$$REQUIRED_GO_VERSION$$"; then \
		echo "Error: Go version $$GO_VERSION is not compatible. Required version is $$REQUIRED_GO_VERSION or newer (only minor)."; \
		exit 1; \
	fi; \
	echo "Go version $$GO_VERSION detected (requirement: $$REQUIRED_GO_VERSION or newer (only minor))"
	opam switch import --switch mina --yes opam.export
	eval $(opam env --switch=mina --set-switch)
	chmod +x scripts/pin-external-packages.sh
	./scripts/pin-external-packages.sh
	@echo "Environment prepared. You can now run 'make build' to build the project."


########################################
## Code

.PHONY: all
all: clean build ## Clean and build the project

.PHONY: clean
clean: ## Remove build artifacts
	$(info Removing previous build artifacts)
	@rm -rf _build
	@rm -rf Cargo.lock target
	@rm -rf src/$(COVERAGE_DIR)
	@rm -rf src/app/libp2p_helper/result src/libp2p_ipc/libp2p_ipc.capnp.go

.PHONY: switch
switch: ## Set up the opam switch
	./scripts/update-opam-switch.sh

.PHONY: ocaml_version
ocaml_version: switch ## Check OCaml version
	@if ! ocamlopt -config | grep "version:" | grep -q $(OCAML_VERSION); then echo "❌ incorrect OCaml version, expected version $(OCAML_VERSION)" ; exit 1; else echo "✅ OCaml version is correct"; fi

.PHONY: ocaml_word_size
ocaml_word_size: switch ## Check OCaml word size
	@if ! ocamlopt -config | grep "word_size:" | grep -q $(WORD_SIZE); then echo "❌ invalid machine word size, expected $(WORD_SIZE)" ; exit 1; else echo "✅ OCaml word size is correct"; fi


.PHONY: check_opam_switch
check_opam_switch: switch ## Verify the opam switch has correct packages
ifneq ($(DISABLE_CHECK_OPAM_SWITCH), true)
	@which check_opam_switch 2>/dev/null >/dev/null || ( echo "The check_opam_switch binary was not found in the PATH, try: opam switch import opam.export" >&2 && exit 1 )
	@check_opam_switch opam.export
endif

.PHONY: ocaml_checks
ocaml_checks: switch ocaml_version ocaml_word_size check_opam_switch ## Run OCaml version and config checks

.PHONY: libp2p_helper
libp2p_helper: ## Build libp2p helper
ifeq (, $(MINA_LIBP2P_HELPER_PATH))
	$(info 🏗️  Building libp2p_helper)
	@make -C src/app/libp2p_helper \
	&& echo "✅ libp2p_helper build complete"
endif

.PHONY: genesis_ledger
genesis_ledger: ocaml_checks ## Build runtime genesis ledger
	$(info 🏗️  Building runtime_genesis_ledger with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune exec \
		--profile=$(DUNE_PROFILE) \
		src/app/runtime_genesis_ledger/runtime_genesis_ledger.exe -- \
		--genesis-dir $(GENESIS_DIR)
	$(info Genesis ledger and genesis proof generated)

.PHONY: check
check: ocaml_checks libp2p_helper ## Check that all OCaml packages build without issues
	dune build @src/check

.PHONY: build
build: ocaml_checks reformat-diff libp2p_helper ## Build the main project executables
	$(info 🏗️  Building Mina with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune build \
		src/app/logproc/logproc.exe \
		src/app/cli/src/mina.exe \
		src/app/generate_keypair/generate_keypair.exe \
		src/app/validate_keypair/validate_keypair.exe \
		src/app/runtime_genesis_ledger/runtime_genesis_ledger.exe \
		src/lib/snark_worker/standalone/run_snark_worker.exe \
		src/app/mina_graphql_client/mina_graphql_client_app.exe \
		src/app/mina_healthcheck/mina_healthcheck.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-daemon-utils
build-daemon-utils: ocaml_checks reformat-diff libp2p_helper ## Build daemon utilities
	$(info 🏗️  Building Mina Daemon related utils with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune build \
		src/app/generate_keypair/generate_keypair.exe \
		src/app/validate_keypair/validate_keypair.exe \
		src/app/runtime_genesis_ledger/runtime_genesis_ledger.exe \
		src/lib/snark_worker/standalone/run_snark_worker.exe \
		src/app/rocksdb-scanner/rocksdb_scanner.exe \
		src/app/mina_graphql_client/mina_graphql_client_app.exe \
		src/app/mina_healthcheck/mina_healthcheck.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"


.PHONY: build-logproc
build-logproc: ocaml_checks reformat-diff libp2p_helper ## Build the logproc executable
	$(info 🏗️  Building logproc with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune build \
		src/app/logproc/logproc.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-mina
build-mina: ocaml_checks reformat-diff libp2p_helper build ## Build mina apps
	$(info 🏗️  Building on commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune build \
		src/app/cli/src/mina.exe \
		src/app/rosetta/rosetta.exe \
		src/app/rosetta/ocaml-signer/signer.exe \
		&& echo "✅ Build complete"

.PHONY: build-archive
build-archive: ocaml_checks reformat-diff ## Build the archive node
	$(info 🏗️  Building archive with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/archive/archive.exe \
		--profile=$(DUNE_PROFILE) && \
		echo "✅ Build complete"

.PHONY: build-archive-utils
build-archive-utils: ocaml_checks reformat-diff ## Build archive node and related utilities
	$(info 🏗️  Building archive utilities with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/archive/archive.exe \
		src/app/replayer/replayer.exe \
		src/app/archive_blocks/archive_blocks.exe \
		src/app/extract_blocks/extract_blocks.exe \
		src/app/missing_blocks_auditor/missing_blocks_auditor.exe \
		src/app/archive_hardfork_toolbox/archive_hardfork_toolbox.exe \
		src/app/dump_slot_ledger/dump_slot_ledger.exe \
		--profile=$(DUNE_PROFILE)  \
		&& echo "✅ Build complete"

.PHONY: build-test-utils
build-test-utils: ocaml_checks reformat-diff ## Build test utilities
	$(info 🏗️  Building test utilities with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/test_executive/test_executive.exe \
		src/app/benchmarks/benchmarks.exe \
		src/app/batch_txn_tool/batch_txn_tool.exe \
		src/app/zkapp_test_transaction/zkapp_test_transaction.exe \
		src/app/rosetta/indexer_test/indexer_test.exe \
		src/app/ledger_export_bench/ledger_export_benchmark.exe \
		src/app/disk_caching_stats/disk_caching_stats.exe \
		src/app/heap_usage/heap_usage.exe \
		src/app/zkapp_limits/zkapp_limits.exe \
		src/lib/snark_worker/standalone/run_snark_worker.exe \
		src/test/command_line_tests/command_line_tests.exe \
		src/test/archive/patch_archive_test/patch_archive_test.exe \
		src/test/archive/archive_node_tests/archive_node_tests.exe \
		src/test/node_status_mock_server/node_status_mock_server.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-delegation-verify
build-delegation-verify: ocaml_checks reformat-diff ## Build delegation verify tool
	$(info 🏗️  Building delegation verify tool with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/delegation_verify/delegation_verify.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-rosetta
build-rosetta: ocaml_checks ## Build Rosetta API components
	$(info 🏗️  Building Rosetta API components with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/archive/archive.exe \
		src/app/rosetta/rosetta.exe \
		src/app/rosetta/ocaml-signer/signer.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-intgtest
build-intgtest: ocaml_checks ## Build integration test tools
	$(info 🏗️  Building integration test tools with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@dune build \
		--profile=$(DUNE_PROFILE) \
		src/app/test_executive/test_executive.exe \
		src/app/logproc/logproc.exe \
		&& echo "✅ Build complete"

.PHONY: build-rosetta-lib-encodings
build-rosetta-lib-encodings: ocaml_checks ## Test Rosetta library encodings
	$(info 🏗️  Building Rosetta library encodings with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
	  src/lib/rosetta_lib/test/test_encodings.exe \
		&& echo "✅ Build complete"

.PHONY: build-replayer
build-replayer: ocaml_checks ## Build the replayer tool
	$(info 🏗️  Building replayer tool with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@ulimit -s 65532 && (ulimit -n 10240 || true) && \
	dune build \
		src/app/replayer/replayer.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-missing-blocks-auditor
build-missing-blocks-auditor: ocaml_checks ## Build missing blocks auditor tool
	$(info 🏗️  Building missing blocks auditor tool with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/missing_blocks_auditor/missing_blocks_auditor.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: extract-blocks
build-extract-blocks: ocaml_checks ## Build the extract_blocks executable
	$(info 🏗️  Building extract_blocks with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/extract_blocks/extract_blocks.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-archive-blocks
build-archive-blocks: ocaml_checks ## Build the archive_blocks executable
	$(info 🏗️  Building archive_blocks with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		src/app/archive_blocks/archive_blocks.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-patch-archive-test
build-patch-archive-test: ocaml_checks ## Build the patch archive test
	$(info 🏗️  Building patch archive test with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@ulimit -s 65532 && (ulimit -n 10240 || true) && \
	dune build \
	  src/app/patch_archive_test/patch_archive_test.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-heap-usage
build-heap-usage: ocaml_checks ## Build heap usage analysis tool
	$(info 🏗️  Building heap usage analysis tool with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@ulimit -s 65532 && (ulimit -n 10240 || true) && \
	dune build \
		src/app/heap_usage/heap_usage.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: build-zkapp-limits
build-zkapp-limits: ocaml_checks ## Build ZkApp limits tool
	$(info 🏗️  Building ZkApp limits tool with profile $(DUNE_PROFILE) and commit $(GITLONGHASH))
	@ulimit -s 65532 && (ulimit -n 10240 || true) && \
	dune build \
		src/app/zkapp_limits/zkapp_limits.exe \
		--profile=$(DUNE_PROFILE) \
		&& echo "✅ Build complete"

.PHONY: dev
dev: build ## Alias for build

.PHONY: update-graphql
update-graphql: ## Update GraphQL schema
	@(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	dune build \
		--profile=$(DUNE_PROFILE) \
		graphql_schema.json

update-rust-vendors: ## Update the Rust vendors
	@echo "Updating Rust vendors in src/lib/crypto/kimchi_bindings/stubs..."
	@cd src/lib/crypto/kimchi_bindings/stubs && cargo vendor kimchi-stubs-vendors

.PHONY: install
install:
	@dune build @install
	@dune install
	@echo "--------------------------------------------------------------"
	@echo "All binaries (resp. libraries) have been installed into $(OPAM_SWITCH_PREFIX)/bin"
	@echo "(resp. ${OPAM_SWITCH_PREFIX}/lib) and the binaries are available in the path."
	@echo "You can list the installed binaries with:"
	@echo "> ls -al ${OPAM_SWITCH_PREFIX}/bin"
	@echo "In particular, you should be able to run the command 'mina'"
	@echo "'logproc', 'rosetta', 'generate_keypair', etc from this shell"

.PHONY: uninstall
uninstall:
	@dune uninstall

########################################
## Lint

.PHONY: reformat
reformat: ocaml_checks ## Reformat all OCaml code
	dune exec \
		--profile=$(DUNE_PROFILE) \
	  src/app/reformat/reformat.exe -- \
		-path .

.PHONY: reformat-diff
reformat-diff: ## Reformat only modified OCaml files
	@FILES=$$(git status -s | cut -c 4- | grep '\.mli\?$$' | while IFS= read -r f; do stat "$$f" >/dev/null 2>&1 && echo "$$f"; done); \
	if [ -n "$$FILES" ]; then ocamlformat --doc-comments=before --inplace $$FILES; fi

.PHONY: check-format
check-format: ocaml_checks ## Check formatting of OCaml code
	dune exec \
		--profile=$(DUNE_PROFILE) \
	  src/app/reformat/reformat.exe -- \
		-path . -check

.PHONY: check-snarky-submodule
check-snarky-submodule: ## Check the snarky submodule
	./scripts/check-snarky-submodule.sh

#######################################
## Bash checks

.PHONY: check-bash
check-bash: ## Run shellcheck on bash scripts
	shellcheck ./scripts/**/*.sh -S warning
	shellcheck ./buildkite/scripts/**/*.sh -S warning

.PHONY: check-docker
check-docker: ## Run hadolint on Docker files
ifdef BUILDKITE
	hadolint --ignore DL3008 --ignore DL3002 --ignore DL3013 --ignore DL3007 --ignore DL3006 --ignore DL3028 dockerfiles/Dockerfile-* dockerfiles/toolchain/* dockerfiles/stages/1-base-deps dockerfiles/stages/daemon/* dockerfiles/stages/archive/* dockerfiles/stages/rosetta/*
else
	docker run --rm -v $(PWD):/workspace -w /workspace \
		hadolint/hadolint hadolint \
		--ignore DL3008 \
		--ignore DL3002 \
		--ignore DL3013 \
		--ignore DL3007 \
		--ignore DL3006 \
		--ignore DL3028 \
		dockerfiles/Dockerfile-* \
		dockerfiles/toolchain/* \
		dockerfiles/stages/1-base-deps \
		dockerfiles/stages/daemon/* \
		dockerfiles/stages/archive/* \
		dockerfiles/stages/rosetta/*
endif

########################################
## Artifacts

.PHONY: build_pv_keys
build_pv_keys: ocaml_checks ## Build proving/verification keys
	$(info Building keys)
	(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune exec \
		--profile=$(DUNE_PROFILE) \
	  src/lib/snark_keys/gen_keys/gen_keys.exe -- \
		--generate-keys-only
	$(info Keys built)

.PHONY: build_or_download_pv_keys
build_or_download_pv_keys: ocaml_checks ## Build or download proving/verification keys
	$(info Building keys)
	(ulimit -s 65532 || true) && (ulimit -n 10240 || true) && \
	env MINA_COMMIT_SHA1=$(GITLONGHASH) \
	dune exec \
		--profile=$(DUNE_PROFILE) \
		src/lib/snark_keys/gen_keys/gen_keys.exe -- \
		--generate-keys-only
	$(info Keys built)

.PHONY: genesiskeys
genesiskeys: ## Generate and copy genesis keys
	@mkdir -p /tmp/artifacts
	@cp _build/default/src/lib/key_gen/sample_keypairs.ml /tmp/artifacts/.
	@cp _build/default/src/lib/key_gen/sample_keypairs.json /tmp/artifacts/.


##############################################
## Genesis ledger in OCaml from running daemon

.PHONY: genesis-ledger-ocaml
genesis-ledger-ocaml: ## Generate OCaml genesis ledger from daemon
	@./scripts/generate-genesis-ledger.py .genesis-ledger.ml.jinja

########################################
## Tests

.PHONY: test-ppx
test-ppx: ## Test PPX extensions
	$(MAKE) -C src/lib/ppx_mina/tests

########################################
## Benchmarks

.PHONY: benchmarks
benchmarks: ocaml_checks ## Build benchmarking tools
	dune build src/app/benchmarks/benchmarks.exe

########################################
# Coverage testing and output

.PHONY: test-coverage
test-coverage: SHELL := /bin/bash
test-coverage: libp2p_helper ## Run tests with coverage instrumentation
	scripts/create_coverage_profiles.sh

.PHONY: coverage-html
coverage-html: ## Generate HTML report from coverage data
ifeq ($(shell find _build/default -name bisect\*.out),"")
	echo "No coverage output; run make test-coverage"
else
	bisect-ppx-report html --source-path=_build/default --coverage-path=_build/default
endif

.PHONY: coverage-summary
coverage-summary: ## Generate coverage summary report
ifeq ($(shell find _build/default -name bisect\*.out),"")
	echo "No coverage output; run make test-coverage"
else
	bisect-ppx-report summary --coverage-path=_build/default --per-file
endif

########################################
# Diagrams for documentation

%.dot.png: %.dot
	dot -Tpng $< > $@

%.tex.pdf: %.tex
	cd $(dir $@) && pdflatex -halt-on-error $(notdir $<)
	cp $(@:.tex.pdf=.pdf) $@

%.tex.png: %.tex.pdf
	convert -density 600x600 $< -quality 90 -resize 1080x1080 $@

%.conv.tex.png: %.conv.tex
	cd $(dir $@) && pdflatex -halt-on-error -shell-escape $(notdir $<)

# TODO: this, but smarter so we don't have to add every library
doc_diagram_sources=$(addprefix docs/res/,*.dot *.tex *.conv.tex)
doc_diagram_sources+=$(addprefix rfcs/res/,*.dot *.tex *.conv.tex)
doc_diagram_sources+=$(addprefix src/lib/transition_frontier/res/,*.dot *.tex *.conv.tex)

.PHONY: doc_diagrams
doc_diagrams: $(addsuffix .png,$(wildcard $(doc_diagram_sources))) ## Generate documentation diagrams

.PHONY: export_git_env_vars
export_git_env_vars: ## Export git environment variables for use in scripts
	KEEP_MY_TAGS_INTACT=true \
		./scripts/export-git-env-vars.sh

########################################
# Debian packages

# Helper function for building Debian packages
define build_debian_package
	$(info 🏗️  Building Debian package $(1) with profile $(DUNE_PROFILE) and commit $(GITLONGHASH) and codename $(CODENAME))
	BUILD_DIR="${PWD}/_build" \
	DUNE_PROFILE=$(DUNE_PROFILE) \
	MINA_DEB_CODENAME="$(CODENAME)" \
	BRANCH_NAME="$(BRANCH_NAME)" \
	./scripts/debian/build.sh $(1)  \
		&& echo "✅ Build complete"
endef

.PHONY: debian-build-archive-devnet
debian-build-archive-devnet: ## Build the Debian archive package for devnet
	$(call build_debian_package,archive_devnet)

.PHONY: debian-build-archive
debian-build-archive: ## Build the Debian archive package for mainnet
	$(call build_debian_package,archive_mainnet)

.PHONY: debian-build-daemon-devnet-generic
debian-build-daemon-devnet-generic: ## Build the Debian daemon package for devnet-generic
	$(call build_debian_package,daemon_devnet_generic)

.PHONY: debian-build-daemon-devnet
debian-build-daemon-devnet: ## Build the Debian daemon package for devnet
	$(call build_debian_package,daemon_devnet)

.PHONY: debian-build-daemon-mainnet
debian-build-daemon-mainnet: ## Build the Debian daemon package for mainnet
	$(call build_debian_package,daemon_mainnet)

.PHONY: debian-build-daemon-mainnet-generic
debian-build-daemon-mainnet-generic: ## Build the Debian daemon package for mainnet-generic
	$(call build_debian_package,daemon_mainnet_generic)

.PHONY: debian-build-daemon-devnet-prefork
debian-build-daemon-devnet-prefork: ## Build the Debian daemon package for automote devnet pre hardfork
	$(call build_debian_package,daemon_devnet_prefork)

.PHONY: debian-build-daemon-mainnet-prefork
debian-build-daemon-mainnet-prefork: ## Build the Debian daemon package for automote mainnet pre hardfork
	$(call build_debian_package,daemon_mainnet_prefork)

.PHONY: debian-build-config-devnet
debian-build-config-devnet: ## Build the Debian config package for devnet
	$(call build_debian_package,daemon_devnet_config)

.PHONY: debian-build-config-mainnet
debian-build-config-mainnet: ## Build the Debian config package for mainnet
	$(call build_debian_package,daemon_mainnet_config)

.PHONY: debian-build-logproc
debian-build-logproc: ## Build the Debian logproc package
	$(call build_debian_package,logproc)

.PHONY: debian-build-functional-tests
debian-build-functional-tests: ## Build the Debian Functional tests package
	$(call build_debian_package,functional_test_suite)

.PHONY: debian-build-prefork-genesis-ledger
debian-build-prefork-genesis-ledger: ## Build the Debian Create Legacy Genesis package
	$(call check_env_var,NETWORK_NAME)
	$(call build_debian_package,prefork_$(NETWORK_NAME)_genesis_ledger)

.PHONY: debian-build-rosetta-generic
debian-build-rosetta-generic: ## Build the Debian Rosetta generic package
	$(call build_debian_package,rosetta_generic)

.PHONY: debian-build-tx-tools
debian-build-tx-tools: ## Build the Debian tx-tools package (batch_txn + zkapp_test_transaction)
	$(call build_debian_package,tx_tools)

.PHONY: debian-build-rosetta-devnet
debian-build-rosetta-devnet: ## Build the Debian Rosetta package for devnet
	$(call build_debian_package,rosetta_devnet)

.PHONY: debian-build-rosetta-mainnet
debian-build-rosetta-mainnet: ## Build the Debian Rosetta package for mainnet
	$(call build_debian_package,rosetta_mainnet)

.PHONY: debian-build-daemon-devnet-postfork
debian-build-daemon-devnet-postfork: ## Build the Debian daemon package for automote devnet post hardfork
	$(call build_debian_package,daemon_devnet_postfork)

.PHONY: debian-build-daemon-mainnet-postfork
debian-build-daemon-mainnet-postfork: ## Build the Debian daemon package for automote mainnet post hardfork
	$(call build_debian_package,daemon_mainnet_postfork)

.PHONY: debian-daemon-storage-toolbox
debian-daemon-storage-toolbox: ## Build the Debian daemon storage toolbox package
	$(call build_debian_package,daemon_storage_toolbox)

.PHONY: debian-download-create-legacy-hardfork
debian-download-create-legacy-hardfork: ## Download and create legacy hardfork Debian packages
	$(info 📦 Downloading legacy hardfork Debian packages for debian $(CODENAME))
	$(call check_env_var,NETWORK_NAME)

	@./buildkite/scripts/release/manager.sh pull --artifacts mina-create-$(NETWORK_NAME)-prefork-genesis  --from-special-folder legacy/debians/$(CODENAME)  --backend hetzner --target _build

.PHONY: debian-reversion
debian-reversion: ## Reversion a .deb package (DEB, NEW_VERSION required; NEW_SUITE, NEW_NAME, OUTPUT optional)
	$(call check_env_var,DEB)
	$(call check_env_var,NEW_VERSION)
	@./scripts/debian/reversion.sh "$(DEB)" "$(NEW_VERSION)" \
		$(if $(NEW_SUITE),--suite "$(NEW_SUITE)") \
		$(if $(NEW_NAME),--name "$(NEW_NAME)") \
		$(if $(OUTPUT),--output "$(OUTPUT)")

########################################
# Release management

.PHONY: cache-get-prefork-debians
cache-get-prefork-debians: ## Download debian packages for prefork genesis creation
	$(info 📦 Downloading prefork Debian packages for debian $(CODENAME))

	$(call check_env_var,NETWORK_NAME)

	@./buildkite/scripts/release/manager.sh pull --artifacts mina-$(NETWORK_NAME)-prefork  --from-special-folder berkeley/debians/$(CODENAME)  --backend hetzner --target _build

.PHONY: cache-get-create-prefork-genesis-debians
cache-get-create-prefork-genesis-debians: ## Download debian packages for prefork genesis creation
	$(info 📦 Downloading prefork genesis creation Debian packages for debian $(CODENAME))
	$(call check_env_var,NETWORK_NAME)
	@./buildkite/scripts/release/manager.sh pull --artifacts mina-$(NETWORK_NAME)-create-genesis-ledger  --from-special-folder berkeley/debians/$(CODENAME)  --backend hetzner --target _build

.PHONY: cache-put-debian
cache-put-debian: ## Upload debian packages for prefork genesis creation
	$(info 📦 Uploading Debian packages for debian $(CODENAME) to CI cache)
	$(call check_env_var,TARGET)
	$(call check_env_var,DEB_PATH)
	@./buildkite/scripts/release/manager.sh persist --codename $(CODENAME)  --target $(TARGET)  --backend hetzner --local-debian-path $(DEB_PATH)

########################################
# Docker images

.PHONY: start-local-debian-repo
start-local-debian-repo: ## Stage locally-built debians into the docker build context
	$(info 📦 Staging local debians from _build into the dockerfiles/ build context)

	@# scripts/docker/build.sh stages any .deb files found in the docker build
	@# context (dockerfiles/) into dockerfiles/_debs and generates an apt index
	@# there, which the Dockerfiles install from. Make the locally-built debians
	@# available by copying them into the context.
	@cp -f _build/*.deb dockerfiles/ \
		&& echo "✅ Local debians staged"

# General function for building Docker images.
# $(1)=service  $(2)=network  $(3)=extra build.sh args (optional, e.g. --deb-suffix generic)
define build_docker_image
	$(info 🐳 Building Docker image for service $(1) with \
		codename $(CODENAME) \
		and version $$MINA_DEB_VERSION \
		and branch $$GITBRANCH \
		and network $(2))

	@export BUILD_DIR=./_build MINA_DEB_CODENAME=$(CODENAME) KEEP_MY_TAGS_INTACT=true && \
	. ./scripts/export-git-env-vars.sh \
	&& ./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service $(1) \
		--version "$$MINA_DEB_VERSION" \
		--branch "$$GITBRANCH" \
		--deb-legacy-version "$${MINA_DEB_LEGACY_VERSION:-}" \
		--docker-registry "europe-west3-docker.pkg.dev/o1labs-192920/euro-docker-repo" \
		--network $(2) \
		$(3) \
		$(DOCKER_LOAD_ONLY_ARG) \
		$(DOCKER_NO_CACHE)

	$(info 📦 cleaning up staged local debians)
	@rm -f dockerfiles/*.deb
endef


.PHONY: docker-build-toolchain
docker-build-toolchain: ## Build the toolchain to be used in CI
	@BUILD_DIR=./_build \
		./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service mina-toolchain \
		--version mina-toolchain-$(CODENAME)-$(GITHASH)

.PHONY: docker-build-archive-devnet
docker-build-archive-devnet: SHELL := /bin/bash
docker-build-archive-devnet: start-local-debian-repo ## Build the archive Docker image for devnet
	$(call build_docker_image,mina-archive,devnet)

.PHONY: docker-build-archive-mainnet
docker-build-archive-mainnet: SHELL := /bin/bash
docker-build-archive-mainnet: start-local-debian-repo ## Build the archive Docker image for mainnet
	$(call build_docker_image,mina-archive,mainnet)

.PHONY: docker-build-daemon-devnet-generic
docker-build-daemon-devnet-generic: SHELL := /bin/bash
docker-build-daemon-devnet-generic: start-local-debian-repo ## Build the daemon Docker image
	$(call build_docker_image,mina-daemon,devnet-generic)

.PHONY: docker-build-daemon-devnet
docker-build-daemon-devnet: SHELL := /bin/bash
docker-build-daemon-devnet: start-local-debian-repo ## Build the daemon Docker image for devnet
	$(call build_docker_image,mina-daemon,devnet,--deb-suffix generic)

.PHONY: docker-build-daemon-mainnet
docker-build-daemon-mainnet: SHELL := /bin/bash
docker-build-daemon-mainnet: start-local-debian-repo ## Build the daemon Docker image for mainnet
	$(call build_docker_image,mina-daemon,mainnet,--deb-suffix generic)


.PHONY: docker-build-daemon-devnet-automode-hardfork
docker-build-daemon-devnet-automode-hardfork: SHELL := /bin/bash
docker-build-daemon-devnet-automode-hardfork: start-local-debian-repo ## Build the daemon Docker image for automode devnet post hardfork
	$(call build_docker_image,mina-daemon-auto-hardfork,devnet)

.PHONY: docker-build-daemon-mainnet-automode-hardfork
docker-build-daemon-mainnet-automode-hardfork: SHELL := /bin/bash
docker-build-daemon-mainnet-automode-hardfork: start-local-debian-repo ## Build the daemon Docker image for automode mainnet post hardfork
	$(call build_docker_image,mina-daemon-auto-hardfork,mainnet)

.PHONY: docker-build-rosetta
docker-build-rosetta-devnet-generic: SHELL := /bin/bash
docker-build-rosetta-devnet-generic: start-local-debian-repo ## Build the Rosetta Docker image
	$(call build_docker_image,mina-rosetta,devnet-generic)

.PHONY: docker-build-rosetta-devnet
docker-build-rosetta-devnet: SHELL := /bin/bash
docker-build-rosetta-devnet: start-local-debian-repo ## Build the Rosetta Docker image for devnet
	$(call build_docker_image,mina-rosetta,devnet,--deb-suffix generic)

.PHONY: docker-build-rosetta-mainnet
docker-build-rosetta-mainnet: SHELL := /bin/bash
docker-build-rosetta-mainnet: start-local-debian-repo ## Build the Rosetta Docker image for mainnet
	$(call build_docker_image,mina-rosetta,mainnet,--deb-suffix generic)

.PHONY: docker-build-base
docker-build-base: SHELL := /bin/bash
docker-build-base: ## Build the shared mina-base image (no mina debs required)
	$(call build_docker_image,mina-base,$(NETWORK))

########################################
# Aggregate local build of all staged docker images

# Package the full debian closure the staged base/archive/daemon/rosetta images
# install from the local build context. This PACKAGES already-compiled binaries
# from _build/ (run the relevant make build* targets first) -- it does not
# compile. Select codename via CODENAME= and network via NETWORK=.
.PHONY: debian-build-all
debian-build-all: ## Package the deb closure for the staged images (CODENAME/NETWORK)
	$(info 📦 Packaging deb closure for network $(NETWORK) / codename $(CODENAME) from _build)
	BUILD_DIR="$(PWD)/_build" \
	DUNE_PROFILE=$(NETWORK) \
	MINA_DEB_CODENAME="$(CODENAME)" \
	BRANCH_NAME="$(BRANCH_NAME)" \
	./scripts/debian/build.sh \
		logproc \
		daemon_storage_toolbox \
		daemon_$(NETWORK)_generic \
		archive_generic \
		archive_$(NETWORK) \
		rosetta_generic \
		rosetta_$(NETWORK) \
		profile_$(NETWORK)

# Build every staged docker image (base, archive, daemon, rosetta) for one
# CODENAME/NETWORK, packaging the deb closure first. Set LOAD_ONLY=1 for a
# purely local build (load into local docker, no registry push).
# DOCKER_NO_CACHE is cleared for the whole run so the base-deps layer these
# images share is built once and reused, rather than three times; pass
# DOCKER_NO_CACHE=--no-cache to force the from-scratch behaviour back on.
.PHONY: docker-build-all
docker-build-all: SHELL := /bin/bash
docker-build-all: ## Build ALL staged docker images for CODENAME/NETWORK (packages debs first)
	$(MAKE) debian-build-all CODENAME=$(CODENAME) NETWORK=$(NETWORK)
	$(MAKE) docker-build-base CODENAME=$(CODENAME) NETWORK=$(NETWORK) LOAD_ONLY=$(LOAD_ONLY) DOCKER_NO_CACHE=
	$(MAKE) docker-build-archive-$(NETWORK) CODENAME=$(CODENAME) LOAD_ONLY=$(LOAD_ONLY) DOCKER_NO_CACHE=
	$(MAKE) docker-build-daemon-$(NETWORK) CODENAME=$(CODENAME) LOAD_ONLY=$(LOAD_ONLY) DOCKER_NO_CACHE=
	$(MAKE) docker-build-rosetta-$(NETWORK) CODENAME=$(CODENAME) LOAD_ONLY=$(LOAD_ONLY) DOCKER_NO_CACHE=

########################################
# Generate hardfork packages

.PHONY: debian-build-hardfork-config
debian-build-hardfork-config: SHELL := /bin/bash
debian-build-hardfork-config: #ocaml_checks ## Generate hardfork packages
	$(info 📦 Generating hardfork debian packages for network $(NETWORK_NAME))

	$(call check_env_var,NETWORK_NAME)
	$(call check_env_var,CODENAME)
	$(call check_env_var,BRANCH_NAME)

	@BUILD_DIR=./_build \
	MINA_DEB_CODENAME=$(CODENAME) \
	BRANCH_NAME=$(BRANCH_NAME) \
	KEEP_MY_TAGS_INTACT=true \
	./scripts/hardfork/release/build-packages.sh daemon_$(NETWORK_NAME)_hardfork_config



.PHONY: docker-build-daemon-hardfork-docker
docker-build-daemon-hardfork-docker: SHELL := /bin/bash
docker-build-daemon-hardfork-docker: ## Generate hardfork packages
	$(info 📦 Generating hardfork docker for network $(NETWORK_NAME))

	$(call check_env_var,NETWORK_NAME)
	$(call check_env_var,CODENAME)
	$(call check_env_var,BRANCH_NAME)

	$(MAKE) debian-build-config-$(NETWORK_NAME)
	$(MAKE) start-local-debian-repo

	@export BUILD_DIR=./_build && \
	export MINA_DEB_CODENAME=$(CODENAME) && \
	export KEEP_MY_TAGS_INTACT=true && \
	. ./scripts/export-git-env-vars.sh && \
	./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service mina-daemon \
		--version "$$MINA_DOCKER_TAG" \
		--deb-version "$$MINA_DEB_VERSION" \
		--branch $(BRANCH_NAME) \
		--network $(NETWORK_NAME) \
		--deb-suffix generic \
		--custom-suffix generic \
		--load-only \
		--no-cache

	cp _build/mina-$(NETWORK_NAME)-config_*.deb .

	@export BUILD_DIR=./_build && \
	export MINA_DEB_CODENAME=$(CODENAME) && \
	export KEEP_MY_TAGS_INTACT=true && \
	. ./scripts/export-git-env-vars.sh && \
	./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service mina-daemon-configured \
		--version "$$MINA_DOCKER_TAG" \
		--deb-version "$$MINA_DEB_VERSION" \
		--branch $(BRANCH_NAME) \
		--network $(NETWORK_NAME) \
		--custom-suffix configured \
		--no-cache \
		--load-only

	$(info 📦 cleaning up staged local debians)
	@rm -f dockerfiles/*.deb

.PHONY: docker-build-hardfork-rosetta-docker
docker-build-hardfork-rosetta-docker: SHELL := /bin/bash
docker-build-hardfork-rosetta-docker: ## Generate hardfork packages
	$(info 📦 Generating hardfork docker for network $(NETWORK_NAME))
	$(call check_env_var,NETWORK_NAME)
	$(call check_env_var,CODENAME)
	$(call check_env_var,BRANCH_NAME)

	$(MAKE) debian-build-config-$(NETWORK_NAME)
	$(MAKE) start-local-debian-repo

	@export BUILD_DIR=./_build && \
	export MINA_DEB_CODENAME=$(CODENAME) && \
	export KEEP_MY_TAGS_INTACT=true && \
	. ./scripts/export-git-env-vars.sh && \
	./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service mina-rosetta \
		--version "$$MINA_DOCKER_TAG" \
		--deb-version "$$MINA_DEB_VERSION" \
		--branch $(BRANCH_NAME) \
		--network $(NETWORK_NAME) \
		--deb-suffix generic \
		--custom-suffix generic \
		--load-only \
		--no-cache

	cp _build/mina-$(NETWORK_NAME)-config_*.deb .

	@export BUILD_DIR=./_build && \
	export MINA_DEB_CODENAME=$(CODENAME) && \
	export KEEP_MY_TAGS_INTACT=true && \
	. ./scripts/export-git-env-vars.sh && \
	./scripts/docker/build.sh \
		--deb-codename $(CODENAME) \
		--service mina-rosetta-configured \
		--version "$$MINA_DOCKER_TAG" \
		--deb-version "$$MINA_DEB_VERSION" \
		--branch $(BRANCH_NAME) \
		--network $(NETWORK_NAME) \
		--custom-suffix configured \
		--custom-arg "--build-arg image_name=mina-rosetta" \
		--no-cache \
		--load-only


	$(info 📦 cleaning up staged local debians)
	@rm -f dockerfiles/*.deb

########################################
# Generate odoc documentation

.PHONY: ml-docs
ml-docs: ocaml_checks ## Generate OCaml documentation
	dune build --profile=$(DUNE_PROFILE) @doc


########################################
# PostgreSQL, used for the archive node

.PHONY: postgres-setup
postgres-setup: ## Set up PostgreSQL database for archive node
	@echo "Setting up PostgreSQL database: ${PG_DB} with user: ${PG_USER}"
	@sudo -u postgres createuser -d -r -s $(PG_USER) 2>/dev/null || true
	@sudo -u postgres psql -c "ALTER USER $(PG_USER) PASSWORD '$(PG_PW)'" 2>/dev/null || true
	@sudo -u postgres createdb -O $(PG_USER) $(PG_DB) 2>/dev/null || true
	@sudo -u postgres createdb -O $(PG_USER) $(PG_USER) 2>/dev/null || true
	@echo "PostgreSQL setup complete"

.PHONY: postgres-login
postgres-login: ## Login to PostgreSQL database
	@echo "Connecting to PostgreSQL database: ${PG_DB} as user: ${PG_USER}"
	@PGPASSWORD=$(PG_PW) psql -h $(PG_HOST) -p $(PG_PORT) -U $(PG_USER) -d $(PG_DB)

.PHONY: postgres-clean
postgres-clean:
	@echo "Dropping DB: ${PG_DB} and user: ${PG_USER}"
	@sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${PG_DB}"
	@sudo -u postgres psql -c "DROP DATABASE IF EXISTS ${PG_USER}"
	@sudo -u postgres psql -c "DROP ROLE IF EXISTS ${PG_USER}"
	@echo "Cleanup complete."

.PHONY: regenerate-archive
regenerate-archive: ## Regenerate archive database with test data
	@echo "Regenerating archive database using configured PG variables"
	@PG_USER=$(PG_USER) PG_PW=$(PG_PW) PG_DB=$(PG_DB) PG_HOST=$(PG_HOST) PG_PORT=$(PG_PORT) \
	./scripts/regenerate-archive.sh
