# Justfile — canonical entry point for build / gen / test / bench / conformance.
#
# `just` is required (https://github.com/casey/just). On macOS:  brew install just
# On Linux:                                                       cargo install just
#
# Quick map of common targets:
#   just                          show available recipes
#   just build                    build both plugins
#   just gen                      regenerate examples/expected/{full,runtime}/*
#   just test                     run the luatest suite
#   just bench                    alloc + throughput per op
#   just conformance              run Google's conformance suite in Docker
#   just examples                 per-example runners (see examples/Justfile)
#
# Sub-recipes live in examples/Justfile (one target per runnable example).

set shell := ["bash", "-cu"]

# ---------------------------------------------------------------------------
# Paths and constants
# ---------------------------------------------------------------------------

plugin            := "protoc-gen-tarantool"
doc_plugin        := "protoc-gen-tarantool-doc"
gen_dir           := "examples/expected"
docs_dir          := "examples/docs"
proto_dir         := "examples/proto"
conformance_proto := "test/conformance/proto"
proto2_test_dir   := "test/proto"
luatest           := ".rocks/bin/luatest"
image             := "tarantool-protobuf-conformance:latest"

# Semicolon-joined LUA_PATH for the luatest suite. Trailing `;;` defers to the
# standard package.path for everything not explicitly listed.
lua_path := "./runtime/?/init.lua;./runtime/?.lua;./" + gen_dir + "/?.lua;./" + gen_dir + "/?/init.lua;./?.lua;./?/init.lua;./test/?.lua;;"

# LUA_CPATH for the optional C runtime (require('pb.c_runtime')). Trailing
# `;;` defers to the standard cpath. The C module is built into
# runtime/pb/c_runtime.{so,dylib} by `just build-c` when PB_ENABLE_C=1 is
# in play; absent without it. See docs/specs/c_accel_build_packaging.md.
lua_cpath := "./runtime/?.so;./runtime/?.dylib;./runtime/?/init.so;./runtime/?/init.dylib;;"

# ---------------------------------------------------------------------------
# Default
# ---------------------------------------------------------------------------

# Show available recipes.
default:
    @just --list

# Build everything from scratch and run the test suite.
all: build gen test

# ---------------------------------------------------------------------------
# Build
# ---------------------------------------------------------------------------

# Build the Lua codegen plugin (./protoc-gen-tarantool).
build:
    go build -o {{plugin}} ./cmd/protoc-gen-tarantool

# Build the Markdown doc plugin (./protoc-gen-tarantool-doc).
build-doc:
    go build -o {{doc_plugin}} ./cmd/protoc-gen-tarantool-doc

# Build the optional C-acceleration runtime into runtime/pb/c_runtime.{so,dylib}.
# Source lives under runtime/pb/c/ once bd-ra6 lands; until then this recipe
# prints a helpful error and exits 1. The runtime is opt-in via PB_ENABLE_C=1
# — see docs/specs/c_accel_build_packaging.md for the full contract.
build-c:
    @if [ ! -d runtime/pb/c ]; then \
        echo "build-c: runtime/pb/c/ does not exist yet."; \
        echo "  The C runtime arrives with bd-ra6 (generic C codec)."; \
        echo "  See docs/specs/c_accel_build_packaging.md."; \
        exit 1; \
    fi
    make -C runtime/pb/c

# Remove built C-runtime artifacts.
clean-c:
    rm -f runtime/pb/c_runtime.so runtime/pb/c_runtime.dylib
    @if [ -d runtime/pb/c ]; then make -C runtime/pb/c clean; fi

# ---------------------------------------------------------------------------
# Codegen
# ---------------------------------------------------------------------------

# Regenerate examples/expected/{full,runtime}/* + conformance protos.
gen: gen-full gen-runtime gen-conformance gen-proto2-tests

# Generate full-mode Lua (inline encode/decode bodies).
gen-full: build
    mkdir -p {{gen_dir}}
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=full,prefix=full \
        -I {{proto_dir}} -I options \
        {{proto_dir}}/*.proto

# Generate runtime-mode Lua (delegates to pb.encode / pb.decode).
gen-runtime: build
    mkdir -p {{gen_dir}}
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=runtime,prefix=runtime \
        -I {{proto_dir}} -I options \
        {{proto_dir}}/*.proto

# Generate the Google conformance protos (TestAllTypesProto3) in both modes.
gen-conformance: build
    mkdir -p {{gen_dir}}
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=full,prefix=full \
        -I {{conformance_proto}} -I options \
        {{conformance_proto}}/*.proto
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=runtime,prefix=runtime \
        -I {{conformance_proto}} -I options \
        {{conformance_proto}}/*.proto

# Generate the proto2 test fixtures (test/proto/*.proto) in both modes.
# Lives outside examples/ because proto2 is exercised through the luatest
# suite, not the per-example runners.
gen-proto2-tests: build
    mkdir -p {{gen_dir}}
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=full,prefix=full \
        -I {{proto2_test_dir}} -I options \
        {{proto2_test_dir}}/*.proto
    protoc \
        --plugin=./{{plugin}} \
        --tarantool_out={{gen_dir}} \
        --tarantool_opt=mode=runtime,prefix=runtime \
        -I {{proto2_test_dir}} -I options \
        {{proto2_test_dir}}/*.proto

# Regenerate Markdown reference docs (examples/docs/*.md) — committed output.
gen-docs: build-doc
    mkdir -p {{docs_dir}}
    protoc \
        --plugin=./{{doc_plugin}} \
        --tarantool-doc_out={{docs_dir}} \
        -I {{proto_dir}} -I options \
        {{proto_dir}}/*.proto

# Regenerate test/interop/fixtures/*.bin via mainline `protoc --encode`.
goldens:
    @for f in test/interop/fixtures/*.txtpb; do \
        type=$(awk '/^# type:/ {print $3; exit}' "$f"); \
        out="${f%.txtpb}.bin"; \
        echo "  protoc --encode=$type < $f > $out"; \
        protoc --encode="$type" -I {{proto_dir}} -I options {{proto_dir}}/hello.proto < "$f" > "$out" || exit $?; \
    done

# ---------------------------------------------------------------------------
# Test
# ---------------------------------------------------------------------------

# Run the luatest suite (639 tests, parametrized over both codegen modes).
test: gen
    LUA_PATH="{{lua_path}}" LUA_CPATH="{{lua_cpath}}" {{luatest}} -v test/

# Run a single luatest group or test. Example:
#   just test-one protobuf_test.lua::hello.full.test_packed_repeated_int32
test-one filter: gen
    LUA_PATH="{{lua_path}}" LUA_CPATH="{{lua_cpath}}" {{luatest}} -v test/{{filter}}

# ---------------------------------------------------------------------------
# Bench
# ---------------------------------------------------------------------------

# Microbench: alloc + throughput per op across 5 payload sizes, both modes.
bench: gen
    tarantool bench/bench.lua --print

# Overwrite bench/baseline.json with current alloc-per-op numbers.
bench-baseline: gen
    tarantool bench/bench.lua --baseline

# Fail with exit 1 if any alloc-per-op regressed >5% vs the baseline.
bench-compare: gen
    tarantool bench/bench.lua --compare

# Per-helper microbench for runtime/pb/wire.lua (every primitive).
bench-wire: gen
    tarantool bench/wire_bench.lua

# Shape-variety microbench (scalar-heavy, packed, nested, maps, oneof, WKT).
bench-shapes: gen
    tarantool bench/shapes_bench.lua

# Trace-stability gate: assert hot paths JIT-compile without fatal aborts.
jit-trace: gen
    tarantool bench/jit_trace.lua

# Regenerate Go-side pb.go + vtproto pb.go in bench/go/pb/. Not committed
# (*.pb.go is gitignored repo-wide); run before `bench-go` on a fresh
# checkout. Requires `protoc-gen-go` and `protoc-gen-go-vtproto` on PATH:
#   go install google.golang.org/protobuf/cmd/protoc-gen-go@latest
#   go install github.com/planetscale/vtprotobuf/cmd/protoc-gen-go-vtproto@latest
gen-go:
    mkdir -p bench/go/pb/hellopb bench/go/pb/proto2pb
    cd bench/go && PATH="$(go env GOPATH)/bin:$PATH" protoc \
        --go_out=. --go_opt=module=github.com/tarantool-protobuf/bench/go \
        --go-vtproto_out=. --go-vtproto_opt=module=github.com/tarantool-protobuf/bench/go \
        --go-vtproto_opt=features=marshal+unmarshal+size \
        -I proto proto/hello.proto proto/proto2_basic.proto

# Go-side comparison bench: same fixtures + sizes as bench.lua, run against
# google.golang.org/protobuf (apiv2) and planetscale/vtprotobuf. Serial by
# default — Go's testing.B doesn't parallelize unless RunParallel is called.
bench-go: gen-go
    cd bench/go && go test -bench=. -benchmem -run=^$ -benchtime=1s ./...

# ---------------------------------------------------------------------------
# Conformance (Google's protobuf conformance suite, in Docker)
# ---------------------------------------------------------------------------

# Build the conformance Docker image (idempotent).
conformance-build:
    docker build -t {{image}} -f docker/conformance.Dockerfile docker/

# Run the conformance suite with --enforce_recommended (strictest mode).
conformance: conformance-build gen
    docker run --rm -v "$(pwd):/work" -w /work {{image}}

# Quick pass without --enforce_recommended.
conformance-quick: conformance-build gen
    docker run --rm -v "$(pwd):/work" -w /work --entrypoint conformance_test_runner {{image}} \
        --failure_list test/conformance/known_failures.txt \
        --text_format_failure_list test/conformance/known_failures_text.txt \
        /usr/bin/tarantool cmd/conformance-runner.lua

# Dump failing_tests.txt under test/conformance for triage.
conformance-refresh-failures: conformance-build gen
    docker run --rm -v "$(pwd):/work" -w /work --entrypoint conformance_test_runner {{image}} \
        --enforce_recommended \
        --output_dir /work/test/conformance \
        /usr/bin/tarantool cmd/conformance-runner.lua || true
    @echo "Inspect test/conformance/*failing_tests.txt and update known_failures.txt as needed."

# Open an interactive shell in the conformance image.
conformance-shell: conformance-build
    docker run --rm -it -v "$(pwd):/work" -w /work --entrypoint bash {{image}}

# ---------------------------------------------------------------------------
# Examples — see examples/Justfile for per-example runners
# ---------------------------------------------------------------------------

# Run an example. `just examples` lists per-example recipes.
examples *ARGS:
    just -f examples/Justfile {{ARGS}}

# ---------------------------------------------------------------------------
# Clean
# ---------------------------------------------------------------------------

# Remove built plugin binaries and regenerated outputs.
clean: clean-c
    rm -f {{plugin}} {{doc_plugin}}
    rm -rf {{gen_dir}}

# Remove Tarantool instance state that leaked from running examples.
clean-state:
    rm -f *.snap *.xlog *.vylog *.run *.pid 512.lock
    rm -rf /tmp/tarantool-protobuf-*
