#!/usr/bin/env bash
set -euo pipefail

BEAGLE_DIR="$(cd "$(dirname "$0")/.." && pwd)"
source "$BEAGLE_DIR/bin/_beagle-racket"
source "$BEAGLE_DIR/share/targets.sh"

usage() {
    cat >&2 <<'EOF'
Usage: beagle fmt --check|--write PATH...

  --check  Report noncanonical Beagle source layout and exit nonzero on drift.
  --write  Rewrite noncanonical source layout atomically.

Directory operands discover tracked Beagle source recursively. Explicit files
are always considered, including fixtures excluded from directory discovery.
EOF
    exit "${1:-2}"
}

[[ $# -ge 1 ]] || usage
mode="$1"
shift
case "$mode" in
    --check|--write) ;;
    --help|-h) usage 0 ;;
    *)
        echo "beagle fmt: expected --check or --write, got '$mode'" >&2
        usage
        ;;
esac

[[ $# -ge 1 ]] || set -- .

declare -A source_extensions=()
for target in "${BEAGLE_TARGET_IDS[@]}"; do
    source_extensions[".${BEAGLE_TARGET_SRC_EXT[$target]}"]=1
done

source_file() {
    local path="$1"
    local extension=".${path##*.}"
    [[ -n "${source_extensions[$extension]:-}" ]]
}

directory_excluded() {
    local path="$1"
    case "$path" in
        beagle-test/*|bin/test/*|docs/archive/*|native-core/validation/*|self-host/fixtures/*|\
        */fixtures/*|*/generated/*|*/vendor/*|*/repros/*|*/corpus/*|out/*|*/out/*)
            return 0
            ;;
        *) return 1 ;;
    esac
}

declare -A seen=()
files=()

add_file() {
    local path
    path="$(realpath "$1")"
    if [[ -z "${seen[$path]:-}" ]]; then
        seen["$path"]=1
        files+=("$path")
    fi
}

for operand in "$@"; do
    if [[ -f "$operand" ]]; then
        if ! source_file "$operand"; then
            echo "beagle fmt: not a Beagle source file: $operand" >&2
            exit 2
        fi
        add_file "$operand"
    elif [[ -d "$operand" ]]; then
        operand_abs="$(realpath "$operand")"
        if ! repo_root="$(git -C "$operand_abs" rev-parse --show-toplevel 2>/dev/null)"; then
            echo "beagle fmt: directory is not inside a Git worktree: $operand" >&2
            exit 2
        fi
        relative="$(realpath --relative-to="$repo_root" "$operand_abs")"
        [[ "$relative" == "." ]] && relative=""
        while IFS= read -r -d '' tracked; do
            source_file "$tracked" || continue
            directory_excluded "$tracked" && continue
            add_file "$repo_root/$tracked"
        done < <(git -C "$repo_root" ls-files -z -- "${relative:-.}")
    else
        echo "beagle fmt: path does not exist: $operand" >&2
        exit 2
    fi
done

if [[ ${#files[@]} -eq 0 ]]; then
    exit 0
fi

mapfile -d '' -t sorted_files < <(printf '%s\0' "${files[@]}" | sort -z -u)

# Formatting is routinely asked the same question more than once in an edit
# cycle (hook, editor, then review). A content-addressed check result is exact:
# the key includes every input byte plus the compiled Racket closure stamp.
# Write mode always executes because its purpose is mutation, not diagnosis.
if [[ "$mode" == "--check" ]]; then
    cache_dir="$BEAGLE_DIR/.beagle/cache/fmt"
    mkdir -p "$cache_dir"
    closure_stamp="$(stat -c '%y:%s' "$BEAGLE_DIR/.beagle/zo-fresh" 2>/dev/null || true)"
    cache_key="$({
        printf '%s\0%s\0' "$closure_stamp" "$mode"
        sha256sum "$BEAGLE_DIR/bin/beagle-fmt"
        for file in "${sorted_files[@]}"; do
            printf '%s\0' "$file"
            sha256sum "$file"
        done
    } | sha256sum | cut -d' ' -f1)"
    cache_out="$cache_dir/$cache_key.out"
    cache_err="$cache_dir/$cache_key.err"
    cache_status="$cache_dir/$cache_key.status"
    if [[ -f "$cache_out" && -f "$cache_err" && -f "$cache_status" ]]; then
        cat "$cache_out"
        cat "$cache_err" >&2
        exit "$(cat "$cache_status")"
    fi

    out_tmp="$(mktemp "$cache_dir/.out.XXXXXX")"
    err_tmp="$(mktemp "$cache_dir/.err.XXXXXX")"
    set +e
    "$RACKET" "$BEAGLE_DIR/beagle-lib/private/signature-format.rkt" \
        "$mode" "${sorted_files[@]}" >"$out_tmp" 2>"$err_tmp"
    status=$?
    set -e
    if [[ $status == 0 || $status == 3 ]]; then
        status_tmp="$(mktemp "$cache_dir/.status.XXXXXX")"
        printf '%s\n' "$status" >"$status_tmp"
        mv -f "$out_tmp" "$cache_out"
        mv -f "$err_tmp" "$cache_err"
        mv -f "$status_tmp" "$cache_status"
    fi
    cat "$out_tmp" 2>/dev/null || cat "$cache_out"
    cat "$err_tmp" 2>/dev/null >&2 || cat "$cache_err" >&2
    exit "$status"
fi

exec "$RACKET" "$BEAGLE_DIR/beagle-lib/private/signature-format.rkt" \
    "$mode" "${sorted_files[@]}"
