SHELL := /usr/bin/env bash
.SHELLFLAGS := -eu -o pipefail -c

.PHONY: help build test clean install lint fmt fmt-check hooks-install vet run-repl run-server run-demo
.PHONY: build-all build-repl build-server build-demo build-cli build-debug build-catalog
.PHONY: build-wasm-browser build-wasm-node build-studio build-tinysqlpage build-migrate
.PHONY: build-query-files build-query-files-wasm build-fsql run-query-files-demo
.PHONY: build-gh-pages-demo check-gh-pages-demo update-gh-pages push-gh-pages
.PHONY: test-all test-unit test-integration test-jsonv2 test-ci coverage build-check wasm-check wasm-smoke tinygo-wasm ci verify verify-ci
.PHONY: test-query-files test-query-files-wasm test-fsql test-gorm test-sqlpackages test-sql-compat test-server
.PHONY: run-wasm-browser run-wasm-node-demo deps deps-all update-deps tidy tidy-all modules-verify bench bench-engine bench-hotpaths bench-stream bench-stream-guard release-check script-lint docker-build info
.DEFAULT_GOAL := help

# Variables
GO := go
GOFLAGS := 
BINARY_DIR := bin
CMD_DIR := cmd
VERSION := $(shell git describe --tags --always --dirty 2>/dev/null || echo "dev")
LDFLAGS := -ldflags "-X main.Version=$(VERSION) -X github.com/SimonWaldherr/tinySQL/internal/engine.BuildVersion=$(VERSION)"
GO_BUILD_FLAGS := $(GOFLAGS) $(LDFLAGS)
GO_TEST_FLAGS ?= -v
COVERPROFILE ?= coverage.out
BENCH_COUNT ?= 3
# The stream guard is deliberately generous on time for shared CI runners but
# strict on allocation: a first result must not allocate a full 20k-row result
# set before it can be observed. Override only with measured platform evidence.
STREAM_FIRST_ROW_MAX_NS ?= 50000000
STREAM_FIRST_ROW_MAX_B ?= 262144
WASM_BROWSER_SCRIPT := ./$(CMD_DIR)/wasm_browser/build.sh
WASM_NODE_SCRIPT := ./$(CMD_DIR)/wasm_node/build.sh
TINYGO_IMAGE ?= tinygo/tinygo:0.41.1
TINYGO_PATH := /usr/local/tinygo/bin:/usr/local/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin
TINYGO_DOCKER_USER := $(shell id -u):$(shell id -g)
QUERY_FILES_DIR := ./$(CMD_DIR)/query_files
QUERY_FILES_WASM_DIR := ./$(CMD_DIR)/query_files_wasm
QUERY_FILES_WASM_SCRIPT := $(QUERY_FILES_WASM_DIR)/build.sh
GH_PAGES_BRANCH ?= gh-pages
GH_PAGES_WORKTREE ?= .gh-pages-worktree
GH_PAGES_COMMIT_MESSAGE ?= Update gh-pages demo
GH_PAGES_DEMO_FILES := index.html app.js wasm-client.js wasm-worker.js workspace-storage.js query_files.wasm query_files.wasm.gz wasm_exec.js tiles-demo.html tiles-demo.js tiles-demo-data.js demo.mbtiles tiles-demo-bavaria.html tiles-demo-bavaria.js
GO_MOD_FILES := $(shell git ls-files -- 'go.mod' ':(glob)**/go.mod' | LC_ALL=C sort)

# Color output
GREEN := \033[0;32m
YELLOW := \033[0;33m
RED := \033[0;31m
NC := \033[0m # No Color

## help: Show this help message
help:
	@echo 'Usage:'
	@echo '  $(YELLOW)make$(NC) $(GREEN)<target>$(NC)'
	@echo ''
	@echo 'Targets:'
	@sed -n 's/^##//p' ${MAKEFILE_LIST} | column -t -s ':' | sed -e 's/^/ /'

## build: Build the main tinySQL CLI
build: build-cli

## build-all: Build all binaries
build-all: build-cli build-repl build-server build-demo build-debug build-catalog build-studio build-tinysqlpage build-migrate build-query-files build-fsql
	@echo "$(GREEN)✓ All binaries built successfully$(NC)"

## build-cli: Build tinySQL CLI
build-cli:
	@echo "$(GREEN)Building tinySQL CLI...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/tinysql ./$(CMD_DIR)/tinysql

## build-repl: Build interactive REPL
build-repl:
	@echo "$(GREEN)Building REPL...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/repl ./$(CMD_DIR)/repl

## build-server: Build SQL server
build-server:
	@echo "$(GREEN)Building server...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/server ./$(CMD_DIR)/server

## build-demo: Build demo application
build-demo:
	@echo "$(GREEN)Building demo...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/demo ./$(CMD_DIR)/demo

## build-debug: Build debug tool
build-debug:
	@echo "$(GREEN)Building debug tool...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/debug ./$(CMD_DIR)/debug

## build-catalog: Build catalog demo
build-catalog:
	@echo "$(GREEN)Building catalog demo...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/catalog-demo ./$(CMD_DIR)/catalog_demo

## build-studio: Build tinySQL Studio (Wails)
build-studio:
	@echo "$(GREEN)Building tinySQL Studio...$(NC)"
	@mkdir -p $(BINARY_DIR)
	cd $(CMD_DIR)/studio && $(GO) build $(GO_BUILD_FLAGS) -o ../../$(BINARY_DIR)/studio .

## build-tinysqlpage: Build tinySQLPage
build-tinysqlpage:
	@echo "$(GREEN)Building tinySQLPage...$(NC)"
	@mkdir -p $(BINARY_DIR)
	$(GO) build $(GO_BUILD_FLAGS) -o $(BINARY_DIR)/tinysqlpage ./$(CMD_DIR)/tinysqlpage

## build-migrate: Build data migration tool
build-migrate:
	@echo "$(GREEN)Building data migration tool...$(NC)"
	@mkdir -p $(BINARY_DIR)
	cd $(CMD_DIR)/migrate && $(GO) build $(GOFLAGS) -o ../../$(BINARY_DIR)/migrate .

## build-wasm-browser: Build WASM for browser
build-wasm-browser:
	@echo "$(GREEN)Building WASM for browser...$(NC)"
	@$(WASM_BROWSER_SCRIPT) --build-only

## build-wasm-node: Build WASM for Node.js
build-wasm-node:
	@echo "$(GREEN)Building WASM for Node.js...$(NC)"
	@$(WASM_NODE_SCRIPT) --build-only

## wasm-check: Compile every WebAssembly target without leaving build artifacts behind
wasm-check:
	@echo "$(GREEN)Checking WebAssembly targets...$(NC)"
	@set -eu; tmpdir="$$(mktemp -d "$${TMPDIR:-/tmp}/tinysql-wasm.XXXXXX")"; \
	trap 'rm -rf "$$tmpdir"' EXIT; \
	GOOS=js GOARCH=wasm $(GO) build -trimpath -o "$$tmpdir/tinysql-browser.wasm" ./$(CMD_DIR)/wasm_browser; \
	GOOS=js GOARCH=wasm $(GO) build -trimpath -o "$$tmpdir/tinysql-node.wasm" ./$(CMD_DIR)/wasm_node; \
	(cd $(QUERY_FILES_WASM_DIR) && GOOS=js GOARCH=wasm $(GO) build -trimpath -o "$$tmpdir/query-files.wasm" .); \
	echo "$(GREEN)✓ WebAssembly targets compile$(NC)"

## wasm-smoke: Check full/minimal WASM dependencies, sizes and JavaScript APIs
wasm-smoke:
	@bash scripts/check-wasm.sh

## tinygo-wasm: Build and smoke-test the Node WASM bundle with the pinned TinyGo Docker image
tinygo-wasm:
	@echo "$(GREEN)Running TinyGo WASM smoke test ($(TINYGO_IMAGE))...$(NC)"
	@command -v docker >/dev/null || (echo "$(RED)docker is required for TinyGo validation$(NC)" && exit 1)
	@command -v node >/dev/null || (echo "$(RED)node is required for the WASM smoke test$(NC)" && exit 1)
	@set -eu; \
	trap 'rm -f cmd/wasm_node/tinySQL.wasm cmd/wasm_node/wasm_exec.js' EXIT; \
	docker run --rm \
		--user "$(TINYGO_DOCKER_USER)" \
		--env HOME=/tmp \
		--env XDG_CACHE_HOME=/tmp/.cache \
		--env PATH="$(TINYGO_PATH)" \
		--volume "$(CURDIR):/src" \
		--workdir /src/cmd/wasm_node \
		$(TINYGO_IMAGE) \
		sh -c 'WASM_COMPILER=tinygo WASM_OPTIMIZE=false ./build.sh --build-only'; \
	node cmd/wasm_node/wasm_runner.js query 'SELECT 1 AS ready'

## build-query-files: Build standalone query_files CLI
build-query-files:
	@echo "$(GREEN)Building query_files CLI...$(NC)"
	@mkdir -p $(BINARY_DIR)
	cd $(QUERY_FILES_DIR) && $(GO) build -trimpath -o ../../$(BINARY_DIR)/query_files .

## build-fsql: Build standalone fsql (File System Query Language) CLI
build-fsql:
	@echo "$(GREEN)Building fsql CLI...$(NC)"
	@mkdir -p $(BINARY_DIR)
	cd ./$(CMD_DIR)/fsql && $(GO) build -trimpath -o ../../$(BINARY_DIR)/fsql .

## build-query-files-wasm: Build query_files_wasm artifacts
build-query-files-wasm:
	@echo "$(GREEN)Building query_files_wasm...$(NC)"
	@$(QUERY_FILES_WASM_SCRIPT) --build-only

## build-gh-pages-demo: Build or reuse the static WASM demo used by gh-pages
build-gh-pages-demo:
	@echo "$(GREEN)Building gh-pages demo (incremental)...$(NC)"
	@$(QUERY_FILES_WASM_SCRIPT) --if-needed --build-only
	@$(MAKE) --no-print-directory check-gh-pages-demo
	@echo "$(GREEN)✓ gh-pages demo artifacts ready$(NC)"

## check-gh-pages-demo: Validate the deployable static gh-pages assets
check-gh-pages-demo:
	@for file in $(GH_PAGES_DEMO_FILES); do \
		if [ ! -s "$(QUERY_FILES_WASM_DIR)/$$file" ]; then \
			echo "$(RED)missing or empty gh-pages asset: $$file$(NC)"; \
			exit 1; \
		fi; \
	done
	@if command -v gzip >/dev/null 2>&1; then gzip -t "$(QUERY_FILES_WASM_DIR)/query_files.wasm.gz"; fi
	@if command -v node >/dev/null 2>&1; then \
		node --check "$(QUERY_FILES_WASM_DIR)/app.js"; \
		node --check "$(QUERY_FILES_WASM_DIR)/wasm-client.js"; \
		node --check "$(QUERY_FILES_WASM_DIR)/wasm-worker.js"; \
		node --check "$(QUERY_FILES_WASM_DIR)/workspace-storage.js"; \
		node --check "$(QUERY_FILES_WASM_DIR)/tiles-demo.js"; \
		node --check "$(QUERY_FILES_WASM_DIR)/tiles-demo-data.js"; \
	else echo "$(YELLOW)node not installed; skipping JavaScript syntax check$(NC)"; fi
	@if command -v tidy >/dev/null 2>&1; then \
		validate_html() { \
			output="$$(mktemp /tmp/tinysql-html.XXXXXX)" || return 1; \
			tidy -q -e "$$1" >"$$output" 2>&1 || true; \
			if grep -qi 'Error:' "$$output"; then cat "$$output"; rm -f "$$output"; return 1; fi; \
			rm -f "$$output"; \
		}; \
		validate_html "$(QUERY_FILES_WASM_DIR)/index.html"; \
		validate_html "$(QUERY_FILES_WASM_DIR)/tiles-demo.html"; \
	elif command -v xmllint >/dev/null 2>&1; then \
		echo "$(YELLOW)HTML Tidy not installed; using xmllint's legacy HTML parser for a basic parse check$(NC)"; \
		xmllint --html --noout "$(QUERY_FILES_WASM_DIR)/index.html" 2>/dev/null; \
		xmllint --html --noout "$(QUERY_FILES_WASM_DIR)/tiles-demo.html" 2>/dev/null; \
	else echo "$(YELLOW)HTML validator not installed; skipping HTML validation$(NC)"; fi

## update-gh-pages: Build demo, safely sync changed assets into gh-pages, and commit them
update-gh-pages: build-gh-pages-demo
	@set -e; \
	worktree="$(GH_PAGES_WORKTREE)"; \
	branch="$(GH_PAGES_BRANCH)"; \
	echo "$(GREEN)Synchronizing $$branch from $(QUERY_FILES_WASM_DIR)...$(NC)"; \
	if [ -e "$$worktree" ]; then \
		if ! git -C "$$worktree" rev-parse --is-inside-work-tree >/dev/null 2>&1; then \
			echo "$(RED)$$worktree exists but is not a git worktree$(NC)"; exit 1; \
		fi; \
		if [ "$$(git -C "$$worktree" branch --show-current)" != "$$branch" ]; then \
			echo "$(RED)$$worktree is not on branch $$branch$(NC)"; exit 1; \
		fi; \
		if [ -n "$$(git -C "$$worktree" status --porcelain)" ]; then \
			echo "$(RED)$$worktree has uncommitted changes; refusing to overwrite them$(NC)"; exit 1; \
		fi; \
	else \
		git worktree add "$$worktree" "$$branch"; \
	fi; \
	changed=0; \
	for file in $(GH_PAGES_DEMO_FILES); do \
		source="$(QUERY_FILES_WASM_DIR)/$$file"; destination="$$worktree/$$file"; \
		if [ -f "$$source" ]; then \
			if [ ! -f "$$destination" ] || ! cmp -s "$$source" "$$destination"; then cp "$$source" "$$destination"; changed=1; fi; \
		elif [ -e "$$destination" ]; then \
			rm -f "$$destination"; changed=1; \
		fi; \
	done; \
	if [ ! -f "$$worktree/.nojekyll" ]; then touch "$$worktree/.nojekyll"; changed=1; fi; \
	cd "$$worktree"; \
	git add -A -- .nojekyll $(GH_PAGES_DEMO_FILES); \
	if git diff --cached --quiet; then \
		echo "$(YELLOW)$$branch already up to date$(NC)"; \
	elif [ "$$changed" -eq 1 ]; then \
		git commit -m "$(GH_PAGES_COMMIT_MESSAGE)"; \
	else \
		echo "$(YELLOW)No generated asset changes to commit$(NC)"; \
	fi

## push-gh-pages: Update and push the gh-pages demo branch
push-gh-pages: update-gh-pages
	@git push origin "$(GH_PAGES_BRANCH)"

## run-wasm-browser: Build and serve browser WASM app on localhost
run-wasm-browser:
	@$(WASM_BROWSER_SCRIPT) --serve

## run-wasm-node-demo: Build and execute Node WASM demo query
run-wasm-node-demo:
	@$(WASM_NODE_SCRIPT) --run

## run-query-files-demo: Run query_files demo script
run-query-files-demo:
	@$(QUERY_FILES_DIR)/demo.sh

## install: Install tinySQL CLI to $GOPATH/bin
install:
	@echo "$(GREEN)Installing tinySQL...$(NC)"
	$(GO) install $(GO_BUILD_FLAGS) ./$(CMD_DIR)/tinysql

## test: Run all tests
test: test-all

## test-all: Run all tests with coverage
test-all:
	@echo "$(GREEN)Running all tests...$(NC)"
	$(GO) test $(GO_TEST_FLAGS) -race -coverprofile=$(COVERPROFILE) ./...
	$(MAKE) test-query-files
	$(MAKE) test-query-files-wasm
	$(MAKE) test-server GO_TEST_FLAGS='$(GO_TEST_FLAGS) -race'

## test-unit: Run unit tests only
test-unit:
	@echo "$(GREEN)Running unit tests...$(NC)"
	$(GO) test $(GO_TEST_FLAGS) -short ./...

## test-integration: Run integration tests
test-integration:
	$(MAKE) --no-print-directory test-sql-compat
	@echo "$(GREEN)Running integration tests...$(NC)"
	$(GO) test $(GO_TEST_FLAGS) -run Integration ./...

## test-jsonv2: Verify persistence compatibility with Go's experimental JSON v2 implementation
test-jsonv2:
	@echo "$(GREEN)Testing JSON v2 compatibility...$(NC)"
	GOEXPERIMENT=jsonv2 $(GO) test $(GO_TEST_FLAGS) ./internal/storage ./internal/engine

## test-ci: Run the complete non-TinyGo test matrix used by continuous integration
test-ci:
	@echo "$(GREEN)Running CI test matrix...$(NC)"
	$(GO) test ./... -count=1
	$(GO) test -tags=tinysql_minimal ./... -count=1
	$(MAKE) --no-print-directory test-sql-compat GO_TEST_FLAGS=-count=1
	$(MAKE) --no-print-directory test-query-files GO_TEST_FLAGS=-count=1
	$(MAKE) --no-print-directory test-query-files-wasm GO_TEST_FLAGS=-count=1
	$(MAKE) --no-print-directory test-server GO_TEST_FLAGS=-count=1
	GOEXPERIMENT=jsonv2 $(GO) test ./internal/storage ./internal/engine -count=1
	$(GO) test ./... -race -count=1
	$(MAKE) --no-print-directory test-sql-compat GO_TEST_FLAGS='-race -count=1'
	$(MAKE) --no-print-directory test-query-files GO_TEST_FLAGS='-race -count=1'
	$(MAKE) --no-print-directory test-query-files-wasm GO_TEST_FLAGS='-race -count=1'
	$(MAKE) --no-print-directory test-server GO_TEST_FLAGS='-race -count=1'
	$(GO) test -coverprofile=$(COVERPROFILE) ./...
	$(GO) tool cover -func=$(COVERPROFILE)

## test-sql-compat: Test GORM, sqlx and Squirrel against tinySQL
test-sql-compat: test-gorm test-sqlpackages

## test-sqlpackages: Test sqlx and Squirrel against tinySQL
test-sqlpackages:
	cd tests/sqlpackages && $(GO) test $(GO_TEST_FLAGS) ./...

## test-gorm: Test the GORM PostgreSQL dialect against tinySQL (no server)
test-gorm:
	cd tests/gorm && $(GO) test $(GO_TEST_FLAGS) ./...

## test-server: Run HTTP/gRPC server and cluster tests in the nested module
test-server:
	cd $(CMD_DIR)/server && $(GO) test $(GO_TEST_FLAGS) ./...

## test-query-files: Run tests for cmd/query_files module
test-query-files:
	@echo "$(GREEN)Running query_files tests...$(NC)"
	cd $(QUERY_FILES_DIR) && $(GO) test $(GO_TEST_FLAGS) ./...

## test-fsql: Run tests for cmd/fsql module
test-fsql:
	@echo "$(GREEN)Running fsql tests...$(NC)"
	cd ./$(CMD_DIR)/fsql && $(GO) test $(GO_TEST_FLAGS) ./...

## test-query-files-wasm: Run tests for cmd/query_files_wasm module
test-query-files-wasm:
	@echo "$(GREEN)Running query_files_wasm tests...$(NC)"
	cd $(QUERY_FILES_WASM_DIR) && $(GO) test $(GO_TEST_FLAGS) ./...

## coverage: Generate and view test coverage report
coverage: test-all
	@echo "$(GREEN)Generating coverage report...$(NC)"
	$(GO) tool cover -html=$(COVERPROFILE)

## bench: Run benchmarks
bench:
	@echo "$(GREEN)Running benchmarks...$(NC)"
	$(GO) test -bench=. -benchmem ./...

## bench-engine: Run all execution-engine microbenchmarks
bench-engine:
	@echo "$(GREEN)Running engine benchmarks...$(NC)"
	$(GO) test -run=none -bench=. -benchmem ./internal/engine

## bench-hotpaths: Benchmark optimized SELECT, INSERT, UPDATE, DELETE, and trigger hot paths
bench-hotpaths:
	@echo "$(GREEN)Running hot-path benchmarks ($(BENCH_COUNT)x)...$(NC)"
	$(GO) test -run=none -bench='^(BenchmarkOrderByWithLimit|BenchmarkInsertIntoLargePKTable|BenchmarkUpdateByPrimaryKey|BenchmarkDeleteByPrimaryKey|BenchmarkTriggerBatchInsert)$$' -benchmem -count=$(BENCH_COUNT) ./internal/engine
	$(GO) test -run=none -bench='^BenchmarkExecute_Insert$$' -benchmem -count=$(BENCH_COUNT) .

## bench-stream: Measure direct and materialized time-to-first-row paths
bench-stream:
	@echo "$(GREEN)Running streaming time-to-first-row benchmarks ($(BENCH_COUNT)x)...$(NC)"
	$(GO) test -run='^$$' -bench='^Benchmark(Execute(Stream|Materialized)FirstRow|DriverStreamFirstRow|CLIStreamFirstRow)$$' -benchmem -count=$(BENCH_COUNT) ./internal/engine ./internal/driver ./cmd/tinysql
	cd ./$(CMD_DIR)/server && $(GO) test -run='^$$' -bench='^BenchmarkServerStreamFirstRow$$' -benchmem -count=$(BENCH_COUNT) .

## bench-stream-guard: Enforce bounded allocation and latency before first streamed row
bench-stream-guard:
	@set -eu; \
	result="$$(mktemp)"; \
	trap 'rm -f "$$result"' EXIT; \
	$(GO) test -run='^$$' -bench='^BenchmarkExecuteStreamFirstRow$$' -benchmem -benchtime=1x ./internal/engine >"$$result"; \
	cat "$$result"; \
	ns="$$(awk '/^BenchmarkExecuteStreamFirstRow/ { for (i = 1; i <= NF; i++) if ($$i == "ns\/op") { print $$(i-1); exit } }' "$$result")"; \
	bytes="$$(awk '/^BenchmarkExecuteStreamFirstRow/ { for (i = 1; i <= NF; i++) if ($$i == "B\/op") { print $$(i-1); exit } }' "$$result")"; \
	test -n "$$ns" && test -n "$$bytes"; \
	if [ "$$ns" -gt "$(STREAM_FIRST_ROW_MAX_NS)" ]; then \
		echo "$(RED)stream first row took $$ns ns/op (budget $(STREAM_FIRST_ROW_MAX_NS))$(NC)"; exit 1; \
	fi; \
	if [ "$$bytes" -gt "$(STREAM_FIRST_ROW_MAX_B)" ]; then \
		echo "$(RED)stream first row allocated $$bytes B/op (budget $(STREAM_FIRST_ROW_MAX_B))$(NC)"; exit 1; \
	fi; \
	echo "$(GREEN)✓ Streaming first-row budgets met: $$ns ns/op, $$bytes B/op$(NC)"

## release-check: Cross-compile every published CLI target without keeping artifacts
release-check:
	@set -eu; \
	tmpdir="$$(mktemp -d)"; \
	trap 'rm -rf "$$tmpdir"' EXIT; \
	for target in linux/amd64 linux/arm64 darwin/amd64 darwin/arm64 windows/amd64 windows/arm64; do \
		goos="$${target%/*}"; goarch="$${target#*/}"; ext=""; \
		if [ "$$goos" = windows ]; then ext=.exe; fi; \
		echo "$(GREEN)→ tinysql $$goos/$$goarch$(NC)"; \
		CGO_ENABLED=0 GOOS="$$goos" GOARCH="$$goarch" $(GO) build -trimpath $(LDFLAGS) -o "$$tmpdir/tinysql-$$goos-$$goarch$$ext" ./$(CMD_DIR)/tinysql; \
	done; \
	echo "$(GREEN)✓ All release CLI targets compile$(NC)"

## lint: Run linter (golangci-lint)
lint:
	@echo "$(GREEN)Running linter...$(NC)"
	@command -v golangci-lint >/dev/null || (echo "$(RED)golangci-lint not found. Install: https://golangci-lint.run/usage/install/$(NC)" && exit 1)
	golangci-lint run ./...

## script-lint: Run shellcheck on repository shell scripts (if available)
script-lint:
	@echo "$(GREEN)Linting shell scripts...$(NC)"
	@if ! command -v shellcheck >/dev/null; then \
		echo "$(YELLOW)shellcheck not installed, skipping$(NC)"; \
	else \
		shellcheck demo_formats.sh cmd/query_files/demo.sh cmd/query_files_wasm/build.sh cmd/wasm_node/build.sh cmd/wasm_browser/build.sh; \
	fi

## fmt: Format all Go files
fmt:
	@echo "$(GREEN)Formatting code...$(NC)"
	@files="$$(git ls-files '*.go')"; \
	if [ -z "$$files" ]; then \
		echo "$(YELLOW)No Go files found$(NC)"; \
		exit 0; \
	fi; \
	gofmt -w $$files; \
	echo "$(GREEN)✓ Go files formatted$(NC)"

## fmt-check: Verify gofmt formatting without modifying files
fmt-check:
	@echo "$(GREEN)Checking Go formatting...$(NC)"
	@files="$$(git ls-files '*.go')"; \
	if [ -z "$$files" ]; then \
		echo "$(YELLOW)No Go files found$(NC)"; \
		exit 0; \
	fi; \
	unformatted="$$(gofmt -l $$files)"; \
	if [ -n "$$unformatted" ]; then \
		echo "$(RED)Unformatted Go files:$(NC)"; \
		echo "$$unformatted"; \
		exit 1; \
	fi; \
	echo "$(GREEN)✓ Go formatting is clean$(NC)"

## hooks-install: Enable the repository-managed pre-commit checks for this clone
hooks-install:
	@git config core.hooksPath .githooks
	@echo "$(GREEN)✓ Repository hooks enabled ($(CURDIR)/.githooks)$(NC)"

## vet: Run go vet
vet:
	@echo "$(GREEN)Running go vet...$(NC)"
	$(GO) vet ./...

## build-check: Ensure all Go packages compile
build-check:
	@echo "$(GREEN)Building all Go packages...$(NC)"
	$(GO) build $(GOFLAGS) ./...
	@tmpdir="$$(mktemp -d)"; \
	trap 'rm -rf "$$tmpdir"' EXIT; \
	(cd $(QUERY_FILES_DIR) && $(GO) build $(GOFLAGS) -o "$$tmpdir/query_files" .)

## tidy: Tidy dependencies
tidy:
	@echo "$(GREEN)Tidying dependencies...$(NC)"
	$(GO) mod tidy

## tidy-all: Tidy the root module and every nested example, tool, and driver module
tidy-all:
	@echo "$(GREEN)Tidying tracked Go modules...$(NC)"
	@for mod in $(GO_MOD_FILES); do \
		dir="$$(dirname "$$mod")"; \
		echo "$(GREEN)→ $$dir$(NC)"; \
		(cd "$$dir" && $(GO) mod tidy); \
	done

## modules-verify: Download and verify checksums for every tracked Go module
modules-verify:
	@echo "$(GREEN)Downloading and verifying tracked Go modules...$(NC)"
	@for mod in $(GO_MOD_FILES); do \
		dir="$$(dirname "$$mod")"; \
		echo "$(GREEN)→ $$dir$(NC)"; \
		(cd "$$dir" && $(GO) mod download && $(GO) mod verify); \
	done

## verify: Run fmt, vet, lint and test
verify: fmt vet lint test
	@echo "$(GREEN)✓ All verifications passed$(NC)"

## verify-ci: CI-safe verification (non-mutating)
verify-ci: fmt-check vet build-check test-all
	@echo "$(GREEN)✓ CI verification passed$(NC)"

## ci: Run the complete standard-Go CI matrix
ci: fmt-check modules-verify build-check wasm-check vet test-ci bench-stream-guard
	@echo "$(GREEN)✓ Standard-Go CI verification passed$(NC)"

## clean: Remove build artifacts
clean:
	@echo "$(GREEN)Cleaning build artifacts...$(NC)"
	rm -rf $(BINARY_DIR)
	rm -f $(COVERPROFILE)
	rm -f cmd/wasm_browser/web/tinySQL.wasm cmd/wasm_browser/web/wasm_exec.js
	rm -f cmd/wasm_node/tinySQL.wasm cmd/wasm_node/wasm_exec.js
	rm -f cmd/query_files_wasm/query_files.wasm cmd/query_files_wasm/query_files.wasm.gz cmd/query_files_wasm/wasm_exec.js
	find . -name "*.db.wal" -type f -delete
	find . -name "*.dat.wal" -type f -delete
	@echo "$(GREEN)✓ Clean complete$(NC)"

## run-repl: Run the REPL
run-repl: build-repl
	@echo "$(GREEN)Starting REPL...$(NC)"
	./$(BINARY_DIR)/repl

## run-server: Run the server
run-server: build-server
	@echo "$(GREEN)Starting server...$(NC)"
	./$(BINARY_DIR)/server

## run-demo: Run the demo
run-demo: build-demo
	@echo "$(GREEN)Starting demo...$(NC)"
	./$(BINARY_DIR)/demo

## deps: Download dependencies
deps:
	@echo "$(GREEN)Downloading dependencies...$(NC)"
	$(GO) mod download

## deps-all: Download dependencies for every tracked Go module
deps-all:
	@echo "$(GREEN)Downloading dependencies for tracked Go modules...$(NC)"
	@for mod in $(GO_MOD_FILES); do \
		dir="$$(dirname "$$mod")"; \
		echo "$(GREEN)→ $$dir$(NC)"; \
		(cd "$$dir" && $(GO) mod download); \
	done

## update-deps: Update dependencies in every tracked Go module
update-deps:
	@echo "$(GREEN)Updating dependencies in tracked Go modules...$(NC)"
	@for mod in $(GO_MOD_FILES); do \
		dir="$$(dirname "$$mod")"; \
		echo "$(GREEN)→ $$dir$(NC)"; \
		(cd "$$dir" && $(GO) get -t -u ./... && $(GO) mod tidy); \
	done

## docker-build: Build Docker image
docker-build:
	@echo "$(GREEN)Building Docker image...$(NC)"
	docker build -t tinysql:$(VERSION) -t tinysql:latest .

## info: Show build information
info:
	@echo "$(YELLOW)Build Information:$(NC)"
	@echo "  Version: $(VERSION)"
	@echo "  Go version: $(shell $(GO) version)"
	@echo "  Binary directory: $(BINARY_DIR)"
	@echo "  Command directory: $(CMD_DIR)"

.PHONY: build-c test-c test-python test-rust test-bindings

UNAME_S := $(shell uname -s)
ifeq ($(UNAME_S),Darwin)
TINYSQL_SHARED_LIB := libtinysql.dylib
TINYSQL_C_LIBS := -framework CoreFoundation -framework Security -lresolv
else
TINYSQL_SHARED_LIB := libtinysql.so
TINYSQL_C_LIBS := -lpthread -lm -ldl
endif
C_ABI_BUILD := bindings/c/build

## build-c: Build the C ABI (static archive, shared library, header) into bindings/c/build
build-c:
	mkdir -p $(C_ABI_BUILD)
	$(GO) build -trimpath -buildmode=c-archive -o $(C_ABI_BUILD)/libtinysql.a ./bindings/c
	$(GO) build -trimpath -buildmode=c-shared -o $(C_ABI_BUILD)/$(TINYSQL_SHARED_LIB) ./bindings/c
	rm -f $(C_ABI_BUILD)/libtinysql.h
	cp bindings/c/include/tinysql.h $(C_ABI_BUILD)/tinysql.h
	@echo "$(GREEN)C ABI ready in $(C_ABI_BUILD)$(NC)"

## test-c: Test the C ABI with the race detector and run the C smoke program
test-c:
	$(GO) test -race ./bindings/c
	mkdir -p $(C_ABI_BUILD)
	$(GO) build -buildmode=c-archive -o $(C_ABI_BUILD)/libtinysql.a ./bindings/c
	$(CC) -Wall -Wextra -Werror -I bindings/c/include bindings/c/example/abi_smoke.c \
		$(C_ABI_BUILD)/libtinysql.a $(TINYSQL_C_LIBS) -o $(C_ABI_BUILD)/abi_smoke
	./$(C_ABI_BUILD)/abi_smoke

## test-python: Build the shared library and run the Python package tests
test-python:
	$(MAKE) -C bindings/python test

## test-rust: Run the Rust crate tests (the build script compiles the C ABI with Go)
test-rust:
	cargo test --manifest-path bindings/rust/Cargo.toml

## test-bindings: Run the C, Python and Rust binding tests
test-bindings: test-c test-python test-rust

.PHONY: build-apple build-apple-macos test-swift
## build-apple: Build Swift XCFramework for macOS, iOS and iOS Simulator (requires Xcode)
build-apple:
	./bindings/apple/build-xcframework.sh

## build-apple-macos: Build a macOS-only Swift XCFramework for local development
build-apple-macos:
	./bindings/apple/build-xcframework.sh macos

## test-swift: Build the macOS bridge and run native Swift integration tests
test-swift: build-apple-macos
	$(GO) test -race ./bindings/c
	swift test --package-path bindings/swift
