#!/bin/zsh
# Control script for the push-to-talk whisper dictation agent (PttDictate.app bundle).
LABEL=com.brady.ptt-dictate
DOMAIN="gui/$(id -u)"
PLIST="$HOME/Library/LaunchAgents/$LABEL.plist"
DIR="$HOME/.dictation"
APP="$DIR/PttDictate.app"
BIN="$APP/Contents/MacOS/ptt-dictate"
# Stable codesign identity (Apple Development) + a real bundle id (com.brady.ptt-dictate
# in Info.plist). TCC keys Mic + Accessibility on the BUNDLE ID, so rebuilds never wipe
# the grants, and `tccutil reset {Microphone,Accessibility} com.brady.ptt-dictate` can
# cleanly repair a bad state — neither was possible with the old bare executable.
SIGN_ID=99F202BCAC5ED8E803E5DAF0E8F5A4B366276831
case "$1" in
  start)   launchctl bootstrap "$DOMAIN" "$PLIST" 2>/dev/null; launchctl kickstart -p "$DOMAIN/$LABEL" ;;
  stop)    launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null && echo "stopped" ;;
  signin)  open "nessotalk://signin" && echo "Opening your browser to connect this Mac to your NessoTalk account." ;;
  restart) launchctl kickstart -k "$DOMAIN/$LABEL" && echo "restarted" ;;
  call)
    # Toggle call-mode in the config (no rebuild): disables/aggressive-denoises for
    # phone/VoIP dictation, then restarts the daemon so the new value is read.
    cfgf="$DIR/config"
    cur=$(grep -E '^CALL_MODE=' "$cfgf" | tail -1 | cut -d= -f2)
    case "${2:-toggle}" in
      on|1)  new=1 ;;
      off|0) new=0 ;;
      *)     [ "$cur" = "1" ] && new=0 || new=1 ;;
    esac
    if grep -qE '^CALL_MODE=' "$cfgf"; then
      sed -i '' -E "s/^CALL_MODE=.*/CALL_MODE=$new/" "$cfgf"
    else
      printf '\nCALL_MODE=%s\n' "$new" >> "$cfgf"
    fi
    launchctl kickstart -k "$DOMAIN/$LABEL" >/dev/null 2>&1
    if [ "$new" = 1 ]; then echo "call-mode ON  — VAD gate lowered + aggressive denoise/AGC (restarted)";
    else echo "call-mode OFF — normal denoise + VAD gate (restarted)"; fi
    ;;
  whisper)
    # Toggle whisper mode (quiet-voice dictation): strong gain/denoise + a low
    # VAD gate, CLI decode path. Same shape as `ptt call`.
    cfgf="$DIR/config"
    cur=$(grep -E '^WHISPER_MODE=' "$cfgf" | tail -1 | cut -d= -f2)
    case "${2:-toggle}" in
      on|1)  new=1 ;;
      off|0) new=0 ;;
      *)     [ "$cur" = "1" ] && new=0 || new=1 ;;
    esac
    if grep -qE '^WHISPER_MODE=' "$cfgf"; then
      sed -i '' -E "s/^WHISPER_MODE=.*/WHISPER_MODE=$new/" "$cfgf"
    else
      printf '\nWHISPER_MODE=%s\n' "$new" >> "$cfgf"
    fi
    launchctl kickstart -k "$DOMAIN/$LABEL" >/dev/null 2>&1
    if [ "$new" = 1 ]; then echo "whisper-mode ON  — dictate at a whisper (strong gain + low VAD gate, restarted)";
    else echo "whisper-mode OFF — normal voice chain (restarted)"; fi
    ;;
  cloud)
    # Toggle the hosted NessoTalk engine (v8.17 cloud pivot). ON = transcribe
    # on our engine, local whisper as automatic fallback. OFF = all-local.
    # Same shape as `ptt call`.
    cfgf="$DIR/config"
    cur=$(grep -E '^CLOUD=' "$cfgf" | tail -1 | cut -d= -f2)
    case "${2:-toggle}" in
      on|1)   new=1 ;;
      off|0)  new=0 ;;
      status) [ "${cur:-1}" = "0" ] && echo "cloud OFF (all-local)" || echo "cloud ON (hosted engine, local fallback)"; exit 0 ;;
      *)      [ "$cur" = "1" ] && new=0 || new=1 ;;
    esac
    if grep -qE '^CLOUD=' "$cfgf"; then
      sed -i '' -E "s/^CLOUD=.*/CLOUD=$new/" "$cfgf"
    else
      printf '\nCLOUD=%s\n' "$new" >> "$cfgf"
    fi
    launchctl kickstart -k "$DOMAIN/$LABEL" >/dev/null 2>&1
    if [ "$new" = 1 ]; then echo "cloud ON  — hosted NessoTalk engine first, local decode as fallback (restarted)";
    else echo "cloud OFF — all-local decode (restarted)"; fi
    ;;
  learn)
    # Teach the dictionary a word: ptt learn "Word" [misheard1 misheard2 ...]
    # Appends to learned-words.txt; hot-reloaded — applies from the next dictation.
    shift
    [ -z "$1" ] && { echo "usage: ptt learn \"Word\" [misheard1 misheard2 ...]"; exit 1; }
    word="$1"; shift
    if [ $# -gt 0 ]; then
      variants=$(IFS=,; echo "$*")
      echo "$word|$variants" >> "$DIR/learned-words.txt"
      echo "learned: $word (heard as: $variants) — applies from the next dictation"
    else
      echo "$word" >> "$DIR/learned-words.txt"
      echo "learned: $word — applies from the next dictation"
    fi
    ;;
  moderules)
    # Extra cue phrases for the agent-brief sorter ("constraint: hands off = 3").
    # Hot-loaded on every dictation — edit, save, done. No restart needed.
    if [ ! -f "$DIR/mode-rules" ]; then
      cat > "$DIR/mode-rules" <<'SEED'
# Teach the mode sorter your own phrasing. One rule per line:
#   <section>: <phrase> [= weight]
# Sections: goal, constraint, done, context. Default weight is 2.
# Examples:
#   constraint: hands off = 3
#   done: acceptance criteria
#   goal: i wanna
SEED
      chmod 600 "$DIR/mode-rules"
    fi
    ${EDITOR:-open -e} "$DIR/mode-rules"
    ;;
  snippets)
    # Open the snippets file (name = expansion; say "snippet <name>" to expand).
    ${EDITOR:-open -e} "$DIR/snippets"
    ;;
  status)  launchctl print "$DOMAIN/$LABEL" 2>/dev/null | grep -iE "state =|pid =" | head -2 || echo "not loaded" ;;
  log)     tail -n 30 -f "$DIR/ptt.log" ;;
  build)
    cd "$DIR" || exit 1
    echo "compiling…"
    swiftc -O ptt-dictate.swift -o ptt-dictate.new || { echo "compile FAILED — keeping current binary"; exit 1; }
    launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null
    mkdir -p "$APP/Contents/MacOS" "$APP/Contents/Resources"
    cp -f Info.plist "$APP/Contents/Info.plist"
    mv -f ptt-dictate.new "$BIN"
    chmod +x "$BIN"
    if codesign -f -s "$SIGN_ID" --timestamp=none "$APP" 2>/dev/null; then
      echo "signed bundle with stable identity (TCC grants survive rebuilds)"
    else
      codesign -f -s - "$APP"
      echo "WARN: stable signing failed, fell back to ad-hoc — grants will reset"
    fi
    launchctl bootstrap "$DOMAIN" "$PLIST" 2>/dev/null
    launchctl kickstart -p "$DOMAIN/$LABEL"
    echo "rebuilt + restarted. Watch: ptt log"
    ;;
  update)
    # Pull the newest signed build from nesso.tech using the licensed key that
    # is already in the config, then restart. No reinstall, no re-download of
    # the speech models.
    KEY=$(grep -E '^LICENSE_KEY=' "$DIR/config" | tail -1 | cut -d= -f2)
    if [ -z "$KEY" ]; then echo "No license key in $DIR/config. Sign in at nesso.tech."; exit 1; fi
    TMP=$(mktemp -d)
    echo "downloading the latest build..."
    if ! curl -fsSL -H "x-license-key: $KEY" "https://nesso.tech/api/update?platform=mac" -o "$TMP/nt.zip"; then
      echo "Could not reach the update server, or your subscription is not active."; rm -rf "$TMP"; exit 1
    fi
    unzip -qo "$TMP/nt.zip" -d "$TMP" || { echo "The downloaded file was not readable."; rm -rf "$TMP"; exit 1; }
    APPSRC=$(find "$TMP" -maxdepth 3 -name PttDictate.app -print -quit)
    if [ -z "$APPSRC" ]; then echo "That build did not contain an app."; rm -rf "$TMP"; exit 1; fi
    launchctl bootout "$DOMAIN/$LABEL" 2>/dev/null
    rm -rf "$APP" && cp -R "$APPSRC" "$DIR/"
    launchctl bootstrap "$DOMAIN" "$PLIST" 2>/dev/null
    launchctl kickstart -p "$DOMAIN/$LABEL"
    rm -rf "$TMP"
    echo "updated and restarted. Your settings and dictionary were kept."
    ;;
  privacy)
    cfgf="$DIR/config"
    mode=$(grep -E '^PRIVACY_MODE=' "$cfgf" | tail -1 | cut -d= -f2); mode=${mode:-1}
    days=$(grep -E '^HISTORY_DAYS=' "$cfgf" | tail -1 | cut -d= -f2); days=${days:-7}
    clip=$(grep -E '^KEEP_LAST_CLIP=' "$cfgf" | tail -1 | cut -d= -f2); clip=${clip:-0}
    echo "NessoTalk privacy"
    if [ "$mode" = "1" ]; then
      echo "  Mode:            private. What you say is never written to disk."
    else
      echo "  Mode:            history kept, encrypted with a key in your Keychain."
    fi
    echo "  Retention:       $days days"
    if [ -f "$DIR/history.enc" ]; then
      echo "  History entries: $(wc -l < "$DIR/history.enc" | tr -d ' ') (encrypted, unreadable without your Keychain)"
    else
      echo "  History entries: none on disk"
    fi
    if [ "$clip" = "1" ] && [ -f /tmp/ptt_last.wav ]; then
      echo "  Audio on disk:   /tmp/ptt_last.wav is being kept. Run: ptt purge"
    else
      echo "  Audio on disk:   none"
    fi
    echo "  Log permissions: $(ls -l "$DIR/ptt.log" 2>/dev/null | cut -d" " -f1)"
    echo "  Transcription:   always on this Mac. Your voice is never uploaded."
    ;;
  purge)
    rm -f "$DIR/history.enc" /tmp/ptt_last.wav /tmp/ptt_rec_* /tmp/ptt_chunk* /tmp/ptt_caption* 2>/dev/null
    if [ -f "$DIR/ptt.log" ]; then : > "$DIR/ptt.log"; chmod 600 "$DIR/ptt.log"; fi
    echo "Everything was erased: history, retained audio, and the log."
    ;;
  devices)  "$BIN" --devices ;;
  miccheck) "$BIN" --mic-check "${2:-}" ;;
  server)
    # Resident whisper-server (model stays loaded -> near-instant pastes).
    # sync: regenerate the plist from the config's VAD_THRESHOLD and restart —
    # needed because the server's VAD gate is fixed at launch, not per-request.
    SLABEL=com.brady.ptt-whisper-server
    SPLIST="$HOME/Library/LaunchAgents/$SLABEL.plist"
    case "${2:-status}" in
      status)  launchctl print "$DOMAIN/$SLABEL" 2>/dev/null | grep -iE "state =|pid =" | head -2 || echo "not loaded"
               curl -s --max-time 5 127.0.0.1:8977/inference -F file=@/dev/null 2>/dev/null | grep -q . \
                 && echo "port 8977: answering" || echo "port 8977: NOT answering (daemon auto-falls back to CLI)" ;;
      log)     tail -n 30 "$DIR/whisper-server.log" ;;
      restart) launchctl kickstart -k "$DOMAIN/$SLABEL" && echo "server restarted (model reloads ~5s)" ;;
      stop)    launchctl bootout "$DOMAIN/$SLABEL" 2>/dev/null && echo "server stopped (dictation falls back to CLI)" ;;
      start)   launchctl bootstrap "$DOMAIN" "$SPLIST" 2>/dev/null; launchctl kickstart -p "$DOMAIN/$SLABEL" ;;
      sync)
        VT=$(grep -E '^VAD_THRESHOLD=' "$DIR/config" | tail -1 | cut -d= -f2)
        VT=${VT:-0.3}
        sed -i '' -E "s|(<string>-vt</string><string>)[0-9.]+(</string>)|\1$VT\2|" "$SPLIST"
        launchctl bootout "$DOMAIN/$SLABEL" 2>/dev/null
        launchctl bootstrap "$DOMAIN" "$SPLIST"
        echo "server plist synced to VAD_THRESHOLD=$VT and restarted" ;;
      *) echo "usage: ptt server {status|log|restart|stop|start|sync}" ;;
    esac
    ;;
  reset-perms)
    echo "wiping TCC for com.brady.ptt-dictate — you'll re-grant Accessibility (and Mic) once…"
    tccutil reset Microphone com.brady.ptt-dictate
    tccutil reset Accessibility com.brady.ptt-dictate
    launchctl kickstart -k "$DOMAIN/$LABEL"
    echo "restarted — enable PttDictate in Privacy & Security ▸ Accessibility."
    ;;
  fixaudio)
    # Manually reset a wedged system audio daemon (the -10868 "capture won't start"
    # after sleep). coreaudiod respawns automatically. Uses the passwordless rule if
    # installed, otherwise prompts for your password.
    echo "resetting coreaudiod (it will respawn in ~1s)…"
    sudo killall coreaudiod && echo "done — tap Right ⌘ to dictate" || echo "failed — reboot if it persists"
    ;;
  *) echo "usage: ptt {start|stop|restart|call [on|off]|status|log|build|devices|miccheck|server [status|log|restart|stop|start|sync]|reset-perms|fixaudio}" ;;
esac
