# Docker variables:
DOCKER_DIR = ./docker
DOCKER_FILE = $(DOCKER_DIR)/Dockerfile
DOCKER_FILE_DEV = $(DOCKER_DIR)/Dockerfile.dev
DOCKER_TAG = pysc2_evolved

# Python variables:
PYTHON_VERSION = 3.11

# Cross-platform file utilities (work on Windows with chocolatey make).
CP    = python scripts/cp.py
MKDIR = python scripts/mkdir_p.py



.PHONY: docker_build
docker_build: ## Builds the runtime Docker image.
	@echo "Building the Dockerfile: $(DOCKER_FILE)"
	@echo "Using Python version: $(PYTHON_VERSION)"
	docker build \
		--build-arg="PYTHON_VERSION=$(PYTHON_VERSION)" \
		-f $(DOCKER_FILE) . \
		--tag=$(DOCKER_TAG)

.PHONY: docker_build_dev
docker_build_dev: ## Builds the dev image with Bazelisk + pinned Bazel (from .bazelversion).
	@echo "Building the dev Dockerfile: $(DOCKER_FILE_DEV)"
	docker build \
		-f $(DOCKER_FILE_DEV) . \
		--tag=$(DOCKER_TAG)-dev

.PHONY: docker_run_dev
docker_run_dev: ## Runs the dev container, mounting the repo as /workspace.
	docker run --rm -it \
		-v "$(CURDIR)":/workspace \
		-v bazel-cache:/bazel-cache \
		$(DOCKER_TAG)-dev

# Bazel variables (local builds use Bazelisk with version from .bazelversion):
BAZELISK ?= bazelisk
BAZEL_FLAGS =

# Python version to target when compiling pybind11 extensions.
# Override on the command line: make bazel_build_extensions_local PY_VERSION=3.12
PY_VERSION ?= 3.11
BAZEL_PY_FLAG = --@rules_python//python/config_settings:python_version=$(PY_VERSION)

.PHONY: bazel_build_converter
bazel_build_converter: ## Builds the C++ converter pybind11 extension inside the dev container.
	docker run --rm \
		-v "$(CURDIR)":/workspace \
		-v bazel-cache:/bazel-cache \
		$(DOCKER_TAG)-dev \
		bazel --output_base=/bazel-cache \
		build //src/pysc2_evolved/env/converter/cc/python:converter

.PHONY: bazel_build_converter_local
bazel_build_converter_local: ## Builds the C++ converter pybind11 extension locally via Bazelisk.
	$(BAZELISK) build $(BAZEL_FLAGS) $(BAZEL_PY_FLAG) \
	//src/pysc2_evolved/env/converter/cc/python:converter

.PHONY: bazel_test_converter
bazel_test_converter: ## Runs all C++ converter unit tests inside the dev container.
	docker run --rm \
		-v "$(CURDIR)":/workspace \
		-v bazel-cache:/bazel-cache \
		$(DOCKER_TAG)-dev \
		bazel --output_base=/bazel-cache test \
		//src/pysc2_evolved/env/converter/cc:all \
		--test_output=errors

.PHONY: bazel_test_converter_local
bazel_test_converter_local: ## Runs all C++ converter unit tests locally via Bazelisk.
	$(BAZELISK) test $(BAZEL_FLAGS) $(BAZEL_PY_FLAG) //src/pysc2_evolved/env/converter/cc:all \
		--test_output=errors

.PHONY: bazel_build_all_local
bazel_build_all_local: ## Builds all Bazel targets locally via Bazelisk.
	$(BAZELISK) build $(BAZEL_FLAGS) $(BAZEL_PY_FLAG) //src/pysc2_evolved/...

.PHONY: bazel_build_uint8_lookup_local
bazel_build_uint8_lookup_local: ## Builds the uint8_lookup pybind11 extension locally via Bazelisk.
	$(BAZELISK) build $(BAZEL_FLAGS) $(BAZEL_PY_FLAG) \
	//src/pysc2_evolved/env/converter/cc/game_data/python:uint8_lookup

.PHONY: bazel_build_python_protos_local
bazel_build_python_protos_local: ## Builds Python proto bindings for the converter and game_data packages via Bazelisk.
	$(BAZELISK) build $(BAZEL_FLAGS) \
	//src/pysc2_evolved/env/converter/proto:converter_py_pb2 \
	//src/pysc2_evolved/env/converter/cc/game_data/proto:buffs_py_pb2 \
	//src/pysc2_evolved/env/converter/cc/game_data/proto:units_py_pb2 \
	//src/pysc2_evolved/env/converter/cc/game_data/proto:upgrades_py_pb2

.PHONY: bazel_build_extensions_local
bazel_build_extensions_local: bazel_build_converter_local bazel_build_uint8_lookup_local bazel_build_python_protos_local ## Builds both pybind11 extensions (converter + uint8_lookup) and Python proto bindings locally via Bazelisk.

# Bazel places py_proto_library outputs inside _virtual_imports/<proto_name>/ due to
# strip_import_prefix = "/src" on the proto_library targets.
PROTO_BIN = bazel-bin/src/pysc2_evolved/env/converter
PROTO_VIRTUAL_CONVERTER = $(PROTO_BIN)/proto/_virtual_imports/converter_proto/pysc2_evolved/env/converter/proto
PROTO_VIRTUAL_GAME_DATA  = $(PROTO_BIN)/cc/game_data/proto/_virtual_imports

# Wheel packaging
# EXT must be set by the caller: "so" on Linux/macOS, "pyd" on Windows.
# Example: make copy_extensions_local EXT=so
.PHONY: copy_extensions_local
copy_extensions_local: ## Copies compiled extensions and Python proto bindings from bazel-bin into the source tree (local builds). Requires EXT=so|pyd.
	$(CP) bazel-bin/src/pysc2_evolved/env/converter/cc/python/converter.$(EXT) \
	   src/pysc2_evolved/env/converter/cc/python/converter.$(EXT)
	$(CP) bazel-bin/src/pysc2_evolved/env/converter/cc/game_data/python/uint8_lookup.$(EXT) \
	   src/pysc2_evolved/env/converter/cc/game_data/python/uint8_lookup.$(EXT)
	$(CP) $(PROTO_VIRTUAL_CONVERTER)/converter_pb2.py \
	   src/pysc2_evolved/env/converter/proto/converter_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/buffs_proto/pysc2_evolved/env/converter/cc/game_data/proto/buffs_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/buffs_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/units_proto/pysc2_evolved/env/converter/cc/game_data/proto/units_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/units_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/upgrades_proto/pysc2_evolved/env/converter/cc/game_data/proto/upgrades_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/upgrades_pb2.py

# EXT must be set by the caller: "so" on Linux/macOS, "pyd" on Windows.
# Example: make stage_extensions_local EXT=so
.PHONY: stage_extensions_local
stage_extensions_local: ## Copies compiled extensions and Python proto bindings from bazel-bin into cc-dist/ for CI artifact upload. Requires EXT=so|pyd.
	$(MKDIR) cc-dist
	$(CP) bazel-bin/src/pysc2_evolved/env/converter/cc/python/converter.$(EXT) \
	   cc-dist/converter.$(EXT)
	$(CP) bazel-bin/src/pysc2_evolved/env/converter/cc/game_data/python/uint8_lookup.$(EXT) \
	   cc-dist/uint8_lookup.$(EXT)
	$(CP) $(PROTO_VIRTUAL_CONVERTER)/converter_pb2.py \
	   cc-dist/converter_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/buffs_proto/pysc2_evolved/env/converter/cc/game_data/proto/buffs_pb2.py \
	   cc-dist/buffs_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/units_proto/pysc2_evolved/env/converter/cc/game_data/proto/units_pb2.py \
	   cc-dist/units_pb2.py
	$(CP) $(PROTO_VIRTUAL_GAME_DATA)/upgrades_proto/pysc2_evolved/env/converter/cc/game_data/proto/upgrades_pb2.py \
	   cc-dist/upgrades_pb2.py

# EXT must be set by the caller: "so" on Linux/macOS, "pyd" on Windows.
# SRCDIR must be set to the directory containing the pre-built extensions.
# Example: make place_extensions_local EXT=so SRCDIR=cc-dist
.PHONY: place_extensions_local
place_extensions_local: ## Places pre-built extensions and Python proto bindings from SRCDIR into the source tree (CI packaging). Requires EXT=so|pyd SRCDIR=path.
	$(CP) $(SRCDIR)/converter.$(EXT) \
	   src/pysc2_evolved/env/converter/cc/python/converter.$(EXT)
	$(CP) $(SRCDIR)/uint8_lookup.$(EXT) \
	   src/pysc2_evolved/env/converter/cc/game_data/python/uint8_lookup.$(EXT)
	$(CP) $(SRCDIR)/converter_pb2.py \
	   src/pysc2_evolved/env/converter/proto/converter_pb2.py
	$(CP) $(SRCDIR)/buffs_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/buffs_pb2.py
	$(CP) $(SRCDIR)/units_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/units_pb2.py
	$(CP) $(SRCDIR)/upgrades_pb2.py \
	   src/pysc2_evolved/env/converter/cc/game_data/proto/upgrades_pb2.py

.PHONY: clean_dist
clean_dist: ## Removes all wheels from dist/ to prevent stale wheel clashes on reinstall.
	python -c "import glob, os; [os.remove(f) for f in glob.glob('dist/*.whl')]"

.PHONY: build_wheel_local
build_wheel_local: clean_dist ## Builds a platform-specific wheel via uv (clears dist/ first).
	uv build --wheel

# Test targets:
.PHONY: test_minor
test_minor: ## Runs minor-marked pytest tests using the editable install. Proto-dependent tests also require Bazel artifacts staged via copy_extensions_local.
	uv run python -m pytest -m minor --tb=short src/

.PHONY: test_minor_ci
test_minor_ci: ## Installs dist/*.whl and runs minor tests exactly as CI does (wheel must already be built). Always restores the editable install on completion or failure.
	uv sync --no-install-project --group dev
	python scripts/install_wheel.py --force-reinstall
	python scripts/run_tests_and_restore.py -m minor --tb=short src/

# EXT must be set: "so" on Linux/macOS, "pyd" on Windows.
# Example: make test_minor_full EXT=pyd
.PHONY: test_minor_full
test_minor_full: bazel_build_extensions_local ## Full end-to-end local CI run: build Bazel artifacts → run C++ tests → copy to src/ → build wheel → install wheel → run minor pytest tests → restore editable install. Requires EXT=so|pyd. Mirrors the CI pipeline exactly.
	$(MAKE) bazel_test_converter_local
	$(MAKE) copy_extensions_local EXT=$(EXT)
	$(MAKE) build_wheel_local
	uv sync --no-install-project --group dev
	python scripts/install_wheel.py --force-reinstall
	python scripts/run_tests_and_restore.py -m minor --tb=short src/

# Wheel verification targets:
.PHONY: smoke_test_local
smoke_test_local: ## Verifies pybind11 extensions are importable and execute C++ code.
	uv run --no-project python scripts/smoke_test_converter.py

.PHONY: install_wheel_local
install_wheel_local: ## Installs the wheel from dist/ into the current Python environment.
	python scripts/install_wheel.py

.PHONY: verify_wheel_local
verify_wheel_local: install_wheel_local smoke_test_local ## Installs wheel from dist/ and runs smoke test.
