#!/usr/bin/env bash
# beagle-build: compile one hosted Beagle source file to target output.
#
# Usage:
#   bin/beagle-build [--target TARGET]
#                    [--module-root LOGICAL=PHYSICAL]...
#                    SOURCE [output]   # `beagle langs` lists
#                                                        # every TARGET + ext
# If the output path is omitted, it is derived from the namespace declared
# in the source via (ns ...) and dropped under .beagle-out/ (gitignored, so
# throwaway emit never pollutes the tracked tree). Override the output root
# with BEAGLE_OUT (e.g. BEAGLE_OUT=runtime/src to build into the runtime project).
# The output extension comes from the target table (`beagle langs --view table`).
#
# --target retargets a source through the production in-process
# reader → parser → checker → emitter path without rewriting the source file.
# Core is not a source emitter: use `beagle build --materializer M`.

set -euo pipefail
source "$(dirname "$0")/_beagle-racket"
source "$(dirname "$0")/_beagle-source-id"
# Target validation and the extension map are DERIVED from
# beagle-lib/private/targets.rkt via the generated share/targets.sh — never
# hand-listed here. Regenerate with `bin/beagle doc-fill`.
source "$(cd "$(dirname "$0")/.." && pwd)/share/targets.sh"

target=""
module_root_args=()
positionals=()
while [[ $# -gt 0 ]]; do
    case "$1" in
        --target)
            [[ $# -ge 2 ]] || {
                echo "beagle-build: --target requires a target" >&2
                exit 2
            }
            [[ -z "$target" ]] || {
                echo "beagle-build: --target may be specified only once" >&2
                exit 2
            }
            target="$2"
            shift 2
            ;;
        --target=*)
            [[ -z "$target" ]] || {
                echo "beagle-build: --target may be specified only once" >&2
                exit 2
            }
            target="${1#*=}"
            shift
            ;;
        --module-root)
            [[ $# -ge 2 ]] || {
                echo "beagle-build: --module-root requires LOGICAL_PREFIX=PHYSICAL_DIRECTORY" >&2
                exit 2
            }
            module_root_args+=(--module-root "$2")
            shift 2
            ;;
        --module-root=*)
            module_root_args+=("$1")
            shift
            ;;
        --)
            shift
            positionals+=("$@")
            break
            ;;
        -*)
            echo "beagle-build: unknown option: $1" >&2
            exit 2
            ;;
        *)
            positionals+=("$1")
            shift
            ;;
    esac
done

if [[ -n "$target" ]] && ! beagle_known_hosted_target "$target"; then
    echo "beagle-build: unsupported hosted --target '$target' (expected clj, js, or nix); Core uses --materializer ($BEAGLE_MATERIALIZER_IDS_LIST)" >&2
    exit 2
fi

if [[ ${#positionals[@]} -lt 1 || ${#positionals[@]} -gt 2 ]]; then
    echo "usage: beagle-build [--target TARGET] [--module-root LOGICAL=PHYSICAL]... SOURCE [OUTPUT]" >&2
    exit 2
fi

src="${positionals[0]}"
beagle_root="$(cd "$(dirname "$0")/.." && pwd)"

if [[ ! -f "$src" ]]; then
    echo "beagle-build: source file not found: $src" >&2
    exit 1
fi

source_profile=""
for _t in "${BEAGLE_TARGET_IDS[@]}"; do
    if [[ "$src" == *".${BEAGLE_TARGET_SRC_EXT[$_t]}" ]]; then
        source_profile="$_t"
        break
    fi
done

if [[ -z "$source_profile" ]]; then
    echo "beagle-build: unsupported source extension: $src (run: beagle langs --view extensions)" >&2
    exit 2
fi

if [[ -z "$target" && "$source_profile" == "core" ]]; then
    echo "beagle-build: .bgl is Beagle Core; use: beagle build --materializer M --out DIR $src ($BEAGLE_MATERIALIZER_IDS_LIST)" >&2
    exit 2
fi

ext="clj"
if [[ -n "$target" ]]; then
    ext="${BEAGLE_TARGET_OUT_EXT[$target]}"
else
    ext="${BEAGLE_TARGET_OUT_EXT[$source_profile]}"
fi

if [[ ${#positionals[@]} -eq 2 ]]; then
    out="${positionals[1]}"
else
    ns="$(grep -E '^\(ns' "$src" | head -1 | \
          sed -E 's/^\(ns[[:space:]]+([a-zA-Z0-9._-]+).*/\1/' | \
          tr -d '[:space:]')"
    [[ -z "$ns" ]] && ns="beagle.user"
    if [[ "$ext" == "clj" ]]; then
        rel_path="$(printf '%s\n' "$ns" | tr '.-' '/_')"
    else
        rel_path="${ns//./\/}"
    fi
    out="${BEAGLE_OUT:-$beagle_root/.beagle-out}/$rel_path.$ext"
fi

mkdir -p "$(dirname "$out")"
# Atomic write: emit to a temp file first, then mv on success. Without this,
# the `>` redirection truncates $out before racket runs — if racket fails
# mid-emit, the file is left empty/partial silently. The atomic rename
# guarantees $out either has the complete new contents or the old contents.
tmp="$(mktemp "${out}.XXXXXX")"
trap 'rm -f "$tmp"' EXIT
if [[ -n "$target" ]]; then
    src_abs="$(realpath "$src")"
    source_id="$(beagle_source_id "$src_abs")"
    source_id="${source_id%.*}.${BEAGLE_TARGET_SRC_EXT[$target]}"
    "$RACKET" "$beagle_root/beagle-lib/private/build-one-cli.rkt" \
        "${module_root_args[@]}" \
        --source "$src_abs" "$source_id" > "$tmp"
else
    src_abs="$(realpath "$src")"
    source_id="$(beagle_source_id "$src_abs")"
    "$RACKET" "$beagle_root/beagle-lib/private/build-one-cli.rkt" \
        "${module_root_args[@]}" \
        --source "$src_abs" "$source_id" > "$tmp"
fi
mv "$tmp" "$out"
trap - EXIT
echo "$src -> $out" >&2

# Optional Nix smoke test: confirm the emitted file parses as valid Nix.
# Set BEAGLE_NIX_EVAL_CHECK=1 to enable. Requires `nix` on PATH.
if [[ "$ext" == "nix" && "${BEAGLE_NIX_EVAL_CHECK:-0}" == "1" ]]; then
    if command -v nix-instantiate >/dev/null; then
        if ! nix-instantiate --parse "$out" >/dev/null 2>/tmp/beagle-nix-check.err; then
            echo "beagle-build: emitted $out failed nix-instantiate --parse:" >&2
            cat /tmp/beagle-nix-check.err >&2
            exit 1
        fi
    else
        echo "beagle-build: BEAGLE_NIX_EVAL_CHECK=1 set but nix-instantiate not on PATH" >&2
    fi
fi
