#!/usr/bin/env bash
set -u

# Graceful skip when not inside an OpenClaw workspace
if [ -z "${OPENCLAW_HOME:-}" ] && [ ! -f "${HOME:-}/.openclaw/openclaw.json" ]; then
  printf '[sevo-init] Not inside an OpenClaw environment, skipping auto-registration.\n'
  printf '[sevo-init] Run "sevo init" manually after setting up OpenClaw.\n'
  exit 0
fi

PLUGIN_ID="sevo-pipeline"
SCRIPT_DIR="$(CDPATH= cd -- "$(dirname -- "$0")" && pwd)"
PLUGIN_DIR="$(CDPATH= cd -- "$SCRIPT_DIR/.." && pwd)"
PLUGIN_MAIN="$PLUGIN_DIR/index.js"
PLUGIN_MANIFEST="$PLUGIN_DIR/openclaw.plugin.json"

log() {
  printf '[sevo-init] %s\n' "$*"
}

warn() {
  printf '[sevo-init][warn] %s\n' "$*" >&2
}

die() {
  printf '[sevo-init][error] %s\n' "$*" >&2
  exit 1
}

expand_home() {
  case "$1" in
    "~")
      printf '%s\n' "$HOME"
      ;;
    "~/"*)
      printf '%s/%s\n' "$HOME" "${1#~/}"
      ;;
    *)
      printf '%s\n' "$1"
      ;;
  esac
}

OPENCLAW_HOME_INPUT="${OPENCLAW_HOME:-$HOME/.openclaw}"
OPENCLAW_HOME="$(expand_home "$OPENCLAW_HOME_INPUT")"
CONFIG_PATH="$OPENCLAW_HOME/openclaw.json"

[ -f "$CONFIG_PATH" ] || die "openclaw.json not found: $CONFIG_PATH"
if [ ! -f "$PLUGIN_MANIFEST" ]; then
  warn "Plugin manifest not found: $PLUGIN_MANIFEST (normal for npm global install)"
  printf '[sevo-init] Run "sevo init" manually to register with OpenClaw.\n'
  exit 0
fi
[ -f "$PLUGIN_MAIN" ] || die "Plugin entrypoint not found: $PLUGIN_MAIN"
command -v node >/dev/null 2>&1 || die "node is required"
command -v openclaw >/dev/null 2>&1 || die "openclaw CLI is required"

log "Using OPENCLAW_HOME=$OPENCLAW_HOME"
log "Config: $CONFIG_PATH"

# ── FR-D05 AC1: Output version ──
NEW_VERSION="$(OPENCLAW_PLUGIN_MAIN="$PLUGIN_MAIN" node -e "
const fs = require('fs');
const src = fs.readFileSync(process.env.OPENCLAW_PLUGIN_MAIN, 'utf8');
const m = src.match(/const SEVO_VERSION\s*=\s*'([^']+)'/);
process.stdout.write(m ? m[1] : 'unknown');
")"
log "SEVO version: $NEW_VERSION"

# ── FR-D05 AC2: Backup before upgrade ──
EXT_DIR="$(dirname "$PLUGIN_DIR")"
INSTALLED_DIR="$EXT_DIR/sevo-pipeline"
STATE_FILE="$PLUGIN_DIR/state/active-pipelines.json"

if [ -d "$INSTALLED_DIR" ] && [ "$INSTALLED_DIR" != "$PLUGIN_DIR" ]; then
  BACKUP_TS="$(date +%Y%m%d-%H%M%S)"
  BACKUP_DIR="${INSTALLED_DIR}.bak.${BACKUP_TS}"
  log "Existing installation detected — backing up to $BACKUP_DIR"
  cp -a "$INSTALLED_DIR" "$BACKUP_DIR"
  if [ -f "$STATE_FILE" ]; then
    BACKUP_STATE="${STATE_FILE}.bak.${BACKUP_TS}"
    cp -a "$STATE_FILE" "$BACKUP_STATE"
    log "State file backed up to $BACKUP_STATE"
  fi
fi

# ── FR-D05 AC4: Major version upgrade confirmation ──
INSTALLED_VERSION=""
if [ -f "$INSTALLED_DIR/index.js" ] && [ "$INSTALLED_DIR" != "$PLUGIN_DIR" ]; then
  INSTALLED_VERSION="$(OPENCLAW_INSTALLED_MAIN="$INSTALLED_DIR/index.js" node -e "
const fs = require('fs');
const src = fs.readFileSync(process.env.OPENCLAW_INSTALLED_MAIN, 'utf8');
const m = src.match(/const SEVO_VERSION\s*=\s*'([^']+)'/);
process.stdout.write(m ? m[1] : '');
" 2>/dev/null || true)"
fi

if [ -n "$INSTALLED_VERSION" ] && [ -n "$NEW_VERSION" ] && [ "$NEW_VERSION" != "unknown" ]; then
  OLD_MAJOR="${INSTALLED_VERSION%%.*}"
  NEW_MAJOR="${NEW_VERSION%%.*}"
  if [ "$OLD_MAJOR" != "$NEW_MAJOR" ]; then
    printf '[sevo-init] ⚠️  Major version upgrade detected: %s → %s\n' "$INSTALLED_VERSION" "$NEW_VERSION"
    printf '[sevo-init] This may include breaking changes. Continue? [y/N] '
    read -r CONFIRM
    case "$CONFIRM" in
      [yY]|[yY][eE][sS]) log "Major upgrade confirmed by user" ;;
      *) die "Major upgrade aborted by user" ;;
    esac
  else
    log "Version upgrade: $INSTALLED_VERSION → $NEW_VERSION (minor/patch — auto-proceeding)"
  fi
fi

log "Verifying SEVO runtime hook declarations"
HOOK_REPORT="$({ OPENCLAW_PLUGIN_MAIN="$PLUGIN_MAIN" node <<'NODE'
const fs = require('fs');
const pluginMain = process.env.OPENCLAW_PLUGIN_MAIN;
const source = fs.readFileSync(pluginMain, 'utf8');

function hasHook(eventName, priority) {
  const escaped = eventName.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
  const pattern = new RegExp(`api\\.on\\(\\s*['\"]${escaped}['\"][\\s\\S]{0,12000}?\\{\\s*priority\\s*:\\s*${priority}\\s*\\}\\s*\\);`, 'm');
  return pattern.test(source);
}

const report = {
  beforePromptBuild850: hasHook('before_prompt_build', 850),
  subagentEnded200: hasHook('subagent_ended', 200)
};

if (!report.beforePromptBuild850 || !report.subagentEnded200) {
  console.error(JSON.stringify(report));
  process.exit(1);
}

process.stdout.write(JSON.stringify(report));
NODE
} 2>&1)" || die "Plugin runtime hook verification failed: $HOOK_REPORT"

CONFIG_REPORT="$({ OPENCLAW_CONFIG_PATH="$CONFIG_PATH" OPENCLAW_PLUGIN_ID="$PLUGIN_ID" node <<'NODE'
const fs = require('fs');
const path = process.env.OPENCLAW_CONFIG_PATH;
const pluginId = process.env.OPENCLAW_PLUGIN_ID;
const original = fs.readFileSync(path, 'utf8');
const config = JSON.parse(original);

if (!config.plugins || typeof config.plugins !== 'object' || Array.isArray(config.plugins)) {
  config.plugins = {};
}
if (!config.plugins.entries || typeof config.plugins.entries !== 'object' || Array.isArray(config.plugins.entries)) {
  config.plugins.entries = {};
}

const existed = Object.prototype.hasOwnProperty.call(config.plugins.entries, pluginId);
const current = config.plugins.entries[pluginId];
const entry = current && typeof current === 'object' && !Array.isArray(current) ? { ...current } : {};
const changes = [];

if (entry.enabled !== true) {
  entry.enabled = true;
  changes.push('enabled=true');
}
if (!entry.hooks || typeof entry.hooks !== 'object' || Array.isArray(entry.hooks)) {
  entry.hooks = {};
}
if (entry.hooks.allowPromptInjection !== true) {
  entry.hooks.allowPromptInjection = true;
  changes.push('hooks.allowPromptInjection=true');
}

config.plugins.entries[pluginId] = entry;
const next = JSON.stringify(config, null, 2) + '\n';
const status = !existed ? 'added' : changes.length > 0 ? 'updated' : 'skipped';

process.stdout.write(JSON.stringify({
  existed,
  changes,
  changed: original !== next,
  status,
  next
}));
NODE
} 2>&1)" || die "Failed to prepare openclaw.json update: $CONFIG_REPORT"

CONFIG_STATUS="$(printf '%s' "$CONFIG_REPORT" | OPENCLAW_CONFIG_REPORT="$CONFIG_REPORT" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_CONFIG_REPORT);
process.stdout.write(report.status || 'unknown');
NODE
)"
CONFIG_CHANGED="$(printf '%s' "$CONFIG_REPORT" | OPENCLAW_CONFIG_REPORT="$CONFIG_REPORT" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_CONFIG_REPORT);
process.stdout.write(report.changed ? 'true' : 'false');
NODE
)"
CONFIG_CHANGES="$(printf '%s' "$CONFIG_REPORT" | OPENCLAW_CONFIG_REPORT="$CONFIG_REPORT" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_CONFIG_REPORT);
process.stdout.write((report.changes || []).join(', '));
NODE
)"

if [ "$CONFIG_CHANGED" = "true" ]; then
  TMP_CONFIG="$(mktemp "${TMPDIR:-/tmp}/sevo-openclaw.XXXXXX")" || die "Failed to allocate temp file"
  trap 'rm -f "$TMP_CONFIG"' EXIT HUP INT TERM
  OPENCLAW_CONFIG_REPORT="$CONFIG_REPORT" node <<'NODE' > "$TMP_CONFIG"
const report = JSON.parse(process.env.OPENCLAW_CONFIG_REPORT);
process.stdout.write(report.next);
NODE
  mv "$TMP_CONFIG" "$CONFIG_PATH" || die "Failed to write $CONFIG_PATH"
  trap - EXIT HUP INT TERM
  log "openclaw.json updated (${CONFIG_STATUS}${CONFIG_CHANGES:+: $CONFIG_CHANGES})"
else
  log "Plugin entry already configured; no JSON changes needed"
fi

log "Classifying agent pool"
AGENT_REPORT="$({ OPENCLAW_CONFIG_PATH="$CONFIG_PATH" node <<'NODE'
const fs = require('fs');
const config = JSON.parse(fs.readFileSync(process.env.OPENCLAW_CONFIG_PATH, 'utf8'));
const agents = Array.isArray(config?.agents?.list) ? config.agents.list : [];

const coding = [];
const audit = [];
const architecture = [];
const general = [];

for (const agent of agents) {
  const id = String(agent && agent.id || '').trim();
  if (!id) continue;
  if (/^audit-/i.test(id)) {
    audit.push(id);
  } else if (/^sa-/i.test(id)) {
    architecture.push(id);
  } else if (/^(dev-\d+|cc|codex|opencode|hermes|free-code)$/i.test(id)) {
    coding.push(id);
  } else {
    general.push(id);
  }
}

const minimalMode = agents.length === 1 && agents[0] && agents[0].id === 'main';
process.stdout.write(JSON.stringify({
  total: agents.length,
  coding,
  audit,
  architecture,
  general,
  minimalMode
}));
NODE
} 2>&1)" || die "Failed to classify agents: $AGENT_REPORT"

print_agent_line() {
  local label="$1"
  local key="$2"
  OPENCLAW_AGENT_REPORT="$AGENT_REPORT" OPENCLAW_AGENT_KEY="$key" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_AGENT_REPORT);
const key = process.env.OPENCLAW_AGENT_KEY;
const value = Array.isArray(report[key]) ? report[key] : [];
process.stdout.write(value.length ? value.join(', ') : '(none)');
NODE
}

AGENT_TOTAL="$(OPENCLAW_AGENT_REPORT="$AGENT_REPORT" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_AGENT_REPORT);
process.stdout.write(String(report.total || 0));
NODE
)"
MINIMAL_MODE="$(OPENCLAW_AGENT_REPORT="$AGENT_REPORT" node <<'NODE'
const report = JSON.parse(process.env.OPENCLAW_AGENT_REPORT);
process.stdout.write(report.minimalMode ? 'true' : 'false');
NODE
)"

printf '发现 %s 个 agent\n' "$AGENT_TOTAL"
printf '编码类：[%s]\n' "$(print_agent_line 'coding' 'coding')"
printf '审计类：[%s]\n' "$(print_agent_line 'audit' 'audit')"
printf '架构类：[%s]\n' "$(print_agent_line 'architecture' 'architecture')"
printf '通用类：[%s]\n' "$(print_agent_line 'general' 'general')"
if [ "$MINIMAL_MODE" = "true" ]; then
  warn '将运行在最小模式'
fi

log "Running doctor (read-only)"
DOCTOR_OUTPUT="$(openclaw doctor --non-interactive --no-workspace-suggestions 2>&1)"
DOCTOR_STATUS=$?
printf '%s\n' "$DOCTOR_OUTPUT"

DOCTOR_ERRORS="$(printf '%s\n' "$DOCTOR_OUTPUT" | sed -n 's/.*Errors:[[:space:]]*\([0-9][0-9]*\).*/\1/p' | head -1)"
if [ -z "$DOCTOR_ERRORS" ]; then
  DOCTOR_ERRORS="unknown"
fi

printf '\n安装结果摘要\n'
printf '  - 插件条目：%s\n' "$CONFIG_STATUS"
printf '  - 运行时 Hook：before_prompt_build(priority 850) 已验证；subagent_ended(priority 200) 已验证\n'
printf '  - Agent 池：%s 个\n' "$AGENT_TOTAL"
printf '  - Doctor 退出码：%s\n' "$DOCTOR_STATUS"
printf '  - Doctor Errors：%s\n' "$DOCTOR_ERRORS"
printf '  - 下一步：doctor 确认 Errors: 0 后，再手动执行 openclaw gateway restart\n'

if [ "$DOCTOR_STATUS" -ne 0 ]; then
  exit "$DOCTOR_STATUS"
fi

case "$DOCTOR_ERRORS" in
  0)
    echo ""
    echo "✅ SEVO installed successfully!"
    echo ""
    echo "Next steps:"
    echo "  1. Restart Gateway:  openclaw gateway restart"
    echo "  2. First project:    Send 'sevo:create hello-sevo' with a requirement"
    echo "  3. Quick start:      Send 'sevo:quickstart' for a built-in demo"
    echo ""
    exit 0
    ;;
  *)
    exit 1
    ;;
esac
