#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat >&2 <<'EOF'
Usage:
  mme-recipe                         interactive menu
  mme-recipe prepare                 install harness and optionally validate CDP health
    --target <repo> --cdp-port <port> --runtime-dir <dir>
  mme-recipe status                  runtime-health (CDP/live)
  mme-recipe runtime-status [--json] structured decision/health status
  mme-recipe decision                runtime-decision (what next?)
  mme-recipe ready                   ensure one healthy extension home tab
  mme-recipe watch                   start/reuse one webpack watcher for fast refresh
  mme-recipe stop                    stop this checkout's watcher
  mme-recipe reopen                  reopen/reload browser via harness helper
  mme-recipe refresh                 fast refresh using an active watcher only
  mme-recipe refresh:once            one-shot refresh-build helper, then ready
  mme-recipe rebuild                 alias for refresh
  mme-recipe sidepanel [cycle|open]  use harness sidepanel helper
  mme-recipe actions [--json]
  mme-recipe doctor [--json]
  mme-recipe run <recipe.json> [args] run recipe with detected CDP/env
  mme-recipe <recipe.json> [args]    shortcut for run

Detects Extension root, recipe runtime context, CDP_PORT, WATCHER_PORT.
EOF
}

script_dir="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd -P)"
runner="$script_dir/metamask-recipe"
runner_root="$(cd "$script_dir/.." && pwd -P)"
# shellcheck disable=SC1091
. "$runner_root/scripts/lib/harness-path.sh"
install_script="$runner_root/scripts/inject-extension-harness.mjs"

find_extension_root() {
  local dir="$(pwd -P)"
  while [ "$dir" != "/" ]; do
    if [ -f "$dir/package.json" ] && { grep -q 'metamask-extension' "$dir/package.json" 2>/dev/null || [[ "$(basename "$dir")" == metamask-extension* ]]; }; then
      printf '%s\n' "$dir"
      return 0
    fi
    dir="$(dirname "$dir")"
  done
  return 1
}

json_field() {
  local file="$1" field="$2"
  [ -f "$file" ] || return 1
  node - "$file" "$field" <<'NODE'
const fs = require('fs');
const [file, field] = process.argv.slice(-2);
try {
  const data = JSON.parse(fs.readFileSync(file, 'utf8'));
  const value = field.split('.').reduce((node, key) => node?.[key], data);
  if (value !== undefined && value !== null && value !== '') process.stdout.write(String(value));
} catch { process.exit(1); }
NODE
}

load_env_file() {
  local file="$1"
  [ -f "$file" ] || return 0
  while IFS= read -r line || [ -n "$line" ]; do
    line="${line#export }"
    case "$line" in
      CDP_PORT=*|RECIPE_CDP_PORT=*|WATCHER_PORT=*|RECIPE_WATCHER_PORT=*)
        key="${line%%=*}"
        val="${line#*=}"
        val="${val%%#*}"
        val="$(printf '%s' "$val" | sed -e 's/^[[:space:]]*//' -e 's/[[:space:]]*$//' -e 's/^"//' -e 's/"$//' -e "s/^'//" -e "s/'$//")"
        [ -n "${!key:-}" ] || export "$key=$val"
        ;;
    esac
  done < "$file"
}

infer_slot_number() {
  local root="$1" base
  base="$(basename "$root")"
  [[ "$base" =~ -([0-9]+)$ ]] && printf '%s\n' "${BASH_REMATCH[1]}"
}

init_context() {
  root="$(find_extension_root)" || { echo "mme-recipe: run from a metamask-extension checkout" >&2; exit 2; }
  runtime_dir="$(recipe_runtime_dir)"
  harness_root="$(harness_root)"
  export RECIPE_RUNTIME_DIR="$runtime_dir" RECIPE_HARNESS_ROOT="$harness_root"
  runtime_json="$root/$runtime_dir/recipe-runtime.json"
  [ -f "$runtime_json" ] || runtime_json="$root/$runtime_dir/agentic-runtime.json"
  load_env_file "$root/$runtime_dir/recipe-runtime.env"
  load_env_file "$root/$runtime_dir/agentic-runtime.env"
  load_env_file "$root/.js.env"
  load_env_file "$root/.env"
  load_env_file "$root/.env.local"
  [ -n "${CDP_PORT:-}" ] || CDP_PORT="$(json_field "$runtime_json" cdpPort || true)"
  [ -n "${WATCHER_PORT:-}" ] || WATCHER_PORT="$(json_field "$runtime_json" watcherPort || json_field "$runtime_json" devServerPort || true)"
  slot="$(infer_slot_number "$root" || true)"
  if [ -n "$slot" ]; then
    [ -n "${CDP_PORT:-}" ] || CDP_PORT="666$slot"
    [ -n "${WATCHER_PORT:-}" ] || WATCHER_PORT="901$slot"
  fi
  export CDP_PORT RECIPE_CDP_PORT="${CDP_PORT:-}" WATCHER_PORT RECIPE_WATCHER_PORT="${WATCHER_PORT:-}"
  mkdir -p "$root/temp/recipe-artifacts"
}


pretty_json() {
  node -e '
let d="";
process.stdin.on("data", c => d += c).on("end", () => {
  const color = process.env.RECIPE_COLOR === "1";
  const c = (code, s) => color ? `\x1b[${code}m${s}\x1b[0m` : s;
  try {
    const v = JSON.parse(d);
    if (v.decision) {
      console.log(`${c("1;33", "Decision")}: ${c("1;32", v.decision)}${v.reasonCode ? ` ${c("2", "—")} ${c("0;37", v.reasonCode)}` : ""}`);
      if (Array.isArray(v.reasons)) for (const r of v.reasons) console.log(`  ${c("2", "-")} ${r}`);
      return;
    }
    if (v.adapter && Array.isArray(v.actions) && v.actions.every(a => a && a.name)) {
      console.log(`${c("1;36", "Actions")} ${c("2", `(${v.adapter})`)}:`);
      for (const a of v.actions) {
        console.log(`  ${c("1;32", a.name)}${a.description ? ` ${c("2", "—")} ${c("0;37", a.description)}` : ""}`);
      }
      return;
    }
    if (v.status && v.adapter) {
      const status = String(v.status).toUpperCase();
      const code = status === "PASS" || status === "OK" ? "1;32" : status === "FAIL" || status === "ERROR" ? "1;31" : "1;33";
      console.log(`${c(code, status)} ${c("1;36", v.adapter)}${v.compatibilityMode ? ` ${c("2", "—")} ${v.compatibilityMode}` : ""}`);
      return;
    }
    if (v.account || v.route || v.deviceName) {
      console.log(`${c("1;36", "Device")}: ${v.deviceName || "?"}${v.platform ? ` ${c("2", `(${v.platform})`)}` : ""}`);
      console.log(`${c("1;36", "Route")}: ${v.route && v.route.name ? v.route.name : JSON.stringify(v.route || null)}`);
      if (v.account) console.log(`${c("1;36", "Account")}: ${v.account.name || "?"} ${c("2", v.account.address || "")}`);
      return;
    }
    console.log(JSON.stringify(v, null, 2));
  } catch {
    process.stdout.write(d);
  }
})'
}

json_requested() {
  for arg in "$@"; do [ "$arg" = "--json" ] && return 0; done
  return 1
}

color_requested() {
  [ "${RECIPE_NO_COLOR:-}" != "1" ] && { [ "${RECIPE_COLOR:-}" = "1" ] || [ "${FORCE_COLOR:-}" = "1" ] || [ -t 1 ]; }
}

pipe_pretty() {
  if color_requested; then (export RECIPE_COLOR=1; pretty_json); else pretty_json; fi
}

run_pretty() {
  if json_requested "$@"; then "$@"; else "$@" | pipe_pretty; fi
}

run_json_pretty() {
  if json_requested "$@"; then "$@"; else "$@" --json | pipe_pretty; fi
}
log() { printf 'mme-recipe: %s\n' "$*" >&2; }
need_cdp() { [ -n "${CDP_PORT:-}" ] || { echo "mme-recipe: CDP_PORT not found" >&2; exit 2; }; }

prepare_extension() {
  local target="" cdp_port="${CDP_PORT:-${RECIPE_CDP_PORT:-}}" runtime_dir="$(recipe_runtime_dir)" validate=false
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --target) target="$2"; shift 2 ;;
      --cdp-port) cdp_port="$2"; validate=true; shift 2 ;;
      --runtime-dir) runtime_dir="$2"; shift 2 ;;
      --validate) validate=true; shift ;;
      -h|--help)
        echo "Usage: mme-recipe prepare [--target <repo>] [--cdp-port <port>] [--runtime-dir <dir>] [--validate]"
        return 0
        ;;
      *) echo "mme-recipe prepare: unknown arg: $1" >&2; return 2 ;;
    esac
  done
  if [ -n "$target" ]; then
    target="$(cd "$target" && pwd -P)"
    (cd "$target" && RECIPE_RUNTIME_DIR="$runtime_dir" "$install_script" --target "$target")
  else
    init_context
    target="$root"
    RECIPE_RUNTIME_DIR="$runtime_dir" "$install_script" --target "$target"
  fi
  if $validate; then
    [ -n "$cdp_port" ] || { echo "mme-recipe prepare: --validate requires --cdp-port or CDP_PORT" >&2; return 2; }
    run_json_pretty "$runner" runtime-health --adapter extension --target "$target" --cdp-port "$cdp_port"
  fi
}

harness_helper() {
  local field="$1" fallback="$2" rel script manifest
  manifest="$root/$harness_root/extension/manifest.json"
  rel="$(json_field "$manifest" "runtimeHelpers.$field" || true)"
  if [ -n "$rel" ]; then
    case "$rel" in
      /*) script="$rel" ;;
      *) script="$root/$rel" ;;
    esac
  else
    script="$root/$fallback"
  fi
  [ -f "$script" ] || { echo "mme-recipe: missing $field helper. Run: recipe sync" >&2; exit 1; }
  printf '%s\n' "$script"
}

status() { init_context; need_cdp; run_json_pretty "$runner" runtime-health --adapter extension --target "$root" --cdp-port "$CDP_PORT" "$@"; }
decision() { init_context; run_json_pretty "$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} "$@"; }
ready() { init_context; need_cdp; exec "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" "$@"; }

runtime_status() {
  local target="" cdp_port="" runtime_dir_arg=""
  while [ "$#" -gt 0 ]; do
    case "$1" in
      --target) target="$2"; shift 2 ;;
      --cdp-port) cdp_port="$2"; shift 2 ;;
      --runtime-dir) runtime_dir_arg="$2"; shift 2 ;;
      --json) shift ;;
      -h|--help)
        echo "Usage: mme-recipe runtime-status [--target <repo>] [--cdp-port <port>] [--runtime-dir <dir>] [--json]"
        return 0
        ;;
      *) echo "mme-recipe runtime-status: unknown arg: $1" >&2; return 2 ;;
    esac
  done
  [ -n "$target" ] && cd "$target"
  [ -n "$runtime_dir_arg" ] && export RECIPE_RUNTIME_DIR="$runtime_dir_arg"
  [ -n "$cdp_port" ] && export CDP_PORT="$cdp_port" RECIPE_CDP_PORT="$cdp_port"
  init_context
  local runtime_abs="$root/$runtime_dir"
  local status_file="$runtime_abs/runtime-status.json"
  mkdir -p "$runtime_abs"
  local decision_json health_json health_exit=0
  decision_json="$("$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ${WATCHER_PORT:+--watch-log "$root/$runtime_dir/webpack.log"} --json)"
  if [ -n "${CDP_PORT:-}" ]; then
    health_json="$("$runner" runtime-health --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json 2>/dev/null)" || health_exit=$?
  else
    health_json=""
  fi
  node - "$root" "$runtime_dir" "${CDP_PORT:-}" "${WATCHER_PORT:-}" "$status_file" "$decision_json" "$health_json" "$health_exit" <<'NODE'
const fs = require('fs');
const path = require('path');
const [target, runtimeDir, cdpPort, watcherPort, statusFile, decisionText, healthText, healthExit] = process.argv.slice(2);
const parse = (label, text) => {
  if (!text) return { value: null, error: null };
  try {
    return { value: JSON.parse(text), error: null };
  } catch (error) {
    return { value: null, error: `${label} JSON parse failed: ${error.message}` };
  }
};
const decisionResult = parse('runtime-decision', decisionText);
const healthResult = parse('runtime-health', healthText);
const decision = decisionResult.value;
const health = healthResult.value;
const ready = decision?.decision === 'ready' && (!health || health.status === 'PASS');
function fixtureStatus() {
  const candidates = [
    process.env.RECIPE_WALLET_FIXTURE,
    path.join(target, runtimeDir, 'wallet-fixture.json'),
    path.join(target, 'temp/runtime/wallet-fixture.json'),
    cdpPort ? path.join(target, `temp/.recipe-validation-${cdpPort}/wallet-fixture.json`) : null,
    path.join(target, 'temp/.agent-validation/wallet-fixture.json'),
  ].filter(Boolean);
  const fixturePath = candidates.find((candidate) => fs.existsSync(candidate)) || null;
  return {
    present: Boolean(fixturePath),
    path: fixturePath,
    candidates,
  };
}
const status = {
  schemaVersion: 1,
  adapter: 'extension',
  target,
  runtimeDir,
  updatedAt: new Date().toISOString(),
  ready,
  reason: ready ? 'ready' : decision?.reasonCode || health?.findings?.[0] || 'not-ready',
  cdp: { port: cdpPort ? Number(cdpPort) : null, healthExit: Number(healthExit || 0), health },
  watcher: { port: watcherPort ? Number(watcherPort) : null },
  fixture: fixtureStatus(),
  decision,
  errors: [decisionResult.error, healthResult.error].filter(Boolean),
};
fs.writeFileSync(statusFile, `${JSON.stringify(status, null, 2)}\n`);
console.log(JSON.stringify(status, null, 2));
NODE
}

do_reopen() {
  init_context; need_cdp
  local s
  s="$(harness_helper reopenBrowser "$runtime_dir/reopen-browser.sh")"
  log "reopen browser cdp=$CDP_PORT watcher=${WATCHER_PORT:-?}"
  bash "$s" --repo "$root" --cdp-port "$CDP_PORT" --runtime-dir "$runtime_dir" ${WATCHER_PORT:+--watcher-port "$WATCHER_PORT"} "$@"
}

watcher_pids() {
  ps -axo pid=,command= | awk -v root="$root" '
    index($0, root) &&
    $0 ~ /\/(node|yarn)( |$)/ &&
    $0 ~ /(yarn start|webpack --watch|development\/webpack\/launch\.ts --watch)/ { print $1 }
  ' 2>/dev/null || true
}

do_watch() {
  init_context
  local pids log_file
  pids="$(watcher_pids)"
  if [ -n "$pids" ]; then
    log "watcher already running ($(pids_inline "$pids"))"
    return 0
  fi
  log_file="$root/$runtime_dir/webpack.log"
  mkdir -p "$(dirname "$log_file")"
  log "starting watcher port=${WATCHER_PORT:-default} log=${log_file#$root/}"
  (cd "$root" && env ${WATCHER_PORT:+PORT="$WATCHER_PORT"} yarn start > "$log_file" 2>&1 & echo $! > "$root/$runtime_dir/webpack.pid")
  sleep 2
  pids="$(watcher_pids)"
  if [ -n "$pids" ]; then
    log "watcher started ($(pids_inline "$pids")); use: recipe refresh"
    return 0
  fi
  echo "mme-recipe: watcher did not start; tail ${log_file#$root/}" >&2
  tail -20 "$log_file" >&2 2>/dev/null || true
  return 1
}

pids_inline() {
  printf '%s' "$1" | tr '\n' ' '
}

latest_source_stamp() {
  node - "$root" <<'NODE'
const { execFileSync } = require('child_process');
const fs = require('fs');
const path = require('path');
const root = process.argv[2];
function lines(args) {
  try { return execFileSync('git', ['-C', root, ...args], { encoding: 'utf8' }).split('\n').filter(Boolean); }
  catch { return []; }
}
const files = new Set([
  ...lines(['diff', '--name-only', 'HEAD']),
  ...lines(['ls-files', '--others', '--exclude-standard']),
]);
let best = { mtime: 0, file: '' };
for (const rel of files) {
  if (!/\.(tsx?|jsx?|mjs|cjs|json|scss|css)$/u.test(rel)) continue;
  const abs = path.join(root, rel);
  if (!fs.existsSync(abs) || !fs.statSync(abs).isFile()) continue;
  const mtime = fs.statSync(abs).mtimeMs;
  if (mtime > best.mtime) best = { mtime, file: rel };
}
if (best.mtime) console.log(`${Math.floor(best.mtime)}\t${best.file}`);
NODE
}

latest_dist_mtime_ms() {
  node - "$root" <<'NODE'
const fs = require('fs');
const path = require('path');
const root = process.argv[2];
const dir = path.join(root, 'dist/chrome');
let best = 0;
function walk(p) {
  if (!fs.existsSync(p)) return;
  for (const name of fs.readdirSync(p)) {
    const abs = path.join(p, name);
    const st = fs.statSync(abs);
    if (st.isDirectory()) walk(abs);
    else if (st.isFile()) best = Math.max(best, st.mtimeMs);
  }
}
walk(dir);
console.log(Math.floor(best));
NODE
}

do_stop() {
  init_context
  local pids
  pids="$(watcher_pids)"
  if [ -z "$pids" ]; then
    log "no watcher running for this checkout"
    rm -f "$root/$runtime_dir/webpack.pid"
    return 0
  fi
  log "stopping watcher ($(pids_inline "$pids"))"
  printf '%s
' "$pids" | xargs kill 2>/dev/null || true
  sleep 2
  pids="$(watcher_pids)"
  if [ -n "$pids" ]; then
    log "force stopping watcher ($(pids_inline "$pids"))"
    printf '%s
' "$pids" | xargs kill -9 2>/dev/null || true
  fi
  rm -f "$root/$runtime_dir/webpack.pid"
}

do_refresh_once() {
  init_context
  local s
  s="$(harness_helper refreshBuild "$runtime_dir/refresh-build.sh")"
  log "one-shot refresh watcher=${WATCHER_PORT:-?}"
  bash "$s" --repo "$root" ${WATCHER_PORT:+--watcher-port "$WATCHER_PORT"} "$@"
  if [ -n "${CDP_PORT:-}" ]; then
    "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json || true
  fi
}

do_refresh() {
  init_context
  local pids stamp src_mtime src_file dist_mtime timeout elapsed
  pids="$(watcher_pids)"
  if [ -z "$pids" ]; then
    echo "mme-recipe: no active watcher found; not starting a full rebuild from refresh" >&2
    echo "mme-recipe: start the slot watcher, or run explicit heavy fallback: recipe refresh:once" >&2
    return 2
  fi

  stamp="$(latest_source_stamp || true)"
  if [ -z "$stamp" ]; then
    log "watcher active ($(pids_inline "$pids")); no changed source files"
    [ -n "${CDP_PORT:-}" ] && "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json || true
    return
  fi
  src_mtime="${stamp%%$'\t'*}"
  src_file="${stamp#*$'\t'}"
  timeout="${RECIPE_REFRESH_TIMEOUT:-90}"
  log "watcher active ($(pids_inline "$pids")); waiting for incremental build of $src_file"

  for elapsed in $(seq 0 "$timeout"); do
    dist_mtime="$(latest_dist_mtime_ms)"
    if [ "$dist_mtime" -ge "$src_mtime" ]; then
      log "dist/chrome timestamp is current (${elapsed}s); checking runtime"
      if [ -n "${CDP_PORT:-}" ]; then
        local ready_json
        ready_json="$($runner ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" --json 2>/dev/null || true)"
        printf '%s
' "$ready_json"
        if printf '%s' "$ready_json" | grep -q '"ready"[[:space:]]*:[[:space:]]*true'; then
          log "runtime ready; run: recipe reopen only if the visible page is stale"
          return
        fi
        echo "mme-recipe: dist is current but runtime is unhealthy; run: recipe reopen, then recipe status" >&2
        return 2
      fi
      log "dist/chrome timestamp is current; run: recipe reopen"
      return
    fi
    if [ $elapsed -gt 0 ] && [ $((elapsed % 10)) -eq 0 ]; then
      log "still waiting for watcher... (${elapsed}s)"
    fi
    sleep 1
  done

  echo "mme-recipe: watcher did not update dist/chrome within ${timeout}s; try: recipe refresh:once" >&2
  return 2
}

sidepanel() {
  init_context; need_cdp
  local s
  s="$(harness_helper sidepanelToggle "$runtime_dir/sidepanel-toggle.sh")"
  bash "$s" "${1:-cycle}" --cdp-port "$CDP_PORT" --agent-dir "$root/$runtime_dir"
}

run_recipe() {
  init_context; need_cdp
  local recipe="$1"; shift
  [ -f "$recipe" ] || { echo "mme-recipe: recipe not found: $recipe" >&2; exit 2; }
  local out="$root/temp/recipe-artifacts/$(basename "$recipe" .json)-$(date -u +%Y%m%dT%H%M%SZ)"
  log "root=$root cdp=$CDP_PORT watcher=${WATCHER_PORT:-?} out=$out"
  exec "$runner" run "$recipe" --adapter extension --project-root "$root" --cdp-port "$CDP_PORT" --artifacts-dir "$out" "$@"
}

interactive() {
  init_context
  echo "mme-recipe (${root}, cdp=${CDP_PORT:-?}, watcher=${WATCHER_PORT:-?})"
  PS3='choose> '
  select choice in status runtime-status decision ready reopen refresh sidepanel actions doctor quit; do
    case "$choice" in
      status) run_json_pretty "$runner" runtime-health --adapter extension --target "$root" --cdp-port "$CDP_PORT" ;;
      runtime-status) runtime_status | pipe_pretty ;;
      decision) run_json_pretty "$runner" runtime-decision --adapter extension --target "$root" ${CDP_PORT:+--cdp-port "$CDP_PORT"} ;;
      ready) "$runner" ensure-ready --adapter extension --target "$root" --cdp-port "$CDP_PORT" ;;
      watch) do_watch ;;
      stop) do_stop ;;
      reopen) do_reopen ;;
      refresh) do_refresh ;;
      sidepanel) sidepanel cycle ;;
      actions) run_json_pretty "$runner" actions --adapter extension ;;
      doctor) run_json_pretty "$runner" doctor --adapter extension --target "$root" ;;
      quit|'') break ;;
    esac
    echo
  done
}

cmd="${1:-interactive}"
[ "$cmd" = "interactive" ] || shift || true
case "$cmd" in
  -h|--help) usage; exit 0 ;;
  interactive) interactive ;;
  prepare) prepare_extension "$@" ;;
  status|health) status "$@" ;;
  runtime-status) runtime_status "$@" ;;
  decision|decide) decision "$@" ;;
  ready|ensure-ready) ready "$@" ;;
  watch|start-watch|watcher) do_watch "$@" ;;
  stop|stop-watch|stop-watcher) do_stop "$@" ;;
  reopen|reload|browser) do_reopen "$@" ;;
  refresh|rebuild|build) do_refresh "$@" ;;
  refresh:once|rebuild:once|build:once) do_refresh_once "$@" ;;
  sidepanel) sidepanel "$@" ;;
  actions) init_context; run_json_pretty "$runner" actions --adapter extension "$@" ;;
  doctor) init_context; run_json_pretty "$runner" doctor --adapter extension --target "$root" "$@" ;;
  run) recipe="${1:-}"; [ -n "$recipe" ] || { usage; exit 2; }; shift; run_recipe "$recipe" "$@" ;;
  *.json) run_recipe "$cmd" "$@" ;;
  *) echo "mme-recipe: unknown command or missing recipe: $cmd" >&2; usage; exit 2 ;;
esac
