#!/usr/bin/env bash
# ask-agy.sh — Claude -> Google Antigravity CLI (`agy`) router (independent second model).
#
# Sibling of ask-grok.sh. Routes a task to the Antigravity CLI (`agy`, a native Go
# binary at ~/AppData/Local/agy/bin/agy) for an independent, different-vendor second
# opinion, verification, code review, vision, or live web search. Multi-model backend
# (Gemini 3.5 Flash / Gemini 3.1 Pro / Claude / GPT-OSS) selectable via --model.
#
# REPLACES the old `gemini` npm CLI (ask-gemini.sh), which broke on this account with
# a Cloud-project eligibility error (throwIneligibleOrProjectIdError). `agy` uses
# Antigravity's own auth (~/.gemini/antigravity-cli/), no GOOGLE_CLOUD_PROJECT needed.
#
# Authoritative flag reference (from `agy --help`, Go flag package — 2026-07-02, v1.0.16):
#   -p | --print | --prompt   Run ONE prompt non-interactively and print the response.
#                             STRING flag: the prompt is its VALUE. MUST be LAST on the
#                             line (a bare `-p --model` makes "--model" the prompt).
#   --model "<name>"          Model for the session. Friendly names from `agy models`,
#                             e.g. "Gemini 3.1 Pro (High)", "Gemini 3.5 Flash (Medium)",
#                             "Claude Opus 4.6 (Thinking)". Omit -> CLI default (Flash).
#   --add-dir <dir>           Add a directory to the workspace (repeatable). Needed so
#                             the agent's file tools can read a review/vision target.
#   --dangerously-skip-permissions   Auto-approve tool calls (else print mode can HANG
#                             on an approval prompt until --print-timeout). Headless-safe.
#   --print-timeout <dur>     Print-mode wait timeout (default 5m0s). We also wrap in the
#                             shell `timeout` as a hard belt-and-suspenders bound.
#   (There is NO JSON/structured-output flag — stdout is plain text/markdown.)
#
# Output contract: plain text on stdout. Tool-using turns (review/search/vision) may
# emit a line or two of tool narration ("I will view the content of ...") before the
# answer; we ask the model to suppress it but tolerate residual. No JSON parsing.
#
# Usage (unchanged from the old skill so callers/SKILL.md stay valid):
#   ask-agy.sh text    "<prompt>"                          # one-shot answer
#   ask-agy.sh text    --prompt-file <path>                # long content
#   ask-agy.sh verify  "<claim or finding to refute>"      # adversarial check
#   ask-agy.sh verify  --prompt-file <path>
#   ask-agy.sh review  <file> [instructions]               # agy reads + reviews one file
#   ask-agy.sh review-files [-i "instr"] <f1> [f2 ...]     # review a SET together
#   ask-agy.sh review-diff [-C <repo-dir>] [-i "instr"] <gitref> [-- <pathspec>]
#   ask-agy.sh image-analyze <image-path> ["question"]     # vision: read image + describe
#   ask-agy.sh search  "<query>"                           # live web search + sources
#   ask-agy.sh raw     <agy args...>                       # escape hatch
#
# Exit: 0 ok, 1 no result, 2 usage, 3 not installed here, 127 agy not found.
set -uo pipefail
SELF="ask-agy"

# --- path conversion: native-Windows path for agy args (no-op off Windows) ---
# agy is a native Windows binary; Git Bash hands it POSIX paths it cannot resolve.
if command -v cygpath >/dev/null 2>&1; then
  winpath() { cygpath -w -- "$1" 2>/dev/null || printf '%s' "$1"; }
else
  winpath() { printf '%s' "$1"; }
fi

# --- identity.json (per-machine, gitignored): is agy installed here? ---
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]:-$0}")" 2>/dev/null && pwd)"
PYBIN="$(command -v py 2>/dev/null || command -v python 2>/dev/null || command -v python3 2>/dev/null || true)"
IDFILE=""
[ -n "${CLAUDETOOLS_ROOT:-}" ] && [ -f "$CLAUDETOOLS_ROOT/.claude/identity.json" ] && IDFILE="$CLAUDETOOLS_ROOT/.claude/identity.json"
[ -z "$IDFILE" ] && IDFILE="$(cd "$SCRIPT_DIR/../../.." 2>/dev/null && pwd)/identity.json"
idagy() {  # read field $1 from identity.json .agy (fallback .gemini), empty if absent
  [ -f "$IDFILE" ] || { echo ""; return; }
  [ -n "$PYBIN" ] || { echo ""; return; }
  "$PYBIN" -c "import json,sys
try:
    d=json.load(sys.stdin); g=(d.get('agy') or d.get('gemini') or {}); v=g.get('$1','')
    print('' if v is None else (str(v).lower() if isinstance(v,bool) else v))
except Exception: print('')" < "$IDFILE"
}

if [ "$(idagy installed)" = "false" ]; then
  echo "[$SELF] agy is not installed on this machine (identity.json agy.installed=false)." >&2
  echo "[$SELF] Antigravity CLI runs only on the fleet host. Route this request there, or install agy + set identity.json agy.installed=true." >&2
  exit 3
fi

# --- locate the agy binary: AGY env > identity.json agy.binary > PATH > known paths ---
AGY="${AGY:-}"
if [ -n "$AGY" ] && [ ! -x "$AGY" ] && ! command -v "$AGY" >/dev/null 2>&1; then
  echo "[$SELF] AGY='$AGY' is not an executable agy binary." >&2; exit 127
fi
cand="$(idagy binary)"
[ -z "$AGY" ] && [ -n "$cand" ] && [ -x "$cand" ] && AGY="$cand"
if [ -z "$AGY" ]; then
  if command -v agy >/dev/null 2>&1; then AGY="$(command -v agy)"; else
    for c in "/c/Users/${USERNAME:-${USER:-x}}/AppData/Local/agy/bin/agy" \
             "$HOME/AppData/Local/agy/bin/agy" "$LOCALAPPDATA/agy/bin/agy" \
             "/usr/local/bin/agy" "$HOME/.local/bin/agy"; do
      [ -n "$c" ] && [ -x "$c" ] && { AGY="$c"; break; }
    done
  fi
fi
[ -z "$AGY" ] && { echo "[$SELF] agy CLI not found (set identity.json agy.binary, AGY=, or install the Antigravity CLI)" >&2; exit 127; }

# Strong model for verify/review*/vision/search; text uses the CLI default (Flash).
STRONG_MODEL="${AGY_MODEL:-Gemini 3.1 Pro (High)}"

MODE="${1:-}"; shift 2>/dev/null || true
[ -z "$MODE" ] && { echo "usage: $SELF {text|verify|review|review-files|review-diff|image-analyze|search|raw} ..." >&2; exit 2; }

TMP="$(mktemp -d)"; trap 'rm -rf "$TMP"' EXIT
PF="$TMP/prompt.txt"; OUT="$TMP/out.txt"; ERR="$TMP/err.txt"
REPO_ROOT="${CLAUDETOOLS_ROOT:-$(cd "$SCRIPT_DIR/../../../.." 2>/dev/null && pwd)}"
_logerr() { bash "$REPO_ROOT/.claude/scripts/log-skill-error.sh" "agy" "$@" >/dev/null 2>&1 || true; }

TIMEOUT_CMD="timeout"
if [[ "${OSTYPE:-}" == "darwin"* ]]; then
  TIMEOUT_CMD="$(command -v gtimeout 2>/dev/null || echo timeout)"
fi

# run agy headless. $1 = hard timeout secs; remaining args = flags placed BEFORE -p.
# The prompt (from $PF) is always the LAST token, as the value of -p. agy has no JSON
# mode, so stdout is the answer (plain text); stderr kept separate for diagnostics.
LAST_FLAGS=()
run_agy() {
  local to="$1"; shift
  LAST_FLAGS=("$@")
  "$TIMEOUT_CMD" "$to" "$AGY" "$@" --print-timeout "${to}s" -p "$(cat "$PF")" \
    >"$OUT" 2>"$ERR" </dev/null || true
}

# agy occasionally returns an empty turn; retry a couple times with backoff, then fail.
emit_or_fail() {
  local txt tries=0 max="${AGY_MAX_TRIES:-3}"
  txt="$(sed -e 's/[[:space:]]*$//' "$OUT" | sed -e :a -e '/^\s*$/{$d;N;ba}')"
  while [ -z "${txt//[[:space:]]/}" ] && [ "$tries" -lt $((max-1)) ] && [ ${#LAST_FLAGS[@]} -ge 0 ]; do
    tries=$((tries+1))
    echo "[$SELF] empty response - retry $tries/$((max-1)) (backoff ${tries}x3s)..." >&2
    sleep $((tries*3))
    run_agy 240 "${LAST_FLAGS[@]}"
    txt="$(cat "$OUT")"
  done
  if [ -n "${txt//[[:space:]]/}" ]; then cat "$OUT"; return 0; fi
  if grep -qiE 'not authenticated|please (log|sign).?in|auth(entication)? (failed|error|required)|token (has )?expired|unauthorized' "$ERR" 2>/dev/null; then
    echo "[$SELF] agy auth error - run 'agy' interactively once to sign in, then retry." >&2
    _logerr "agy auth/login failure" --context "mode=$MODE"
  else
    echo "[$SELF] no response from agy after $max attempts. stderr tail:" >&2
    tail -3 "$ERR" >&2 2>/dev/null || true
    _logerr "agy returned no response (empty after $max attempts)" --context "mode=$MODE err=$(tail -1 "$ERR" 2>/dev/null | tr -d '\n' | cut -c1-80)"
  fi
  exit 1
}

# Copy review/vision targets into an included workspace dir so agy's file tools can
# reach them regardless of location/spaces; --add-dir this dir.
INCLUDE_DIR="$TMP/inbox"
prep_includes() { mkdir -p "$INCLUDE_DIR"; }

NO_TOOLS='Do not use any tools; answer directly in text.'
NO_NARRATE='Do not narrate your tool use or steps; output only the final answer.'

case "$MODE" in
  text|verify)
    SRC=""
    if [ "${1:-}" = "--prompt-file" ]; then
      [ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
      SRC="$(cat "$2")"
    else
      SRC="${1:-}"
    fi
    [ -z "$SRC" ] && { echo "usage: $SELF $MODE \"<prompt>\" | $SELF $MODE --prompt-file <path>" >&2; exit 2; }
    if [ "$MODE" = "verify" ]; then
      printf 'You are an adversarial reviewer giving an independent second opinion. Evaluate the following claim/finding/document: try hard to find any way it is WRONG, incomplete, unsupported, or overstated. Then give a clear VERDICT (correct / partly correct / incorrect) plus specific justification. %s\n\nContent:\n%s' "$NO_TOOLS" "$SRC" > "$PF"
      run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
    else
      printf '%s\n\n%s' "$NO_TOOLS" "$SRC" > "$PF"
      run_agy 240 --dangerously-skip-permissions
    fi
    emit_or_fail
    ;;

  review|file)
    [ -z "${1:-}" ] && { echo "usage: $SELF review <file-path> [instructions]" >&2; exit 2; }
    target="$1"
    instr="${2:-Give an independent, critical review of this file: accuracy, gaps/omissions, bugs, and concrete improvements. Be specific.}"
    if   [ -f "$target" ];            then resolved="$target"
    elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
    else echo "[$SELF] file not found: $target" >&2; exit 2; fi
    prep_includes
    base="$(basename "$resolved")"; cp -f "$resolved" "$INCLUDE_DIR/$base"
    tgt_win="$(winpath "$INCLUDE_DIR/$base")"; inc_win="$(winpath "$INCLUDE_DIR")"
    printf 'Read the file at this absolute path, then perform the task and stop. Do not modify anything. %s\nPath: %s\n\nTask: %s' "$NO_NARRATE" "$tgt_win" "$instr" > "$PF"
    run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
    emit_or_fail
    ;;

  review-files)
    instr='Independently review these files together as a unit: correctness/bugs, gaps, cross-file consistency, and concrete improvements. Be specific and cite file:line.'
    files=()
    while [ $# -gt 0 ]; do
      case "$1" in
        -i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
        *)          files+=("$1"); shift ;;
      esac
    done
    [ ${#files[@]} -eq 0 ] && { echo "usage: $SELF review-files [-i \"instructions\"] <file> [file ...]" >&2; exit 2; }
    prep_includes
    list=""; declare -A seen=()
    for f in "${files[@]}"; do
      if   [ -f "$f" ];            then r="$f"
      elif [ -f "$REPO_ROOT/$f" ]; then r="$REPO_ROOT/$f"
      else echo "[$SELF] file not found: $f" >&2; exit 2; fi
      base="$(basename "$r")"
      if [ -n "${seen[$base]:-}" ]; then
        n=1; while [ -e "$INCLUDE_DIR/${n}_${base}" ]; do n=$((n+1)); done; base="${n}_${base}"
      fi
      seen[$base]=1; cp -f "$r" "$INCLUDE_DIR/$base"
      list+="- $(winpath "$INCLUDE_DIR/$base")
"
    done
    inc_win="$(winpath "$INCLUDE_DIR")"
    printf 'Read EACH of these files (absolute paths), then perform the task across ALL of them and stop. Do not modify anything. %s\n\nFiles:\n%s\nTask: %s' "$NO_NARRATE" "$list" "$instr" > "$PF"
    run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
    emit_or_fail
    ;;

  review-diff)
    gdir="$REPO_ROOT"
    instr='Review this git diff: correctness/bugs introduced, regressions, missing edge cases, and concrete fixes. Focus on the CHANGES. Be specific and cite file:line.'
    ref=""; pathspec=()
    while [ $# -gt 0 ]; do
      case "$1" in
        -C|--dir)   gdir="${2:-}"; shift 2 2>/dev/null || shift ;;
        -i|--instr) instr="${2:-}"; shift 2 2>/dev/null || shift ;;
        --)         shift; while [ $# -gt 0 ]; do pathspec+=("$1"); shift; done ;;
        *)          if [ -z "$ref" ]; then ref="$1"; else pathspec+=("$1"); fi; shift ;;
      esac
    done
    [ -z "$ref" ] && { echo "usage: $SELF review-diff [-C <repo-dir>] [-i \"instr\"] <gitref> [-- <pathspec>]" >&2; exit 2; }
    [ -d "$gdir" ] || { [ -d "$REPO_ROOT/$gdir" ] && gdir="$REPO_ROOT/$gdir"; }
    git -C "$gdir" rev-parse --git-dir >/dev/null 2>&1 || { echo "[$SELF] not a git repo: $gdir" >&2; exit 2; }
    if [ ${#pathspec[@]} -gt 0 ]; then
      git -C "$gdir" diff "$ref" -- "${pathspec[@]}" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
    else
      git -C "$gdir" diff "$ref" > "$TMP/diff.txt" 2>"$TMP/differr.txt"
    fi
    [ -s "$TMP/diff.txt" ] || { echo "[$SELF] empty/failed diff for '$ref' in $gdir: $(head -1 "$TMP/differr.txt" 2>/dev/null)" >&2; exit 1; }
    gdir_win="$(winpath "$gdir")"
    { printf 'Review the following unified git diff. %s\n%s\nYou may read any changed file for full context (paths are relative to %s; strip a/ b/ prefixes). Do not modify anything.\n\n=== BEGIN DIFF ===\n' "$instr" "$NO_NARRATE" "$gdir_win"; cat "$TMP/diff.txt"; printf '\n=== END DIFF ===\n'; } > "$PF"
    run_agy 360 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$gdir_win"
    emit_or_fail
    ;;

  image-analyze|image|vision)
    [ -z "${1:-}" ] && { echo "usage: $SELF image-analyze <image-path> [\"question\"]" >&2; exit 2; }
    target="$1"; question="${2:-Describe exactly what is in this image.}"
    if   [ -f "$target" ];            then resolved="$target"
    elif [ -f "$REPO_ROOT/$target" ]; then resolved="$REPO_ROOT/$target"
    else echo "[$SELF] image not found: $target" >&2; exit 2; fi
    prep_includes
    base="$(basename "$resolved")"; cp -f "$resolved" "$INCLUDE_DIR/$base"
    img_win="$(winpath "$INCLUDE_DIR/$base")"; inc_win="$(winpath "$INCLUDE_DIR")"
    printf 'Read the image at this absolute path and describe exactly what you see. Report only what is actually present; do not guess or invent content. Then stop. %s\nImage path: %s\n\nQuestion: %s' "$NO_NARRATE" "$img_win" "$question" > "$PF"
    run_agy 300 --dangerously-skip-permissions --model "$STRONG_MODEL" --add-dir "$inc_win"
    emit_or_fail
    ;;

  search|websearch)
    SRC=""
    if [ "${1:-}" = "--prompt-file" ]; then
      [ -f "${2:-}" ] || { echo "[$SELF] prompt file not found: ${2:-}" >&2; exit 2; }
      SRC="$(cat "$2")"
    else
      SRC="${1:-}"
    fi
    [ -z "$SRC" ] && { echo "usage: $SELF search \"<query>\" | $SELF search --prompt-file <path>" >&2; exit 2; }
    printf 'Search the web for current, live information answering the following, then stop. Answer concisely and ALWAYS include the source URLs you used (a Sources list of full URLs). Do not fabricate URLs. %s\n\nQuery: %s' "$NO_NARRATE" "$SRC" > "$PF"
    run_agy 240 --dangerously-skip-permissions --model "$STRONG_MODEL"
    emit_or_fail
    ;;

  raw)
    "$AGY" "$@"
    ;;

  *)
    echo "[$SELF] unknown mode '$MODE' (use text|verify|review|review-files|review-diff|image-analyze|search|raw)" >&2; exit 2 ;;
esac
