#!/usr/bin/env bash
# git_hook_audit_app_drift - Pre-commit guard: block commit if staged code
# changes target an app with uses_functions drift in its app.md.
#
# Skips:
#   - Commits where ALL staged files are app.md (corrective edits)
#   - Apps without drift
#   - Repos that aren't fn_registry-aware (no FN_REGISTRY_ROOT and no registry.db at top)
#
# Returns 0 (allow), 1 (block), 2 (config error)

set -euo pipefail

git_hook_audit_app_drift() {
    local repo_dir="${1:-}"
    if [[ -z "$repo_dir" ]]; then
        echo "ERROR: repo_dir required" >&2
        return 2
    fi
    # Accept both regular repos (.git is a directory) and worktrees (.git is a
    # file containing "gitdir: ..." pointer).
    if [[ ! -d "$repo_dir/.git" && ! -f "$repo_dir/.git" ]]; then
        echo "ERROR: $repo_dir is not a git repo" >&2
        return 2
    fi

    # Find fn_registry root
    local registry_root="${FN_REGISTRY_ROOT:-}"
    if [[ -z "$registry_root" ]]; then
        if [[ -f "$repo_dir/registry.db" ]]; then
            registry_root="$repo_dir"
        elif [[ -f "$repo_dir/../../registry.db" ]]; then
            registry_root="$(cd "$repo_dir/../.." && pwd)"
        elif [[ -f "$repo_dir/../../../registry.db" ]]; then
            registry_root="$(cd "$repo_dir/../../.." && pwd)"
        fi
    fi
    if [[ -z "$registry_root" || ! -f "$registry_root/registry.db" ]]; then
        echo "[hook] FN_REGISTRY_ROOT not resolvable; skipping uses_functions check" >&2
        return 0
    fi

    local fn_bin="$registry_root/fn"
    if [[ ! -x "$fn_bin" ]]; then
        echo "[hook] $fn_bin not built; skipping uses_functions check" >&2
        return 0
    fi

    # Get staged files relative to repo root
    local staged
    staged=$(git -C "$repo_dir" diff --cached --name-only --diff-filter=ACMR)
    if [[ -z "$staged" ]]; then
        return 0
    fi

    # Detect which apps have changed code (any file other than app.md)
    # An app dir contains an app.md. Find ancestor with app.md for each staged file.
    local -A touched_apps=()
    local rel
    while IFS= read -r rel; do
        # Skip pure app.md edits — those are corrective
        local base
        base=$(basename "$rel")
        if [[ "$base" == "app.md" ]]; then
            continue
        fi
        # Walk up looking for app.md (including repo root)
        local dir
        dir=$(dirname "$rel")
        # First check repo root itself
        if [[ -f "$repo_dir/app.md" ]]; then
            touched_apps["."]=1
            continue
        fi
        while [[ "$dir" != "." && "$dir" != "/" ]]; do
            if [[ -f "$repo_dir/$dir/app.md" ]]; then
                touched_apps["$dir"]=1
                break
            fi
            dir=$(dirname "$dir")
        done
    done <<< "$staged"

    if [[ ${#touched_apps[@]} -eq 0 ]]; then
        return 0
    fi

    # Get drift report from fn doctor (run from registry root so registry.db resolves)
    local drift_json
    drift_json=$(cd "$registry_root" && "$fn_bin" doctor uses-functions --json 2>/dev/null) || {
        echo "[hook] fn doctor failed; allowing commit" >&2
        return 0
    }

    # Determine repo's location relative to registry_root, since touched_apps
    # are repo-relative and audits are registry-relative.
    local repo_abs
    repo_abs=$(cd "$repo_dir" && pwd)
    local registry_abs
    registry_abs=$(cd "$registry_root" && pwd)
    local repo_rel="${repo_abs#"$registry_abs/"}"
    if [[ "$repo_rel" == "$repo_abs" ]]; then
        repo_rel=""
    fi

    # For each touched app, check if it appears in drift report.
    # Drift JSON is a list of {AppID, DirPath, Lang, Missing, Unused}.
    local blocking=0
    local app
    for app in "${!touched_apps[@]}"; do
        local dir_path_full
        if [[ "$app" == "." ]]; then
            dir_path_full="$repo_rel"
        elif [[ -n "$repo_rel" ]]; then
            dir_path_full="$repo_rel/$app"
        else
            dir_path_full="$app"
        fi

        # Match by DirPath (registry-relative path)
        local hit
        hit=$(printf '%s' "$drift_json" | python3 -c "
import json, sys
data = json.load(sys.stdin)
target = '$dir_path_full'.rstrip('/')
for a in data or []:
    dp = (a.get('DirPath') or '').rstrip('/')
    if dp == target:
        miss = a.get('Missing') or []
        unused = a.get('Unused') or []
        if miss or unused:
            print(f\"{a.get('AppID','?')}|missing={','.join(miss[:5])}|unused={','.join(unused[:5])}\")
            break
" 2>/dev/null) || hit=""

        if [[ -n "$hit" ]]; then
            echo "[hook] BLOCK: app at $dir_path_full has uses_functions drift:" >&2
            echo "       $hit" >&2
            blocking=1
        fi
    done

    if [[ $blocking -eq 1 ]]; then
        echo "" >&2
        echo "Fix app.md uses_functions, run \`./fn index\`, then retry commit." >&2
        echo "To bypass (not recommended): git commit --no-verify" >&2
        return 1
    fi

    return 0
}

if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    git_hook_audit_app_drift "$@"
fi
