.PHONY: install check types lint format test coverage clean ui-install ui-dev ui-build e2e e2e-api e2e-playwright build release-check version bump-dry bump-patch bump-minor bump-major release release-pr release-tag

# Install all dependencies
install:
	uv sync --all-extras

# Full verification suite - run after every change
check: types lint test docstrings ui-check
	@echo "✅ All checks passed"

# Individual checks
types:
	uv run mypy src/ --strict

# Check for missing docstrings on public APIs
docstrings:
	@uv run python -c " \
import ast; \
from pathlib import Path; \
missing = []; \
[missing.extend([f'{py}:{n.lineno} - {n.name}' for n in ast.walk(ast.parse(py.read_text())) \
  if isinstance(n, (ast.FunctionDef, ast.AsyncFunctionDef, ast.ClassDef)) \
  and not (n.name.startswith('_') and not n.name.startswith('__')) \
  and not ast.get_docstring(n)]) \
for py in Path('src/stemtrace').rglob('*.py') if '__pycache__' not in str(py)]; \
[print(m) for m in missing]; \
exit(1) if missing else print('✓ All public APIs have docstrings') \
"

lint:
	uv run ruff check src/ tests/
	uv run ruff format --check src/ tests/
	uv run lint-imports

format:
	uv run ruff format src/ tests/
	uv run ruff check --fix src/ tests/

test:
	uv run pytest --cov=stemtrace --cov-report=term-missing --cov-fail-under=95

# Run tests without coverage (faster iteration)
test-fast:
	uv run pytest -x -q

# Run only unit tests
test-unit:
	uv run pytest tests/unit/ -v

# Run integration tests
test-integration:
	uv run pytest -m integration -v

# Show coverage report
coverage:
	uv run pytest --cov=stemtrace --cov-report=html
	@echo "Open htmlcov/index.html to view coverage report"

# Clean build artifacts
clean:
	rm -rf .pytest_cache .mypy_cache .ruff_cache .import_linter_cache htmlcov .coverage dist/
	find . -type d -name __pycache__ -exec rm -rf {} + 2>/dev/null || true

# =============================================================================
# Frontend (React UI)
# =============================================================================
FRONTEND_DIR := src/stemtrace/server/ui/frontend

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

# Run frontend dev server (with HMR)
ui-dev:
	cd $(FRONTEND_DIR) && npm run dev

# Build frontend for production
ui-build:
	cd $(FRONTEND_DIR) && npm run build

# Lint and type check frontend (Biome + tsc)
ui-check:
	cd $(FRONTEND_DIR) && npm run check && npm run typecheck

# Auto-fix frontend lint issues
ui-fix:
	cd $(FRONTEND_DIR) && npm run fix

# =============================================================================
# Build & Release
# =============================================================================
# Build package locally (verify before release)
build:
	rm -rf dist/
	uv run python -m build
	uv run twine check dist/*
	@echo "✅ Build successful. Files in dist/"
	@ls -la dist/

# Full pre-release checklist
release-check:
	@echo "=== Pre-release Checklist ==="
	@echo "0. Checking CHANGELOG.md has [Unreleased] entries..."
	@uv run python scripts/changelog_section.py Unreleased > /dev/null
	@echo "1. Running all checks..."
	$(MAKE) check
	@echo ""
	@echo "2. Building package..."
	$(MAKE) build
	@echo ""
	@echo "✅ Ready to release!"
	@echo ""

# =============================================================================
# Versioning (bump-my-version)
# =============================================================================
# Show current version
version:
	@uv run bump-my-version show current_version

# Dry run - show what would happen
bump-dry:
	uv run bump-my-version bump patch --dry-run --verbose

# Bump patch version (0.1.0 -> 0.1.1)
bump-patch:
	uv run bump-my-version bump patch
	@NEW_VER=$$(uv run bump-my-version show current_version); \
	echo "✅ Version bumped to $$NEW_VER"; \
	echo ""; \
	echo "Note: releases use make release-pr, which runs the bump itself; this target is for manual use only"

# Bump minor version (0.1.0 -> 0.2.0)
bump-minor:
	uv run bump-my-version bump minor
	@NEW_VER=$$(uv run bump-my-version show current_version); \
	echo "✅ Version bumped to $$NEW_VER"; \
	echo ""; \
	echo "Note: releases use make release-pr, which runs the bump itself; this target is for manual use only"

# Bump major version (0.1.0 -> 1.0.0)
bump-major:
	uv run bump-my-version bump major
	@NEW_VER=$$(uv run bump-my-version show current_version); \
	echo "✅ Version bumped to $$NEW_VER"; \
	echo ""; \
	echo "Note: releases use make release-pr, which runs the bump itself; this target is for manual use only"

# Tag and push to trigger release workflow
release:
	@echo "Releases go through a PR: make release-pr BUMP=patch|minor|major, then make release-tag."
	@exit 1

# Open a release PR: preflight, checks, release/vX.Y.Z branch, version bump, PR with notes.
# Usage: make release-pr BUMP=patch|minor|major
release-pr:
	@set -e; \
	case "$(BUMP)" in patch|minor|major) ;; *) echo "Usage: make release-pr BUMP=patch|minor|major"; exit 1;; esac; \
	test "$$(git rev-parse --abbrev-ref HEAD)" = main || { echo "❌ Run from main"; exit 1; }; \
	test -z "$$(git status --porcelain)" || { echo "❌ Working tree not clean"; exit 1; }; \
	gh auth status >/dev/null 2>&1 || { echo "❌ gh is not authenticated (gh auth login)"; exit 1; }; \
	git fetch -q --tags origin main; \
	test -z "$$(git log HEAD..origin/main --oneline)" || { echo "❌ main is behind origin/main"; exit 1; }; \
	NEW=$$(uv run bump-my-version show new_version --increment $(BUMP)); \
	! git rev-parse -q --verify "refs/heads/release/v$$NEW" >/dev/null || { echo "❌ Local branch release/v$$NEW exists"; exit 1; }; \
	test -z "$$(git ls-remote --heads origin "release/v$$NEW")" || { echo "❌ Remote branch release/v$$NEW exists"; exit 1; }; \
	! git rev-parse -q --verify "refs/tags/v$$NEW" >/dev/null || { echo "❌ Tag v$$NEW exists"; exit 1; }; \
	$(MAKE) release-check; \
	git switch -c "release/v$$NEW"; \
	uv run bump-my-version bump $(BUMP); \
	NOTES=$$(mktemp); trap 'rm -f "$$NOTES"' EXIT; \
	uv run python scripts/changelog_section.py "$$NEW" > "$$NOTES"; \
	git push -u origin "release/v$$NEW" || { echo "❌ Push failed; local branch release/v$$NEW holds the bump commit"; exit 1; }; \
	gh pr create --base main --head "release/v$$NEW" --title "chore: release v$$NEW" --body-file "$$NOTES" \
		|| { echo "❌ PR creation failed; release/v$$NEW is pushed, open the PR manually"; exit 1; }; \
	echo ""; \
	echo "✅ Release PR opened for v$$NEW"; \
	echo "Next: gh pr checks --watch, merge with a merge commit, then make release-tag"

# Tag the merged release on main; the tag push triggers the Release workflow.
release-tag:
	@set -e; \
	git switch -q main; \
	git pull -q --ff-only origin main; \
	VERSION=$$(uv run bump-my-version show current_version); \
	TAG="v$$VERSION"; \
	SUBJECT=$$(git log -1 --format=%s); \
	test "$$(git rev-list --parents -n 1 HEAD | wc -w)" -eq 3 || { echo "❌ HEAD of main is not a merge commit (merge the release PR with a merge commit)"; exit 1; }; \
	case "$$SUBJECT" in *"/release/$$TAG"|*"release $$TAG"|*"release $$TAG "*) ;; *) echo "❌ HEAD of main is not the $$TAG release PR merge: $$SUBJECT"; exit 1;; esac; \
	git fetch -q --tags origin; \
	! git rev-parse -q --verify "refs/tags/$$TAG" >/dev/null || { echo "❌ $$TAG already exists"; exit 1; }; \
	git tag -a "$$TAG" -m "Release $$TAG"; \
	git push origin "$$TAG"; \
	echo "✅ Tagged $$TAG → Release workflow publishes PyPI, GHCR and the GitHub Release"

# =============================================================================
# E2E Testing
# =============================================================================

# Run Playwright E2E tests in mock mode (no Docker required)
# This is the default and recommended way to run E2E tests locally
e2e-mock:
	cd $(FRONTEND_DIR) && npm test

# Start E2E test environment (Docker)
e2e-up:
	docker compose -f docker-compose.e2e.yml build
	docker compose -f docker-compose.e2e.yml up -d --wait
	@echo "Waiting for services..."
	@uv run python scripts/wait_for_http.py http://localhost:8000/stemtrace/api/health --timeout 60
	@echo "✅ E2E environment ready at http://localhost:8000"

# Stop E2E test environment
e2e-down:
	docker compose -f docker-compose.e2e.yml down -v

# Run API E2E tests (requires e2e-up first)
e2e-api:
	uv run pytest tests/e2e/ -m e2e -v

# Run Playwright E2E tests against real Docker backend
e2e-playwright-real:
	cd $(FRONTEND_DIR) && E2E_MODE=real PLAYWRIGHT_BASE_URL=http://localhost:8000 npm test

# Run all E2E tests against Docker (full integration)
e2e:
	$(MAKE) e2e-up
	$(MAKE) e2e-api && $(MAKE) e2e-playwright-real; \
	status=$$?; \
	$(MAKE) e2e-down; \
	exit $$status

# =============================================================================
# E2E Testing (RabbitMQ broker)
# =============================================================================

e2e-up-rabbitmq:
	docker compose -f docker-compose.e2e.rabbitmq.yml build
	docker compose -f docker-compose.e2e.rabbitmq.yml up -d --wait
	@echo "Waiting for services..."
	@uv run python scripts/wait_for_http.py http://localhost:8000/stemtrace/api/health --timeout 60
	@echo "✅ RabbitMQ E2E environment ready at http://localhost:8000"

e2e-down-rabbitmq:
	docker compose -f docker-compose.e2e.rabbitmq.yml down -v

e2e-api-rabbitmq:
	CELERY_BROKER_URL=amqp://guest:guest@localhost:5672// \
	CELERY_RESULT_BACKEND=redis://localhost:16380/1 \
	uv run pytest tests/e2e/ -m e2e -v

e2e-playwright-real-rabbitmq:
	cd $(FRONTEND_DIR) && E2E_MODE=real PLAYWRIGHT_BASE_URL=http://localhost:8000 npm test

e2e-rabbitmq:
	$(MAKE) e2e-up-rabbitmq
	$(MAKE) e2e-api-rabbitmq && $(MAKE) e2e-playwright-real-rabbitmq; \
	status=$$?; \
	$(MAKE) e2e-down-rabbitmq; \
	exit $$status

# Quick E2E alias (mock mode, no Docker)
e2e-quick: e2e-mock
