#!/usr/bin/env bash
# A thin wrapper around FocusPill's local command channel. No server, no port, no daemon:
# it posts a macOS distributed notification that the running app listens for, and reads the
# app's own JSON files for anything it needs to report back.
#
#   focuspill start "rewrite the intro" 25   start a session
#   focuspill pause                          pause or resume
#   focuspill stop                           stop and log what happened so far
#   focuspill log "code review" 40           record work the timer did not watch
#   focuspill status                         what the app is doing right now
#   focuspill today                          today's sessions and total
#   focuspill history [n]                    the last n sessions, default 10
#   focuspill mode pomodoro|timer            chain a break after work, or just count down
#   focuspill sound on|off                   the chime when a session ends
#   focuspill themes                         list installed themes
#   focuspill theme <id>                     switch theme
#   focuspill install-theme <folder|zip>     install a theme and switch to it
#   focuspill open-themes                    print the themes folder path
set -uo pipefail

SUPPORT="${FOCUSPILL_DATA_DIR:-$HOME/Library/Application Support/FocusPill}"
SESSIONS="$SUPPORT/sessions.json"
THEMES="$SUPPORT/Themes"
DUMP="/tmp/focuspill-status.json"

running() { pgrep -f "FocusPill.app/Contents/MacOS/FocusPill" >/dev/null; }

need_app() {
  running && return 0
  echo "FocusPill is not running. Open it from Applications first." >&2
  return 1
}

post() { # post key=value...
  local js='ObjC.import("Foundation"); var info = $({'
  local first=1
  for pair in "$@"; do
    [ $first -eq 0 ] && js="$js,"
    first=0
    js="$js\"${pair%%=*}\":\"$(printf '%s' "${pair#*=}" | sed 's/"/\\"/g')\""
  done
  js="$js}); \$.NSDistributedNotificationCenter.defaultCenter.postNotificationNameObjectUserInfoDeliverImmediately('FocusPillCommand', \$(), info, true);"
  osascript -l JavaScript -e "$js" >/dev/null
}

status_json() { # refreshes and prints the app's own state
  rm -f "$DUMP"
  post cmd=dump "path=$DUMP"
  for _ in 1 2 3 4 5 6; do
    [ -s "$DUMP" ] && { cat "$DUMP"; return 0; }
    sleep 0.3
  done
  echo "{}"
}

case "${1:-}" in
  start)
    need_app || exit 1
    goal="${2:?usage: focuspill start \"goal\" [minutes]}"
    minutes="${3:-25}"
    post cmd=start "goal=$goal" "seconds=$(python3 -c "print(int(float('$minutes')*60))")"
    echo "started: $goal, $minutes min"
    ;;

  pause) need_app || exit 1; post cmd=toggle-pause; echo "toggled pause" ;;
  stop)  need_app || exit 1; post cmd=stop;  echo "stopped, session logged" ;;

  log)
    need_app || exit 1
    goal="${2:?usage: focuspill log \"what you did\" [minutes]}"
    post cmd=log "goal=$goal" "minutes=${3:-25}"
    echo "logged: $goal, ${3:-25} min"
    ;;

  status)
    need_app || exit 1
    status_json >/dev/null
    python3 - "$DUMP" <<'PY'
import json, os, sys
path = sys.argv[1]
d = json.load(open(path)) if os.path.exists(path) else {}
if not d:
    print("no answer from the app"); raise SystemExit(1)
phase = d.get("phase", "idle")
theme, key = d.get("theme"), d.get("hotKey")
if phase == "idle":
    print(f"idle · theme {theme} · shortcut {key}")
else:
    left = int(d.get("remaining", 0))
    goal = d.get("goal") or "no goal"
    print(f"{phase} · {left // 60}:{left % 60:02d} left · {goal} · theme {theme}")
PY
    ;;

  today)
    python3 - "$SESSIONS" <<'PY'
import json, os, sys
from datetime import datetime
path = sys.argv[1]
if not os.path.exists(path):
    print("no sessions yet"); raise SystemExit
data = json.load(open(path))
today = datetime.now().date().isoformat()
mine = [s for s in data if s["startedAt"][:10] == today]
if not mine:
    print("nothing logged today"); raise SystemExit
total = sum(s["actualSeconds"] for s in mine if s.get("kind", "focus") == "focus")
for s in sorted(mine, key=lambda s: s["startedAt"]):
    mark = "done" if s["completed"] else "cut short"
    print(f'{s["startedAt"][11:16]}  {s["actualSeconds"]//60:>3} min  {s["goal"]}  ({mark})')
print(f'\ntotal focused today: {total//3600}h{(total%3600)//60:02d}' if total >= 3600
      else f'\ntotal focused today: {total//60} min')
PY
    ;;

  history)
    python3 - "$SESSIONS" "${2:-10}" <<'PY'
import json, os, sys
path, limit = sys.argv[1], int(sys.argv[2])
if not os.path.exists(path):
    print("no sessions yet"); raise SystemExit
for s in json.load(open(path))[:limit]:
    mark = "done" if s["completed"] else "cut short"
    print(f'{s["startedAt"][:16].replace("T", " ")}  {s["actualSeconds"]//60:>3} min  {s["goal"]}  ({mark})')
PY
    ;;

  mode)
    need_app || exit 1
    case "${2:-}" in
      pomodoro|timer) post cmd=mode "id=$2"; echo "mode: $2" ;;
      *) echo "usage: focuspill mode <pomodoro|timer>" ;;
    esac
    ;;

  sound)
    need_app || exit 1
    case "${2:-}" in
      on|off) post cmd=sound "on=$([ "$2" = on ] && echo true || echo false)"; echo "sound: $2" ;;
      *) echo "usage: focuspill sound <on|off>" ;;
    esac
    ;;

  themes)
    need_app || exit 1
    status_json | python3 -c 'import json,sys; d=json.load(sys.stdin); print("\n".join(("* " if t==d.get("theme") else "  ")+t for t in d.get("themes",[])))'
    ;;

  theme)
    need_app || exit 1
    post cmd=theme "id=${2:?usage: focuspill theme <id>}"
    echo "switched to ${2}"
    ;;

  install-theme)
    source="${2:?usage: focuspill install-theme <folder or zip>}"
    mkdir -p "$THEMES"
    if [ -d "$source" ]; then
      name="$(basename "${source%/}")"
      rm -rf "$THEMES/$name"
      cp -R "${source%/}" "$THEMES/$name"
    else
      cp "$source" "$THEMES/"
    fi
    echo "installed into $THEMES"
    if running; then
      sleep 2   # the app watches the folder and reloads on its own
      id="$(python3 -c "import json,sys;print(json.load(open('$THEMES/$(basename "${source%/}")/theme.json'))['id'])" 2>/dev/null || true)"
      [ -n "$id" ] && post cmd=theme "id=$id" && echo "switched to $id"
    fi
    ;;

  open-themes) echo "$THEMES" ;;

  *)
    sed -n '2,20p' "$0" | sed 's/^# \{0,1\}//'
    ;;
esac
