#!/bin/zsh
# chrome-extension-status — single-shot diagnostic for any Chrome/Edge extension
# in the user's main browser. Returns enabled state + disable reason + a
# human-actionable next step. Designed so AI agents stop running into the
# "extension-ping timed out" wall and immediately know what's wrong.
#
# Usage:
#   chrome-extension-status [EXT_ID]
#                           [--browser chrome|edge]
#                           [--all] [--load-path PATH]
#                           [--json] [--pretty]
#
# Defaults:
#   - With no args, reports HelperAI's chrome-debugger-extension
#     (oobplhedlijogkaocifdakbfkpagbpik).
#   - --all dumps every loaded extension.
#   - --load-path falls back to the standard ChromeAI / HelperAI dirs to
#     resolve EXT_ID from a manifest if not given.
#
# Output (per extension):
#   {
#     "id": "...",
#     "name": "...",
#     "version": "...",
#     "enabled": true|false,
#     "state": "ENABLED" | "DISABLED" | "TERMINATED" | ...
#     "disable_reasons": ["USER_ACTION", ...],
#     "service_worker_started": true|false,
#     "next_step": "..."   // human-actionable advice
#   }
#
# next_step decision tree:
#   - enabled + sw_started        → "ready"
#   - enabled + !sw_started       → "open chrome://extensions or edge://extensions and click Service Worker (background page) to wake; usually auto-wakes on first message"
#   - !enabled, USER_ACTION       → "manually toggle on at chrome://extensions/?id=<id> or edge://extensions/?id=<id>; programmatic enable is BLOCKED by browser anti-phishing"
#   - !enabled, CORRUPT_MANIFEST  → "run `bin/background-web-control reload-unpacked-extension --path …` to repair"
#   - !enabled, other             → "see chrome://extensions for details"
set -euo pipefail
BIN_DIR=${0:A:h}

usage() {
  awk 'NR == 1 { next } /^set -/ { exit } { sub(/^# ?/, ""); print }' "${(%):-%x}"
}

DEFAULT_HELPERAI_EXT='oobplhedlijogkaocifdakbfkpagbpik'

ext_id=""
report_all=0
json_out=0
pretty=0
load_path=""
browser="Google Chrome"
extensions_url="chrome://extensions"

normalize_browser() {
  local raw="${1:-Google Chrome}"
  local key="${raw:l}"
  key="${key// /-}"
  key="${key//_/-}"
  case "$key" in
    chrome|google-chrome|googlechrome) echo "Google Chrome" ;;
    edge|microsoft-edge|microsoftedge|msedge) echo "Microsoft Edge" ;;
    *) echo "$raw" ;;
  esac
}

extension_manager_url_for_browser() {
  case "$(normalize_browser "$1")" in
    "Microsoft Edge") echo "edge://extensions" ;;
    *) echo "chrome://extensions" ;;
  esac
}

while [[ $# -gt 0 ]]; do
  case "$1" in
    --browser)    browser="$(normalize_browser "$2")"; extensions_url="$(extension_manager_url_for_browser "$2")"; shift 2;;
    --all)        report_all=1; shift;;
    --json)       json_out=1; shift;;
    --pretty)     pretty=1; shift;;
    --load-path)  load_path="$2"; shift 2;;
    -h|--help)    usage; exit 0;;
    *)
      if [[ -z "$ext_id" ]]; then ext_id="$1"; shift
      else echo "chrome-extension-status: unknown arg: $1" >&2; exit 2
      fi;;
  esac
done

if [[ -z "$ext_id" && $report_all -eq 0 ]]; then
  ext_id="$DEFAULT_HELPERAI_EXT"
fi

# Need a browser extension-manager tab to query developerPrivate. Make one if missing.
HAS_EXT_TAB=$(/usr/bin/osascript <<OSA 2>&1
tell application "$browser"
  repeat with w in windows
    repeat with t in tabs of w
      if URL of t starts with "$extensions_url" then
        return "yes"
      end if
    end repeat
  end repeat
  return "no"
end tell
OSA
)

if [[ "$HAS_EXT_TAB" != "yes" ]]; then
  /usr/bin/osascript >/dev/null 2>&1 <<OSA
tell application "$browser"
  if (count windows) is 0 then return
  set newTab to make new tab at end of tabs of front window with properties {URL:"$extensions_url/"}
end tell
OSA
  /bin/sleep 2
fi

# Build the JS payload
JS_FILE=$(/usr/bin/mktemp -t chromeextstatus.XXXXXX.js)
trap "rm -f $JS_FILE" EXIT

WANT_ID="${ext_id:-}"
WANT_ALL=$report_all

/bin/cat > "$JS_FILE" <<JS
window.__chromeExtStatus = {pending: true};
chrome.developerPrivate.getExtensionsInfo({}, function(items) {
  var wantAll = ${WANT_ALL} === 1;
  var wantId = "${WANT_ID}";
  if (wantAll) {
    var arr = [];
    for (var k = 0; k < items.length; k++) {
      var it = items[k];
      arr.push({id: it.id, name: it.name, version: it.version, state: it.state, enabled: it.state === "ENABLED", disable_reasons: it.disableReasons || []});
    }
    window.__chromeExtStatus = {pending: false, extensions: arr};
    return;
  }
  var ext = items.find(function(x){return x.id === wantId;});
  if (!ext) { window.__chromeExtStatus = {pending: false, error: "extension not found", id: wantId}; return; }
  var nextStep = "ready";
  if (ext.state !== "ENABLED") {
    var r = ext.disableReasons || [];
    if (r.indexOf("CORRUPT_MANIFEST") >= 0 || r.indexOf("CORRUPT") >= 0) nextStep = "run reload-unpacked-extension to repair (CORRUPT)";
    else if (r.indexOf("USER_ACTION") >= 0) nextStep = "manually toggle ON at ${extensions_url}/?id=" + ext.id + " (programmatic enable is BLOCKED).";
    else nextStep = "open ${extensions_url}/?id=" + ext.id;
  }
  window.__chromeExtStatus = {pending: false, id: ext.id, name: ext.name, version: ext.version, state: ext.state, enabled: ext.state === "ENABLED", disable_reasons: ext.disableReasons || [], install_warnings: (ext.installWarnings || []).map(function(w){return w.message;}), service_worker_started: !!((ext.views || []).find(function(v){return v.type === "EXTENSION_SERVICE_WORKER_BACKGROUND" && v.renderProcessId > 0;})), next_step: nextStep};
});
({invoked: true})
JS

# Phase 1: invoke
"$BIN_DIR/background-web-control" --browser "$browser" eval --url-contains "$extensions_url" --js-file "$JS_FILE" >/dev/null 2>&1 || true

# Phase 2: poll for result up to 15s (Chrome may rate-limit developerPrivate
# calls when invoked rapidly across multiple tools — give it room)
POLL_FILE=$(/usr/bin/mktemp -t chromeextstatuspoll.XXXXXX.js)
echo 'window.__chromeExtStatus' > "$POLL_FILE"
RAW=""
for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
  /bin/sleep 1
  RAW=$("$BIN_DIR/background-web-control" --browser "$browser" eval --url-contains "$extensions_url" --js-file "$POLL_FILE" 2>&1 || true)
  if ! echo "$RAW" | /usr/bin/grep -q '"pending": true'; then
    break
  fi
done
rm -f "$POLL_FILE"

# Parse out the value — extract the nested {...} after "value":
PARSED=$(echo "$RAW" | /usr/bin/python3 -c '
import sys, json, re
text = sys.stdin.read()
# Find "value": followed by an object/array balanced by braces
i = text.find("\"value\":")
if i < 0:
    print(json.dumps({"_no_value_match": True, "_raw": text[:500]}))
    sys.exit(0)
# Skip whitespace after the colon
j = i + len("\"value\":")
while j < len(text) and text[j] in " \t\n\r":
    j += 1
if j >= len(text):
    print(json.dumps({"_no_value_match": True, "_raw": text[:500]}))
    sys.exit(0)
opener = text[j]
if opener not in "{[":
    # primitive
    rest = text[j:]
    end = rest.find("\n  }")
    seg = rest[:end] if end >= 0 else rest
    print(seg.strip().rstrip(","))
    sys.exit(0)
closer = "}" if opener == "{" else "]"
depth = 0
in_str = False
escape = False
end_idx = -1
for k in range(j, len(text)):
    ch = text[k]
    if in_str:
        if escape:
            escape = False
        elif ch == "\\":
            escape = True
        elif ch == "\"":
            in_str = False
    else:
        if ch == "\"":
            in_str = True
        elif ch == opener:
            depth += 1
        elif ch == closer:
            depth -= 1
            if depth == 0:
                end_idx = k + 1
                break
if end_idx < 0:
    print(json.dumps({"_no_value_match": True, "_raw": text[:500]}))
    sys.exit(0)
try:
    obj = json.loads(text[j:end_idx])
    print(json.dumps(obj, ensure_ascii=False))
except Exception as exc:
    print(json.dumps({"_parse_error": str(exc), "_raw": text[j:end_idx][:500]}))
' 2>&1 || true)

if ! echo "$PARSED" | /usr/bin/python3 -m json.tool >/dev/null 2>&1 || echo "$PARSED" | /usr/bin/grep -q '"_no_value_match"\|"_parse_error"'; then
  if [[ $report_all -eq 0 && "$ext_id" == "$DEFAULT_HELPERAI_EXT" ]]; then
    INSPECT_RAW=$("$BIN_DIR/background-web-control" --browser "$browser" inspect-unpacked-extension --path "$BIN_DIR/../components/macos/browser-automation/chrome-debugger-extension" --pretty 2>/dev/null || true)
    PARSED=$(printf '%s' "$INSPECT_RAW" | /usr/bin/python3 -c '
import sys, json

raw = sys.stdin.read()
browser = sys.argv[1]
extensions_url = sys.argv[2]
try:
    data = json.loads(raw)
except Exception as exc:
    print(json.dumps({
        "error": f"{extensions_url} developerPrivate status failed and profile fallback failed",
        "parse_error": str(exc),
        "raw": raw[:500],
    }, ensure_ascii=False))
    raise SystemExit(0)

cfg = data.get("config") or {}
raw_reasons = cfg.get("disable_reasons") or []
if not isinstance(raw_reasons, list):
    raw_reasons = [raw_reasons]
extension_id = data.get("extensionId") or ""
reason_names = {
    1: "USER_ACTION",
    2: "PERMISSIONS_INCREASE",
    4: "RELOAD",
    8: "UNSUPPORTED_REQUIREMENT",
    16: "CORRUPTED",
    32: "REMOTE_INSTALL",
    64: "EXTERNAL_EXTENSION",
}
disable_reasons = [reason_names.get(x, f"UNKNOWN_{x}") for x in raw_reasons]
state = "DISABLED" if disable_reasons else ("ENABLED" if data.get("installed") else "NOT_INSTALLED")
if not data.get("installed"):
    next_step = f"install the HelperAI chrome-debugger-extension in the main {browser} profile"
elif disable_reasons:
    next_step = (
        f"extension is disabled in main {browser} profile; programmatic enable/reload is blocked by the browser. "
        f"Use the existing main {browser} {extensions_url}/?id={extension_id} "
        "to enable it or inspect the disable reason."
    )
else:
    next_step = f"ready from profile state; if runtime still fails, wake/reload the service worker in main {browser}"

print(json.dumps({
    "id": extension_id,
    "name": "HelperAI Chrome debugger extension",
    "version": cfg.get("manifest", {}).get("version") or cfg.get("version") or "",
    "state": state,
    "enabled": state == "ENABLED",
    "disable_reasons": disable_reasons,
    "service_worker_started": bool(cfg.get("has_started_service_worker")),
    "source": "Secure Preferences fallback",
    "next_step": next_step,
}, ensure_ascii=False))
' "$browser" "$extensions_url")
  fi
fi

if [[ $pretty -eq 1 ]]; then
  echo "$PARSED" | /usr/bin/python3 -m json.tool
elif [[ $json_out -eq 1 ]]; then
  echo "$PARSED"
else
  # Human-friendly output
  HUMAN_SCRIPT=$(/usr/bin/mktemp -t chromeextstatusprint.XXXXXX.py)
  /bin/cat > "$HUMAN_SCRIPT" <<'PYEOF'
import sys, json
data = json.loads(sys.stdin.read())
def line(item):
    print("{}  [{}]  {} v{}".format(item["id"], item["state"], item["name"], item["version"]))
    dr = item.get("disable_reasons")
    if dr:
        if isinstance(dr, dict):
            active = [k for k, v in dr.items() if v]
            if active:
                print("  disable_reasons: " + ", ".join(active))
        elif dr:
            print("  disable_reasons: " + str(dr))
    if item.get("install_warnings"):
        print("  install_warnings: " + str(item["install_warnings"]))
    if "service_worker_started" in item:
        print("  service_worker_started: " + str(item["service_worker_started"]))
    if "next_step" in item:
        print("  -> " + item["next_step"])
if "error" in data:
    print("error: " + str(data["error"]))
    sys.exit(1)
if data.get("pending") is True:
    print("ERROR: result still pending after polling — chrome.developerPrivate.getExtensionsInfo callback didn't fire in time. Either chrome://extensions tab isn't fully loaded, or the API is being rate-limited.")
    sys.exit(1)
if "extensions" in data:
    for it in data["extensions"]:
        line(it); print()
elif "id" in data:
    line(data)
else:
    print("UNEXPECTED RESULT:")
    print(json.dumps(data, indent=2, ensure_ascii=False))
    sys.exit(1)
PYEOF
  echo "$PARSED" | /usr/bin/python3 "$HUMAN_SCRIPT"
  rm -f "$HUMAN_SCRIPT"
fi
