.PHONY: all build build-local build-edge build-edge-host build-node build-node-target build-node-targets pack-node-target pack-edge archive-edge tidy test test-e2e test-control-plane-edge-wire test-credential-slot-smoke test-openai-ollama test-openai-lemonade test-openai-glm-coding test-hot-path-agent-smoke-self-test test-hot-path-agent-smoke-preflight test-hot-path-agent-smoke test-single-request-claude-smoke-self-test test-single-request-claude-smoke-preflight test-single-request-claude-smoke-validate test-single-request-claude-smoke readability-audit proto proto-dart client-test client-build-web clean

GOFLAGS ?= -trimpath
BUILD_DIR ?= build
BUILD_BIN_DIR ?= $(BUILD_DIR)/bin
ARTIFACT_DIR ?= $(BUILD_DIR)/artifacts
PACK_DIR ?= $(BUILD_DIR)/packages
EDGE_TARGET ?= $(shell go env GOOS)-$(shell go env GOARCH)
EDGE_TARGET_PARTS = $(subst -, ,$(EDGE_TARGET))
EDGE_GOOS = $(word 1,$(EDGE_TARGET_PARTS))
EDGE_GOARCH = $(word 2,$(EDGE_TARGET_PARTS))
EDGE_PACKAGE_NAME ?= iop-edge-$(EDGE_TARGET)
EDGE_PACKAGE ?= $(PACK_DIR)/$(EDGE_PACKAGE_NAME).tar.gz
EDGE_PACKAGE_TMP ?= $(BUILD_DIR)/.edge-package
EDGE_HOST_BINARY ?= $(BUILD_BIN_DIR)/iop-edge-host
NODE_TARGET ?= $(shell go env GOOS)-$(shell go env GOARCH)
NODE_TARGETS ?= linux-arm64 linux-amd64 darwin-arm64 darwin-amd64 windows-arm64 windows-amd64
NODE_TARGET_PARTS = $(subst -, ,$(NODE_TARGET))
NODE_GOOS = $(word 1,$(NODE_TARGET_PARTS))
NODE_GOARCH = $(word 2,$(NODE_TARGET_PARTS))
IOP_CONTROL_PLANE_HTTP_URL ?= http://localhost:18000
IOP_CONTROL_PLANE_WIRE_URL ?= ws://localhost:19080/client
all: build

build: build-node-targets
	$(MAKE) build-edge
	$(MAKE) archive-edge

build-local: build-edge build-node

build-edge:
	@test -n "$(EDGE_GOOS)" && test -n "$(EDGE_GOARCH)" || (echo "EDGE_TARGET must be <goos>-<goarch>" >&2; exit 2)
	mkdir -p $(BUILD_BIN_DIR)
	GOOS=$(EDGE_GOOS) GOARCH=$(EDGE_GOARCH) go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-edge ./apps/edge/cmd/edge

build-edge-host:
	mkdir -p $(BUILD_BIN_DIR)
	go build $(GOFLAGS) -o $(EDGE_HOST_BINARY) ./apps/edge/cmd/edge

build-node:
	mkdir -p $(BUILD_BIN_DIR)
	go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node ./apps/node/cmd/node

build-node-target:
	@test -n "$(NODE_GOOS)" && test -n "$(NODE_GOARCH)" || (echo "NODE_TARGET must be <goos>-<goarch>" >&2; exit 2)
	mkdir -p $(BUILD_BIN_DIR)
	GOOS=$(NODE_GOOS) GOARCH=$(NODE_GOARCH) go build $(GOFLAGS) -o $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) ./apps/node/cmd/node

build-node-targets: build-edge-host
	rm -rf "$(ARTIFACT_DIR)"
	@for target in $(NODE_TARGETS); do \
		echo "==> node target $$target"; \
		$(MAKE) build-node-target NODE_TARGET=$$target; \
		$(EDGE_HOST_BINARY) bootstrap pack --target $$target --node-binary $(BUILD_BIN_DIR)/iop-node-$$target --output $(ARTIFACT_DIR); \
	done

pack-node-target: build-edge-host build-node-target
	$(EDGE_HOST_BINARY) bootstrap pack --target $(NODE_TARGET) --node-binary $(BUILD_BIN_DIR)/iop-node-$(NODE_TARGET) --output $(ARTIFACT_DIR)

pack-edge: build-edge archive-edge

archive-edge:
	@test -d "$(ARTIFACT_DIR)/bootstrap" || (echo "missing node bootstrap artifacts; run make pack-node-target NODE_TARGET=<goos>-<goarch> first" >&2; exit 2)
	rm -rf "$(EDGE_PACKAGE_TMP)"
	mkdir -p "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/artifacts" "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/logs" "$(PACK_DIR)"
	cp "$(BUILD_BIN_DIR)/iop-edge" "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/iop-edge"
	cp -R "$(ARTIFACT_DIR)/." "$(EDGE_PACKAGE_TMP)/$(EDGE_PACKAGE_NAME)/artifacts/"
	tar -C "$(EDGE_PACKAGE_TMP)" -czf "$(EDGE_PACKAGE)" "$(EDGE_PACKAGE_NAME)"
	rm -rf "$(EDGE_PACKAGE_TMP)"
	@echo "wrote $(EDGE_PACKAGE)"

tidy:
	go mod tidy

test:
	go test ./...

readability-audit:
	python3 scripts/readability_audit.py --check --input-mode worktree --output build/readability-audit.json

test-e2e:
	@echo "NOTE: test-e2e runs auxiliary smoke (Edge-Node + OpenAI) plus Control Plane-Edge wire smoke; completion still requires user-flow verification when changing runtime paths."
	./scripts/e2e-smoke.sh
	./scripts/e2e-openai-ollama.sh
	./scripts/e2e-control-plane-edge-wire.sh

test-control-plane-edge-wire:
	./scripts/e2e-control-plane-edge-wire.sh
	IOP_SECURE_DELIVERY_E2E=1 go test -count=1 -run '^TestSecureDeliveryThreeProcess$$' ./apps/control-plane/cmd/control-plane

test-credential-slot-smoke:
	./scripts/e2e-credential-slot-smoke.sh

test-openai-ollama:
	./scripts/e2e-openai-ollama.sh

test-openai-lemonade:
	./scripts/e2e-openai-lemonade.sh

# Dedicated required diagnostic for the built-in glm_coding Coding Plan profile.
# Deterministic, credential-free Edge -> Node -> loopback-provider full-cycle.
# Reported separately from auxiliary test-e2e; intentionally not part of it.
test-openai-glm-coding:
	./scripts/e2e-openai-glm-coding.sh

# Hot Path Claude/Pi agent smoke harness entry points
# (scripts/e2e-hot-path-agents.sh). Three isolated targets keep credential-free
# behavioral validation, external input preflight, and the credentialed two-agent
# matrix separate. The credentialed matrix is reported separately and is
# intentionally NOT part of test, test-e2e, or any aggregate local target.
#
# -self-test is credential-free and takes no variables; build it into local
# verification. -preflight and -run forward caller-supplied variables only: no
# secret, endpoint, config, or model value is read, defaulted, or serialized by
# Make, and the harness never echoes one. The harness fingerprints the current
# worktree and validates Edge/Pi/CLI runtime, base/profile and per-scenario alias
# identity plus a live observation log before any agent invocation; any missing or
# mismatched input causes the harness to exit 69 (GNU Make then reports the failed
# recipe with process status 2 and `Error 69` in stderr).
#
# Required caller inputs include base/profile, direct/pass/repair/slow aliases,
# Edge binary/config, Pi config dir, current runtime evidence, one live
# observation log, disposable workspace/output, and secret env-var names. All are
# caller-supplied with no defaults:
#   IOP_HOT_SMOKE_CLAUDE_BIN         path to the claude runner binary
#   IOP_HOT_SMOKE_PI_BIN             path to the pi runner binary
#   IOP_HOT_SMOKE_RUNTIME_EVIDENCE   runtime identity evidence JSON (source/worktree
#                                    fingerprint + edge/pi/claude binary + config +
#                                    fixture + base/profile + alias digests)
#   IOP_HOT_SMOKE_BASE_URL           IOP Hot Path base URL (bound to Claude via env)
#   IOP_HOT_SMOKE_DIRECT_MODEL       preset alias for the direct scenario
#   IOP_HOT_SMOKE_PASS_MODEL         preset alias for light-pass/write-unavailable
#   IOP_HOT_SMOKE_REPAIR_MODEL       preset alias for the repair scenario
#   IOP_HOT_SMOKE_SLOW_MODEL         preset alias for the timeout-cancel scenario
#   IOP_HOT_SMOKE_EDGE_BIN           path to the selected IOP Edge binary
#   IOP_HOT_SMOKE_EDGE_CONFIG        path to the selected Edge config file
#   PI_CODING_AGENT_DIR              Pi config dir (also exported to the pi child)
#   IOP_HOT_SMOKE_PI_PROVIDER        pi provider name selecting the IOP preset
#   IOP_HOT_SMOKE_OBSERVATION_FILE   live Edge log holding hot_path_observation JSON
#   IOP_HOT_SMOKE_WORKSPACE_PARENT   disposable workspace parent dir
#   IOP_HOT_SMOKE_OUTPUT             manifest output path
#   IOP_HOT_SMOKE_CLAUDE_SECRET_ENV  name of the env var holding the claude secret
#   IOP_HOT_SMOKE_PI_SECRET_ENV      name of the env var holding the pi secret
# Optional variables (forwarded only when set):
#   IOP_HOT_SMOKE_FIXTURE            fixture/schema path (defaults to harness schema)
test-hot-path-agent-smoke-self-test:
	./scripts/e2e-hot-path-agents.sh --self-test

test-hot-path-agent-smoke-preflight:
	./scripts/e2e-hot-path-agents.sh --preflight-only \
		--claude "$(IOP_HOT_SMOKE_CLAUDE_BIN)" \
		--pi "$(IOP_HOT_SMOKE_PI_BIN)" \
		--runtime-evidence "$(IOP_HOT_SMOKE_RUNTIME_EVIDENCE)" \
		--base-url "$(IOP_HOT_SMOKE_BASE_URL)" \
		--direct-model "$(IOP_HOT_SMOKE_DIRECT_MODEL)" \
		--pass-model "$(IOP_HOT_SMOKE_PASS_MODEL)" \
		--repair-model "$(IOP_HOT_SMOKE_REPAIR_MODEL)" \
		--slow-model "$(IOP_HOT_SMOKE_SLOW_MODEL)" \
		--edge-bin "$(IOP_HOT_SMOKE_EDGE_BIN)" \
		--edge-config "$(IOP_HOT_SMOKE_EDGE_CONFIG)" \
		--pi-config-dir "$(PI_CODING_AGENT_DIR)" \
		--pi-provider "$(IOP_HOT_SMOKE_PI_PROVIDER)" \
		--observation-file "$(IOP_HOT_SMOKE_OBSERVATION_FILE)" \
		--workspace-root "$(IOP_HOT_SMOKE_WORKSPACE_PARENT)" \
		--output "$(IOP_HOT_SMOKE_OUTPUT)" \
		--claude-secret-env "$(IOP_HOT_SMOKE_CLAUDE_SECRET_ENV)" \
		--pi-secret-env "$(IOP_HOT_SMOKE_PI_SECRET_ENV)" \
		$(if $(IOP_HOT_SMOKE_FIXTURE),--fixture "$(IOP_HOT_SMOKE_FIXTURE)")

test-hot-path-agent-smoke:
	./scripts/e2e-hot-path-agents.sh --run \
		--claude "$(IOP_HOT_SMOKE_CLAUDE_BIN)" \
		--pi "$(IOP_HOT_SMOKE_PI_BIN)" \
		--runtime-evidence "$(IOP_HOT_SMOKE_RUNTIME_EVIDENCE)" \
		--base-url "$(IOP_HOT_SMOKE_BASE_URL)" \
		--direct-model "$(IOP_HOT_SMOKE_DIRECT_MODEL)" \
		--pass-model "$(IOP_HOT_SMOKE_PASS_MODEL)" \
		--repair-model "$(IOP_HOT_SMOKE_REPAIR_MODEL)" \
		--slow-model "$(IOP_HOT_SMOKE_SLOW_MODEL)" \
		--edge-bin "$(IOP_HOT_SMOKE_EDGE_BIN)" \
		--edge-config "$(IOP_HOT_SMOKE_EDGE_CONFIG)" \
		--pi-config-dir "$(PI_CODING_AGENT_DIR)" \
		--pi-provider "$(IOP_HOT_SMOKE_PI_PROVIDER)" \
		--observation-file "$(IOP_HOT_SMOKE_OBSERVATION_FILE)" \
		--workspace-root "$(IOP_HOT_SMOKE_WORKSPACE_PARENT)" \
		--output "$(IOP_HOT_SMOKE_OUTPUT)" \
		--claude-secret-env "$(IOP_HOT_SMOKE_CLAUDE_SECRET_ENV)" \
		--pi-secret-env "$(IOP_HOT_SMOKE_PI_SECRET_ENV)" \
		$(if $(IOP_HOT_SMOKE_FIXTURE),--fixture "$(IOP_HOT_SMOKE_FIXTURE)")

# S12 Claude single-request smoke harness. The self-test is credential-free;
# all other targets require caller-supplied runtime facts and remain outside
# test/test-e2e and every aggregate target.
#
# Required caller inputs: IOP_SINGLE_REQUEST_SMOKE_CLAUDE_BIN,
# IOP_SINGLE_REQUEST_SMOKE_RUNTIME_EVIDENCE, IOP_SINGLE_REQUEST_SMOKE_BASE_URL,
# IOP_SINGLE_REQUEST_SMOKE_MODEL, IOP_SINGLE_REQUEST_SMOKE_EDGE_BIN,
# IOP_SINGLE_REQUEST_SMOKE_NODE_BIN,
# IOP_SINGLE_REQUEST_SMOKE_EDGE_CONFIG, IOP_SINGLE_REQUEST_SMOKE_OBSERVATION_FILE,
# IOP_SINGLE_REQUEST_SMOKE_METRICS_URL, IOP_SINGLE_REQUEST_SMOKE_WORKSPACE,
# IOP_SINGLE_REQUEST_SMOKE_OUTPUT, and IOP_SINGLE_REQUEST_SMOKE_SECRET_ENV.
# Values are forwarded only; Make neither defaults nor serializes them.
test-single-request-claude-smoke-self-test:
	./scripts/e2e-single-request-claude.sh --self-test

test-single-request-claude-smoke-preflight:
	./scripts/e2e-single-request-claude.sh --preflight-only \
		--claude "$(IOP_SINGLE_REQUEST_SMOKE_CLAUDE_BIN)" \
		--runtime-evidence "$(IOP_SINGLE_REQUEST_SMOKE_RUNTIME_EVIDENCE)" \
		--base-url "$(IOP_SINGLE_REQUEST_SMOKE_BASE_URL)" \
		--model "$(IOP_SINGLE_REQUEST_SMOKE_MODEL)" \
		--edge-bin "$(IOP_SINGLE_REQUEST_SMOKE_EDGE_BIN)" \
		--node-bin "$(IOP_SINGLE_REQUEST_SMOKE_NODE_BIN)" \
		--edge-config "$(IOP_SINGLE_REQUEST_SMOKE_EDGE_CONFIG)" \
		--observation-file "$(IOP_SINGLE_REQUEST_SMOKE_OBSERVATION_FILE)" \
		--metrics-url "$(IOP_SINGLE_REQUEST_SMOKE_METRICS_URL)" \
		--workspace "$(IOP_SINGLE_REQUEST_SMOKE_WORKSPACE)" \
		--output "$(IOP_SINGLE_REQUEST_SMOKE_OUTPUT)" \
		--secret-env "$(IOP_SINGLE_REQUEST_SMOKE_SECRET_ENV)"

test-single-request-claude-smoke-validate:
	./scripts/e2e-single-request-claude.sh --validate-manifest "$(IOP_SINGLE_REQUEST_SMOKE_OUTPUT)"

test-single-request-claude-smoke:
	./scripts/e2e-single-request-claude.sh --run \
		--claude "$(IOP_SINGLE_REQUEST_SMOKE_CLAUDE_BIN)" \
		--runtime-evidence "$(IOP_SINGLE_REQUEST_SMOKE_RUNTIME_EVIDENCE)" \
		--base-url "$(IOP_SINGLE_REQUEST_SMOKE_BASE_URL)" \
		--model "$(IOP_SINGLE_REQUEST_SMOKE_MODEL)" \
		--edge-bin "$(IOP_SINGLE_REQUEST_SMOKE_EDGE_BIN)" \
		--node-bin "$(IOP_SINGLE_REQUEST_SMOKE_NODE_BIN)" \
		--edge-config "$(IOP_SINGLE_REQUEST_SMOKE_EDGE_CONFIG)" \
		--observation-file "$(IOP_SINGLE_REQUEST_SMOKE_OBSERVATION_FILE)" \
		--metrics-url "$(IOP_SINGLE_REQUEST_SMOKE_METRICS_URL)" \
		--workspace "$(IOP_SINGLE_REQUEST_SMOKE_WORKSPACE)" \
		--output "$(IOP_SINGLE_REQUEST_SMOKE_OUTPUT)" \
		--secret-env "$(IOP_SINGLE_REQUEST_SMOKE_SECRET_ENV)"

# Requires: protoc + protoc-gen-go (go install google.golang.org/protobuf/cmd/protoc-gen-go@latest)
proto:
	protoc \
		--go_out=. \
		--go_opt=module=iop \
		--proto_path=. \
		proto/iop/runtime.proto \
		proto/iop/node.proto \
		proto/iop/control.proto \
		proto/iop/job.proto

# Try finding protoc-gen-dart in PATH or common fallback locations
PROTOC_GEN_DART := $(shell which protoc-gen-dart 2>/dev/null)
ifeq ($(PROTOC_GEN_DART),)
  PROTOC_GEN_DART := $(wildcard $(HOME)/.pub-cache/bin/protoc-gen-dart)
endif
ifeq ($(PROTOC_GEN_DART),)
  PROTOC_GEN_DART := $(wildcard /config/.pub-cache/bin/protoc-gen-dart)
endif

proto-dart:
ifeq ($(PROTOC_GEN_DART),)
	@echo "Error: protoc-gen-dart not found."
	@echo "Please install it by running: flutter pub global activate protoc_plugin"
	@exit 1
endif
	mkdir -p apps/client/lib/gen
	protoc \
		--plugin=protoc-gen-dart=$(PROTOC_GEN_DART) \
		--dart_out=apps/client/lib/gen \
		--proto_path=. \
		--proto_path=/config/.local/include \
		proto/iop/runtime.proto \
		proto/iop/node.proto \
		proto/iop/control.proto \
		proto/iop/job.proto

client-test:
	cd apps/client && flutter test

client-build-web:
	cd apps/client && flutter build web \
		--dart-define=IOP_CONTROL_PLANE_HTTP_URL=$(IOP_CONTROL_PLANE_HTTP_URL) \
		--dart-define=IOP_CONTROL_PLANE_WIRE_URL=$(IOP_CONTROL_PLANE_WIRE_URL)

clean:
	rm -rf build
	rm -rf dist
	rm -f iop.db
	rm -f build/readability-audit.json
