SRC_DIR = c_src
DUCKDB_DIR = $(SRC_DIR)/duckdb
DUCKDB_ARCHIVE = $(SRC_DIR)/duckdb.tar.gz
DUCKDB_MANIFEST = $(DUCKDB_DIR)/.sources
DUCKDB_ARCHIVE_CHECKSUM = $(DUCKDB_DIR)/.archive.checksum

CXXFLAGS = -O3 -std=c++11

# Statically link the DuckDB build-baseline extensions (see
# extension/extension_config.cmake upstream): core_functions and parquet.
CXXFLAGS += -DDUCKDB_EXTENSION_CORE_FUNCTIONS_LINKED=1
CXXFLAGS += -DDUCKDB_EXTENSION_PARQUET_LINKED=1

# Include roots emitted by DuckDB's scripts/package_build.py and recorded
# in c_src/duckdb/.include_dirs at generation time (see bin/regen_duckdb.sh).
DUCKDB_INCLUDE_DIRS = $(shell test -f $(DUCKDB_DIR)/.include_dirs && cat $(DUCKDB_DIR)/.include_dirs)
CXXFLAGS += $(foreach dir, $(DUCKDB_INCLUDE_DIRS), -I"$(DUCKDB_DIR)/$(dir)")
CXXFLAGS += -I"$(ERTS_INCLUDE_DIR)"
CXXFLAGS += -DNDEBUG=1

KERNEL_NAME := $(shell uname -s)

PRIV_DIR = $(MIX_APP_PATH)/priv
LIB_NAME = $(PRIV_DIR)/duckdb_nif.so

ifneq ($(CROSSCOMPILE),)
	CXXFLAGS += -fPIC -fvisibility=hidden
	LDFLAGS += -fPIC -shared
else
	ifeq ($(KERNEL_NAME), Linux)
		CXXFLAGS += -fPIC -fvisibility=hidden
		LDFLAGS += -fPIC -shared
	endif
	ifeq ($(KERNEL_NAME), Darwin)
		CXXFLAGS += -fPIC
		LDFLAGS += -dynamiclib -undefined dynamic_lookup
	endif
	ifeq ($(KERNEL_NAME), $(filter $(KERNEL_NAME),OpenBSD FreeBSD NetBSD))
		CXXFLAGS += -fPIC
		LDFLAGS += -fPIC -shared
	endif
endif

# Compile the file list emitted by DuckDB's scripts/package_build.py
# (unity builds + directly referenced sources), plus the NIF files.
# See c_src/duckdb/.sources for the generated list.
GENERATED_SRC = $(shell test -f $(DUCKDB_MANIFEST) && cat $(DUCKDB_MANIFEST))
NIF_SRC = $(SRC_DIR)/nif.cpp $(SRC_DIR)/config.cpp $(SRC_DIR)/term.cpp $(SRC_DIR)/term_to_value.cpp $(SRC_DIR)/value_to_term.cpp
SRC = $(addprefix $(DUCKDB_DIR)/, $(GENERATED_SRC)) $(NIF_SRC)

OBJ = $(patsubst %.cpp, %.o, $(patsubst %.cc, %.o, $(subst $(SRC_DIR), $(PRIV_DIR), $(SRC))))

.PRECIOUS: $(PRIV_DIR)/. $(PRIV_DIR)%/.

$(PRIV_DIR):
	mkdir -p $@

$(PRIV_DIR)/.:
	mkdir -p $@

$(PRIV_DIR)%/.:
	mkdir -p $@

.SECONDEXPANSION:

$(PRIV_DIR)/%.o: $(SRC_DIR)/%.cpp | $$(@D)/.
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(PRIV_DIR)/%.o: $(SRC_DIR)/%.cc | $$(@D)/.
	$(CXX) $(CXXFLAGS) -c $< -o $@

$(LIB_NAME): $(OBJ)
	$(CXX) $(LDFLAGS) $^ -o $@

all: prepare-duckdb
	$(MAKE) build

build: $(PRIV_DIR) $(SRC) $(LIB_NAME)

prepare-duckdb:
	@set -eu; \
	test -f "$(DUCKDB_ARCHIVE)" || { echo "ERROR: missing $(DUCKDB_ARCHIVE)"; exit 1; }; \
	archive_checksum="$$(cksum < "$(DUCKDB_ARCHIVE)")"; \
	installed_checksum="$$(cat "$(DUCKDB_ARCHIVE_CHECKSUM)" 2>/dev/null || true)"; \
	if [ ! -f "$(DUCKDB_MANIFEST)" ] || [ "$$archive_checksum" != "$$installed_checksum" ]; then \
		echo "Extracting vendored DuckDB sources"; \
		extract_dir="$$(mktemp -d "$(SRC_DIR)/duckdb.extract.XXXXXX")"; \
		trap 'rm -rf "$$extract_dir"' EXIT; \
		tar -xzf "$(DUCKDB_ARCHIVE)" -C "$$extract_dir"; \
		test -f "$$extract_dir/duckdb/.sources" || { echo "ERROR: invalid $(DUCKDB_ARCHIVE)"; exit 1; }; \
		rm -rf "$(DUCKDB_DIR)"; \
		mv "$$extract_dir/duckdb" "$(DUCKDB_DIR)"; \
		printf '%s\n' "$$archive_checksum" > "$(DUCKDB_ARCHIVE_CHECKSUM)"; \
		find "$(DUCKDB_DIR)" -type f -exec touch {} +; \
	fi

# Guard against a broken/partial vendor tree: every file in .sources must
# exist on disk (run this in CI to catch rsync/LFS accidents).
verify-sources: prepare-duckdb
	@missing=""; while IFS= read -r f; do \
	  [ -f "$(DUCKDB_DIR)/$$f" ] || missing="$$missing $$f"; \
	done < "$(DUCKDB_DIR)/.sources"; \
	if [ -n "$$missing" ]; then \
	  echo "ERROR: missing vendored sources:$$missing"; exit 1; \
	fi; \
	echo "OK: all vendored sources present"

clean:
	$(RM) -rf run $(OBJ)
	$(RM) -f $(LIB_NAME)

.PHONY: all build prepare-duckdb clean verify-sources
