#!/usr/bin/env bash
# sidepanel-toggle.sh — window-mode control for the extension instance.
#
# Purpose:
#   App control: drives chrome.sidePanel for one extension instance's CDP
#   browser (status/open/close/toggle/cycle) without global shortcuts.
#
# Inputs: subcommand (status|open|close|toggle|cycle, default status);
#   --cdp-port <port> (required), --ext-id <id>, --agent-dir <dir>,
#   --settle-ms <ms>, --dapp-url <url>; env REPO, CDP_PORT, EXT_ID,
#   SETTLE_MS, AGENT_DIR, EXTENSION_START_URL.
# Outputs: status/progress lines on stdout.
#   Exit 0 — action done; 1 — repo/args missing or action failed;
#   2 — non-numeric port / CDP unreachable.
# Never touches: other instances' browsers (single CDP port scope); the
#   dist build (read-only).
#
# Usage:
#   bash sidepanel-toggle.sh status --cdp-port 6663
#   bash sidepanel-toggle.sh open   --cdp-port 6663 [--ext-id <extension id>]
#   bash sidepanel-toggle.sh close  --cdp-port 6663
#   bash sidepanel-toggle.sh toggle --cdp-port 6663
#   bash sidepanel-toggle.sh cycle  --cdp-port 6663 [--settle-ms 10000]
#
# CDP can inspect, activate, and close sidepanel targets. Opening the side panel
# is done from a clicked extension-page button so Chrome treats
# `chrome.sidePanel.open()` as a user gesture while the action remains scoped to
# this slot's CDP browser. This avoids global Alt+Shift+M shortcut ambiguity
# when several Chromium profiles are running.
set -euo pipefail

ACTION="${1:-status}"
if [ "$ACTION" = "-h" ] || [ "$ACTION" = "--help" ]; then
  echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>] [--dapp-url <url>]"
  exit 0
fi
shift || true

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
# shellcheck disable=SC1091
for _hp in "$SCRIPT_DIR/lib/harness-path.sh" "$SCRIPT_DIR/../../adapters/shared/harness-path.sh" "$SCRIPT_DIR/../shared/harness-path.sh"; do
  [ -f "$_hp" ] && { . "$_hp"; break; }
done
unset _hp
REPO="${REPO:-}"
if [ -z "$REPO" ]; then
  d="$SCRIPT_DIR"
  while [ "$d" != "/" ]; do
    if [ -f "$d/package.json" ] && [ -d "$d/dist/chrome" ]; then
      REPO="$d"
      break
    fi
    d="$(dirname "$d")"
  done
fi
if [ -z "$REPO" ]; then
  echo "FAIL: could not resolve repo root from ${SCRIPT_DIR}; pass REPO=/path/to/metamask-extension" >&2
  exit 1
fi
cd "$REPO"
if command -v recipe_runtime_dir >/dev/null 2>&1; then
  RUNTIME_DIR="$(recipe_runtime_dir)"
else
  RUNTIME_DIR="${RECIPE_RUNTIME_DIR:-temp/recipe/runtime}"
fi
RUNTIME_DIST_DIR="${RECIPE_RUNTIME_DIST_DIR:-runtime-dist}"

CDP_PORT="${CDP_PORT:-}"
EXT_ID="${EXT_ID:-}"
SETTLE_MS="${SETTLE_MS:-10000}"
AGENT_DIR="${AGENT_DIR:-$SCRIPT_DIR}"
DAPP_URL="${EXTENSION_START_URL:-https://metamask.github.io/test-dapp/}"

while [[ $# -gt 0 ]]; do
  case "$1" in
    --cdp-port) CDP_PORT="$2"; shift 2 ;;
    --ext-id) EXT_ID="$2"; shift 2 ;;
    --agent-dir) AGENT_DIR="$2"; shift 2 ;;
    --settle-ms) SETTLE_MS="$2"; shift 2 ;;
    --dapp-url) DAPP_URL="$2"; shift 2 ;;
    -h|--help)
      echo "Usage: sidepanel-toggle.sh <status|open|close|toggle|cycle> --cdp-port <port> [--ext-id <id>] [--agent-dir <dir>] [--settle-ms <ms>] [--dapp-url <url>]"
      exit 0
      ;;
    *) echo "unknown arg: $1" >&2; exit 1 ;;
  esac
done

if [ -z "$CDP_PORT" ]; then
  echo "FAIL: --cdp-port is required" >&2
  exit 1
fi
if ! [[ "$CDP_PORT" =~ ^[0-9]+$ ]]; then
  echo "FAIL: CDP port \"$CDP_PORT\" must be numeric" >&2
  exit 2
fi
case "$DAPP_URL" in
  http://*|https://*) ;;
  *) echo "FAIL: --dapp-url must use http or https" >&2; exit 1 ;;
esac
if ! curl -s -m 3 "http://127.0.0.1:${CDP_PORT}/json/version" >/dev/null 2>&1; then
  echo "FAIL: CDP not reachable on port ${CDP_PORT}" >&2
  exit 2
fi

json_list() {
  curl -s "http://127.0.0.1:${CDP_PORT}/json/list"
}

focus_browser() {
  [ "${MM_HARNESS_FOCUS_BROWSER:-}" = "1" ]
}

find_sidepanel_id() {
  local ext_id
  ext_id="$(resolve_ext_id)"
  json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); ids=[t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html')]; print(ids[0] if ids else '')"
}

find_dapp_id() {
  json_list | DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); wanted=os.environ['DAPP_URL'].rstrip('/'); ids=[t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').rstrip('/')==wanted]; print(ids[0] if ids else '')"
}

surface_counts() {
  local ext_id
  ext_id="$(resolve_ext_id)"
  json_list | EXT_ID="$ext_id" DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); wanted=os.environ['DAPP_URL'].rstrip('/'); pages=[t for t in d if t.get('type')=='page']; homes=sum(t.get('url','').startswith('chrome-extension://'+ext+'/home.html') for t in pages); panels=sum(t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html') for t in pages); dapps=sum(t.get('url','').rstrip('/')==wanted for t in pages); products=sum(t.get('url','').startswith('chrome-extension://'+ext+'/') for t in pages); print(homes,panels,dapps,products)"
}

ensure_dapp_tab() {
  [ -n "$(find_dapp_id)" ] && return 0
  local encoded
  encoded="$(DAPP_URL="$DAPP_URL" python3 -c "import os,urllib.parse; print(urllib.parse.quote(os.environ['DAPP_URL'], safe=''))")"
  curl -fsS -X PUT "http://127.0.0.1:${CDP_PORT}/json/new?${encoded}" >/dev/null
}

ensure_home_page() {
  local ext_id encoded home_ids first
  ext_id="$(resolve_ext_id)"
  [ -n "$ext_id" ] || { echo "FAIL: could not resolve extension id for CDP ${CDP_PORT}" >&2; return 1; }
  home_ids="$(json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); print('\\n'.join(t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/home.html')))" )"
  if [ -n "$home_ids" ]; then
    first=1
    while IFS= read -r target_id; do
      [ -n "$target_id" ] || continue
      if [ "$first" -eq 1 ]; then first=0; continue; fi
      curl -fsS "http://127.0.0.1:${CDP_PORT}/json/close/${target_id}" >/dev/null
    done <<< "$home_ids"
    return 0
  fi
  encoded="$(EXT_ID="$ext_id" python3 -c "import os,urllib.parse; print(urllib.parse.quote('chrome-extension://'+os.environ['EXT_ID']+'/home.html#/', safe=''))")"
  curl -fsS -X PUT "http://127.0.0.1:${CDP_PORT}/json/new?${encoded}" >/dev/null
}

resolve_ext_id() {
  if [[ "$EXT_ID" =~ ^[a-p]{32}$ ]]; then
    printf '%s\n' "$EXT_ID"
    return
  fi

  local manifest_id
  manifest_id="$(
    SCRIPT_DIR="$SCRIPT_DIR" REPO="$REPO" RUNTIME_DIR="$RUNTIME_DIR" RUNTIME_DIST_DIR="$RUNTIME_DIST_DIR" node <<'NODE' 2>/dev/null || true
const path = require('node:path');
const { extensionIdFromManifestFile } = require(path.join(process.env.SCRIPT_DIR, 'lib/extension-id.cjs'));
const id = extensionIdFromManifestFile(path.join(process.env.REPO, process.env.RUNTIME_DIR, process.env.RUNTIME_DIST_DIR, 'manifest.json'));
if (id) process.stdout.write(id);
NODE
  )"
  if [[ "$manifest_id" =~ ^[a-p]{32}$ ]]; then
    printf '%s\n' "$manifest_id"
    return
  fi

  for idf in "$REPO/$RUNTIME_DIR/extension.id" "$AGENT_DIR/extension.id"; do
    if [ -f "$idf" ]; then
      local marker_id
      marker_id="$(tr -d '[:space:]' < "$idf")"
      if [[ "$marker_id" =~ ^[a-p]{32}$ ]]; then
        printf '%s\n' "$marker_id"
        return
      fi
    fi
  done
  json_list | python3 -c "import json,re,sys; d=json.load(sys.stdin); ids=[]; [ids.extend(re.findall(r'^chrome-extension://([^/]+)/', t.get('url',''))) for t in d]; print(ids[0] if ids else '')"
}

activate_sandbox_page() {
  local ext_id target_id
  ext_id="$(resolve_ext_id)"
  target_id="$(
    EXT_ID="$ext_id" json_list | python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); pages=[t for t in d if t.get('type')=='page']; target=next((t for t in pages if ext and t.get('url','').startswith('chrome-extension://'+ext+'/') and 'sidepanel.html' not in t.get('url','')), None) or next((t for t in pages if t.get('url','').startswith('http://') or t.get('url','').startswith('https://')), None) or (pages[0] if pages else None); print(target.get('id','') if target else '')"
  )"
  if [ -n "$target_id" ] && focus_browser; then
    curl -s "http://127.0.0.1:${CDP_PORT}/json/activate/${target_id}" >/dev/null || true
  fi
}

now_ms() {
  python3 -c "import time; print(int(time.time() * 1000))"
}

wait_for_sidepanel() {
  local start deadline now
  start="$(now_ms)"
  deadline=$((start + SETTLE_MS))
  while :; do
    if [ -n "$(find_sidepanel_id)" ]; then
      return 0
    fi
    now="$(now_ms)"
    if [ "$now" -ge "$deadline" ]; then
      return 1
    fi
    sleep 0.2
  done
}

wait_for_surface_invariant() {
  local expected="$1" start deadline now home_count panel_count dapp_count product_count
  start="$(now_ms)"
  deadline=$((start + SETTLE_MS))
  while :; do
    read -r home_count panel_count dapp_count product_count <<< "$(surface_counts)"
    if [ "$expected" = "fullscreen" ] && [ "$home_count" -eq 1 ] && [ "$panel_count" -eq 0 ] && [ "$product_count" -eq 1 ]; then return 0; fi
    if [ "$expected" = "sidepanel" ] && [ "$home_count" -eq 0 ] && [ "$panel_count" -eq 1 ] && [ "$dapp_count" -eq 1 ] && [ "$product_count" -eq 1 ]; then return 0; fi
    now="$(now_ms)"
    [ "$now" -lt "$deadline" ] || return 1
    sleep 0.2
  done
}

print_targets() {
  json_list | python3 -c "import json,sys; d=json.load(sys.stdin); [print(f'  {t.get(\"type\",\"?\")[:18]:18s} {t.get(\"url\",\"\")[:100]}') for t in d]"
}

activate_dapp_tab() {
  local target_id
  target_id="$(
    json_list | DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); wanted=os.environ['DAPP_URL'].rstrip('/'); pages=[t for t in d if t.get('type')=='page']; target=next((t for t in pages if t.get('url','').rstrip('/')==wanted), None); print(target.get('id','') if target else '')"
  )"
  if [ -n "$target_id" ] && focus_browser; then
    curl -s "http://127.0.0.1:${CDP_PORT}/json/activate/${target_id}" >/dev/null || true
  fi
}

cleanup_sidepanel_tabs() {
  local ext_id close_ids
  ext_id="$(resolve_ext_id)"
  [ -n "$ext_id" ] || return 0
  close_ids="$(
    json_list | EXT_ID="$ext_id" DAPP_URL="$DAPP_URL" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); wanted=os.environ['DAPP_URL'].rstrip('/'); has_panel=any(t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html') for t in d); has_dapp=any(t.get('type')=='page' and t.get('url','').rstrip('/')==wanted for t in d); print('\\n'.join(t.get('id','') for t in d if has_panel and has_dapp and t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/home.html')))"
  )"
  if [ -n "$close_ids" ]; then
    while IFS= read -r target_id; do
      [ -n "$target_id" ] || continue
      curl -s "http://127.0.0.1:${CDP_PORT}/json/close/${target_id}" >/dev/null || true
    done <<< "$close_ids"
  fi
  activate_dapp_tab
}

dedupe_sidepanel_tabs() {
  local ext_id sidepanel_ids first
  ext_id="$(resolve_ext_id)"
  sidepanel_ids="$(json_list | EXT_ID="$ext_id" python3 -c "import json,os,sys; d=json.load(sys.stdin); ext=os.environ.get('EXT_ID',''); print('\\n'.join(t.get('id','') for t in d if t.get('type')=='page' and t.get('url','').startswith('chrome-extension://'+ext+'/sidepanel.html')))" )"
  first=1
  while IFS= read -r target_id; do
    [ -n "$target_id" ] || continue
    if [ "$first" -eq 1 ]; then first=0; continue; fi
    curl -fsS "http://127.0.0.1:${CDP_PORT}/json/close/${target_id}" >/dev/null
  done <<< "$sidepanel_ids"
}

status_sidepanel() {
  local sp
  sp="$(find_sidepanel_id)"
  if [ -n "$sp" ]; then
    echo "[sidepanel] open target ${sp}"
  else
    echo "[sidepanel] closed"
  fi
}

close_sidepanel() {
  local sp
  sp="$(find_sidepanel_id)"
  if [ -z "$sp" ]; then
    echo "[sidepanel] already closed"
    ensure_home_page
    wait_for_surface_invariant fullscreen || { echo "FAIL: fullscreen surface did not converge to one Home target" >&2; return 1; }
    return 0
  fi
  curl -s "http://127.0.0.1:${CDP_PORT}/json/close/${sp}" >/dev/null
  ensure_home_page
  wait_for_surface_invariant fullscreen || { echo "FAIL: fullscreen surface did not converge to one Home target" >&2; return 1; }
  echo "[sidepanel] closed target ${sp}"
}

open_sidepanel() {
  ensure_dapp_tab
  if [ -n "$(find_sidepanel_id)" ]; then
    dedupe_sidepanel_tabs
    cleanup_sidepanel_tabs
    wait_for_surface_invariant sidepanel || { echo "FAIL: sidepanel surface did not converge to one panel and a dapp host" >&2; return 1; }
    echo "[sidepanel] already open"
    return 0
  fi

  activate_sandbox_page

  local ext_id
  ext_id="$(resolve_ext_id)"
  if [ -z "$ext_id" ]; then
    echo "FAIL: could not resolve extension id for CDP ${CDP_PORT}" >&2
    exit 4
  fi

CDP_PORT="$CDP_PORT" EXT_ID="$ext_id" SCRIPT_DIR="$SCRIPT_DIR" node <<'NODE'
const path = require('node:path');
const {
  connectBrowserViaCdp,
  evaluatePageViaCdp,
} = require(path.join(process.env.SCRIPT_DIR, 'lib/playwright-cdp.cjs'));

async function clickVisibleSafeHit(page, selector, successSelector, timeoutMs) {
  const deadline = Date.now() + timeoutMs;
  while (Date.now() < deadline) {
    if (await page.locator(successSelector).isVisible().catch(() => false)) return;
    const candidate = page.locator(selector);
    const count = await candidate.count();
    if (count > 1) throw new Error(`expected one visible ${selector} control, found ${count}`);
    if (count === 1 && await candidate.isVisible().catch(() => false)) {
      try {
        const point = await evaluatePageViaCdp(page, (candidateSelector) => {
          const matches = [...document.querySelectorAll(candidateSelector)];
          if (matches.length !== 1) return null;
          const [element] = matches;
          const rect = element.getBoundingClientRect();
          const points = [
            [rect.left + rect.width / 2, rect.top + rect.height / 2],
            [rect.left + rect.width / 4, rect.top + rect.height / 2],
            [rect.left + rect.width * 3 / 4, rect.top + rect.height / 2],
            [rect.left + rect.width / 2, rect.top + rect.height / 4],
            [rect.left + rect.width / 2, rect.top + rect.height * 3 / 4],
          ];
          const safe = points.find(([x, y]) => {
            const hit = document.elementFromPoint(x, y);
            return hit === element || Boolean(hit && element.contains(hit));
          });
          return safe ? { x: safe[0], y: safe[1] } : null;
        }, selector);
        if (point) await page.mouse.click(point.x, point.y);
      } catch (error) {
        if (!/detached|not attached|execution context was destroyed/iu.test(String(error?.message || error))) throw error;
      }
    }
    await page.waitForTimeout(50);
  }
  throw new Error(`visible ${selector} control did not activate within ${timeoutMs}ms`);
}

(async () => {
  const port = process.env.CDP_PORT;
  const extId = process.env.EXT_ID;
  const browser = await connectBrowserViaCdp(`http://127.0.0.1:${port}`);
  const context = browser.contexts()[0];
  const page = await context.newPage();
  try {
    await page.goto(`chrome-extension://${extId}/popup-init.html`, {
      waitUntil: 'domcontentloaded',
      timeout: 15000,
    });
    if (process.env.MM_HARNESS_FOCUS_BROWSER === '1') {
      await page.bringToFront();
    }
    await clickVisibleSafeHit(
      page,
      '[data-testid="account-options-menu-button"]',
      '[data-testid="global-menu-toggle-view"]',
      5000,
    );
    const toggle = page.locator('[data-testid="global-menu-toggle-view"]');
    await toggle.click({ timeout: 5000 });
  } finally {
    if (!page.isClosed()) await page.close();
  }
  if (typeof browser.disconnect === 'function') {
    await browser.disconnect();
  } else {
    await browser.close();
  }
})().catch((error) => {
  console.error(`FAIL: ${error.message || error}`);
  process.exit(4);
});
NODE

  if wait_for_sidepanel; then
    dedupe_sidepanel_tabs
    cleanup_sidepanel_tabs
    wait_for_surface_invariant sidepanel || { echo "FAIL: sidepanel surface did not converge to one panel and a dapp host" >&2; return 1; }
    echo "[sidepanel] opened"
    return 0
  fi

  echo "FAIL: sidepanel target did not appear within ${SETTLE_MS}ms after Chrome accepted the open request" >&2
  echo "      Current CDP targets:" >&2
  print_targets >&2
  exit 3
}

case "$ACTION" in
  status) status_sidepanel ;;
  open) open_sidepanel ;;
  close) close_sidepanel ;;
  toggle)
    if [ -n "$(find_sidepanel_id)" ]; then
      close_sidepanel
    else
      open_sidepanel
    fi
    ;;
  cycle)
    close_sidepanel
    sleep 1
    open_sidepanel
    ;;
  *)
    echo "Usage: bash sidepanel-toggle.sh {status|open|close|toggle|cycle} --cdp-port PORT [--ext-id ID] [--agent-dir DIR] [--settle-ms N]" >&2
    exit 1
    ;;
esac
