.PHONY: help dev dev-full dev-backend dev-backend-rust precommit-install precommit-run build build-rust build-frontend check fmt lint clean \
        up down logs shell release release-full release-native release-source \
        run run-py test test-py test-rust parity coverage coverage-py coverage-rust ci \
        install install-py install-docs install-dev install-frontend docs-serve docs-build publish \
        dev-rust dev-rust-full dev-rust-down dev-rust-check \
        dev-desktop build-desktop

COMPOSE      := docker compose
COMPOSE_DEV  := $(COMPOSE) -f compose.dev.yaml
RUST_API     := http://localhost:8080
FRONTEND_DIR := frontend
RUST_DIR     := rust
PKG_MGR      := $(shell command -v bun /dev/null 2>/dev/null || (command -v pnpm 2>/dev/null && echo pnpm) || echo npm)
PYTHON       := python3

help: ## Show this help
	@grep -E '^[a-zA-Z_-]+:.*##' $(MAKEFILE_LIST) | \
		awk 'BEGIN{FS=":.*## "}{printf "  \033[36m%-22s\033[0m %s\n", $$1, $$2}' | sort
	@echo $(PKG_MGR)

# ── Runtime ─────────────────────────────────────────────────────────────────

run: ## Start the backend via run.sh (respects WACTORZ_BACKEND)
	./run.sh

run-py: ## Explicitly start the Python backend
	WACTORZ_BACKEND=python ./run.sh

dev-backend: ## Start the backend in dev mode (defaults to Python REST on :8080)
	WACTORZ_DEV_MODE=1 ./run.sh

dev-backend-rust: ## Start the Rust backend while keeping dev-mode defaults elsewhere
	WACTORZ_DEV_MODE=1 WACTORZ_BACKEND=rust ./run.sh

# ── Rust isolated dev ────────────────────────────────────────────────────────

dev-rust: ## Start mosquitto + run Rust server natively (REST :8080, WS :8081)
	$(COMPOSE_DEV) up -d --wait mosquitto
	cargo run -p wactorz-server -- --no-cli

dev-rust-full: ## Start mosquitto + fuseki + run Rust server natively
	$(COMPOSE_DEV) up -d --wait mosquitto fuseki
	cargo run -p wactorz-server -- --no-cli \
	  --fuseki-url http://localhost:3030 \
	  --fuseki-dataset wactorz

dev-rust-down: ## Stop the Rust dev infrastructure (mosquitto + fuseki)
	$(COMPOSE_DEV) stop mosquitto fuseki

dev-rust-check: ## Smoke-test a running Rust server (health, actors, config)
	@echo "── /health ──────────────────────────────"
	@curl -sf $(RUST_API)/health | $(PYTHON) -m json.tool
	@echo "── /api/actors ──────────────────────────"
	@curl -sf $(RUST_API)/api/actors | $(PYTHON) -m json.tool
	@echo "── /api/config ──────────────────────────"
	@curl -sf $(RUST_API)/api/config | $(PYTHON) -m json.tool
	@echo "── diff vs Python (:8000) ───────────────"
	@diff \
	  <(curl -sf $(RUST_API)/api/config | $(PYTHON) -m json.tool 2>/dev/null) \
	  <(curl -sf http://localhost:8000/api/config | $(PYTHON) -m json.tool 2>/dev/null) \
	  && echo "configs match ✓" || echo "(diff shown above)"

# ── Development ─────────────────────────────────────────────────────────────

dev: ## Start mock stack (mosquitto + mock-agents only)
	$(COMPOSE_DEV) up

dev-down: ## Stop mock stack
	$(COMPOSE_DEV) down

dev-full: ## Start full stack in dev mode (Python + mock agents + Vite)
	$(COMPOSE_DEV) up -d && WACTORZ_DEV_MODE=1 ./run.sh &
	cd $(FRONTEND_DIR) && $(PKG_MGR) run dev

dev-ui: ## Start Vite dev server only (needs mosquitto running)
	cd $(FRONTEND_DIR) && $(PKG_MGR) run dev

dev-desktop: ## Start Tauri desktop app in dev mode (hot-reload)
	cd $(FRONTEND_DIR) && cargo tauri dev

build-desktop: ## Build Tauri desktop app (release bundle)
	cd $(FRONTEND_DIR) && cargo tauri build

# ── Build ───────────────────────────────────────────────────────────────────

build: build-rust build-frontend build-py ## Build everything

build-py: ## Build Python wheel and check with twine
	$(PYTHON) -m pip wheel . --no-deps -w dist/ -q
	$(PYTHON) -m pip install --quiet twine
	$(PYTHON) -m twine check dist/*.whl

build-rust: ## Build Rust workspace (release)
	cargo build --release

build-frontend: ## Build Vite frontend and sync to installed package
	cd $(FRONTEND_DIR) && $(PKG_MGR) run build
	@INST=$$($(PYTHON) -m pip show wactorz 2>/dev/null | awk '/^Location:/{print $$2}'); \
	INST="$$INST/wactorz/static/app"; \
	if [ -d "$$INST" ] && [ "$$INST" != "$(CURDIR)/static/app" ]; then \
	  echo "Syncing static/app → $$INST"; \
	  cp -r static/app/ "$$INST/"; \
	fi

check: ## Cargo check (fast, no codegen)
	cargo check

fmt: ## Format Rust + TypeScript
	cargo fmt
	cd $(FRONTEND_DIR) && $(PKG_MGR) run fmt 2>/dev/null || $(PKG_MGR) x prettier --write "src/**/*.ts"

format: fmt ## Format Rust + TypeScript

lint: ## Clippy + TS typecheck
	cargo clippy -- -D warnings
	cd $(FRONTEND_DIR) && $(PKG_MGR) run typecheck

# ── Docker stack ────────────────────────────────────────────────────────────

up: ## Start full stack (build if needed)
	$(COMPOSE) up --build -d

down: ## Stop full stack
	$(COMPOSE) down

logs: ## Follow full stack logs
	$(COMPOSE) logs -f

logs-%: ## Follow logs for a specific service, e.g. make logs-wactorz
	$(COMPOSE) logs -f $*

shell: ## Open a shell in the wactorz container
	$(COMPOSE) exec wactorz sh

shell-%: ## Open a shell in a running container, e.g. make shell-wactorz
	$(COMPOSE) exec $* sh

# ── Release packaging ───────────────────────────────────────────────────────

release: ## Package pre-built Docker image release (.tar.gz)
	bash scripts/package-release.sh

release-full: ## Package full release zip (source + image)
	bash scripts/package-full-release.sh

release-native: ## Package native binary release (.tar.gz)
	bash scripts/package-native.sh

release-source: ## Package source-only tarball
	bash scripts/package-source.sh

# ── Misc ────────────────────────────────────────────────────────────────────

clean: ## Remove Rust build artifacts and frontend dist
	cargo clean
	rm -rf $(FRONTEND_DIR)/dist

install: install-py install-frontend ## Install everything (Python + frontend)

install-py: ## Install Python package in editable mode with all extras
	$(PYTHON) -m pip install -e ".[all]"

install-docs: ## Install docs dependencies (MkDocs Material + mkdocstrings + mike)
	$(PYTHON) -m pip install -e ".[docs]"

install-dev: ## Install everything including dev/docs deps
	$(PYTHON) -m pip install -e ".[all,docs,dev]"

install-frontend: ## Install frontend dependencies
	cd $(FRONTEND_DIR) && $(PKG_MGR) install

precommit-install: ## Install the git pre-commit hook
	pre-commit install

precommit-run: ## Run all configured pre-commit hooks across the repo
	pre-commit run --all-files

test: test-py test-rust parity ## Run Python, Rust, and cross-backend parity tests

test-py: ## Run Python tests
	$(PYTHON) -m unittest discover -s tests -p 'test_*.py'

test-rust: ## Run Rust tests (excludes desktop Tauri crate — needs GTK/WebKit on Linux)
	cargo test --workspace --exclude wactorz-desktop

parity: ## Prove Python and Rust core supervisor semantics match
	$(PYTHON) scripts/check_backend_parity.py

coverage: coverage-py coverage-rust ## Generate Python and Rust coverage reports

coverage-py: ## Generate Python coverage XML + terminal report
	mkdir -p coverage
	$(PYTHON) -m coverage run -m unittest discover -s tests -p 'test_*.py'
	$(PYTHON) -m coverage xml -o coverage/python-coverage.xml
	$(PYTHON) -m coverage report

coverage-rust: ## Generate Rust coverage with cargo-llvm-cov
	mkdir -p coverage
	cargo llvm-cov --workspace --exclude wactorz-desktop \
	  --ignore-filename-regex 'wactorz-agents|wactorz-server|src-tauri|backend_parity' \
	  --lcov --output-path coverage/rust.lcov

docs-serve: ## Build docs + serve locally on :8001
	$(PYTHON) -W ignore::UserWarning:pdoc scripts/build_docs.py --serve

docs-build: ## Build full docs site (markdown→HTML + rustdoc + typedoc) into static/docs/
	$(PYTHON) -W ignore::UserWarning:pdoc scripts/build_docs.py --full

publish: ## Build wheel + sdist and upload to PyPI (requires twine + API token)
	$(PYTHON) scripts/build.py --upload

ci: test coverage ## Run the local CI-equivalent checks
