.PHONY: start start-prod stop logs clean build dev-backend dev-frontend setup check test-shell lint-backend .env kiro-login bump desktop desktop-dev desktop-target

# ─── Configuration ───────────────────────────────────────────────────────────
APP_NAME    := kronn
VERSION     := $(shell cat VERSION 2>/dev/null || echo "0.0.0")
PORT        := 3140
DOCKER_COMP := docker compose

# ─── Colors ──────────────────────────────────────────────────────────────────
RED    := \033[0;31m
GREEN  := \033[0;32m
YELLOW := \033[0;33m
CYAN   := \033[0;36m
RESET  := \033[0m

# ─── Main Commands ───────────────────────────────────────────────────────────

## Auto-detect host environment and write .env for docker compose
.env:
	@echo "# Auto-generated by Kronn — do not edit" > .env
	@uname_s=$$(uname -s 2>/dev/null); \
	if [ "$$uname_s" = "Darwin" ]; then \
		echo "KRONN_HOST_OS=macOS" >> .env; \
		echo "KRONN_MACOS_SSH_SOCK=/run/host-services/ssh-auth.sock" >> .env; \
		if [ -d /opt/homebrew/bin ]; then \
			echo "KRONN_GLOBAL_BIN=/opt/homebrew/bin" >> .env; \
			echo "$(CYAN)  Detected macOS (Apple Silicon)$(RESET)"; \
		elif [ -d /usr/local/bin ]; then \
			echo "KRONN_GLOBAL_BIN=/usr/local/bin" >> .env; \
			echo "$(CYAN)  Detected macOS (Intel)$(RESET)"; \
		fi; \
	elif grep -qiE "microsoft|wsl" /proc/version 2>/dev/null; then \
		echo "KRONN_HOST_OS=WSL" >> .env; \
		echo "$(CYAN)  Detected WSL2$(RESET)"; \
	else \
		echo "KRONN_HOST_OS=Linux" >> .env; \
		echo "$(CYAN)  Detected Linux$(RESET)"; \
	fi
	@# Detect npm global bin directory (where `npm install -g` puts binaries)
	@npm_bin=$$(npm bin -g 2>/dev/null || npm config get prefix 2>/dev/null | xargs -I{} echo "{}/bin"); \
	if [ -d "$$npm_bin" ]; then \
		echo "KRONN_NPM_BIN=$$npm_bin" >> .env; \
		echo "$(CYAN)  npm global bin: $$npm_bin$(RESET)"; \
	fi
	@echo "KRONN_HOST_UID=$$(id -u)" >> .env
	@echo "KRONN_HOST_GID=$$(id -g)" >> .env
	@echo "UID=$$(id -u)" >> .env
	@echo "GID=$$(id -g)" >> .env
	@# Auto-detect repos dir = parent of Kronn install dir.
	@# Edge case (TD-20260507-makefile-repos-rel-home-edge): when Kronn lives
	@# directly under $$HOME, the parent IS $$HOME, which collides with the
	@# read-only `${HOME}:/host-home:ro` mount in docker-compose.yml. Refuse
	@# rather than silently produce a broken mount layout — the operator
	@# either moves Kronn under a sub-dir OR sets KRONN_REPOS_DIR explicitly.
	@repos_dir=$$(cd "$(CURDIR)/.." && pwd); \
	if [ -z "$$KRONN_REPOS_DIR" ] && [ "$$repos_dir" = "$$HOME" ]; then \
		echo "$(RED)KRONN_REPOS_DIR auto-detect picked $$HOME, which collides with the read-only /host-home mount.$(RESET)"; \
		echo "$(YELLOW)Move Kronn into a sub-directory (e.g. $$HOME/Kronn-host/Kronn)$(RESET)"; \
		echo "$(YELLOW)OR set KRONN_REPOS_DIR=path/to/your/repos before running 'make start'.$(RESET)"; \
		exit 1; \
	fi; \
	if [ -n "$$KRONN_REPOS_DIR" ]; then repos_dir="$$KRONN_REPOS_DIR"; fi; \
	repos_rel=$${repos_dir#$$HOME/}; \
	echo "KRONN_REPOS_DIR=$$repos_dir" >> .env; \
	echo "KRONN_REPOS_REL=$$repos_rel" >> .env; \
	echo "$(CYAN)  Repos dir: $$repos_dir (rw mount)$(RESET)"
	@# Docker socket GID (for docker compose access from container)
	@if [ -S /var/run/docker.sock ]; then \
		docker_gid=$$(stat -c '%g' /var/run/docker.sock 2>/dev/null || stat -f '%g' /var/run/docker.sock 2>/dev/null || echo ""); \
		if [ -n "$$docker_gid" ]; then \
			echo "KRONN_DOCKER_GID=$$docker_gid" >> .env; \
			echo "$(CYAN)  Docker socket GID: $$docker_gid$(RESET)"; \
		fi; \
	fi
	@# 0.8.2 — Auto-detect version-manager bin paths so workflow Exec
	@# steps can call `cargo`, `bun`, etc. without manual config. Each
	@# path is written ONLY if the directory exists; missing → silent skip
	@# so users without rustup/Bun aren't penalized.
	@if [ -d "$$HOME/.cargo/bin" ]; then \
		echo "KRONN_CARGO_BIN=$$HOME/.cargo/bin" >> .env; \
		echo "$(CYAN)  Cargo bin: $$HOME/.cargo/bin$(RESET)"; \
	fi
	@if [ -d "$$HOME/.rustup" ]; then \
		echo "KRONN_RUSTUP_HOME=$$HOME/.rustup" >> .env; \
		echo "$(CYAN)  Rustup home: $$HOME/.rustup$(RESET)"; \
	fi
	@if [ -d "$$HOME/.bun/bin" ]; then \
		echo "KRONN_BUN_BIN=$$HOME/.bun/bin" >> .env; \
		echo "$(CYAN)  Bun bin: $$HOME/.bun/bin$(RESET)"; \
	fi
	@# Auto-detect host system binary paths for terminal access
	@sysbin=""; \
	for dir in /usr/bin /usr/sbin /snap/bin; do \
		[ -d "$$dir" ] && sysbin="$${sysbin:+$$sysbin:}$$dir"; \
	done; \
	if [ -n "$$sysbin" ]; then \
		echo "KRONN_HOST_SYSBIN=$$sysbin" >> .env; \
		echo "$(CYAN)  Host system binaries: $$sysbin$(RESET)"; \
	fi
	@# Auto-detect host network IPs for multi-user identity display
	@host_ips=$$(ip -4 addr show 2>/dev/null | awk '/^[0-9]/{split($$2,a,/[:@]/); iface=a[1]} /inet /{split($$2,a,"/"); ip=a[1]; if(ip~/^127\./)next; if(iface~/^(lo|docker|br-|veth)/)next; printf "%s%s:%s",sep,iface,ip; sep=","}'); \
	if [ -n "$$host_ips" ]; then \
		echo "KRONN_HOST_IPS=$$host_ips" >> .env; \
		echo "$(CYAN)  Host IPs: $$host_ips$(RESET)"; \
	fi
	@echo "# Add extra repo dirs (colon-separated) for rw access:" >> .env
	@echo "# KRONN_EXTRA_REPOS=$$HOME/Desktop/projects:$$HOME/Work" >> .env
	@# Ensure .claude.json exists as a file (Docker creates a directory if missing)
	@if [ ! -e "$$HOME/.claude.json" ]; then \
		echo '{}' > "$$HOME/.claude.json"; \
		echo "$(CYAN)  Created ~/.claude.json (empty file)$(RESET)"; \
	elif [ -d "$$HOME/.claude.json" ]; then \
		echo "$(YELLOW)  WARNING: ~/.claude.json is a directory — fixing$(RESET)"; \
		rmdir "$$HOME/.claude.json" 2>/dev/null && echo '{}' > "$$HOME/.claude.json" || \
		echo "$(RED)  ERROR: Cannot fix ~/.claude.json — remove it manually$(RESET)"; \
	fi

## Generate docker-compose.override.yml for extra repo dirs (KRONN_EXTRA_REPOS)
.PHONY: _gen-override
_gen-override:
	@extra_repos=$$(sed -n 's/^KRONN_EXTRA_REPOS=//p' .env | tail -n 1); \
	if [ -n "$$extra_repos" ]; then \
		echo "# Auto-generated — extra rw mounts from KRONN_EXTRA_REPOS" > docker-compose.override.yml; \
		echo "services:" >> docker-compose.override.yml; \
		echo "  backend:" >> docker-compose.override.yml; \
		echo "    volumes:" >> docker-compose.override.yml; \
		IFS=':'; for dir in $$extra_repos; do \
			rel=$${dir#$$HOME/}; \
			echo "      - $$dir:/host-home/$$rel:rw" >> docker-compose.override.yml; \
			echo "$(CYAN)  Extra rw mount: $$dir$(RESET)"; \
		done; \
	elif [ -f docker-compose.override.yml ]; then \
		rm -f docker-compose.override.yml; \
	fi

## Write/remove KRONN_RUST_LOG in .env based on DEBUG=1.
## docker-compose.yml reads `RUST_LOG=${KRONN_RUST_LOG:-}`, so setting it
## here enables verbose backend logs for the next `up -d` without touching
## the persisted `config.server.debug_mode`.
_apply-debug-flag:
	@sed -i.bak '/^KRONN_RUST_LOG=/d' .env 2>/dev/null || true
	@rm -f .env.bak
	@if [ "$(DEBUG)" = "1" ]; then \
		echo "KRONN_RUST_LOG=kronn=debug,tower_http=debug" >> .env; \
		echo "$(CYAN)  Debug mode: verbose logs enabled (DEBUG=1)$(RESET)"; \
	fi

## Start everything (Docker, fast build — no LTO, ~4x faster than prod)
## Pass DEBUG=1 to force verbose logs (kronn=debug,tower_http=debug) for this run.
## Without DEBUG=1, the backend picks the level from config.server.debug_mode.
start: .env _gen-override _apply-debug-flag
	@echo "$(GREEN)▸ Building $(APP_NAME) (fast profile)...$(RESET)"
	@CARGO_PROFILE=fast $(DOCKER_COMP) up -d --build
	@echo ""
	@echo "  $(CYAN)╭──╮$(RESET)"
	@echo "  $(CYAN)│$(GREEN)⚡$(CYAN)│$(RESET) $(GREEN)Kronn v$(VERSION)$(RESET)"
	@echo "  $(CYAN)╰──╯$(RESET) Services running."
	@echo ""
	@echo "       → $(GREEN)http://localhost:$(PORT)$(RESET)"
	@echo ""
	@echo "       $(CYAN)kronn stop$(RESET)     Stop"
	@echo "       $(CYAN)kronn logs$(RESET)     Logs"
	@echo "       $(CYAN)kronn restart$(RESET)  Restart"
	@echo ""

## Production build (Docker, release with LTO — slow but optimized binary)
start-prod: .env _gen-override
	@echo "$(GREEN)▸ Building $(APP_NAME) (production release)...$(RESET)"
	@$(DOCKER_COMP) up -d --build

## Stop all services
stop:
	@echo "$(YELLOW)▸ Stopping $(APP_NAME)...$(RESET)"
	@$(DOCKER_COMP) down
	@echo ""
	@echo "  $(CYAN)╭──╮$(RESET)"
	@echo "  $(CYAN)│$(RED)⚡$(CYAN)│$(RESET) $(RED)Kronn v$(VERSION)$(RESET)"
	@echo "  $(CYAN)╰──╯$(RESET) Services stopped."
	@echo ""

## Tail logs
logs:
	@$(DOCKER_COMP) logs -f

## Login to Kiro from the backend container (headless-safe OAuth device flow)
kiro-login: .env _gen-override
	@echo "$(CYAN)▸ Kiro login (device flow)...$(RESET)"
	@$(DOCKER_COMP) exec backend /bin/sh -lc 'set -e; PATH="$$HOME/.local/bin:$$PATH"; command -v kiro-cli >/dev/null 2>&1 || (command -v unzip >/dev/null 2>&1 || { echo "Missing unzip in backend image. Rebuild with make start."; exit 1; } ; echo "Installing kiro-cli in container..." ; curl -fsSL https://cli.kiro.dev/install | bash); kiro-cli login --use-device-flow'

## Clean everything
clean:
	@echo "$(YELLOW)▸ Cleaning up...$(RESET)"
	@$(DOCKER_COMP) down -v --remove-orphans
	@rm -rf target backend/target frontend/node_modules frontend/dist
	@rm -f docker-compose.override.yml

## Production build (no Docker)
build:
	@echo "$(GREEN)▸ Building backend...$(RESET)"
	cd backend && cargo build --release
	@echo "$(GREEN)▸ Building frontend...$(RESET)"
	cd frontend && pnpm install && pnpm build
	@echo "$(GREEN)▸ Done. Binary at target/release/$(APP_NAME)$(RESET)"

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

## Backend dev with hot reload
dev-backend:
	@echo "$(GREEN)▸ Starting backend (watch mode)...$(RESET)"
	cd backend && cargo watch -x run

## Frontend dev server
dev-frontend:
	@echo "$(GREEN)▸ Starting frontend dev server...$(RESET)"
	cd frontend && pnpm install && pnpm dev

## Run both in dev mode (requires tmux or two terminals)
dev:
	@echo "$(YELLOW)Run in two terminals:$(RESET)"
	@echo "  make dev-backend"
	@echo "  make dev-frontend"

# ─── Utilities ───────────────────────────────────────────────────────────────

## Check prerequisites
check:
	@echo "$(CYAN)Checking prerequisites...$(RESET)"
	@command -v docker >/dev/null 2>&1 && echo "  ✓ Docker" || echo "  ✗ Docker (required)"
	@command -v cargo  >/dev/null 2>&1 && echo "  ✓ Rust/Cargo" || echo "  ✗ Rust/Cargo (optional, for dev)"
	@command -v node   >/dev/null 2>&1 && echo "  ✓ Node.js" || echo "  ✗ Node.js (optional, for dev)"
	@command -v pnpm   >/dev/null 2>&1 && echo "  ✓ pnpm" || echo "  ✗ pnpm (optional, for dev)"

## Generate TypeScript types from Rust models
typegen:
	@echo "$(GREEN)▸ Generating TypeScript types...$(RESET)"
	cd backend && cargo test export_bindings -- --nocapture
	@echo "$(GREEN)▸ Types written to frontend/src/types/generated.ts$(RESET)"

## Run backend tests (skips ts-rs exports to avoid overwriting generated.ts)
test-backend:
	@echo "$(CYAN)▸ Running backend tests...$(RESET)"
	cd backend && cargo test --lib -- --skip export_bindings

## Run Python helper tests (MCP bridge auto-inheritance contract).
## Stdlib `unittest` only — no extra dev deps. The MCP script
## (`disc-introspection-mcp.py`) drives the kronn-internal MCP server
## that 5+ agent CLIs talk to, so its helpers MUST stay green.
test-python:
	@echo "$(CYAN)▸ Running Python helper tests...$(RESET)"
	python3 -m unittest discover -s backend/scripts -p 'test_*.py' -v

## Run frontend unit tests (vitest)
test-frontend:
	@echo "$(CYAN)▸ Running frontend unit tests (vitest)...$(RESET)"
	cd frontend && pnpm test

## Run frontend E2E tests (Playwright). Pre-req: `make dev` running (Vite + backend).
test-e2e:
	@echo "$(CYAN)▸ Running E2E tests (Playwright)...$(RESET)"
	cd frontend && pnpm test:e2e

## Run frontend E2E tests with the Playwright UI (visual debug).
test-e2e-ui:
	@echo "$(CYAN)▸ Launching Playwright UI...$(RESET)"
	cd frontend && pnpm test:e2e:ui

## Run clippy lints in Docker (same toolchain as CI)
lint-backend:
	@echo "$(CYAN)▸ Running cargo clippy in Docker...$(RESET)"
	@docker build --target linter -f backend/Dockerfile backend/

## Run clippy locally with the ts-rs 9.x noise filtered out.
## ts-rs's proc macro emits "failed to parse serde attribute" for any
## #[serde] attribute combination it doesn't recognize (skip_serializing_if,
## deserialize_with, ...). These don't affect the generated TypeScript and
## aren't actionable on our side — wait for the ts-rs 10 release. Until
## then, this target makes `cargo clippy` actually scannable for real
## warnings.
lint-backend-local:
	@echo "$(CYAN)▸ Running cargo clippy locally (ts-rs noise filtered)...$(RESET)"
	@cd backend && cargo clippy --all-targets 2>&1 \
	  | awk 'BEGIN { skip=0 } \
	         /^warning: failed to parse serde attribute/ { skip=6; next } \
	         skip>0 { skip--; next } \
	         { print }' \
	  | tee /dev/stderr | grep -qE "^(warning|error):" && exit 1 || exit 0

## Run shell script tests (bats)
test-shell:
	@echo "$(CYAN)▸ Running shell tests...$(RESET)"
	@./tests/bats/run.sh

## ─── Sidecars (Python helpers) ──────────────────────────────────────────────

## Install the kronn-docs sidecar (document generation: PDF/DOCX/XLSX/CSV/PPTX).
## Creates a dedicated venv at ~/.kronn/venv/docs and installs the pinned deps.
## Idempotent — reruns just upgrade deps in place.
docs-setup:
	@echo "$(CYAN)▸ Setting up kronn-docs sidecar...$(RESET)"
	@command -v python3 >/dev/null 2>&1 || { echo "$(RED)python3 is required$(RESET)"; exit 1; }
	@mkdir -p $$HOME/.kronn/venv
	@if [ ! -d $$HOME/.kronn/venv/docs ]; then \
		echo "  · Creating venv at ~/.kronn/venv/docs"; \
		python3 -m venv $$HOME/.kronn/venv/docs; \
	fi
	@echo "  · Installing kronn-docs + deps (this may take ~1-2 min on first run)"
	@$$HOME/.kronn/venv/docs/bin/pip install --quiet --upgrade pip
	@$$HOME/.kronn/venv/docs/bin/pip install --quiet -e backend/sidecars/docs
	@echo "$(GREEN)▸ kronn-docs sidecar ready.$(RESET)"
	@echo "  WeasyPrint needs cairo/pango/gdk-pixbuf system libs on your OS —"
	@echo "  if PDF generation fails with a 'library not found' error, install them:"
	@echo "    apt:  sudo apt install libcairo2 libpango-1.0-0 libpangoft2-1.0-0 libgdk-pixbuf-2.0-0"
	@echo "    brew: brew install cairo pango gdk-pixbuf libffi"
	@echo "    win:  use the GTK+ runtime (see weasyprint.readthedocs.io)"

## ─── Desktop app (Tauri) ────────────────────────────────────────────────────

## Build the desktop app for the current platform
desktop:
	@echo "$(CYAN)▸ Building desktop app...$(RESET)"
	@cd frontend && pnpm build
	@cd desktop && pnpm build
	@echo "$(GREEN)✓ Desktop app built$(RESET)"
	@echo "  Output: desktop/src-tauri/target/release/bundle/"

## Run the desktop app in dev mode (hot reload)
desktop-dev:
	@echo "$(CYAN)▸ Starting desktop app (dev)...$(RESET)"
	@cd desktop && pnpm dev

## Build desktop app for a specific target (cross-compilation)
## Usage: make desktop-target T=x86_64-pc-windows-msvc
desktop-target:
ifndef T
	$(error Usage: make desktop-target T=<rust-target-triple>  Ex: x86_64-pc-windows-msvc, aarch64-apple-darwin)
endif
	@echo "$(CYAN)▸ Building desktop app for $(T)...$(RESET)"
	@cd frontend && pnpm build
	@cd desktop/src-tauri && cargo build --release --target $(T)
	@echo "$(GREEN)✓ Desktop app built for $(T)$(RESET)"

help:
	@echo ""
	@echo "$(CYAN)$(APP_NAME) — Enter the grid. Command your agents.$(RESET)"
	@echo ""
	@echo "$(GREEN)Usage:$(RESET)"
	@echo "  make start          Build & launch (Docker, fast — default)"
	@echo "  make start-prod     Build & launch (Docker, release + LTO)"
	@echo "  make stop           Stop services"
	@echo "  make logs           Tail logs"
	@echo "  make clean          Remove containers & data"
	@echo "  make build          Production build (native)"
	@echo "  make dev-backend    Rust hot reload"
	@echo "  make dev-frontend   Vite dev server"
	@echo "  make check          Verify prerequisites"
	@echo "  make kiro-login     Kiro OAuth login (device flow in container)"
	@echo "  make typegen        Sync Rust → TS types"
	@echo "  make test-shell     Run shell tests (bats)"
	@echo "  make bump V=x.y.z  Bump version everywhere"
	@echo "  make desktop        Build desktop app (current platform)"
	@echo "  make desktop-dev    Run desktop app (dev mode)"
	@echo "  make desktop-target T=<triple>  Cross-compile desktop app"
	@echo ""

## ─── Version bump ────────────────────────────────────────────────────────────
## Usage: make bump V=0.2.0
bump:
ifndef V
	$(error Usage: make bump V=x.y.z)
endif
	@echo "$(YELLOW)▸ Bumping version to $(V)...$(RESET)"
	@echo "$(V)" > VERSION
	@sed -i 's/^version = ".*"/version = "$(V)"/' backend/Cargo.toml
	@sed -i 's/^version = ".*"/version = "$(V)"/' desktop/src-tauri/Cargo.toml
	@sed -i 's/"version": ".*"/"version": "$(V)"/' frontend/package.json
	@sed -i 's/"version": ".*"/"version": "$(V)"/' desktop/package.json
	@sed -i 's/"version": ".*"/"version": "$(V)"/' desktop/src-tauri/tauri.conf.json
	@sed -i 's/Kronn v[0-9]\+\.[0-9]\+\.[0-9]\+/Kronn v$(V)/' README.md
	@# 0.8.6 — also bump the hardcoded version in the public site (FR/EN/ES).
	@# Pre-fix `make bump` skipped these and we shipped 0.8.6 with the site
	@# still claiming v0.8.5 on the early-access disclaimer + credits line.
	@# Pattern: any "v<semver>" occurrence in site/*.html is the Kronn version
	@# (no other tokens use that prefix today).
	@sed -i 's/v[0-9]\+\.[0-9]\+\.[0-9]\+/v$(V)/g' site/index.html site/en.html site/es.html
	@# Sync Cargo.lock workspace entries so `cargo check --locked` stays green in CI.
	@# `cargo update --workspace --offline` only touches local package versions in the
	@# lock file — no network, no dep bumps. Required after editing a workspace
	@# member's own `version` in Cargo.toml.
	@echo "$(YELLOW)▸ Syncing Cargo.lock files...$(RESET)"
	@cd backend && cargo update --workspace --offline >/dev/null 2>&1 || \
		{ echo "$(YELLOW)  backend: offline failed, retrying online$(RESET)"; cd ../backend && cargo update --workspace >/dev/null; }
	@cd desktop/src-tauri && cargo update --workspace --offline >/dev/null 2>&1 || \
		{ echo "$(YELLOW)  desktop: offline failed, retrying online$(RESET)"; cd ../../desktop/src-tauri && cargo update --workspace >/dev/null; }
	@echo "$(GREEN)✓ Version bumped to $(V) in all files$(RESET)"
	@echo "  Files updated: VERSION, backend/Cargo.toml, desktop/src-tauri/Cargo.toml,"
	@echo "  frontend/package.json, desktop/package.json, desktop/src-tauri/tauri.conf.json, README.md"
	@echo "  + Cargo.lock (backend, desktop/src-tauri) synced via cargo update --workspace"
