#!/bin/sh
# aidown installer: curl -fsSL https://aidown.dev/install | sh
# Hooks run synchronously (a few ms; sending happens in a detached background process), because
# async hooks are killed when a one-shot run like `claude -p` exits right after an error.
# Adds small hooks to Claude Code (~/.claude/settings.json) and Codex (~/.codex/hooks.json).
# Every edited file gets a backup first. Undo everything: aidown uninstall
set -e

AIDOWN_URL="${AIDOWN_URL:-https://aidown.dev}"
AIDOWN_HOME="${AIDOWN_HOME:-$HOME/.aidown}"
BIN="$AIDOWN_HOME/bin/aidown"
say() { printf 'aidown: %s\n' "$*"; }

ask() { # question → 0 for yes. Reads the terminal, because stdin is this script.
  if [ -n "$AIDOWN_YES" ]; then return 0; fi
  if [ ! -r /dev/tty ]; then say "no terminal to ask; run with AIDOWN_YES=1 to accept."; return 1; fi
  printf '%s [Y/n] ' "$1" >/dev/tty; read -r a </dev/tty || a=n
  case "$a" in ''|y|Y|yes|YES) return 0 ;; *) return 1 ;; esac
}

json_tool() {
  if [ -n "$AIDOWN_JSON_TOOL" ]; then echo "$AIDOWN_JSON_TOOL"; return; fi # for tests
  if command -v jq >/dev/null 2>&1; then echo jq; return; fi
  if command -v node >/dev/null 2>&1; then echo node; return; fi
  echo none
}

# add_hooks FILE SPEC: SPEC lines are "Event|matcher|subcommand|async(true/false)"
add_hooks() {
  f="$1"; spec="$2"; tool=$(json_tool)
  mkdir -p "$(dirname "$f")"
  [ -s "$f" ] || echo '{}' >"$f"
  cp "$f" "$f.aidown-bak-$(date +%Y%m%d%H%M%S)"
  case "$tool" in
    jq)
      filter='.'
      for line in $spec; do
        ev=$(echo "$line" | cut -d'|' -f1); m=$(echo "$line" | cut -d'|' -f2)
        sub=$(echo "$line" | cut -d'|' -f3); as=$(echo "$line" | cut -d'|' -f4)
        h="{\"type\":\"command\",\"command\":\"\$HOME/.aidown/bin/aidown hook $sub\",\"timeout\":3"
        [ "$as" = true ] && h="$h,\"async\":true"
        h="$h}"
        g="{\"hooks\":[$h]}"; [ -n "$m" ] && g="{\"matcher\":\"$m\",\"hooks\":[$h]}"
        filter="$filter | .hooks[\"$ev\"] = (((.hooks[\"$ev\"] // []) | map(.hooks |= map(select((.command // \"\") | contains(\".aidown/bin/aidown hook $sub\") | not))) | map(select(.hooks | length > 0))) + [$g])"
      done
      jq "$filter" "$f" >"$f.tmp" && mv "$f.tmp" "$f" ;;
    node)
      SPEC="$spec" node -e '
        const fs=require("fs"),f=process.argv[1],j=JSON.parse(fs.readFileSync(f,"utf8")||"{}");
        j.hooks=j.hooks||{};
        for (const line of process.env.SPEC.trim().split(/\s+/)) {
          const [ev,m,sub,as]=line.split("|");
          const cmd="$HOME/.aidown/bin/aidown hook "+sub;
          const h={type:"command",command:cmd,timeout:3}; if(as==="true") h.async=true;
          const groups=(j.hooks[ev]||[]).map(g=>({...g,hooks:(g.hooks||[]).filter(x=>!String(x.command||"").includes(".aidown/bin/aidown hook "+sub))})).filter(g=>g.hooks.length);
          groups.push(m?{matcher:m,hooks:[h]}:{hooks:[h]}); j.hooks[ev]=groups;
        }
        fs.writeFileSync(f, JSON.stringify(j,null,2)+"\n");' "$f" ;;
    *)
      say "needs jq or node to edit $f safely. Install jq (brew install jq) and run again."
      return 1 ;;
  esac
}

command -v curl >/dev/null 2>&1 || { say "curl is required."; exit 1; }

has_claude=; has_codex=
{ command -v claude >/dev/null 2>&1 || [ -d "$HOME/.claude" ]; } && has_claude=1
{ command -v codex >/dev/null 2>&1 || [ -d "$HOME/.codex" ]; } && has_codex=1
if [ -z "$has_claude$has_codex" ]; then say "found neither Claude Code nor Codex. Nothing to do."; exit 0; fi

found=""; [ -n "$has_claude" ] && found="Claude Code"; [ -n "$has_codex" ] && found="${found:+$found, }Codex"
cat <<EOF

aidown sends anonymous error reports to $AIDOWN_URL, so everyone can see
when an AI tool is failing. Found: $found

  Sent:      error type, tool, model, tool version, hourly turn count, random install id
  Not sent:  prompts, code, file paths, project names, email, account, tokens

EOF
ask "Install aidown?" || { say "cancelled. Nothing changed."; exit 0; }

mkdir -p "$AIDOWN_HOME/bin"
curl -fsSL "$AIDOWN_URL/aidown.sh" -o "$BIN.tmp" && mv "$BIN.tmp" "$BIN" && chmod +x "$BIN"
if [ ! -s "$AIDOWN_HOME/id" ]; then
  od -An -N16 -tx1 /dev/urandom | tr -d ' \n' >"$AIDOWN_HOME/id"
fi
say "installed $BIN"

if [ -n "$has_claude" ] && ask "Add the hook to Claude Code (~/.claude/settings.json)?"; then
  add_hooks "$HOME/.claude/settings.json" "SessionStart||claude-start|false PostModelSwitch||claude-switch|false Stop||claude-stop|false StopFailure||claude-fail|false" \
    && say "Claude Code: hooks added (active in new sessions)"
fi
if [ -n "$has_codex" ] && ask "Add the hook to Codex (~/.codex/hooks.json)?"; then
  add_hooks "$HOME/.codex/hooks.json" "Stop||codex|false SessionEnd||codex|false" \
    && say "Codex: hooks added. Run /hooks in Codex once and trust the aidown hook."
fi

case ":$PATH:" in
  *":$AIDOWN_HOME/bin:"*) ;;
  *) for rc in "$HOME/.zshrc" "$HOME/.bashrc"; do
       [ -f "$rc" ] && ! grep -q '.aidown/bin' "$rc" && printf '\nexport PATH="$HOME/.aidown/bin:$PATH"  # aidown\n' >>"$rc"
     done
     say "open a new terminal to use the aidown command" ;;
esac

cat <<EOF

Done. Check it:   aidown status
Pause sending:    aidown pause
Remove all:       aidown uninstall   (also deletes your reports on the server)
What we send:     $AIDOWN_URL/cli
EOF
