.PHONY: all frontend backend backend-epics backend-debug release release-epics test bench race lint clean run

# --------------------------------------------------------------------------- #
# Sources — adding any file here triggers a frontend or backend rebuild        #
# --------------------------------------------------------------------------- #

FRONTEND_SRCS := $(shell find web/src -name '*.tsx' -o -name '*.ts' -o -name '*.css') \
                 $(wildcard web/vendor/*.mjs web/vendor/*.js web/vendor/*.css) \
                 tools/buildfrontend/main.go

GO_SRCS := $(shell find cmd internal -name '*.go') web/embed.go go.mod go.sum

# --------------------------------------------------------------------------- #
# Outputs                                                                      #
# --------------------------------------------------------------------------- #

FRONTEND_OUT := web/dist/main.js
BINARY       := dist/uopi

# --------------------------------------------------------------------------- #
# Top-level targets                                                            #
# --------------------------------------------------------------------------- #

all: $(BINARY)

frontend: $(FRONTEND_OUT)

backend: $(BINARY)

# --------------------------------------------------------------------------- #
# Build rules                                                                  #
# --------------------------------------------------------------------------- #

# Bundle TypeScript/TSX → web/dist/ using esbuild (pure Go, no npm)
$(FRONTEND_OUT): $(FRONTEND_SRCS)
	@mkdir -p web/dist
	go run ./tools/buildfrontend/main.go

# Compile the self-contained binary (embeds web/dist at build time)
$(BINARY): $(GO_SRCS) $(FRONTEND_OUT)
	@mkdir -p dist
	go build -ldflags="-s -w" -o $(BINARY) ./cmd/uopi

# Build with EPICS Channel Access support.
#
# Auto-detection: if EPICS_BASE is set in the environment, CGO flags are
# derived automatically.  Override any variable on the command line:
#
#   make backend-epics EPICS_BASE=/path/to/epics/base EPICS_ARCH=linux-x86_64
#
# If EPICS_BASE is not set, set it to the directory that contains
# include/cadef.h and lib/<arch>/libca.so.

EPICS_BASE ?= $(EPICS_BASE)

# Detect host arch from EPICS startup script if not provided.
EPICS_ARCH ?= $(shell \
  if [ -x "$(EPICS_BASE)/startup/EpicsHostArch" ]; then \
    "$(EPICS_BASE)/startup/EpicsHostArch"; \
  elif [ -x "$(EPICS_BASE)/lib" ]; then \
    ls "$(EPICS_BASE)/lib" | grep linux | head -1; \
  else \
    echo "linux-x86_64"; \
  fi)

EPICS_CGO_CFLAGS  ?= -I$(EPICS_BASE)/include -I$(EPICS_BASE)/include/os/Linux -I$(EPICS_BASE)/include/compiler/gcc
EPICS_CGO_LDFLAGS ?= -L$(EPICS_BASE)/lib/$(EPICS_ARCH) -lca -lCom -Wl,-rpath,$(EPICS_BASE)/lib/$(EPICS_ARCH)

backend-epics: $(FRONTEND_OUT)
	@if [ -z "$(EPICS_BASE)" ]; then \
	  echo "ERROR: EPICS_BASE is not set."; \
	  echo "  export EPICS_BASE=/path/to/epics/base"; \
	  echo "  then re-run: make backend-epics"; \
	  exit 1; \
	fi
	@echo "Building with EPICS support"
	@echo "  EPICS_BASE = $(EPICS_BASE)"
	@echo "  EPICS_ARCH = $(EPICS_ARCH)"
	@mkdir -p dist
	CGO_ENABLED=1 \
	  CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \
	  CGO_LDFLAGS="$(EPICS_CGO_LDFLAGS)" \
	  go build -tags epics -ldflags="-s -w" -o $(BINARY) ./cmd/uopi
	@echo "Built: $(BINARY)  (EPICS CA enabled)"

# Unstripped binary for debugging
backend-debug: $(FRONTEND_OUT)
	@mkdir -p dist
	go build -o $(BINARY) ./cmd/uopi

# --------------------------------------------------------------------------- #
# Portable release targets                                                     #
# --------------------------------------------------------------------------- #
#
# Two flavours depending on whether EPICS is needed:
#
#   make release            — pure-Go, CGO_ENABLED=0, 100 % static binary.
#                             No EPICS; works anywhere without extra libs.
#
#   make release-epics      — CGO build with EPICS CA.
#                             Tries to link libca/libCom statically (if .a
#                             archives are present in $(EPICS_LIBDIR)).
#                             Falls back to bundled shared libs + $ORIGIN
#                             rpath when only .so files are available.
#                             The output directory (dist/release/) contains
#                             everything needed to run on the target machine.
#
# Cross-compilation (no-EPICS only): override RELEASE_OS / RELEASE_ARCH.
#   make release RELEASE_OS=linux RELEASE_ARCH=arm64

RELEASE_OS   ?= linux
RELEASE_ARCH ?= amd64
RELEASE_DIR  := dist/release
EPICS_LIBDIR := $(EPICS_BASE)/lib/$(EPICS_ARCH)

# Fully static, no CGO — the most portable build possible.
release: $(FRONTEND_OUT)
	@mkdir -p $(RELEASE_DIR)
	CGO_ENABLED=0 GOOS=$(RELEASE_OS) GOARCH=$(RELEASE_ARCH) \
	  go build -ldflags="-s -w" -o $(RELEASE_DIR)/uopi ./cmd/uopi
	@echo "Built: $(RELEASE_DIR)/uopi  (fully static, no EPICS)"

# EPICS release — maximum static linking.
# Strategy A (preferred): if libca.a + libCom.a exist, embed them directly
#   → single self-contained binary, no extra files needed.
# Strategy B (fallback): copy the shared libs next to the binary and set an
#   $ORIGIN rpath so the OS finds them regardless of installation prefix.
release-epics: $(FRONTEND_OUT)
	@if [ -z "$(EPICS_BASE)" ]; then \
	  echo "ERROR: EPICS_BASE is not set."; \
	  echo "  export EPICS_BASE=/path/to/epics/base"; \
	  exit 1; \
	fi
	@mkdir -p $(RELEASE_DIR)
	@echo "Building portable EPICS release"
	@echo "  EPICS_BASE = $(EPICS_BASE)"
	@echo "  EPICS_ARCH = $(EPICS_ARCH)"
	@if [ -f "$(EPICS_LIBDIR)/libca.a" ] && [ -f "$(EPICS_LIBDIR)/libCom.a" ]; then \
	  echo "  Strategy A: static .a archives found — embedding libca + libCom"; \
	  CGO_ENABLED=1 GOOS=linux GOARCH=$(RELEASE_ARCH) \
	    CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \
	    CGO_LDFLAGS="-Wl,-Bstatic -L$(EPICS_LIBDIR) -lca -lCom -Wl,-Bdynamic -lm -lpthread -lrt -ldl" \
	    go build -tags epics \
	      -ldflags="-s -w -linkmode external" \
	      -o $(RELEASE_DIR)/uopi ./cmd/uopi; \
	  echo "Built: $(RELEASE_DIR)/uopi  (EPICS statically embedded)"; \
	else \
	  echo "  Strategy B: no .a archives — bundling shared libs with \$$ORIGIN rpath"; \
	  CGO_ENABLED=1 GOOS=linux GOARCH=$(RELEASE_ARCH) \
	    CGO_CFLAGS="$(EPICS_CGO_CFLAGS)" \
	    CGO_LDFLAGS="-L$(EPICS_LIBDIR) -lca -lCom -Wl,-rpath,\$$ORIGIN" \
	    go build -tags epics \
	      -ldflags="-s -w" \
	      -o $(RELEASE_DIR)/uopi ./cmd/uopi; \
	  cp $(EPICS_LIBDIR)/libca.so*  $(RELEASE_DIR)/; \
	  cp $(EPICS_LIBDIR)/libCom.so* $(RELEASE_DIR)/; \
	  echo "Built: $(RELEASE_DIR)/  (deploy the whole directory together)"; \
	  ls $(RELEASE_DIR)/; \
	fi

# --------------------------------------------------------------------------- #
# Dev / CI                                                                     #
# --------------------------------------------------------------------------- #

test:
	go test ./...

# Run tests with the race detector enabled.
race:
	go test -race ./...

# Run all benchmarks and print memory allocations.
bench:
	go test -bench=. -benchmem -benchtime=3s ./internal/...

lint:
	go vet ./...

run: $(BINARY)
	$(BINARY)

clean:
	rm -rf dist/ web/dist/
