# Lux Universe Makefile
# 5-node networks (mainnet + testnet + devnet) + services
#
# Architecture:
#   - Nodes: Run locally via `lux cli` (not Docker)
#   - Services: Run via Docker (explorers, exchange, bridge, graph-node)
#
# Quick start:
#   make start network=main   # Start mainnet
#   make start network=test   # Start testnet
#   make start network=dev    # Start devnet
#   make stop network=main    # Stop mainnet
#   make status               # Show all status

SHELL := /bin/bash
.DEFAULT_GOAL := help

# Colors
RESET := \033[0m
BOLD := \033[1m
GREEN := \033[32m
YELLOW := \033[33m
BLUE := \033[34m
RED := \033[31m

# Paths
STATE_DIR := $(HOME)/work/lux/state
GENESIS_DIR := $(HOME)/work/lux/genesis

# Network parameter (REQUIRED for network operations)
# Usage: make start network=main
network ?=

# Normalize short forms
ifeq ($(network),main)
  override network := mainnet
endif
ifeq ($(network),test)
  override network := testnet
endif
ifeq ($(network),dev)
  override network := devnet
endif

# Port configuration per network
# Mainnet: 9630-9638, Testnet: 9640-9648
ifeq ($(network),mainnet)
  HTTP_PORT := 9630
  STAKING_PORT := 9631
  EXPLORER_PORT := 4000
  CHAIN_ID := 96369
  ZOO_CHAIN_ID := 200200
else ifeq ($(network),testnet)
  HTTP_PORT := 9640
  STAKING_PORT := 9641
  EXPLORER_PORT := 4001
  CHAIN_ID := 96368
  ZOO_CHAIN_ID := 200201
else
  HTTP_PORT := 9650
  STAKING_PORT := 9651
  EXPLORER_PORT := 4002
  CHAIN_ID := 1337
  ZOO_CHAIN_ID := 1338
endif

# Sibling-clone manifest (REPOS.yaml) replaces .gitmodules.
# Each Lux ecosystem repo lives at $(HOME)/work/lux/<name> as a free-standing
# sibling clone pinned to a semver tag — never a nested submodule.
# Helper: scripts/repos.sh {init,update,list} (reads REPOS.yaml via yq).
REPOS_HELPER := $(realpath $(dir $(lastword $(MAKEFILE_LIST))))/scripts/repos.sh

##@ Quick Start

# Binaries
LUXD := $(HOME)/work/lux/node/build/luxd
ZOOD := $(HOME)/work/zoo/node/build/zood
HANZOD := $(HOME)/work/hanzo/node/target/release/hanzod
PLUGIN_DIR := $(HOME)/.lux/plugins/current
LOCAL_DIR := /tmp/lux-localnet
LIGHT_MNEMONIC := light light light light light light light light light light light energy

# VM IDs
ZOO_VMID := 2n2njofjYvece8gZWCNnc1mqkcqfW6kbrhPRZPVzwxrSQrQ4gE
HANZO_VMID := 2CwRhy6P6fQyn3JtZW68BFvn2nRwZ4Z8iar8EEeeLpGKmn5XRT

.PHONY: chain
chain: plugins ## Start just the blockchain (luxd + VM plugins)

.PHONY: up
up: chain ## Start full local universe (chain + services)
	@echo "$(BOLD)Starting Lux localnet (luxd --dev)...$(RESET)"
	@rm -rf $(LOCAL_DIR) && mkdir -p $(LOCAL_DIR)
	@LIGHT_MNEMONIC="$(LIGHT_MNEMONIC)" $(LUXD) --dev \
		--http-host=127.0.0.1 --http-port=9660 \
		--data-dir=$(LOCAL_DIR) \
		--plugin-dir=$(PLUGIN_DIR) \
		--log-level=error 2>/dev/null &
	@sleep 6
	@echo "$(GREEN)luxd running on http://127.0.0.1:9660$(RESET)"
	@curl -s http://127.0.0.1:9660/v1/info -X POST -H 'Content-Type: application/json' \
		-d '{"jsonrpc":"2.0","id":1,"method":"info.getNodeID"}' 2>/dev/null | \
		python3 -c "import sys,json; r=json.load(sys.stdin)['result']; print('  NodeID: ' + r['nodeID'])" 2>/dev/null || true
	@echo "$(GREEN)VM plugins: zoo-evm, hanzo-evm, lux-evm$(RESET)"
	@echo "$(GREEN)Chains: P, X, C, Z$(RESET)"
	@echo ""
	@echo "$(BOLD)Endpoints:$(RESET)"
	@echo "  P-Chain: http://127.0.0.1:9660/v1/chain/P"
	@echo "  X-Chain: http://127.0.0.1:9660/v1/chain/X"
	@echo "  C-Chain: http://127.0.0.1:9660/v1/chain/C/rpc"
	@echo "  Z-Chain: http://127.0.0.1:9660/v1/chain/z-chain/rpc"
	@echo ""
	@echo "  Funded: 0x9011E888 (from LIGHT_MNEMONIC)"
	@echo "  Stop:   make down"

.PHONY: down
down: ## Stop local dev network
	@echo "$(BOLD)Stopping localnet...$(RESET)"
	@pkill -f "luxd.*9660" 2>/dev/null || true
	@echo "$(GREEN)Stopped$(RESET)"

.PHONY: plugins
plugins: ## Install VM plugins (zood, hanzod)
	@mkdir -p $(PLUGIN_DIR)
	@if [ ! -f $(ZOOD) ]; then echo "Building zood..." && cd $(HOME)/work/zoo/node && \
		GONOSUMCHECK="*" GONOSUMDB="*" GOPRIVATE="*" go build -o build/zood . 2>/dev/null; fi
	@if [ ! -f $(HANZOD) ]; then echo "Building hanzod..." && cd $(HOME)/work/hanzo/evm && \
		cargo build --release -p hanzo-node 2>/dev/null; fi
	@ln -sf $(ZOOD) $(PLUGIN_DIR)/$(ZOO_VMID) 2>/dev/null || true
	@ln -sf $(HANZOD) $(PLUGIN_DIR)/$(HANZO_VMID) 2>/dev/null || true

.PHONY: all
all: ## Start all networks + services
	@$(MAKE) -s start network=mainnet &
	@$(MAKE) -s start network=testnet &
	@$(MAKE) -s start network=devnet &
	@wait
	@$(MAKE) -s services
	@echo "$(GREEN)$(BOLD)Lux Universe running (mainnet/testnet/devnet)!$(RESET)"
	@$(MAKE) -s status-all

.PHONY: stop-all
stop-all: ## Stop everything
	@$(MAKE) -s stop network=mainnet
	@$(MAKE) -s stop network=testnet
	@$(MAKE) -s stop network=devnet
	@$(MAKE) -s stop-services

##@ Sibling Repos (REPOS.yaml — no submodules)

.PHONY: init
init: ## Clone every sibling repo at its pinned semver (~/work/lux/<name>)
	@echo "$(BOLD)Cloning sibling repos at pinned semver tags...$(RESET)"
	@$(REPOS_HELPER) init

.PHONY: update
update: ## Fetch + checkout pinned tag (all repos, or one via name=<repo>)
	@echo "$(BOLD)Updating siblings to pinned tags...$(RESET)"
	@$(REPOS_HELPER) update $(name)

.PHONY: sync
sync: init ## Alias for init — ensure all siblings cloned at pinned tag

.PHONY: list
list: ## List sibling repos + pinned tags from REPOS.yaml
	@$(REPOS_HELPER) list

##@ Network Management

.PHONY: start
start: ## Start network (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Starting $(network) (port $(HTTP_PORT))...$(RESET)"
	@RUN_E2E=true lux network start --$(network) 2>&1 | tail -5
	@sleep 3
	@$(MAKE) -s health network=$(network)
	@$(MAKE) -s track network=$(network)

.PHONY: stop
stop: ## Stop network (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Stopping $(network)...$(RESET)"
	@RUN_E2E=true lux network stop --$(network) --force 2>/dev/null || true
	@echo "$(GREEN)$(network) stopped$(RESET)"

.PHONY: restart
restart: ## Restart network (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@$(MAKE) -s stop network=$(network)
	@$(MAKE) -s start network=$(network)

.PHONY: clean
clean: ## Clean network state (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@echo "$(YELLOW)Cleaning $(network) state...$(RESET)"
	@RUN_E2E=true lux network clean --$(network) 2>/dev/null || true
	@echo "$(GREEN)$(network) state cleaned$(RESET)"

.PHONY: clean-all
clean-all: ## Clean ALL network state (all networks)
	@echo "$(YELLOW)Cleaning all network state...$(RESET)"
	@RUN_E2E=true lux network clean --mainnet 2>/dev/null || true
	@RUN_E2E=true lux network clean --testnet 2>/dev/null || true
	@RUN_E2E=true lux network clean --devnet 2>/dev/null || true
	@rm -rf $(HOME)/.lux/runs/* 2>/dev/null || true
	@echo "$(GREEN)All network state cleaned$(RESET)"

##@ Health Checks

.PHONY: health
health: ## Check network health (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@echo "$(BOLD)$(network) Health ($(HTTP_PORT)-$$(($(HTTP_PORT)+8))):$(RESET)"
	@for i in 0 2 4 6 8; do \
		port=$$(($(HTTP_PORT)+$$i)); \
		healthy=$$(curl -s -m 1 http://127.0.0.1:$$port/v1/health 2>/dev/null | jq -r '.healthy // false' 2>/dev/null || echo "false"); \
		if [ "$$healthy" = "true" ]; then \
			echo "  Node $$port: $(GREEN)healthy$(RESET)"; \
		else \
			echo "  Node $$port: $(YELLOW)offline$(RESET)"; \
		fi; \
	done

.PHONY: track
track: ## Ensure tracked chains are deployed (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@bash scripts/ensure-tracked-chains.sh $(network) $(STATE_DIR)

##@ Chain Import

# RLP file paths per network
ifeq ($(network),mainnet)
  RLP_DIR := lux-mainnet
  RLP_CCHAIN := lux-mainnet-96369.rlp
  RLP_ZOO := zoo-mainnet-200200.rlp
  ZOO_BC := zoo
else ifeq ($(network),testnet)
  RLP_DIR := lux-testnet
  RLP_CCHAIN := lux-testnet-96368.rlp
  RLP_ZOO := zoo-testnet-200201.rlp
  ZOO_BC := zootest
endif

.PHONY: import
import: ## Import chains (network=main|test required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ]; then \
		echo "$(RED)Error: network=main|test required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Importing $(network) chains...$(RESET)"
	@echo "  C-Chain ($(CHAIN_ID))..."
	@curl -s -X POST "http://127.0.0.1:$(HTTP_PORT)/v1/chain/C/rpc" -H "Content-Type: application/json" \
		-d '{"jsonrpc":"2.0","method":"admin_importChain","params":["$(STATE_DIR)/rlp/$(RLP_DIR)/$(RLP_CCHAIN)"],"id":1}' 2>/dev/null | jq -r '.result // .error // "sent"' &
	@echo "  Zoo ($(ZOO_CHAIN_ID))..."
	@if [ -f "$(STATE_DIR)/rlp/zoo-$(network)/$(RLP_ZOO)" ]; then \
		curl -s -X POST "http://127.0.0.1:$(HTTP_PORT)/v1/chain/$(ZOO_BC)/rpc" -H "Content-Type: application/json" \
			-d '{"jsonrpc":"2.0","method":"admin_importChain","params":["$(STATE_DIR)/rlp/zoo-$(network)/$(RLP_ZOO)"],"id":1}' 2>/dev/null | jq -r '.result // .error // "sent"' & \
	fi
	@wait
	@echo "$(GREEN)$(network) import started$(RESET)"

##@ Snapshot & Resume

.PHONY: snapshot
snapshot: ## Snapshot network (network=main|test required, name=<name>)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ]; then \
		echo "$(RED)Error: network=main|test required$(RESET)"; exit 1; fi
	@snap_name="$(name)"; \
	if [ -z "$$snap_name" ]; then snap_name="$(network)-$$(date +%Y%m%d-%H%M%S)"; fi; \
	echo "$(BOLD)Snapshotting $(network) as '$$snap_name'...$(RESET)"; \
	RUN_E2E=true lux network stop --$(network) --force --snapshot-name "$$snap_name" 2>/dev/null || true; \
	echo "$(GREEN)$(network) snapshot '$$snap_name' saved$(RESET)"

.PHONY: resume
resume: ## Resume network from snapshot (network=main|test required, name=<name>)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ]; then \
		echo "$(RED)Error: network=main|test required$(RESET)"; exit 1; fi
	@if [ -z "$(name)" ]; then \
		echo "$(BOLD)Available $(network) snapshots:$(RESET)"; \
		ls -1 $(HOME)/.lux/snapshots 2>/dev/null | grep $(network) || echo "  None"; \
		exit 1; \
	fi
	@echo "$(BOLD)Resuming $(network) from '$(name)'...$(RESET)"
	@RUN_E2E=true lux network start --$(network) --snapshot-name "$(name)"
	@$(MAKE) -s health network=$(network)

.PHONY: snapshots
snapshots: ## List all snapshots
	@echo "$(BOLD)Available snapshots:$(RESET)"
	@ls -1 $(HOME)/.lux/snapshots 2>/dev/null || echo "  None"

##@ Bootstrap & Snapshot Management

# Bootstrap source directory
BOOTSTRAP_DIR := $(STATE_DIR)/snapshots

.PHONY: bootstrap
bootstrap: ## Bootstrap network from snapshot (network=main|test|dev required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Bootstrapping $(network)...$(RESET)"
	@mkdir -p $(HOME)/.lux/snapshots
	@if [ "$(network)" = "mainnet" ]; then \
		BOOTSTRAP_NAME="mainnet-bootstrap"; \
	elif [ "$(network)" = "testnet" ]; then \
		BOOTSTRAP_NAME="testnet-bootstrap"; \
	else \
		BOOTSTRAP_NAME="devnet-bootstrap"; \
	fi;
	@if [ -d "$(BOOTSTRAP_DIR)/$$BOOTSTRAP_NAME" ]; then \
		echo "$(BLUE)Extracting bootstrap snapshot...$(RESET)"; \
		cd $(HOME)/.lux/snapshots && \
		cat "$(BOOTSTRAP_DIR)/$$BOOTSTRAP_NAME/$(network).tar.gz.*" > $$BOOTSTRAP_NAME.tar.gz 2>/dev/null || true; \
		if [ -f "$$BOOTSTRAP_NAME.tar.gz" ]; then \
			tar -xzf $$BOOTSTRAP_NAME.tar.gz && \
			rm -f $$BOOTSTRAP_NAME.tar.gz; \
		elif [ -d "$$BOOTSTRAP_NAME" ]; then \
			echo "$(YELLOW)Already extracted$(RESET)"; \
		else \
			echo "$(RED)Error: Bootstrap extraction failed$(RESET)"; exit 1; \
		fi;
		# Move extracted data to network directory
		if [ -d "$$BOOTSTRAP_NAME/run_*" ]; then \
			LATEST_RUN=$(ls -td $$BOOTSTRAP_NAME/run_* | head -1); \
			echo "$(BLUE)Setting up network from $$LATEST_RUN...$(RESET)"; \
			mkdir -p $(HOME)/.lux/$(network); \
			for node_num in 1 2 3 4 5; do \
				if [ -d "$$LATEST_RUN/node$$node_num" ]; then \
					cp -r "$$LATEST_RUN/node$$node_num" "$(HOME)/.lux/$(network)/"; \
				fi; \
			done; \
			cp -f "$$LATEST_RUN/prometheus.yaml" "$(HOME)/.lux/$(network)/" 2>/dev/null || true; \
			ln -sf "$(basename $$LATEST_RUN)" "$$BOOTSTRAP_NAME/current"; \
			echo "$(GREEN)$(network) bootstrap setup complete$(RESET)"; \
		else \
			echo "$(RED)Error: No run data found in bootstrap$(RESET)"; exit 1; \
		fi; \
	else \
		echo "$(RED)Error: Bootstrap not found at $(BOOTSTRAP_DIR)/$$BOOTSTRAP_NAME$(RESET)"; \
		echo "$(YELLOW)Try: git lfs pull --include='state/snapshots/$(network)-bootstrap/*'$(RESET)"; \
		exit 1; \
	fi

.PHONY: bootstrap-start
bootstrap-start: ## Bootstrap and start network (network=main|test|dev required)
	@$(MAKE) -s bootstrap network=$(network)
	@echo "$(BOLD)Starting $(network)...$(RESET)"
	@RUN_E2E=true lux network start --$(network)
	@$(MAKE) -s health network=$(network)
	@$(MAKE) -s track network=$(network)

.PHONY: bootstrap-clean
bootstrap-clean: ## Clean bootstrap data
	@echo "$(BOLD)Cleaning bootstrap data...$(RESET)"
	@rm -rf $(HOME)/.lux/snapshots/*-bootstrap*
	@echo "$(GREEN)Bootstrap data cleaned$(RESET)"

.PHONY: snapshot-save
snapshot-save: ## Save network snapshot (network=main|test|dev required, name=<name>)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@snap_name="$(name)"; \
	if [ -z "$$snap_name" ]; then snap_name="$(network)-$$(date +%Y%m%d-%H%M%S)"; fi; \
	mkdir -p $(HOME)/.lux/snapshots; \
	echo "$(BOLD)Saving $(network) snapshot '$$snap_name'...$(RESET)"; \
	RUN_E2E=true lux network stop --$(network) --force 2>/dev/null || true; \
	cp -r $(HOME)/.lux/$(network) $(HOME)/.lux/snapshots/$$snap_name && \
	tar -czf $(HOME)/.lux/snapshots/$$snap_name.tar.gz -C $(HOME)/.lux/snapshots $$snap_name && \
	rm -rf $(HOME)/.lux/snapshots/$$snap_name && \
	echo "$(GREEN)$(network) snapshot '$$snap_name' saved$(RESET)"

.PHONY: snapshot-load
snapshot-load: ## Load network snapshot (network=main|test|dev required, name=<name>)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required$(RESET)"; exit 1; fi
	@if [ -z "$(name)" ]; then \
		echo "$(BOLD)Available $(network) snapshots:$(RESET)"; \
		ls -1 $(HOME)/.lux/snapshots 2>/dev/null | grep $(network) || echo "  None"; \
		exit 1; \
	fi
	@echo "$(BOLD)Loading $(network) from '$(name)'...$(RESET)"
	@RUN_E2E=true lux network stop --$(network) --force 2>/dev/null || true; \
	mkdir -p $(HOME)/.lux/$(network).backup.$$(date +%Y%m%d-%H%M%S) && \
	mv $(HOME)/.lux/$(network) $(HOME)/.lux/$(network).backup.$$(date +%Y%m%d-%H%M%S) 2>/dev/null || true; \
	tar -xzf $(HOME)/.lux/snapshots/$(name).tar.gz -C $(HOME)/.lux && \
	mv $(HOME)/.lux/snapshots/$(name) $(HOME)/.lux/$(network) && \
	echo "$(GREEN)$(network) snapshot '$(name)' loaded$(RESET)"

.PHONY: snapshot-list
snapshot-list: ## List all snapshots
	@echo "$(BOLD)Available snapshots:$(RESET)"
	@ls -1 $(HOME)/.lux/snapshots 2>/dev/null | sed 's/\.tar\.gz$$//' || echo "  None"

.PHONY: snapshot-clean
snapshot-clean: ## Clean all snapshots
	@echo "$(BOLD)Cleaning all snapshots...$(RESET)"
	@rm -rf $(HOME)/.lux/snapshots/*
	@echo "$(GREEN)All snapshots cleaned$(RESET)"

##@ AMM Testing

# AMM network name mapping
ifeq ($(network),mainnet)
  AMM_NETWORK := lux
  AMM_ZOO := zoo
else ifeq ($(network),testnet)
  AMM_NETWORK := lux-testnet
  AMM_ZOO := zoo
endif

.PHONY: amm
amm: ## Test AMM on C-chain (network=main|test required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ]; then \
		echo "$(RED)Error: network=main|test required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Testing AMM on $(network) C-chain ($(CHAIN_ID))...$(RESET)"
	@lux amm status --network $(AMM_NETWORK) --rpc http://127.0.0.1:$(HTTP_PORT)/v1/chain/C/rpc 2>&1 | head -20

.PHONY: amm-zoo
amm-zoo: ## Test AMM on Zoo (network=main|test required)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ]; then \
		echo "$(RED)Error: network=main|test required$(RESET)"; exit 1; fi
	@echo "$(BOLD)Testing AMM on $(network) Zoo ($(ZOO_CHAIN_ID))...$(RESET)"
	@lux amm status --network $(AMM_ZOO) --rpc http://127.0.0.1:$(HTTP_PORT)/v1/chain/$(ZOO_BC)/rpc 2>&1 | head -20

##@ Docker Services

.PHONY: services
services: ## Start all Docker services
	@echo "$(BOLD)Starting Docker services...$(RESET)"
	docker compose up -d
	@echo "$(GREEN)Services started!$(RESET)"
	@$(MAKE) -s service-status

.PHONY: stop-services
stop-services: ## Stop all Docker services
	@echo "$(BOLD)Stopping Docker services...$(RESET)"
	docker compose down
	@echo "$(GREEN)Services stopped$(RESET)"

.PHONY: service-status
service-status: ## Show Docker service status
	@echo "$(BOLD)Docker Services:$(RESET)"
	@docker compose ps --format "table {{.Name}}\t{{.Status}}\t{{.Ports}}" 2>/dev/null || echo "  No services running"

.PHONY: service-logs
service-logs: ## Tail Docker service logs
	docker compose logs -f --tail=100

.PHONY: explorers
explorers: ## Start block explorers
	docker compose up -d explorer-lux explorer-zoo

.PHONY: exchange
exchange: ## Start exchange (DEX)
	docker compose up -d exchange

.PHONY: bridge
bridge: ## Start bridge
	docker compose up -d bridge

.PHONY: graph
graph: ## Start Graph Protocol
	docker compose up -d graph-node-lux

##@ Status

.PHONY: status
status: ## Show network status (network=main|test|dev required, or status-all for all)
	@if [ "$(network)" != "mainnet" ] && [ "$(network)" != "testnet" ] && [ "$(network)" != "devnet" ]; then \
		echo "$(RED)Error: network=main|test|dev required (or use status-all)$(RESET)"; exit 1; fi
	@echo "$(BOLD)═══ $(network) ($(HTTP_PORT)) ═══$(RESET)"
	@$(MAKE) -s health network=$(network)
	@echo ""
	@cblock=$$(curl -s -m 1 -X POST -H "Content-Type: application/json" \
		--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
		http://127.0.0.1:$(HTTP_PORT)/v1/chain/C/rpc 2>/dev/null | jq -r '.result // "N/A"' 2>/dev/null); \
	if [ -z "$$cblock" ]; then cblock="N/A"; fi; \
	echo "  C-Chain Block: $$cblock"
	@bash scripts/check-tracked-chains.sh $(network) $(HTTP_PORT)
	@echo ""
	@echo "$(BOLD)Endpoints:$(RESET)"
	@echo "  C-Chain RPC: http://127.0.0.1:$(HTTP_PORT)/v1/chain/C/rpc"
	@echo "  Explorer:    http://127.0.0.1:$(EXPLORER_PORT)"

.PHONY: status-all
status-all: ## Show all networks + services status
	@echo "$(BOLD)═══════════════════════════════════════$(RESET)"
	@echo "$(BOLD)        LUX UNIVERSE STATUS            $(RESET)"
	@echo "$(BOLD)═══════════════════════════════════════$(RESET)"
	@echo ""
	@echo "$(BOLD)═══ MAINNET (9630) ═══$(RESET)"
	@$(MAKE) -s health network=mainnet 2>/dev/null || echo "  Mainnet: offline"
	@cblock=$$(curl -s -m 1 -X POST -H "Content-Type: application/json" \
		--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
		http://127.0.0.1:9630/v1/chain/C/rpc 2>/dev/null | jq -r '.result // "N/A"' 2>/dev/null); \
	if [ -z "$$cblock" ]; then cblock="N/A"; fi; \
	echo "  C-Chain Block: $$cblock"
	@bash scripts/check-tracked-chains.sh mainnet 9630
	@echo ""
	@echo "$(BOLD)═══ TESTNET (9640) ═══$(RESET)"
	@$(MAKE) -s health network=testnet 2>/dev/null || echo "  Testnet: offline"
	@cblock=$$(curl -s -m 1 -X POST -H "Content-Type: application/json" \
		--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
		http://127.0.0.1:9640/v1/chain/C/rpc 2>/dev/null | jq -r '.result // "N/A"' 2>/dev/null); \
	if [ -z "$$cblock" ]; then cblock="N/A"; fi; \
	echo "  C-Chain Block: $$cblock"
	@bash scripts/check-tracked-chains.sh testnet 9640
	@echo ""
	@echo "$(BOLD)═══ DEVNET (9650) ═══$(RESET)"
	@$(MAKE) -s health network=devnet 2>/dev/null || echo "  Devnet: offline"
	@cblock=$$(curl -s -m 1 -X POST -H "Content-Type: application/json" \
		--data '{"jsonrpc":"2.0","method":"eth_blockNumber","params":[],"id":1}' \
		http://127.0.0.1:9650/v1/chain/C/rpc 2>/dev/null | jq -r '.result // "N/A"' 2>/dev/null); \
	if [ -z "$$cblock" ]; then cblock="N/A"; fi; \
	echo "  C-Chain Block: $$cblock"
	@bash scripts/check-tracked-chains.sh devnet 9650
	@echo ""
	@$(MAKE) -s service-status
	@echo ""
	@echo "$(BOLD)Endpoints:$(RESET)"
	@echo "  Mainnet C-Chain: http://127.0.0.1:9630/v1/chain/C/rpc"
	@echo "  Testnet C-Chain: http://127.0.0.1:9640/v1/chain/C/rpc"
	@echo "  Devnet C-Chain:  http://127.0.0.1:9650/v1/chain/C/rpc"
	@echo "  Explorer:        http://127.0.0.1:4000"

##@ Build

.PHONY: build
build: build-node build-cli ## Build core components

.PHONY: build-node
build-node: ## Build lux node
	@echo "$(BOLD)Building node...$(RESET)"
	@if [ -f node/Makefile ]; then $(MAKE) -C node build; \
	else (cd node && go build -o build/luxd ./main); fi

.PHONY: build-cli
build-cli: ## Build lux CLI
	@echo "$(BOLD)Building CLI...$(RESET)"
	@(cd cli && go build -o build/lux ./main)

.PHONY: build-evm
build-evm: ## Build EVM plugin
	@(cd evm && go build -o build/evm ./plugin)

.PHONY: build-docker
build-docker: ## Build Docker images
	docker compose build

##@ Testing

.PHONY: test
test: ## Run tests for core components
	@for mod in node geth evm cli; do \
		if [ -f "$$mod/go.mod" ]; then \
			echo "$(BLUE)Testing $$mod...$(RESET)"; \
			(cd $$mod && go test -race -timeout 5m ./...) || exit 1; \
		fi; \
	done

##@ E2E Testing (Playwright)

# ONE axis: LUX_ENV=local|devnet|testnet|mainnet (default mainnet). Every url,
# port and chainId is resolved by e2e/env.ts from NETWORKS.yaml — never here.
# ONE viewport set: projects mobile(390x844) tablet(834x1194) laptop(1440x900),
# so "mobile and desktop" is the same three frames every run. Narrow a run with
# `vp=<name>` and a spec with `spec=<path>`.
# Screenshots (pass AND fail) land in ~/Desktop/lux-e2e/$(LUX_ENV)/.
LUX_ENV ?= mainnet
PW := LUX_ENV=$(LUX_ENV) npx playwright test $(if $(vp),--project=$(vp),)

.PHONY: e2e
e2e: ## Run all E2E tests (LUX_ENV=local|devnet|testnet|mainnet, vp=mobile|tablet|laptop)
	@echo "$(BOLD)E2E — LUX_ENV=$(LUX_ENV)$(if $(vp), project=$(vp),) → ~/Desktop/lux-e2e/$(LUX_ENV)$(RESET)"
	@$(PW) $(spec)

.PHONY: e2e-mainnet e2e-testnet e2e-devnet e2e-local
e2e-mainnet: ## Run all E2E against mainnet
	@$(MAKE) -s e2e LUX_ENV=mainnet
e2e-testnet: ## Run all E2E against testnet
	@$(MAKE) -s e2e LUX_ENV=testnet
e2e-devnet: ## Run all E2E against devnet
	@$(MAKE) -s e2e LUX_ENV=devnet
e2e-local: ## Run all E2E against the local stack (make start network=main + ./local/up.sh)
	@$(MAKE) -s e2e LUX_ENV=local

.PHONY: e2e-login
e2e-login: ## Prove sign-in for real (needs LUX_E2E_PASSWORD)
	@if [ -z "$$LUX_E2E_PASSWORD" ]; then \
		echo "$(RED)LUX_E2E_PASSWORD is not set — export it first (never commit it)$(RESET)"; exit 1; fi
	@$(PW) e2e/50-login.spec.ts

# ── Is the chain alive ──────────────────────────────────────────────────────
# The one question nothing in the suite asked until 00-liveness. Gates on the
# TIP, calibrated against the chain's own recent cadence — never on readiness,
# which read 5/5 Ready on all fifteen pods while mainnet had been frozen for 59
# hours. Runs in ~25s; run it first, and believe it over any dashboard.
.PHONY: e2e-live
e2e-live: ## Is the chain actually producing blocks? (tip freshness, all 3 networks)
	@$(PW) e2e/00-liveness.spec.ts

# ── Every page of every product ─────────────────────────────────────────────
# Routes come from e2e/routes.ts, so these two targets grow with the products
# and never with the Makefile.
.PHONY: e2e-surfaces
e2e-surfaces: ## Sweep every declared route of every product, anonymously
	@$(PW) e2e/60-surfaces.spec.ts

.PHONY: e2e-console
e2e-console: ## Sweep all 43 lux.cloud dashboard routes SIGNED IN (needs LUX_E2E_PASSWORD)
	@if [ -z "$$LUX_E2E_PASSWORD" ]; then \
		echo "$(RED)LUX_E2E_PASSWORD is not set — the authenticated sweep FAILS rather than"; \
		echo "skipping, because a skipped sweep is the same colour as a passing one$(RESET)"; exit 1; fi
	@$(PW) e2e/61-console.spec.ts

.PHONY: e2e-institutional
e2e-institutional: ## Score the institutional baseline, 27 screens (needs LUX_E2E_PASSWORD)
	@LUX_SHOT_SET=institutional-baseline $(PW) e2e/51-institutional-baseline.spec.ts

# The pre-go-live gate: the load-bearing surfaces at all three frames, in
# minutes rather than hours. `make e2e` runs everything including the very large
# legacy broker/exchange specs, which are serial within their own file and so
# dominate a full run.
.PHONY: e2e-gate
e2e-gate: ## Run the go-live gate (liveness + surfaces + validators + defi + bridge + dex + login)
	@$(PW) e2e/00-liveness.spec.ts e2e/00-health.spec.ts e2e/01-explorer.spec.ts \
		e2e/08-validator-lifecycle.spec.ts \
		e2e/09-defi-flow.spec.ts e2e/13-bridge-mpc.spec.ts e2e/14-dex-contracts.spec.ts \
		e2e/14-network-independence.spec.ts e2e/16-app-chains.spec.ts \
		e2e/60-surfaces.spec.ts \
		$(if $(LUX_E2E_PASSWORD),e2e/50-login.spec.ts e2e/61-console.spec.ts,)

.PHONY: e2e-health
e2e-health: ## Run health/RPC E2E tests only
	@$(PW) e2e/00-health.spec.ts

.PHONY: e2e-explorer
e2e-explorer: ## Run explorer E2E tests
	@$(PW) e2e/01-explorer.spec.ts

.PHONY: e2e-exchange
e2e-exchange: ## Run exchange E2E tests
	@$(PW) e2e/02-exchange.spec.ts

.PHONY: e2e-bridge
e2e-bridge: ## Run bridge E2E tests
	@$(PW) e2e/03-bridge.spec.ts

.PHONY: e2e-staking
e2e-staking: ## Run staking/delegation E2E tests
	@$(PW) e2e/04-staking.spec.ts

.PHONY: e2e-cloud
e2e-cloud: ## Run cloud bootnode E2E tests
	@$(PW) e2e/05-cloud.spec.ts

.PHONY: e2e-wallet
e2e-wallet: ## Run wallet E2E tests
	@$(PW) e2e/06-wallet.spec.ts

.PHONY: e2e-validator
e2e-validator: ## Run local validator E2E tests (local stack only)
	@$(MAKE) -s e2e LUX_ENV=local spec=e2e/07-validator.spec.ts

.PHONY: e2e-install
e2e-install: ## Install Playwright + browsers
	@npm ci
	@npx playwright install --with-deps chromium

.PHONY: e2e-report
e2e-report: ## Open Playwright HTML report
	@npx playwright show-report

.PHONY: e2e-shots
e2e-shots: ## Open the screenshot directory for this env
	@open ~/Desktop/lux-e2e/$(LUX_ENV) 2>/dev/null || echo "no shots yet for LUX_ENV=$(LUX_ENV)"

##@ Development

.PHONY: dev
dev: init all ## Quick dev setup (starts both networks)

.PHONY: reset
reset: ## Reset submodules
	git submodule foreach --recursive git reset --hard
	git submodule update --init --recursive

.PHONY: clean-build
clean-build: ## Clean build artifacts
	@for mod in node geth evm cli; do \
		[ -d "$$mod" ] && (cd $$mod && go clean -cache 2>/dev/null) || true; \
	done

.PHONY: fmt
fmt: ## Format Go code
	@for mod in node geth evm cli consensus; do \
		[ -f "$$mod/go.mod" ] && (cd $$mod && go fmt ./...) || true; \
	done

.PHONY: lint
lint: ## Lint Go code
	@for mod in node geth evm cli; do \
		[ -f "$$mod/go.mod" ] && (cd $$mod && go vet ./...) || true; \
	done

##@ Help

.PHONY: help
help: ## Show this help
	@awk 'BEGIN {FS = ":.*##"; printf "$(BOLD)Lux Universe$(RESET) - Triple 5-node networks + services\n\nUsage:\n  make $(BLUE)<target>$(RESET) [network=main|test|dev]\n"} \
		/^[a-zA-Z_-]+:.*?##/ { printf "  $(BLUE)%-18s$(RESET) %s\n", $$1, $$2 } \
		/^##@/ { printf "\n$(BOLD)%s$(RESET)\n", substr($$0, 5) }' $(MAKEFILE_LIST)

local: ## Boot the full stack locally on the hanzo cloud binary (seedable, no external IAM)
	@./local/up.sh

##@ IAM — Lux's identity app graph (deterministic, from this repo only)

# infra/k8s/iam/provision.yaml is the ONE source of truth for Lux's OAuth
# clients — never hand-edit an application in a console, or the next converge
# silently reverts it and the drift is invisible until a login breaks.
#
# There are no targets here any more. Converging used to be `make iam-provision`,
# which built the ConfigMap the Job reads with `kubectl create configmap
# --from-file` against a context written into this file, so the document the Job
# actually mounted lived only in whatever cluster somebody last typed at. Both
# objects are declared in infra/k8s/iam/kustomization.yaml now, so the converge
# is an apply of a directory and `kubectl diff -k` says whether it is needed.
#
#   kubectl --context <ctx> diff  -k infra/k8s/iam
#   kubectl --context <ctx> apply -k infra/k8s/iam
#   kubectl --context <ctx> -n hanzo logs job/lux-iam-provision --tail=100
