#!/usr/bin/env bash
# Link one public Beagle function as a native executable. The entry may take no
# source parameters or one `(Vec String)` containing process arguments after
# argv[0].
#
# Usage:
#   beagle native-exe --out FILE --entry NS/NAME [--cc CC] [--static]
#                     [--artifacts DIR]
#                     [--module-root LOGICAL_PREFIX=PHYSICAL_DIRECTORY]...
#                     SOURCE...
#
# The frozen native program report and C17 projection artifacts are copied to DIR (default:
# FILE.artifacts). FILE is published only after the complete C17 projection,
# entry ABI validation, strict compilation, and link all pass.

set -euo pipefail

BEAGLE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
BIN="$BEAGLE_DIR/bin"
CORE_BUILD="$BIN/beagle-build-core"
MODULE_SOURCE_ROOT_CLI="$BEAGLE_DIR/beagle-lib/private/module-source-root-cli.rkt"
source "$BIN/_beagle-racket"
source "$BIN/_beagle-source-id"

usage() {
    sed -n '2,11p' "${BASH_SOURCE[0]}" | sed 's/^# \{0,1\}//'
}

die() {
    echo "beagle native-exe: $*" >&2
    exit 2
}

out=""
entry=""
artifacts=""
cc="${CC:-cc}"
static_link=0
sources=()
module_root_args=()
module_root_specs=()

while [[ $# -gt 0 ]]; do
    case "$1" in
        --out)
            [[ $# -ge 2 ]] || die "--out needs an executable path"
            [[ -z "$out" ]] || die "--out may be specified only once"
            out="$2"
            shift 2
            ;;
        --out=*)
            [[ -z "$out" ]] || die "--out may be specified only once"
            out="${1#*=}"
            shift
            ;;
        --entry)
            [[ $# -ge 2 ]] || die "--entry needs a qualified NS/NAME"
            [[ -z "$entry" ]] || die "--entry may be specified only once"
            entry="$2"
            shift 2
            ;;
        --entry=*)
            [[ -z "$entry" ]] || die "--entry may be specified only once"
            entry="${1#*=}"
            shift
            ;;
        --artifacts)
            [[ $# -ge 2 ]] || die "--artifacts needs a directory"
            [[ -z "$artifacts" ]] || die "--artifacts may be specified only once"
            artifacts="$2"
            shift 2
            ;;
        --artifacts=*)
            [[ -z "$artifacts" ]] || die "--artifacts may be specified only once"
            artifacts="${1#*=}"
            shift
            ;;
        --cc)
            [[ $# -ge 2 ]] || die "--cc needs one compiler executable"
            cc="$2"
            shift 2
            ;;
        --cc=*)
            cc="${1#*=}"
            shift
            ;;
        --static)
            static_link=1
            shift
            ;;
        --module-root)
            [[ $# -ge 2 ]] ||
                die "--module-root needs LOGICAL_PREFIX=PHYSICAL_DIRECTORY"
            module_root_args+=(--module-root "$2")
            module_root_specs+=("$2")
            shift 2
            ;;
        --module-root=*)
            module_root_args+=(--module-root "${1#*=}")
            module_root_specs+=("${1#*=}")
            shift
            ;;
        --help|-h)
            usage
            exit 0
            ;;
        --)
            shift
            sources+=("$@")
            break
            ;;
        -*)
            die "unknown option: $1 (try --help)"
            ;;
        *)
            sources+=("$1")
            shift
            ;;
    esac
done

[[ -n "$out" ]] || die "--out FILE is required"
[[ -n "$entry" ]] || die "--entry NS/NAME is required"
[[ ${#sources[@]} -gt 0 ]] || die "provide at least one source path"
[[ "$entry" == */* ]] || die "--entry must be qualified as NS/NAME: $entry"
entry_namespace="${entry%/*}"
entry_name="${entry##*/}"
[[ -n "$entry_namespace" && -n "$entry_name" && "$entry_namespace" != */* ]] ||
    die "--entry must contain exactly one namespace separator: $entry"

out_parent="$(dirname -- "$out")"
out_name="$(basename -- "$out")"
[[ -n "$out_name" && "$out_name" != "." && "$out_name" != ".." ]] ||
    die "--out must name an executable file: $out"
mkdir -p "$out_parent"
out_parent="$(cd "$out_parent" && pwd)"
out="$out_parent/$out_name"
[[ ! -d "$out" ]] || die "--out names a directory: $out"

if [[ -z "$artifacts" ]]; then
    artifacts="$out.artifacts"
fi
mkdir -p "$artifacts"
artifacts="$(cd "$artifacts" && pwd)"

published_names=(
    source.facts
    report.txt
    module.native-program
    module.native-program.sha256
    module_0.h
    module_0.c
    module_0.ssa
    native_shim.h
    native_shim.c
    native_unicode15_data.h
    UNICODE-LICENSE.txt
    native_parallel.h
    native_parallel.c
    entry.c
    native-exe.log
    native-exe.report.txt
)
for name in "${published_names[@]}"; do
    [[ "$out" != "$artifacts/$name" ]] ||
        die "--out conflicts with the $name artifact: $out"
done

work="$(mktemp -d "$out_parent/.beagle-native-exe.XXXXXX")"
started=0
success=0
cleanup() {
    local rc=$?
    rm -rf "${work:?}"
    if [[ "$started" == "1" && "$success" != "1" ]]; then
        rm -f -- "$out"
        for name in "${published_names[@]}"; do
            rm -f -- "$artifacts/$name"
        done
    fi
    return "$rc"
}
trap cleanup EXIT

started=1
rm -f -- "$out"
for name in "${published_names[@]}"; do
    rm -f -- "$artifacts/$name"
done

[[ -x "$CORE_BUILD" ]] ||
    die "Core build command is unavailable: $CORE_BUILD"
[[ -x "$BIN/beagle-ast" ]] || die "AST command is unavailable: $BIN/beagle-ast"
[[ -f "$MODULE_SOURCE_ROOT_CLI" ]] ||
    die "module source root resolver is unavailable: $MODULE_SOURCE_ROOT_CLI"
command -v python3 >/dev/null 2>&1 || die "required command is unavailable: python3"
command -v bb >/dev/null 2>&1 || die "required command is unavailable: bb"
cc_path="$(command -v -- "$cc" 2>/dev/null || true)"
[[ -n "$cc_path" && -x "$cc_path" ]] || die "C17 compiler is unavailable: $cc"

# A completed native executable is an authenticated whole result.  Cold-
# authority builds may reuse it; mutable per-unit development facts must reach
# beagle-build-core instead of disappearing behind an older whole result.
dev_fact_reuse=0
case "${BEAGLE_DEV_FACT_REUSE:-0}" in
    0) ;;
    1)
        if [[ "${BEAGLE_FACT_REUSE_FORBIDDEN:-0}" == 1 ]]; then
            echo "beagle native-exe: dev-facts FORBIDDEN cold-authority" >&2
        else
            dev_fact_reuse=1
        fi
        ;;
    *) die "BEAGLE_DEV_FACT_REUSE must be 0 or 1" ;;
esac
result_cache_enabled=1
if [[ "$dev_fact_reuse" == 1 ]]; then
    result_cache_enabled=0
    echo "beagle native-exe: result cache BYPASS dev-fact-reuse" >&2
fi
result_cache_root="${BEAGLE_NATIVE_EXE_CACHE:-${XDG_CACHE_HOME:-$HOME/.cache}/beagle/native-executables}"
if [[ "$result_cache_enabled" == 1 && "$result_cache_root" != /* ]]; then
    die "BEAGLE_NATIVE_EXE_CACHE must be an absolute path"
fi

result_cache_entry_valid() {
    local entry_dir="$1"
    local expected_key="$2"
    [[ -d "$entry_dir" && -x "$entry_dir/executable" ]] || return 1
    [[ -f "$entry_dir/READY" && "$(cat "$entry_dir/READY")" == "$expected_key" ]] ||
        return 1
    [[ -f "$entry_dir/MANIFEST.sha256" ]] || return 1
    (cd "$entry_dir" && sha256sum -c MANIFEST.sha256 >/dev/null 2>&1)
}

publish_cached_result() {
    local entry_dir="$1"
    local cached_entry_line name cached_executable
    echo "beagle native-exe: result cache HIT $result_cache_key" >&2
    for name in "${published_names[@]}"; do
        [[ -f "$entry_dir/artifacts/$name" ]] || continue
        cp -- "$entry_dir/artifacts/$name" "$artifacts/$name"
    done
    cached_entry_line="$(sed -n '1p' "$entry_dir/artifacts/native-exe.report.txt")"
    [[ "$cached_entry_line" == "native-exe-entry PASS "* ]] ||
        die "cached native executable has malformed entry report"
    c17_line="native-exe-c17 PASS compiler=$cc_path output=$out"
    printf '%s\n%s\n' "$cached_entry_line" "$c17_line" \
        >"$artifacts/native-exe.report.txt"
    cached_executable="$work/native-executable.cache-hit"
    cp -- "$entry_dir/executable" "$cached_executable"
    chmod +x "$cached_executable"
    mv -f -- "$cached_executable" "$out"
    success=1
    cat "$artifacts/report.txt"
    printf '%s\n%s\n' "$cached_entry_line" "$c17_line"
}

store_cached_result() {
    local entry_dir="$1"
    local stage name
    mkdir -p "$result_cache_root"
    stage="$(mktemp -d "$result_cache_root/.publish.$result_cache_key.XXXXXX")"
    mkdir -p "$stage/artifacts"
    cp -- "$linked" "$stage/executable"
    chmod +x "$stage/executable"
    for name in "${published_names[@]}"; do
        [[ -f "$artifacts/$name" ]] || continue
        cp -- "$artifacts/$name" "$stage/artifacts/$name"
    done
    (
        cd "$stage"
        sha256sum executable artifacts/* >MANIFEST.sha256
    )
    printf '%s\n' "$result_cache_key" >"$stage/READY"
    (
        exec {cache_lock_fd}>"$result_cache_root/.publish.lock"
        flock "$cache_lock_fd"
        if result_cache_entry_valid "$entry_dir" "$result_cache_key"; then
            rm -rf "${stage:?}"
        else
            rm -rf "${entry_dir:?}"
            mv -- "$stage" "$entry_dir"
        fi
    )
}

normalized_sources=()
source_ids=()
for source in "${sources[@]}"; do
    [[ -f "$source" ]] || die "source file not found: $source"
    source="$(realpath "$source")"
    normalized_sources+=("$source")
    source_ids+=("$(beagle_source_id "$source" "${module_root_specs[@]}")")
done

closure_arguments=("${module_root_args[@]}")
for source_index in "${!normalized_sources[@]}"; do
    closure_arguments+=(
        --source
        "${normalized_sources[$source_index]}"
        "${source_ids[$source_index]}"
    )
done
closure_fields_file="$work/module-source-closure.nul"
closure_log="$work/module-source-closure.log"
set +e
"$RACKET" "$MODULE_SOURCE_ROOT_CLI" "${closure_arguments[@]}" \
    >"$closure_fields_file" 2>"$closure_log"
closure_rc=$?
set -e
if [[ $closure_rc -ne 0 ]]; then
    sed -n '1,200p' "$closure_log" >&2
    die "could not resolve module source closure"
fi
closure_fields=()
mapfile -d '' -t closure_fields <"$closure_fields_file"
[[ ${#closure_fields[@]} -gt 0 && $(( ${#closure_fields[@]} % 2 )) -eq 0 ]] ||
    die "module source root resolver returned malformed closure fields"
closure_sources=()
closure_source_ids=()
for ((field_index = 0; field_index < ${#closure_fields[@]}; field_index += 2)); do
    closure_sources+=("${closure_fields[$field_index]}")
    closure_source_ids+=("${closure_fields[$((field_index + 1))]}")
done

bundle_ast="$work/source_bundle.ast.json"
bundle_log="$work/source_bundle.ast.log"
set +e
BEAGLE_AST_CLOSURE_FIELDS="$closure_fields_file" \
    "$BIN/beagle-ast" --bundle "${module_root_args[@]}" -- \
    "${normalized_sources[@]}" \
    >"$bundle_ast" 2>"$bundle_log"
bundle_rc=$?
set -e
if [[ $bundle_rc -ne 0 ]]; then
    sed -n '1,200p' "$bundle_log" >&2
    die "could not inspect exports in coherent source bundle"
fi

# The bundle is namespace-ordered and contains each checked program beside its
# public-interface digest.  A completed-result hit here skips source-fact
# projection, Native Core lowering, materialization, and the C17 link, while a
# changed implementation or interface necessarily changes the key.
result_cache_key=""
result_cache_entry=""
if [[ "$result_cache_enabled" == 1 ]]; then
    result_key_args=(
        --bundle "$bundle_ast"
        --entry "$entry"
        --static "$static_link"
        --platform "$(uname -srm)"
        --semantic "$BEAGLE_DIR/native-core/src/native/core.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/stages.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/simd.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/obligations.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/slice.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/unit_reuse.bclj"
        --semantic "$BEAGLE_DIR/native-core/src/native/unit_compile.bclj"
        --lowering "$BEAGLE_DIR/native-core/src/native/lower.bclj"
        --materializer "$BEAGLE_DIR/native-core/src/native/c11.bclj"
        --materializer "$BEAGLE_DIR/native-core/src/native/fold_c17.bclj"
        --materializer "$BEAGLE_DIR/native-core/src/native/body_c17.bclj"
        --materializer "$BEAGLE_DIR/native-core/src/native/body_slice.bclj"
        --materializer "$BEAGLE_DIR/native-core/shim/native_shim.c"
        --materializer "$BEAGLE_DIR/native-core/shim/native_shim.h"
        --materializer "$BEAGLE_DIR/native-core/shim/native_parallel.c"
        --materializer "$BEAGLE_DIR/native-core/shim/native_parallel.h"
        --materializer "$BEAGLE_DIR/native-core/shim/native_unicode15_data.h"
        --materializer "$BEAGLE_DIR/native-core/shim/UNICODE-LICENSE.txt"
        --materializer "$BEAGLE_DIR/share/targets.sh"
        --tool "$CORE_BUILD"
        --tool "$BIN/beagle-core-compiler-projection"
        --tool "$BIN/beagle-native-exe"
        --tool "$BEAGLE_DIR/native-core/bin/source-facts.clj"
        --tool "$BEAGLE_DIR/native-core/bin/semantic_read_store.clj"
        --tool "$BEAGLE_DIR/native-core/bin/emit-workers"
        --tool "$BEAGLE_DIR/native-core/validation/build-finalize.clj"
        --tool "$cc_path"
        --tool "$(command -v python3)"
        --tool "$(command -v bb)"
        --tool "$RACKET"
    )
    if [[ -n "${BEAGLE_NATIVE_COMPILER_BIN:-}" ]]; then
        result_key_args+=(--native-compiler "$(realpath "$BEAGLE_NATIVE_COMPILER_BIN")")
    fi
    result_cache_key="$("$BIN/_beagle-native-exe-result-key" "${result_key_args[@]}")"
    result_cache_entry="$result_cache_root/$result_cache_key"
    if result_cache_entry_valid "$result_cache_entry" "$result_cache_key"; then
        publish_cached_result "$result_cache_entry"
        exit 0
    fi
    echo "beagle native-exe: result cache MISS $result_cache_key" >&2
fi

python3 - "$bundle_ast" "$work" "${closure_source_ids[@]}" <<'PY'
import json
import pathlib
import sys

bundle_path = pathlib.Path(sys.argv[1])
work = pathlib.Path(sys.argv[2])
source_ids = sys.argv[3:]
bundle = json.loads(bundle_path.read_text())
module_list = bundle["modules"]
modules = {module["source"]: module["program"] for module in module_list}
if len(modules) != len(module_list):
    raise SystemExit("beagle native-exe: bundle AST contains duplicate source ids")
if set(modules) != set(source_ids):
    missing = sorted(set(source_ids) - set(modules))
    extra = sorted(set(modules) - set(source_ids))
    raise SystemExit(
        f"beagle native-exe: bundle AST source mismatch: missing={missing} extra={extra}"
    )
for index, source_id in enumerate(source_ids):
    destination = work / f"source_{index}.ast.json"
    destination.write_text(
        json.dumps(modules[source_id], sort_keys=True, separators=(",", ":")) + "\n"
    )
PY

ast_args=()
for source_index in "${!closure_sources[@]}"; do
    ast="$work/source_${source_index}.ast.json"
    ast_args+=("${closure_source_ids[$source_index]}" "$ast")
done

set +e
entry_metadata="$(python3 - "$entry" "${ast_args[@]}" <<'PY'
import json
import sys


def fail(message):
    print(f"beagle native-exe: {message}", file=sys.stderr)
    raise SystemExit(2)


target = sys.argv[1]
namespace, name = target.split("/", 1)
arguments = sys.argv[2:]
if len(arguments) % 2:
    fail("internal AST argument mismatch")

matches = []
for source, ast_path in zip(arguments[0::2], arguments[1::2]):
    try:
        with open(ast_path, encoding="utf-8") as stream:
            ast = json.load(stream)
    except (OSError, ValueError) as error:
        fail(f"could not read AST for {source}: {error}")
    if ast.get("namespace") != namespace:
        continue
    for form in ast.get("forms", []):
        if form.get("node") == "defn" and form.get("name") == name:
            matches.append((source, form))

if not matches:
    fail(f"exported entry not found: {target}")
if len(matches) != 1:
    files = ", ".join(source for source, _ in matches)
    fail(f"entry is ambiguous across source files: {target} ({files})")

source, form = matches[0]
if form.get("private") is True:
    fail(f"entry is private, not exported: {target} ({source})")
if form.get("private") is not False:
    fail(f"entry export visibility is unavailable: {target} ({source})")
parameters = form.get("params")
if not isinstance(parameters, list):
    fail(f"entry parameters are unavailable: {target}")
if form.get("rest") is not False:
    fail(f"entry must not have a rest parameter: {target}")
entry_mode = "zero"
if parameters:
    if len(parameters) != 1:
        fail(
            f"entry must have zero parameters or one (Vec String) parameter: "
            f"{target} has {len(parameters)}"
        )
    parameter = parameters[0]
    annotation = parameter.get("ann") if isinstance(parameter, dict) else None
    arguments = annotation.get("args") if isinstance(annotation, dict) else None
    string_argument = arguments[0] if isinstance(arguments, list) and len(arguments) == 1 else None
    if not (
        isinstance(parameter, dict)
        and parameter.get("type") == "param"
        and parameter.get("constraint") is None
        and isinstance(annotation, dict)
        and annotation.get("kind") == "app"
        and annotation.get("name") == "Vec"
        and isinstance(string_argument, dict)
        and string_argument.get("kind") == "prim"
        and string_argument.get("name") == "String"
    ):
        fail(
            f"entry parameter must be exactly (Vec String): {target}"
        )
    entry_mode = "argv"
return_type = form.get("ret")
if not isinstance(return_type, dict):
    fail(f"entry needs an explicit -> Int or -> Nil return: {target}")
if return_type.get("kind") != "prim" or return_type.get("name") not in {"Int", "Nil"}:
    rendered = return_type.get("name", return_type.get("kind", "unknown"))
    fail(f"entry return must be Int or Nil: {target} returns {rendered}")

print(return_type["name"])
print(entry_mode)
PY
)"
metadata_rc=$?
set -e
[[ $metadata_rc -eq 0 ]] || exit "$metadata_rc"
mapfile -t entry_metadata_lines <<<"$entry_metadata"
[[ ${#entry_metadata_lines[@]} -eq 2 ]] ||
    die "entry metadata is incomplete: $entry"
entry_return="${entry_metadata_lines[0]}"
entry_mode="${entry_metadata_lines[1]}"

module_dir="$work/module"
mkdir -p "$module_dir"
set +e
"$CORE_BUILD" --materializer c17 --out "$module_dir" --entry "$entry" \
    "${module_root_args[@]}" -- \
    "${normalized_sources[@]}" >"$work/core-build.log" 2>&1
module_rc=$?
set -e
if [[ $module_rc -ne 0 ]]; then
    sed -n '1,240p' "$work/core-build.log" >&2
    exit "$module_rc"
fi

for name in source.facts report.txt module.native-program module.native-program.sha256 \
    module_0.h module_0.c native_shim.h native_shim.c \
    native_unicode15_data.h UNICODE-LICENSE.txt; do
    [[ -f "$module_dir/$name" ]] || die "Core build omitted required artifact: $name"
done

report="$module_dir/report.txt"
for required in \
    "stage typed-to-native COMPLETE" \
    "native-lowering-result NativeLoweringCompleteV0" \
    "materialize-c17 OK module_0.h module_0.c" \
    "result PASS"; do
    grep -Fqx "$required" "$report" ||
        die "Core build report omitted required verdict: $required"
done

obligation_count="$(grep -c '^obligation-projection PASS ' "$report" || true)"
[[ "$obligation_count" == "10" ]] ||
    die "Core build report has $obligation_count/10 passing obligations"

mapfile -t program_counts < <(awk '$1 == "program-functions" && NF == 2 { print $2 }' "$report")
[[ ${#program_counts[@]} -eq 1 && "${program_counts[0]}" =~ ^[0-9]+$ ]] ||
    die "Core build report has no unique program-functions count"
lowered_count="$(grep -c '^lowered fn_[0-9][0-9]* ' "$report" || true)"
[[ "$lowered_count" == "${program_counts[0]}" ]] ||
    die "C17 projection is incomplete: lowered $lowered_count/${program_counts[0]} functions"

set +e
function_index="$(python3 - "$entry_namespace" "$entry_name" \
    "$module_dir/source.facts" "$module_dir/module.native-program" \
    "${program_counts[0]}" <<'PY'
import pathlib
import sys


def fail(message):
    print(f"beagle native-exe: {message}", file=sys.stderr)
    raise SystemExit(2)


namespace, name, facts_path, program_path, expected_count_text = sys.argv[1:]
subjects = {}
for line in pathlib.Path(facts_path).read_text(encoding="utf-8").splitlines():
    fields = line.split("\t")
    if len(fields) != 4:
        continue
    subject, predicate, kind, value = fields
    if kind == "t" and predicate in {
        "form-kind", "semantic-unit-module", "semantic-unit-name"
    }:
        subjects.setdefault(subject, {})[predicate] = value

matches = [
    subject
    for subject, fields in subjects.items()
    if fields.get("form-kind") == "defn"
    and fields.get("semantic-unit-module") == namespace
    and fields.get("semantic-unit-name") == name
]
if len(matches) != 1:
    fail(f"entry source identity is not unique: {namespace}/{name}")


def canonical_id(domain, parts):
    encoded = "".join(f"{len(part)}:{part}" for part in parts)
    return f"native-id-v0:{len(domain)}:{domain}:{encoded}"


source_id = canonical_id("native-slice-v0/node", matches)
target_id = canonical_id("native-lower-v0/function", [source_id, name])
encoding = pathlib.Path(program_path).read_text(encoding="utf-8")
marker = "18:native-function-v0:"
function_ids = []
position = 0
while True:
    marker_position = encoding.find(marker, position)
    if marker_position < 0:
        break
    length_start = marker_position + len(marker)
    length_end = encoding.find(":", length_start)
    if length_end < 0 or not encoding[length_start:length_end].isdigit():
        fail("frozen Native program has a malformed function identity")
    value_start = length_end + 1
    value_end = value_start + int(encoding[length_start:length_end])
    function_ids.append(encoding[value_start:value_end])
    position = value_end

expected_count = int(expected_count_text)
if len(function_ids) != expected_count or len(set(function_ids)) != expected_count:
    fail(
        "frozen Native program function identities do not match its report: "
        f"{len(function_ids)}/{expected_count}"
    )
ordered_ids = sorted(function_ids)
if target_id not in ordered_ids:
    fail(f"exported entry was not lowered: {namespace}/{name}")
print(ordered_ids.index(target_id))
PY
)"
function_index_rc=$?
set -e
[[ $function_index_rc -eq 0 ]] || exit "$function_index_rc"
[[ "$function_index" =~ ^[0-9]+$ ]] ||
    die "lowered entry index is malformed: $entry"
entry_row_count="$(awk -v symbol="fn_$function_index" -v name="$entry_name" \
    '$1 == "lowered" && $2 == symbol && $3 == name { count++ } END { print count + 0 }' \
    "$report")"
[[ "$entry_row_count" == 1 ]] ||
    die "lowered entry identity disagrees with the Core report: $entry"
symbol="native_m0_fn_$function_index"

mapfile -t prototypes < <(grep -E "^[A-Za-z_][A-Za-z0-9_]* ${symbol}\\(.*\\);$" \
    "$module_dir/module_0.h" || true)
[[ ${#prototypes[@]} -eq 1 ]] ||
    die "generated C17 header has no unique prototype for $entry ($symbol)"
prototype="${prototypes[0]}"
if [[ "$prototype" =~ ^([A-Za-z_][A-Za-z0-9_]*)[[:space:]]+${symbol}\((.*)\)\;$ ]]; then
    c_return="${BASH_REMATCH[1]}"
    c_parameters="${BASH_REMATCH[2]}"
else
    die "generated C17 entry prototype is malformed: $prototype"
fi

needs_arena=0
needs_capability=0
entry_parameter_type=""
implicit_parameters="$c_parameters"
if [[ "$entry_mode" == "argv" ]]; then
    if [[ "$c_parameters" =~ ^(.*)(native_m0_type_[0-9]+)[[:space:]]+native_v_0$ ]]; then
        implicit_parameters="${BASH_REMATCH[1]}"
        entry_parameter_type="${BASH_REMATCH[2]}"
        implicit_parameters="${implicit_parameters%, }"
        [[ -n "$implicit_parameters" ]] || implicit_parameters="void"
    else
        die "argv entry has unsupported generated parameter ABI: $entry ($c_parameters)"
    fi
fi
case "$implicit_parameters" in
    void)
        abi="pure"
        call_arguments=""
        ;;
    "native_arena *arena")
        abi="arena"
        needs_arena=1
        call_arguments="&arena"
        ;;
    "const native_capability *capability")
        abi="capability"
        needs_capability=1
        call_arguments="&capability"
        ;;
    "native_arena *arena, const native_capability *capability")
        abi="arena+capability"
        needs_arena=1
        needs_capability=1
        call_arguments="&arena, &capability"
        ;;
    *)
        die "entry has unsupported generated ABI parameters: $entry ($c_parameters)"
        ;;
esac
if [[ "$entry_mode" == "argv" ]]; then
    needs_arena=1
    if [[ -n "$call_arguments" ]]; then
        call_arguments="$call_arguments, arguments"
    else
        call_arguments="arguments"
    fi
fi

[[ "$c_return" =~ ^native_m0_type_[0-9]+$ ]] ||
    die "entry has unsupported generated ABI return: $entry ($c_return)"
if [[ "$entry_return" == "Int" ]]; then
    grep -Fqx "typedef int64_t $c_return;" "$module_dir/module_0.h" ||
        die "entry Int return is not represented as int64_t: $entry ($c_return)"
fi

entry_source="$work/entry.c"
{
    cat <<EOF
/* Generated by beagle native-exe; do not edit. */
#include "module_0.h"

#include <limits.h>
#include <stdint.h>
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
EOF
    if [[ "$needs_arena" == "1" ]]; then
        cat <<'EOF'

#define BEAGLE_NATIVE_ARENA_BYTES ((size_t)(64U * 1024U * 1024U))
EOF
    fi
    if [[ "$entry_mode" == "argv" ]]; then
        cat <<'EOF'

int main(int argc, char **argv) {
EOF
    else
        cat <<'EOF'

int main(void) {
EOF
    fi
    if [[ "$needs_arena" == "1" ]]; then
        cat <<'EOF'
  uint8_t *arena_storage = (uint8_t *)malloc(BEAGLE_NATIVE_ARENA_BYTES);
  native_arena arena;
  if (arena_storage == NULL) {
    fputs("beagle native entry: arena allocation failed\n", stderr);
    return EXIT_FAILURE;
  }
  native_arena_init(&arena, arena_storage, BEAGLE_NATIVE_ARENA_BYTES);
EOF
    fi
    if [[ "$needs_capability" == "1" ]]; then
        cat <<'EOF'
  /* One process-local nonzero authority backs the root region capabilities
     projected into this executable. */
  const native_capability capability = { UINT64_C(1) };
EOF
    fi
    if [[ "$entry_mode" == "argv" ]]; then
        cat <<EOF
  $entry_parameter_type arguments = native_vec_new(
      &arena, (int64_t)(argc > 1 ? argc - 1 : 0),
      (int64_t)sizeof(uint64_t), _Alignof(uint64_t));
  for (int argument_index = 1; argument_index < argc; argument_index += 1) {
    size_t argument_length = strlen(argv[argument_index]);
    uint8_t *argument_bytes = NULL;
    uint64_t argument = native_text_alloc(
        &arena, (uint64_t)argument_length, &argument_bytes);
    if (argument_length > 0U) {
      memcpy(argument_bytes, argv[argument_index], argument_length);
    }
    arguments = native_vec_push(
        &arena, arguments, &argument, (int64_t)sizeof(uint64_t),
        _Alignof(uint64_t));
  }
EOF
    fi
    if [[ "$entry_return" == "Int" ]]; then
        cat <<EOF
  int64_t status = (int64_t)$symbol($call_arguments);
EOF
        if [[ "$needs_arena" == "1" ]]; then
            cat <<'EOF'
  native_arena_destroy(&arena);
  free(arena_storage);
EOF
        fi
        cat <<'EOF'
  if ((status < (int64_t)INT_MIN) || (status > (int64_t)INT_MAX)) {
    fputs("beagle native entry: Int result is outside the C process-status range\n",
          stderr);
    return EXIT_FAILURE;
  }
  return (int)status;
EOF
    else
        cat <<EOF
  (void)$symbol($call_arguments);
EOF
        if [[ "$needs_arena" == "1" ]]; then
            cat <<'EOF'
  native_arena_destroy(&arena);
  free(arena_storage);
EOF
        fi
        cat <<'EOF'
  return EXIT_SUCCESS;
EOF
    fi
    cat <<'EOF'
}
EOF
} >"$entry_source"

linked="$work/native-executable"
link_flags=()
parallel_sources=()
if grep -q '^parallel-plan ' "$report"; then
    for name in native_parallel.h native_parallel.c; do
        [[ -f "$module_dir/$name" ]] || die "parallel Core build omitted $name"
    done
    parallel_sources=("$module_dir/native_parallel.c")
    link_flags+=(-pthread -ffp-contract=off)
fi
if [[ $static_link -eq 1 ]]; then
    link_flags+=(-static)
fi
set +e
"$cc_path" -std=c17 -pedantic -Wall -Wextra -Werror \
    "${link_flags[@]}" \
    -I "$module_dir" \
    "$module_dir/module_0.c" \
    "$module_dir/native_shim.c" \
    "${parallel_sources[@]}" \
    "$entry_source" \
    -o "$linked" >"$work/native-exe.log" 2>&1
link_rc=$?
set -e
if [[ $link_rc -ne 0 ]]; then
    sed -n '1,240p' "$work/native-exe.log" >&2
    die "C17 compile/link failed with $cc_path"
fi
[[ -x "$linked" ]] || die "C17 compiler reported success without an executable"

for name in source.facts report.txt module.native-program module.native-program.sha256 \
    module_0.h module_0.c native_shim.h native_shim.c \
    native_unicode15_data.h UNICODE-LICENSE.txt; do
    cp -- "$module_dir/$name" "$artifacts/$name"
done
if [[ ${#parallel_sources[@]} -gt 0 ]]; then
    cp -- "$module_dir/native_parallel.h" "$artifacts/native_parallel.h"
    cp -- "$module_dir/native_parallel.c" "$artifacts/native_parallel.c"
fi
cp -- "$entry_source" "$artifacts/entry.c"
cp -- "$work/native-exe.log" "$artifacts/native-exe.log"

entry_line="native-exe-entry PASS name=$entry symbol=$symbol return=$entry_return abi=$abi"
if [[ "$entry_mode" == "argv" ]]; then
    entry_line="$entry_line args=vec-string"
fi
c17_line="native-exe-c17 PASS compiler=$cc_path output=$out"
printf '%s\n%s\n' "$entry_line" "$c17_line" >"$artifacts/native-exe.report.txt"
if [[ "$result_cache_enabled" == 1 ]]; then
    store_cached_result "$result_cache_entry"
fi
mv -f -- "$linked" "$out"
success=1

cat "$report"
printf '%s\n%s\n' "$entry_line" "$c17_line"
