#!/bin/zsh
# upload-smart — auto-detect upload widget type (Element Plus / Ant Design /
# Aliyun Fusion / native dragger / native picture-card / vanilla input) and
# pick the right injection strategy + verification anchor.
#
# Usage:
#   upload-smart --url-contains URL --selector SEL --paths "p1[,p2,...]"
#                [--browser chrome|edge]
#                [--field-index N]   # if SEL matches multiple slots
#                [--no-wait]         # don't poll for success state
#                [--pretty]
#
# Output (one-line JSON unless --pretty):
#   {"ok": true, "framework": "element-plus", "method": "set-file",
#    "verify_selector": ".el-upload-list__item.is-success",
#    "items_found": 1}
#
# Strategy matrix (auto-detected by walking up from the file input):
#   - .el-upload (Element Plus picture-card)  → set-file plan + verify .el-upload-list__item.is-success
#   - .el-upload-dragger (Element Plus drag)  → JS DataTransfer drop + verify .el-upload-list__item.is-success
#   - .ant-upload (Ant Design)                → set-file plan + verify .ant-upload-list-item-done
#   - .next-upload (Aliyun Fusion)            → set-file plan + verify .next-upload-list-item-done
#   - vanilla <input type=file>               → set-file plan; no verification anchor
#
# After dispatch, if --no-wait isn't set, polls the verify selector for up
# to 15 s waiting for is-success / is-done class to appear.
set -euo pipefail
BIN_DIR=${0:A:h}

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

url=""
selector=""
paths=""
browser="Google Chrome"
field_index=0
no_wait=0
pretty=0

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

while [[ $# -gt 0 ]]; do
  case "$1" in
    --url-contains) url="$2"; shift 2;;
    --selector)     selector="$2"; shift 2;;
    --paths)        paths="$2"; shift 2;;
    --browser)      browser="$(normalize_browser "$2")"; shift 2;;
    --field-index)  field_index="$2"; shift 2;;
    --no-wait)      no_wait=1; shift;;
    --pretty)       pretty=1; shift;;
    -h|--help)      usage; exit 0;;
    *) echo "upload-smart: unknown arg: $1" >&2; exit 2;;
  esac
done

if [[ -z "$url" || -z "$selector" || -z "$paths" ]]; then
  echo 'upload-smart: --url-contains, --selector, --paths are all required' >&2
  exit 2
fi

# Detect framework via DOM walk
SEL_JSON=$(SEL="$selector" /usr/bin/python3 -c 'import json,os;print(json.dumps(os.environ["SEL"]))')

DETECT_JS=$(/usr/bin/mktemp -t uploadsmartdetect.XXXXXX.js)
trap "rm -f $DETECT_JS" EXIT

/bin/cat > "$DETECT_JS" <<JS
(() => {
  const sel = ${SEL_JSON};
  const isXPath = sel.startsWith("/") || sel.startsWith("(/");
  let nodes = [];
  if (isXPath) {
    const xr = document.evaluate(sel, document, null, XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null);
    for (var i = 0; i < xr.snapshotLength; i++) nodes.push(xr.snapshotItem(i));
  } else {
    nodes = Array.from(document.querySelectorAll(sel));
  }
  if (!nodes.length) return {error: "selector matched nothing", selector: sel};
  const idx = ${field_index};
  const node = nodes[idx];
  if (!node) return {error: "field-index out of range", count: nodes.length};
  // Walk UP, looking for known framework class markers
  let p = node;
  let framework = "vanilla";
  let widgetEl = null;
  for (let d = 0; d < 12 && p; d++) {
    const cls = (p.className || "").toString();
    if (cls.includes("el-upload-dragger")) { framework = "element-plus-dragger"; widgetEl = p; break; }
    if (cls.includes("el-upload"))         { framework = "element-plus";        widgetEl = p; break; }
    if (cls.includes("ant-upload-drag"))   { framework = "ant-design-dragger";  widgetEl = p; break; }
    if (cls.includes("ant-upload"))        { framework = "ant-design";          widgetEl = p; break; }
    if (cls.includes("next-upload"))       { framework = "aliyun-fusion";       widgetEl = p; break; }
    p = p.parentElement;
  }
  return {framework, hasWidget: !!widgetEl, count_matched: nodes.length, picked_idx: idx};
})()
JS

DETECT_RAW=$("$BIN_DIR/background-web-control" --browser "$browser" eval --url-contains "$url" --js-file "$DETECT_JS" 2>&1 || true)
FRAMEWORK=$(echo "$DETECT_RAW" | /usr/bin/python3 -c '
import sys, re, json
text = sys.stdin.read()
m = re.search(r"\"framework\":\s*\"([^\"]+)\"", text)
print(m.group(1) if m else "vanilla")
')

# Pick injection method + verify anchor
case "$FRAMEWORK" in
  element-plus|element-plus-dragger)
    method="set-file"
    verify_selector=".el-upload-list__item.is-success"
    ;;
  ant-design|ant-design-dragger)
    method="set-file"
    verify_selector=".ant-upload-list-item-done"
    ;;
  aliyun-fusion)
    method="set-file"
    verify_selector=".next-upload-list-item-done"
    ;;
  *)
    method="set-file"
    verify_selector=""
    ;;
esac

# Build the set-file plan - one step per path
PLAN_JSON=$(IFS=',' /usr/bin/python3 -c "
import json, sys
paths = sys.argv[1].split(',')
selector = sys.argv[2]
steps = []
for p in paths:
    p = p.strip()
    if not p: continue
    steps.append({'action': 'set-file', 'xpath' if (selector.startswith('/') or selector.startswith('(/')) else 'selector': selector, 'paths': [p]})
    steps.append({'action': 'sleep', 'seconds': 2.0})
print(json.dumps({'steps': steps}, ensure_ascii=False))
" "$paths" "$selector")

PLAN_FILE=$(/usr/bin/mktemp -t uploadsmartplan.XXXXXX.json)
echo "$PLAN_JSON" > "$PLAN_FILE"

PLAN_RESULT=$("$BIN_DIR/background-web-control" --browser "$browser" plan --plan-file "$PLAN_FILE" --url-contains "$url" 2>&1 || true)
rm -f "$PLAN_FILE"

# Wait for success class to appear (skip if no verify anchor)
SUCCESS_COUNT=0
EXPECTED_COUNT=$(echo "$paths" | /usr/bin/awk -F',' '{print NF}')
if [[ -n "$verify_selector" && $no_wait -eq 0 ]]; then
  WAIT_JS=$(/usr/bin/mktemp -t uploadsmartwait.XXXXXX.js)
  WAIT_SEL_JSON=$(VS="$verify_selector" /usr/bin/python3 -c 'import json,os;print(json.dumps(os.environ["VS"]))')
  /bin/cat > "$WAIT_JS" <<JS
(() => document.querySelectorAll(${WAIT_SEL_JSON}).length)()
JS
  for attempt in 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15; do
    /bin/sleep 1
    R=$("$BIN_DIR/background-web-control" --browser "$browser" eval --url-contains "$url" --js-file "$WAIT_JS" 2>&1 || true)
    SUCCESS_COUNT=$(echo "$R" | /usr/bin/python3 -c '
import sys,re; m=re.search(r"\"value\":\s*(\d+)", sys.stdin.read()); print(m.group(1) if m else "0")
')
    if [[ "$SUCCESS_COUNT" -ge "$EXPECTED_COUNT" ]]; then break; fi
  done
  rm -f "$WAIT_JS"
fi

OK=true
[[ -n "$verify_selector" && "$SUCCESS_COUNT" -lt "$EXPECTED_COUNT" ]] && OK=false

PY_OK=$([[ "$OK" == "true" ]] && echo "True" || echo "False")
INDENT=""
if [[ $pretty -eq 1 ]]; then INDENT=", indent=2"; fi

OK=$PY_OK FRAMEWORK=$FRAMEWORK METHOD=$method VS=$verify_selector EXPECTED=$EXPECTED_COUNT SUCCESS=$SUCCESS_COUNT INDENT=$INDENT \
/usr/bin/python3 -c '
import json, os
print(json.dumps({
  "ok": (os.environ["OK"] == "True"),
  "framework": os.environ["FRAMEWORK"],
  "method": os.environ["METHOD"],
  "verify_selector": os.environ["VS"],
  "expected_count": int(os.environ["EXPECTED"]),
  "success_count": int(os.environ["SUCCESS"]),
}, ensure_ascii=False, indent=2 if os.environ.get("INDENT") else None))
'
