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

SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
SKILL_DIR="$(cd "$SCRIPT_DIR/.." && pwd)"
CONFIG_FILE="${DEEPMAP_CONFIG_PATH:-}"
CONFIG_STDIN=0
CONFIG_JSON=""
SITE_URL=""
AUTH_TOKEN=""
EVENT_SEQ=0
TOKEN_REFRESHED=0
CURRENT_ASK_TASK_ID=""
ASK_FINISHED=0

emit_missing_dependency() {
  local dependency="$1"
  printf '{"protocol":"infcode.deepmap.v1","seq":1,"channel":"ui","type":"error","message":"Missing required dependency: %s","payload":{"error_code":"missing_dependency","dependency":"%s"}}\n' "$dependency" "$dependency"
  printf '{"status":"error","error_code":"missing_dependency","message":"Missing required dependency: %s"}\n' "$dependency"
}

check_dependencies() {
  local dependency
  for dependency in curl; do
    if ! command -v "$dependency" >/dev/null 2>&1; then
      emit_missing_dependency "$dependency"
      exit 127
    fi
  done
}

usage() {
  cat <<'EOF'
Usage: deepmap.sh [--config PATH] <command> [args]
       deepmap.sh --config-stdin <command> [args]

Commands:
  list   [--q QUERY] [--limit N] [--offset N]
  search --query QUERY --repo OWNER/REPO [--repo OWNER/REPO ...] [--limit N] [--branch BRANCH] [--commit COMMIT]
  ask    --query QUERY --repo OWNER/REPO [--repo OWNER/REPO ...] [--mode fast|balanced|deep] [--branch BRANCH] [--commit COMMIT]

The ask command submits the DeepMap investigation and polls internally until a
terminal result is returned. It prints DeepMap GUI events and the final result.
EOF
}

die() {
  printf '%s\n' "$*" >&2
  exit 1
}

json_escape() {
  local s="${1:-}"
  local code ch escaped
  s=${s//\\/\\\\}
  s=${s//\"/\\\"}
  s=${s//$'\b'/\\b}
  s=${s//$'\f'/\\f}
  s=${s//$'\n'/\\n}
  s=${s//$'\r'/\\r}
  s=${s//$'\t'/\\t}
  for code in {1..7} 11 {14..31}; do
    printf -v ch '%b' "\\x$(printf '%02x' "$code")"
    printf -v escaped '\\u%04x' "$code"
    s=${s//"$ch"/$escaped}
  done
  printf '%s' "$s"
}

json_get_string() {
  local field="$1"
  awk -v key="$field" '
    BEGIN { ORS="" }
    { text = text (NR > 1 ? "\n" : "") $0 }
    END {
      pattern = "\"" key "\"[ \t\r\n]*:[ \t\r\n]*\""
      if (!match(text, pattern)) exit 1
      i = RSTART + RLENGTH
      out = ""
      esc = 0
      for (; i <= length(text); i++) {
        c = substr(text, i, 1)
        if (esc) {
          if (c == "n") out = out "\n"
          else if (c == "r") out = out "\r"
          else if (c == "t") out = out "\t"
          else if (c == "b") out = out "\b"
          else if (c == "f") out = out "\f"
          else out = out c
          esc = 0
        } else if (c == "\\") {
          esc = 1
        } else if (c == "\"") {
          print out
          exit 0
        } else {
          out = out c
        }
      }
      exit 1
    }
  '
}

json_get_strings() {
  local field="$1"
  awk -v key="$field" '
    BEGIN { ORS="" }
    { text = text (NR > 1 ? "\n" : "") $0 }
    END {
      pattern = "\"" key "\"[ \t\r\n]*:[ \t\r\n]*\""
      first = 1
      while (match(text, pattern)) {
        i = RSTART + RLENGTH
        out = ""
        esc = 0
        for (; i <= length(text); i++) {
          c = substr(text, i, 1)
          if (esc) {
            if (c == "n") out = out "\n"
            else if (c == "r") out = out "\r"
            else if (c == "t") out = out "\t"
            else if (c == "b") out = out "\b"
            else if (c == "f") out = out "\f"
            else out = out c
            esc = 0
          } else if (c == "\\") {
            esc = 1
          } else if (c == "\"") {
            if (!first) printf "\n"
            printf "%s", out
            first = 0
            text = substr(text, i + 1)
            break
          } else {
            out = out c
          }
        }
        if (i > length(text)) break
      }
    }
  '
}

json_extract_data() {
  awk '
    BEGIN { ORS="" }
    { input = input (NR > 1 ? "\n" : "") $0 }
    function extract_after_key(text, key,    pattern, pos, i, c, start, depth, esc, quote, opener, closer) {
      pattern = "\"" key "\"[ \t\r\n]*:"
      if (!match(text, pattern)) return ""
      pos = RSTART + RLENGTH
      while (pos <= length(text) && substr(text, pos, 1) ~ /[ \t\r\n]/) pos++
      opener = substr(text, pos, 1)
      if (opener == "{") closer = "}"
      else if (opener == "[") closer = "]"
      else return ""
      depth = 0
      esc = 0
      quote = 0
      for (i = pos; i <= length(text); i++) {
        c = substr(text, i, 1)
        if (quote) {
          if (esc) esc = 0
          else if (c == "\\") esc = 1
          else if (c == "\"") quote = 0
        } else {
          if (c == "\"") quote = 1
          else if (c == opener) depth++
          else if (c == closer) {
            depth--
            if (depth == 0) return substr(text, pos, i - pos + 1)
          }
        }
      }
      return ""
    }
    END {
      text = input
      if (text ~ /"ok"[ \t\r\n]*:[ \t\r\n]*false/) {
        err = extract_after_key(text, "error")
        if (err == "") err = "deepmap_error"
        msg = ""
        pattern = "\"message\"[ \t\r\n]*:[ \t\r\n]*\""
        if (match(text, pattern)) {
          pos = RSTART + RLENGTH
          for (i = pos; i <= length(text); i++) {
            c = substr(text, i, 1)
            if (c == "\"") break
            msg = msg c
          }
        }
        print err (msg ? ": " msg : "") > "/dev/stderr"
        exit 2
      }
      data = extract_after_key(text, "data")
      if (data != "") print data
      else print text
    }
  '
}

sanitize_text() {
  local text="${1:-}"
  printf '%s' "$text" | awk '
    function trim_path_suffix(raw) {
      while (raw ~ /[.,;:)]$/) raw = substr(raw, 1, length(raw) - 1)
      return raw
    }
    function path_label(raw,    path, parts, count, leaf, parent, grand) {
      path = trim_path_suffix(raw)
      count = split(path, parts, "/")
      leaf = parts[count]
      parent = parts[count - 1]
      grand = parts[count - 2]
      if (parent ~ /^(arch|subdoc)$/ && leaf ~ /^v[0-9]+$/) return parent "/" leaf
      if (leaf ~ /^[0-9a-fA-F]{7,40}$/ && grand != "") return grand
      if (leaf ~ /\.[^.]+$/) sub(/\.[^.]+$/, "", leaf)
      return leaf != "" ? leaf : "[REDACTED_PATH]"
    }
    function replace_paths(line,    out, raw) {
      out = ""
      while (match(line, /\/(Users|home|var|tmp|data)\/[^[:space:]"<>]+/)) {
        raw = substr(line, RSTART, RLENGTH)
        out = out substr(line, 1, RSTART - 1) path_label(raw)
        line = substr(line, RSTART + RLENGTH)
      }
      return out line
    }
    {
      line = $0
      gsub(/Bearer[ \t]+[A-Za-z0-9._~+\/=-]+/, "Bearer [REDACTED]", line)
      gsub(/([^[:space:]"<>]+\/)?config\.local\.json/, "[REDACTED_CONFIG_PATH]", line)
      line = replace_paths(line)
      printf "%s%s", (NR > 1 ? "\n" : ""), line
    }
  '
}

json_status_payload() {
  printf '{"status":"%s"}' "$(json_escape "$1")"
}

json_status_phase_payload() {
  printf '{"status":"%s","phase":"%s"}' "$(json_escape "$1")" "$(json_escape "$2")"
}

emit_event() {
  local type="$1" message="${2:-}" payload="${3:-}"
  EVENT_SEQ=$((EVENT_SEQ + 1))
  message="$(sanitize_text "$message")"
  printf '{"protocol":"infcode.deepmap.v1","seq":%s,"channel":"ui","type":"%s"' "$EVENT_SEQ" "$(json_escape "$type")"
  [[ -n "$message" ]] && printf ',"message":"%s"' "$(json_escape "$message")"
  [[ -n "$payload" && "$payload" != "{}" ]] && printf ',"payload":%s' "$payload"
  printf '}\n'
}

emit_error_result() {
  local error_code="$1" message="$2"
  EVENT_SEQ=$((EVENT_SEQ + 1))
  message="$(sanitize_text "$message")"
  printf '{"protocol":"infcode.deepmap.v1","seq":%s,"channel":"ui","type":"error","message":"%s","payload":{"error_code":"%s"}}\n' "$EVENT_SEQ" "$(json_escape "$message")" "$(json_escape "$error_code")"
  printf '{"status":"error","error_code":"%s","message":"%s"}\n' "$(json_escape "$error_code")" "$(json_escape "$message")"
}

emit_model_result() {
  local data="$1"
  local content
  content="$(printf '%s' "$data" | json_get_string answer || true)"
  [[ -n "$content" ]] || content="$data"
  content="$(sanitize_text "$content")"
  EVENT_SEQ=$((EVENT_SEQ + 1))
  printf '{"protocol":"infcode.deepmap.v1","seq":%s,"channel":"model","type":"result","content":"%s"}\n' "$EVENT_SEQ" "$(json_escape "$content")"
}

emit_ask_submitted() {
  local task_id="$1"
  EVENT_SEQ=$((EVENT_SEQ + 1))
  printf '{"protocol":"infcode.deepmap.v1","seq":%s,"channel":"ui","type":"ask_submitted","message":"DeepMap ask submitted","payload":{"task_id":"%s"}}\n' "$EVENT_SEQ" "$(json_escape "$task_id")"
}

emit_error_from_response() {
  local body="$1" fallback_code="$2" fallback_message="$3"
  local error_code message
  error_code="$(printf '%s' "$body" | json_get_string error || true)"
  [[ -n "$error_code" ]] || error_code="$(printf '%s' "$body" | json_get_string code || true)"
  [[ -n "$error_code" ]] || error_code="$fallback_code"
  message="$(printf '%s' "$body" | json_get_string message || true)"
  [[ -n "$message" ]] || message="$(printf '%s' "$body" | json_get_string detail || true)"
  [[ -n "$message" ]] || message="$fallback_message"
  emit_error_result "$error_code" "$message"
}

network_error_body() {
  local method="$1" path="$2" curl_rc="$3" attempts="$4"
  printf '{"error":"network_error","code":"network_error","message":"DeepMap network request failed before receiving an HTTP response. Please check connectivity and retry.","detail":"curl exited with code %s","method":"%s","path":"%s","attempts":%s}\n' \
    "$(json_escape "$curl_rc")" "$(json_escape "$method")" "$(json_escape "$path")" "$attempts"
}

cancel_current_ask() {
  if [[ -n "${CURRENT_ASK_TASK_ID:-}" && "$ASK_FINISHED" != "1" ]]; then
    request_json POST "/api/repos/ask/$CURRENT_ASK_TASK_ID/cancel" "{}" 5 0 >/dev/null 2>&1 || true
  fi
}

trap cancel_current_ask TERM INT

resolve_config_file() {
  if [[ "$CONFIG_STDIN" == "1" ]]; then
    return
  fi
  if [[ -n "${CONFIG_FILE:-}" ]]; then
    return
  fi
  if [[ -n "${DEEPMAP_CONFIG_PATH:-}" ]]; then
    CONFIG_FILE="$DEEPMAP_CONFIG_PATH"
    return
  fi
  if [[ -f "$SKILL_DIR/config.local.json" ]]; then
    CONFIG_FILE="$SKILL_DIR/config.local.json"
    return
  fi
}

load_config() {
  if [[ "$CONFIG_STDIN" == "1" ]]; then
    if [[ -z "${CONFIG_JSON:-}" ]]; then
      CONFIG_JSON="$(cat)"
    fi
    SITE_URL="$(printf '%s' "$CONFIG_JSON" | json_get_string siteUrl || true)"
    AUTH_TOKEN="$(printf '%s' "$CONFIG_JSON" | json_get_string token || true)"
    SITE_URL="${SITE_URL%/}"
    if [[ -z "$SITE_URL" ]]; then
      emit_error_result "invalid_config" "Missing siteUrl in stdin config"
      return 1
    fi
    if [[ -z "$AUTH_TOKEN" ]]; then
      emit_error_result "invalid_config" "Missing token in stdin config"
      return 1
    fi
    return 0
  fi

  resolve_config_file
  if [[ -z "${CONFIG_FILE:-}" ]]; then
    emit_error_result "missing_config" "Missing DEEPMAP_CONFIG_PATH or $SKILL_DIR/config.local.json"
    return 1
  fi
  if [[ ! -f "$CONFIG_FILE" ]]; then
    emit_error_result "missing_config" "config.local.json not found: $CONFIG_FILE"
    return 1
  fi

  CONFIG_JSON="$(cat "$CONFIG_FILE")"
  SITE_URL="$(printf '%s' "$CONFIG_JSON" | json_get_string siteUrl || true)"
  AUTH_TOKEN="$(printf '%s' "$CONFIG_JSON" | json_get_string token || true)"
  SITE_URL="${SITE_URL%/}"
  if [[ -z "$SITE_URL" ]]; then
    emit_error_result "invalid_config" "Missing siteUrl in config.local.json"
    return 1
  fi
  if [[ -z "$AUTH_TOKEN" ]]; then
    emit_error_result "invalid_config" "Missing token in config.local.json"
    return 1
  fi
}

format_config_repos() {
  if [[ "$CONFIG_STDIN" == "1" ]]; then
    if [[ -z "${CONFIG_JSON:-}" ]]; then
      CONFIG_JSON="$(cat)"
    fi
    local repos
    repos="$(printf '%s' "$CONFIG_JSON" | json_get_strings remoteRepo | awk 'BEGIN{ORS=""} {printf "%s%s", (NR>1 ? ", " : ""), $0}')"
    [[ -n "$repos" ]] && printf '%s' "$repos" || printf 'configured DeepMap repositories'
    return
  fi
  resolve_config_file
  [[ -n "${CONFIG_FILE:-}" && -f "$CONFIG_FILE" ]] || { printf 'configured DeepMap repositories'; return; }
  local config repos
  config="$(cat "$CONFIG_FILE")"
  repos="$(printf '%s' "$config" | json_get_strings remoteRepo | awk 'BEGIN{ORS=""} {printf "%s%s", (NR>1 ? ", " : ""), $0}')"
  [[ -n "$repos" ]] && printf '%s' "$repos" || printf 'configured DeepMap repositories'
}

urlencode() {
  local s="${1:-}" out="" i c hex
  local old_lc="${LC_ALL:-}"
  LC_ALL=C
  for ((i=0; i<${#s}; i++)); do
    c="${s:i:1}"
    case "$c" in
      [a-zA-Z0-9.~_/-]) out+="$c" ;;
      ' ') out+="%20" ;;
      *) printf -v hex '%%%02X' "'$c"; out+="$hex" ;;
    esac
  done
  [[ -n "$old_lc" ]] && LC_ALL="$old_lc" || unset LC_ALL
  printf '%s' "$out"
}

json_field() {
  local field="$1"
  json_get_string "$field"
}

parse_access_token() {
  local raw
  raw="$(cat)"
  printf '%s' "$raw" | json_get_string access_token || printf '%s' "$raw" | json_get_string token || true
}

refresh_token_once() {
  [[ "$TOKEN_REFRESHED" == "0" ]] || return 1
  TOKEN_REFRESHED=1

  local raw=""
  if [[ -n "${DEEPMAP_REFRESH_COMMAND:-}" ]]; then
    set +e
    raw="$($DEEPMAP_REFRESH_COMMAND 2>/dev/null)"
    local rc=$?
    set -e
    [[ $rc -eq 0 ]] || return 1
  elif [[ -n "${DEEPMAP_REFRESH_URL:-}" ]]; then
    local tmpfile http_code curl_rc
    tmpfile=$(mktemp)
    set +e
    http_code=$(curl -sS -X POST "$DEEPMAP_REFRESH_URL" --connect-timeout 10 --max-time 30 -H "Authorization: Bearer $AUTH_TOKEN" -H "Content-Type: application/json" -o "$tmpfile" -w "%{http_code}" 2>/dev/null)
    curl_rc=$?
    set -e
    raw="$(cat "$tmpfile")"
    rm -f "$tmpfile"
    [[ $curl_rc -eq 0 && "$http_code" -ge 200 && "$http_code" -lt 300 ]] || return 1
  else
    return 1
  fi

  local new_token
  new_token="$(printf '%s' "$raw" | parse_access_token)"
  [[ -n "$new_token" ]] || return 1
  AUTH_TOKEN="$new_token"
}

request_json() {
  local method="$1" path="$2" body="${3:-}" max_time="${4:-${DEEPMAP_REQUEST_MAX_TIME_SECONDS:-60}}" allow_refresh="${5:-1}"
  local url="$SITE_URL$path"
  local max_retries=3 attempt=0
  local network_attempt=0 network_max_retries
  if [[ "$method" == "POST" ]]; then
    network_max_retries="${DEEPMAP_POST_NETWORK_MAX_RETRIES:-1}"
  else
    network_max_retries="${DEEPMAP_NETWORK_MAX_RETRIES:-3}"
  fi

  while true; do
    local tmpfile http_code curl_rc
    tmpfile=$(mktemp)
    set +e
    if [[ -n "$body" ]]; then
      http_code=$(curl -sS -X "$method" "$url" \
        --connect-timeout "${DEEPMAP_CONNECT_TIMEOUT_SECONDS:-10}" \
        --max-time "$max_time" \
        -H "Authorization: Bearer $AUTH_TOKEN" \
        -H "Content-Type: application/json" \
        -d "$body" \
        -o "$tmpfile" -w "%{http_code}" 2>/dev/null)
    else
      http_code=$(curl -sS -X "$method" "$url" \
        --connect-timeout "${DEEPMAP_CONNECT_TIMEOUT_SECONDS:-10}" \
        --max-time "$max_time" \
        -H "Authorization: Bearer $AUTH_TOKEN" \
        -o "$tmpfile" -w "%{http_code}" 2>/dev/null)
    fi
    curl_rc=$?
    set -e

    if [[ $curl_rc -ne 0 ]]; then
      rm -f "$tmpfile"
      network_attempt=$((network_attempt + 1))
      if (( network_attempt <= network_max_retries )); then
        sleep "$network_attempt"
        continue
      fi
      network_error_body "$method" "$path" "$curl_rc" "$network_attempt"
      return 75
    fi

    if [[ "$http_code" == "401" || "$http_code" == "403" ]]; then
      if [[ "$allow_refresh" == "1" ]] && grep -Eqi 'token_expired|invalid_token|expired' "$tmpfile"; then
        if refresh_token_once; then
        rm -f "$tmpfile"
        allow_refresh=0
        continue
        fi
      fi
    fi

    if [[ "$http_code" == "429" || "$http_code" == "502" || "$http_code" == "503" || "$http_code" == "504" ]]; then
      attempt=$((attempt + 1))
      if [[ $attempt -gt $max_retries ]]; then
        cat "$tmpfile"
        rm -f "$tmpfile"
        return 22
      fi
      rm -f "$tmpfile"
      sleep "$attempt"
      continue
    fi

    if [[ "$http_code" -ge 400 ]]; then
      cat "$tmpfile"
      rm -f "$tmpfile"
      return 22
    fi
    cat "$tmpfile"
    rm -f "$tmpfile"
    return 0
  done
}

unwrap_data() {
  json_extract_data
}

json_body_search() {
  local query="$1" limit="$2" branch="$3" commit="$4"
  shift 4
  local body repo sep=""
  body="{\"query\":\"$(json_escape "$query")\",\"limit\":$limit"
  body+=",\"repos\":["
  for repo in "$@"; do
    body+="$sep\"$(json_escape "$repo")\""
    sep=","
  done
  body+="]"
  [[ -n "$branch" ]] && body+=",\"branch\":\"$(json_escape "$branch")\""
  [[ -n "$commit" ]] && body+=",\"commit\":\"$(json_escape "$commit")\""
  body+="}"
  printf '%s' "$body"
}

json_body_ask() {
  local query="$1" mode="$2" branch="$3" commit="$4"
  shift 4
  local body repo sep=""
  body="{\"query\":\"$(json_escape "$query")\",\"repos\":["
  for repo in "$@"; do
    body+="$sep\"$(json_escape "$repo")\""
    sep=","
  done
  body+="],\"mode\":\"$(json_escape "$mode")\""
  [[ -n "$branch" ]] && body+=",\"branch\":\"$(json_escape "$branch")\""
  [[ -n "$commit" ]] && body+=",\"commit\":\"$(json_escape "$commit")\""
  body+="}"
  printf '%s' "$body"
}

cmd_list() {
  local q="" limit=100 offset=0
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --q) q="$2"; shift 2 ;;
      --limit) limit="$2"; shift 2 ;;
      --offset) offset="$2"; shift 2 ;;
      *) emit_error_result "invalid_param" "Unknown list arg: $1"; return 1 ;;
    esac
  done
  local path="/api/repos/list?limit=$limit&offset=$offset"
  [[ -n "$q" ]] && path="$path&q=$(urlencode "$q")"
  local raw rc data
  set +e
  raw="$(request_json GET "$path")"
  rc=$?
  set -e
  if [[ $rc -ne 0 ]]; then
    if [[ -n "$raw" ]]; then
      emit_error_from_response "$raw" "http_request_failed" "DeepMap list request failed"
    else
      emit_error_result "http_request_failed" "DeepMap list request failed"
    fi
    return 1
  fi
  set +e
  data="$(printf '%s' "$raw" | unwrap_data)"
  rc=$?
  set -e
  if [[ $rc -ne 0 ]]; then
    emit_error_from_response "$raw" "deepmap_error" "DeepMap list failed"
    return 1
  fi
  printf '%s\n' "$data"
}

cmd_search() {
  local query="" limit=5 branch="" commit=""
  local repos=()
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --query) query="$2"; shift 2 ;;
      --repo) repos+=("$2"); shift 2 ;;
      --limit) limit="$2"; shift 2 ;;
      --branch) branch="$2"; shift 2 ;;
      --commit) commit="$2"; shift 2 ;;
      *) emit_error_result "invalid_param" "Unknown search arg: $1"; return 1 ;;
    esac
  done
  [[ -n "$query" ]] || { emit_error_result "missing_query" "search requires --query"; return 1; }
  [[ ${#repos[@]} -gt 0 ]] || { emit_error_result "missing_repo" "search requires at least one --repo"; return 1; }
  emit_event "search_start" "检索代码知识"
  local raw rc data
  set +e
  raw="$(request_json POST /api/repos/search "$(json_body_search "$query" "$limit" "$branch" "$commit" "${repos[@]}")" "${DEEPMAP_SEARCH_MAX_TIME_SECONDS:-30}")"
  rc=$?
  set -e
  if [[ $rc -ne 0 ]]; then
    if [[ -n "$raw" ]]; then
      emit_error_from_response "$raw" "http_request_failed" "DeepMap search request failed"
    else
      emit_error_result "http_request_failed" "DeepMap search request failed"
    fi
    return 1
  fi
  set +e
  data="$(printf '%s' "$raw" | unwrap_data)"
  rc=$?
  set -e
  if [[ $rc -ne 0 ]]; then
    emit_error_from_response "$raw" "deepmap_error" "DeepMap search failed"
    return 1
  fi
  printf '%s\n' "$data"
  emit_event "run_end" "DeepMap search finished" '{"status":"finished"}'
}

cmd_ask() {
  local query="" mode="balanced" branch="" commit="" poll_interval=5 max_wait="${DEEPMAP_ASK_MAX_WAIT_SECONDS:-300}"
  local poll_max_time="${DEEPMAP_POLL_MAX_TIME_SECONDS:-15}"
  local repos=()
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --query) query="$2"; shift 2 ;;
      --repo) repos+=("$2"); shift 2 ;;
      --mode) mode="$2"; shift 2 ;;
      --branch) branch="$2"; shift 2 ;;
      --commit) commit="$2"; shift 2 ;;
      --poll-interval) poll_interval="$2"; shift 2 ;;
      --max-wait) max_wait="$2"; shift 2 ;;
      *) emit_error_result "invalid_param" "Unknown ask arg: $1"; return 1 ;;
    esac
  done
  [[ -n "$query" ]] || { emit_error_result "missing_query" "ask requires --query"; return 1; }
  [[ ${#repos[@]} -gt 0 ]] || { emit_error_result "missing_repo" "ask requires at least one --repo"; return 1; }

  emit_event "run_start" "调用代码知识"
  emit_event "ask_start" "提交深度查询"

  local body submitted data task_id started now elapsed status phase msg consecutive_poll_timeouts=0
  body="$(json_body_ask "$query" "$mode" "$branch" "$commit" "${repos[@]}")"
  set +e
  submitted="$(request_json POST /api/repos/ask "$body")"
  local submit_rc=$?
  set -e
  if [[ $submit_rc -ne 0 ]]; then
    if [[ -n "$submitted" ]]; then
      emit_error_from_response "$submitted" "http_request_failed" "DeepMap ask request failed"
    else
      emit_error_result "http_request_failed" "DeepMap ask request failed"
    fi
    return 1
  fi
  set +e
  data="$(printf '%s' "$submitted" | unwrap_data)"
  submit_rc=$?
  set -e
  if [[ $submit_rc -ne 0 ]]; then
    emit_error_from_response "$submitted" "deepmap_error" "DeepMap ask failed"
    return 1
  fi
  task_id="$(printf '%s' "$data" | json_field task_id)"
  [[ -n "$task_id" ]] || die "DeepMap ask response missing task_id"
  CURRENT_ASK_TASK_ID="$task_id"
  ASK_FINISHED=0
  emit_ask_submitted "$task_id"

  started="$(date +%s)"
  while true; do
    now="$(date +%s)"
    elapsed=$((now - started))
    if (( elapsed >= max_wait )); then
      emit_event "error" "DeepMap ask polling timed out" '{"error_code":"ask_poll_timeout"}'
      emit_event "run_end" "DeepMap ask timed out" '{"status":"timeout"}'
      cancel_current_ask
      printf '{"status":"timeout","error_code":"ask_poll_timeout","message":"DeepMap ask polling timed out"}\n'
      return 1
    fi

    local status_raw poll_rc
    set +e
    status_raw="$(request_json GET "/api/repos/ask/$task_id" "" "$poll_max_time" 2>/dev/null)"
    poll_rc=$?
    set -e
    if [[ $poll_rc -ne 0 ]]; then
      local poll_error_code
      poll_error_code="$(printf '%s' "$status_raw" | json_get_string error || true)"
      consecutive_poll_timeouts=$((consecutive_poll_timeouts + 1))
      if [[ "$poll_error_code" == "network_error" ]]; then
        # infcode_change - use a client-only event type instead of overloading server progress phase.
        emit_event "ask_network_retry" "DeepMap network unavailable while polling ask status" "{\"status\":\"polling\",\"error_code\":\"network_error\",\"consecutive_failures\":$consecutive_poll_timeouts}"
        if (( consecutive_poll_timeouts >= 3 )); then
          emit_event "error" "DeepMap ask polling failed because the request did not reach the backend" '{"error_code":"network_error"}'
          emit_event "run_end" "DeepMap ask stopped after network failures" '{"status":"error","error_code":"network_error"}'
          printf '{"status":"error","error_code":"network_error","message":"DeepMap ask polling failed because the request did not reach the backend"}\n'
          return 1
        fi
        sleep "$poll_interval"
        continue
      fi
      if (( consecutive_poll_timeouts >= 3 )); then
        emit_event "error" "DeepMap ask polling timed out" '{"error_code":"ask_poll_timeout"}'
        emit_event "run_end" "DeepMap ask timed out" '{"status":"timeout"}'
        cancel_current_ask
        printf '{"status":"timeout","error_code":"ask_poll_timeout","message":"DeepMap ask polling timed out"}\n'
        return 1
      fi
      sleep "$poll_interval"
      continue
    fi
    consecutive_poll_timeouts=0

    data="$(printf '%s' "$status_raw" | unwrap_data)"
    status="$(printf '%s' "$data" | json_field status)"
    phase="$(printf '%s' "$data" | json_field phase || true)"
    msg="$(printf '%s' "$data" | json_field message || true)"
    emit_event "ask_progress" "${msg:-$status}" "$(json_status_phase_payload "$status" "$phase")"

    case "$status" in
      finished)
        emit_event "ask_result" "DeepMap ask finished" "$(json_status_payload "$status")"
        emit_model_result "$data"
        emit_event "run_end" "DeepMap ask finished" "$(json_status_payload "$status")"
        ASK_FINISHED=1
        printf '%s\n' "$data"
        return 0
        ;;
      failed|cancelled|timeout)
        emit_event "error" "${msg:-DeepMap ask $status}" "$(json_status_payload "$status")"
        emit_event "run_end" "DeepMap ask $status" "$(json_status_payload "$status")"
        ASK_FINISHED=1
        printf '%s\n' "$data"
        return 1
        ;;
    esac
    sleep "$poll_interval"
  done
}

cmd_sync_skill() {
  while [[ $# -gt 0 ]]; do
    case "$1" in
      --config) CONFIG_FILE="$2"; shift 2 ;;
      *) die "Unknown __sync-skill arg: $1" ;;
    esac
  done
  load_config
  local template_file="$SKILL_DIR/SKILL.md.template"
  local skill_file="$SKILL_DIR/SKILL.md"
  [[ -f "$template_file" ]] || die "SKILL.md.template not found at $template_file"
  local repos_formatted
  repos_formatted="$(format_config_repos)"
  awk -v repos="$repos_formatted" '{ gsub(/\{\{AVAILABLE_REPOS\}\}/, repos); print }' "$template_file" > "$skill_file"
  printf 'Generated SKILL.md (%s)\n' "$repos_formatted" >&2
}

if [[ "${BASH_SOURCE[0]}" != "$0" ]]; then
  return 0 2>/dev/null || exit 0
fi

while [[ $# -gt 0 ]]; do
  case "$1" in
    --config) CONFIG_FILE="$2"; CONFIG_STDIN=0; shift 2 ;;
    --env) CONFIG_FILE="$2"; CONFIG_STDIN=0; shift 2 ;;
    --config-stdin) CONFIG_STDIN=1; CONFIG_FILE=""; shift ;;
    -h|--help) usage; exit 0 ;;
    *) break ;;
  esac
done

[[ $# -gt 0 ]] || { usage; exit 1; }
command="$1"
shift

case "$command" in
  __sync-skill) cmd_sync_skill "$@"; exit $? ;;
esac

check_dependencies
load_config

case "$command" in
  list) cmd_list "$@" ;;
  search) cmd_search "$@" ;;
  ask) cmd_ask "$@" ;;
  *) emit_error_result "invalid_command" "Unknown command: $command" ;;
esac
