UV ?= uv
CLI := $(UV) run uisurf_agent run
PYTHON_CHECK_PATHS ?= src/uisurf_agent

HOST ?= 127.0.0.1
BROWSER_PORT ?= 8001
DESKTOP_PORT ?= 8002
MOBILE_PORT ?= 8003
MOBILE_SERIAL ?=
HEADLESS ?= false
FAST_MODE ?= true
INCLUDE_THOUGHTS ?= false
MAX_OBSERVATION_IMAGES ?= 2
OBSERVATION_SCALE ?= 0.75
DESKTOP_OBSERVATION_DELAY_MS ?= 750
MOBILE_OBSERVATION_DELAY_MS ?= 1500

TASK ?=
BROWSER_TASK ?=
DESKTOP_TASK ?=
BROWSER_TASK_DEFAULT ?= Open example.com and summarize the page
DESKTOP_TASK_DEFAULT ?= Open Terminal and run pwd
MAX_STEPS ?= 20
AUTO_MODE ?= false
IMAGE_NAME ?= uisurf-agent:latest
BUILD_CONTEXT ?= .
DOCKERFILE_PATH ?= ./docker/Dockerfile

.DEFAULT_GOAL := help
.PHONY: help build-image docker-build check-local fix-local docs-check docs check-pre-commit pre-commit-install pre-commit-update run-dev run-all run-browser run-desktop run-mobile run-browser-interactive run-desktop-interactive

help:
	@printf '%s\n' \
		'make build-image IMAGE_NAME=uisurf-agent:latest' \
		'make docker-build IMAGE_NAME=uisurf-agent:latest' \
		'make check-local' \
		'make fix-local' \
		'make docs-check' \
		'make docs' \
		'make check-pre-commit' \
		'make pre-commit-install' \
		'make run-browser HOST=127.0.0.1 BROWSER_PORT=8001' \
		'make run-desktop HOST=127.0.0.1 DESKTOP_PORT=8002' \
		'make run-mobile HOST=127.0.0.1 MOBILE_PORT=8003 MOBILE_SERIAL=<device-serial> MOBILE_OBSERVATION_DELAY_MS=1500' \
		'make run-dev HOST=127.0.0.1 BROWSER_PORT=8001 DESKTOP_PORT=8002 MOBILE_PORT=8003' \
		'make run-all HOST=127.0.0.1 BROWSER_PORT=8001 DESKTOP_PORT=8002 MOBILE_PORT=8003' \
		'make run-browser-interactive TASK="Open example.com and summarize the page"' \
		'make run-desktop-interactive TASK="Open Terminal and run pwd"'

check-local:
	$(UV) run ruff check $(PYTHON_CHECK_PATHS)
	$(UV) run black --check $(PYTHON_CHECK_PATHS)
	$(UV) run isort --check-only $(PYTHON_CHECK_PATHS)

fix-local:
	$(UV) run isort $(PYTHON_CHECK_PATHS)
	$(UV) run black $(PYTHON_CHECK_PATHS)
	$(UV) run ruff check --fix $(PYTHON_CHECK_PATHS)

docs-check:
	$(UV) run mkdocs build --strict -d /tmp/uisurf-agent-mkdocs-check

docs:
	$(UV) run mkdocs build --strict

check-pre-commit:
	$(UV) run pre-commit run --all-files

pre-commit-install:
	$(UV) run pre-commit install

pre-commit-update:
	$(UV) run pre-commit autoupdate

build-image:
	IMAGE_NAME="$(IMAGE_NAME)" \
	BUILD_CONTEXT="$(BUILD_CONTEXT)" \
	DOCKERFILE_PATH="$(DOCKERFILE_PATH)" \
	./build-image.sh

docker-build: build-image

run-dev: run-all

run-browser:
	@if lsof -nP -iTCP:$(BROWSER_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(BROWSER_PORT) is already in use. Stop the existing process or rerun with BROWSER_PORT=<free-port>."; \
		exit 1; \
	fi
	$(CLI) browser_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(BROWSER_PORT) \
		$(if $(filter true,$(FAST_MODE)),--fast-mode,--no-fast-mode) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE)

run-desktop:
	@if lsof -nP -iTCP:$(DESKTOP_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(DESKTOP_PORT) is already in use. Stop the existing process or rerun with DESKTOP_PORT=<free-port>."; \
		exit 1; \
	fi
	$(CLI) desktop_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(DESKTOP_PORT) \
		--desktop-observation-delay-ms $(DESKTOP_OBSERVATION_DELAY_MS) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE)

run-mobile:
	@if lsof -nP -iTCP:$(MOBILE_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(MOBILE_PORT) is already in use. Stop the existing process or rerun with MOBILE_PORT=<free-port>."; \
		exit 1; \
	fi
	$(CLI) mobile_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(MOBILE_PORT) \
		$(if $(MOBILE_SERIAL),--mobile-serial $(MOBILE_SERIAL),) \
		--mobile-observation-delay-ms $(MOBILE_OBSERVATION_DELAY_MS) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE)

run-all:
	@if lsof -nP -iTCP:$(BROWSER_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(BROWSER_PORT) is already in use. Stop the existing process or rerun with BROWSER_PORT=<free-port>."; \
		exit 1; \
	fi
	@if lsof -nP -iTCP:$(DESKTOP_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(DESKTOP_PORT) is already in use. Stop the existing process or rerun with DESKTOP_PORT=<free-port>."; \
		exit 1; \
	fi
	@if lsof -nP -iTCP:$(MOBILE_PORT) -sTCP:LISTEN >/dev/null 2>&1; then \
		echo "Port $(MOBILE_PORT) is already in use. Stop the existing process or rerun with MOBILE_PORT=<free-port>."; \
		exit 1; \
	fi
	@browser_pid=''; \
	desktop_pid=''; \
	mobile_pid=''; \
	trap 'if [ -n "$$browser_pid" ]; then kill "$$browser_pid" 2>/dev/null || true; fi; if [ -n "$$desktop_pid" ]; then kill "$$desktop_pid" 2>/dev/null || true; fi; if [ -n "$$mobile_pid" ]; then kill "$$mobile_pid" 2>/dev/null || true; fi; wait "$$browser_pid" 2>/dev/null || true; wait "$$desktop_pid" 2>/dev/null || true; wait "$$mobile_pid" 2>/dev/null || true; exit 130' INT TERM; \
	$(CLI) browser_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(BROWSER_PORT) \
		$(if $(filter true,$(FAST_MODE)),--fast-mode,--no-fast-mode) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE) & \
	browser_pid=$$!; \
	$(CLI) desktop_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(DESKTOP_PORT) \
		--desktop-observation-delay-ms $(DESKTOP_OBSERVATION_DELAY_MS) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE) & \
	desktop_pid=$$!; \
	$(CLI) mobile_agent \
		--mode a2a \
		--host $(HOST) \
		--port $(MOBILE_PORT) \
		$(if $(MOBILE_SERIAL),--mobile-serial $(MOBILE_SERIAL),) \
		--mobile-observation-delay-ms $(MOBILE_OBSERVATION_DELAY_MS) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE) & \
	mobile_pid=$$!; \
	wait $$browser_pid; \
	browser_status=$$?; \
	wait $$desktop_pid; \
	desktop_status=$$?; \
	wait $$mobile_pid; \
	mobile_status=$$?; \
	test $$browser_status -eq 0 && test $$desktop_status -eq 0 && test $$mobile_status -eq 0

run-browser-interactive:
	$(CLI) browser_agent \
		--task "$(or $(BROWSER_TASK),$(TASK),$(BROWSER_TASK_DEFAULT))" \
		--max-steps $(MAX_STEPS) \
		$(if $(filter true,$(HEADLESS)),--headless,) \
		$(if $(filter true,$(AUTO_MODE)),--auto-mode,) \
		$(if $(filter true,$(FAST_MODE)),--fast-mode,--no-fast-mode) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE)

run-desktop-interactive:
	$(CLI) desktop_agent \
		--task "$(or $(DESKTOP_TASK),$(TASK),$(DESKTOP_TASK_DEFAULT))" \
		--max-steps $(MAX_STEPS) \
		$(if $(filter true,$(AUTO_MODE)),--auto-mode,) \
		--desktop-observation-delay-ms $(DESKTOP_OBSERVATION_DELAY_MS) \
		$(if $(filter true,$(INCLUDE_THOUGHTS)),--include-thoughts,--no-include-thoughts) \
		--max-observation-images $(MAX_OBSERVATION_IMAGES) \
		--observation-scale $(OBSERVATION_SCALE)
