#!/bin/bash
# Pre-commit hook: verify all defuns in staged .org files compile in the daemon.
# For each changed .org file, it tangles to .lisp then sends the entire file
# to the daemon for compilation. This catches undefined symbol references,
# syntax errors, and broken function bodies.
#
# Install:
#   ln -sf ../../scripts/pre-commit-repl-check .git/hooks/pre-commit
#
# Requires: running daemon on port 9105, repl script, emacs with ob-tangle.
#
# Returns 0 (pass) or 1 (fail).

set -euo pipefail
IFS=$'\n\t'

REPL=$(command -v repl 2>/dev/null || echo "/home/user/.opencode/bin/repl")
PORT=9105
PROJECT_DIR=$(git rev-parse --show-toplevel 2>/dev/null || echo "/home/user/memex/projects/passepartout")

# Check daemon connectivity
if ! timeout 2 bash -c "echo >/dev/tcp/127.0.0.1/$PORT" 2>/dev/null; then
    echo "ERROR: Daemon not reachable on 127.0.0.1:$PORT. Start it first." >&2
    exit 1
fi

# Collect changed .org files from the index
CHANGED=$(git diff --cached --name-only --diff-filter=ACM | grep '\.org$' || true)
if [ -z "$CHANGED" ]; then
    exit 0
fi

FAILED=0
for orgfile in $CHANGED; do
    [ -f "$orgfile" ] || continue

    # Determine the tangle target from the org file's PROPERTY line
    TANGLE=$(grep 'header-args.*:tangle' "$orgfile" | sed "s/.*:tangle //" | head -1 || true)
    if [ -z "$TANGLE" ]; then
        echo "SKIP: $orgfile — no :tangle header" >&2
        continue
    fi

    # Resolve relative tangle path
    ORG_DIR=$(dirname "$orgfile")
    LISP_FILE=$(cd "$ORG_DIR" && realpath -m "$TANGLE" 2>/dev/null || echo "$ORG_DIR/$TANGLE")

    # Tangle the org file to lisp
    if ! emacs --batch -L "$PROJECT_DIR" --eval "(require 'ob-tangle)" \
         --eval "(org-babel-tangle-file \"$ORG_DIR/$(basename "$orgfile")\")" \
         </dev/null 2>/dev/null; then
        echo "FAIL: $orgfile — tangling failed" >&2
        FAILED=1
        continue
    fi

    if [ ! -f "$LISP_FILE" ]; then
        echo "SKIP: $orgfile — tangle target $LISP_FILE not found" >&2
        continue
    fi

    # Compile the lisp file in the daemon.
    # We send a Lisp form that compiles the file and returns T or an error string.
    # Using format to avoid backquote/comma issues.
    LISP_ABS=$(realpath "$LISP_FILE" 2>/dev/null || echo "$LISP_FILE")
    CODE=$(cat <<-LISPEOF
    (let ((*standard-output* (make-broadcast-stream))
          (*error-output* (make-broadcast-stream)))
      (handler-case
          (progn
            (compile-file "$LISP_ABS")
            (load (compile-file-pathname "$LISP_ABS"))
            (format nil "OK"))
        (error (c)
          (format nil "COMPILE-ERROR: ~a" c))))
LISPEOF
)
    RESULT=$(printf '%s' "$CODE" | timeout 10 "$REPL" 2>/dev/null || echo "DAEMON-UNREACHABLE")

    if echo "$RESULT" | grep -q '^COMPILE-ERROR:\|^DAEMON-UNREACHABLE\|^$'; then
        echo "REJECT: $(basename "$orgfile") — compilation failed: $RESULT" >&2
        FAILED=1
    else
        echo "OK: $(basename "$orgfile")" >&2
    fi
done

if [ "$FAILED" -eq 1 ]; then
    echo "" >&2
    echo "COMMIT REJECTED: REPL compilation check failed." >&2
    echo "Fix errors, or bypass with: git commit --no-verify" >&2
    exit 1
fi
