#!/usr/bin/env bash
# fs5 — Android FS5 device management CLI
# Device: exone GmbH FS5 (Android 9)
# Usage: ./fs5 <command> [options]
set -uo pipefail
# Note: -e intentionally omitted; we handle errors explicitly for correct exit codes

# ── Resolve script directory ──────────────────────────────────────────────────
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# ── Load config from .env (environment variables take precedence) ─────────────
ENV_FILE="${SCRIPT_DIR}/.env"
if [[ -f "${ENV_FILE}" ]]; then
  # Load only variables not already set in the environment
  while IFS='=' read -r key value; do
    # Skip comments and empty lines
    [[ "${key}" =~ ^[[:space:]]*# ]] && continue
    [[ -z "${key}" ]] && continue
    key="${key// /}"
    # Only set if not already exported in environment
    if [[ -z "${!key+x}" ]]; then
      export "${key}=${value}"
    fi
  done < "${ENV_FILE}"
fi

# Defaults (fallback if neither env nor .env provided)
DEVICE_SERIAL="${DEVICE_SERIAL:-RD51QE202392}"
LOG_FILE="${LOG_FILE:-${SCRIPT_DIR}/fs5.log}"
ADB_TIMEOUT="${ADB_TIMEOUT:-10}"

# ── Load libraries ────────────────────────────────────────────────────────────
# shellcheck source=lib/output.sh
source "${SCRIPT_DIR}/lib/output.sh"
# shellcheck source=lib/adb.sh
source "${SCRIPT_DIR}/lib/adb.sh"
# shellcheck source=lib/input.sh
source "${SCRIPT_DIR}/lib/input.sh"

# ── Global flags ──────────────────────────────────────────────────────────────
JSON_OUTPUT=false
FORCE=false

# ── Usage ─────────────────────────────────────────────────────────────────────
usage() {
  cat <<EOF
Usage: fs5 <command> [options]

Device Management:
  status              Show device health and info
  push <src> <dst>    Push file/dir to device
  pull <src> <dst>    Pull file/dir from device
  app list            List installed apps
  app install <apk>   Install APK on device
  app uninstall <pkg> Uninstall package from device
  shell [cmd]         Execute shell command on device
  logs                Stream or capture logcat output

Remote Control (AI capabilities):
  screen              Launch scrcpy GUI mirror with input
  screenshot [path]   Capture screen to PNG file
  tap <x> <y>         Send tap at coordinates
  swipe <x1> <y1> <x2> <y2> [ms]  Send swipe gesture
  type <text>         Type text into focused field
  key <keycode>       Send Android key event
  launch <package>    Launch app by package name
  ocr                 Screenshot + extract all text via OCR
  ui dump [path]      Dump UI hierarchy XML (UIAutomator)
  ui elements         List all interactive UI elements
  ui find <text>      Find element by text, print coordinates
  ui tap <text>       Find element by text and tap it
  sms list            List recent SMS messages
  sms otp             Extract latest OTP code from SMS
  sms watch           Watch for new SMS messages

Global Options:
  --json              Output as JSON
  --force             Allow destructive/overwrite operations
  --help              Show this help

Exit Codes:
  0  Success
  1  General error
  2  Device not found

Device: ${DEVICE_SERIAL}
EOF
}

# ── Parse global flags (modifies globals, returns remaining args via array) ───
# Usage: parse_global_flags REMAINING_ARRAY_NAME "$@"
parse_global_flags() {
  local -n _remaining_ref="${1}"
  shift
  _remaining_ref=()
  for arg in "$@"; do
    case "${arg}" in
      --json)    JSON_OUTPUT=true ;;
      --force)   FORCE=true ;;
      --help|-h) usage; exit 0 ;;
      *)         _remaining_ref+=("${arg}") ;;
    esac
  done
}

# ── Command: status ───────────────────────────────────────────────────────────
cmd_status() {
  local show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --json)  JSON_OUTPUT=true ;;
      --help)  show_help=true ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 status [--json]

Show device health information including model, Android version,
battery level, storage usage, and ADB connection state.
EOF
    return 0
  fi

  adb_check_device

  local model android_version battery_level battery_charging
  local storage_total storage_used storage_free storage_pct adb_state ts

  model=$(adb_get_prop "ro.product.model")
  android_version=$(adb_get_prop "ro.build.version.release")
  battery_level=$(adb_battery_level)
  battery_charging=$(adb_battery_charging)
  adb_state=$(adb -s "${DEVICE_SERIAL}" get-state 2>/dev/null || echo "unknown")
  ts=$(date -Iseconds)

  # Storage: total used free pct
  read -r storage_total storage_used storage_free storage_pct \
    <<< "$(adb_storage_info)"

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    out_json \
      "serial=${DEVICE_SERIAL}" \
      "model=${model}" \
      "manufacturer=exone GmbH" \
      "android_version=${android_version}" \
      "battery_level=${battery_level}" \
      "battery_charging=${battery_charging}" \
      "storage_total_kb=${storage_total}" \
      "storage_used_kb=${storage_used}" \
      "storage_free_kb=${storage_free}" \
      "storage_percent_used=${storage_pct}" \
      "adb_state=${adb_state}" \
      "timestamp=${ts}"
  else
    echo "Device:          exone GmbH ${model}"
    echo "Serial:          ${DEVICE_SERIAL}"
    echo "Android:         ${android_version}"
    echo "ADB State:       ${adb_state}"
    echo "Battery:         ${battery_level}% (charging: ${battery_charging})"
    echo "Storage:         ${storage_used}K used / ${storage_total}K total (${storage_pct}%)"
    echo "Timestamp:       ${ts}"
  fi
}

# ── Command: push ─────────────────────────────────────────────────────────────
cmd_push() {
  local src="" dst="" show_help=false force_flag=""

  for arg in "$@"; do
    case "${arg}" in
      --help)  show_help=true ;;
      --force) force_flag="--force" ;;
      *)
        if [[ -z "${src}" ]]; then src="${arg}"
        elif [[ -z "${dst}" ]]; then dst="${arg}"
        fi
        ;;
    esac
  done

  if [[ "${FORCE}" == "true" ]]; then force_flag="--force"; fi

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 push <local-path> <device-path> [--force]

Push a local file or directory to the device.
Use --force to overwrite existing files on the device.
EOF
    return 0
  fi

  if [[ -z "${src}" ]] || [[ -z "${dst}" ]]; then
    out_error "Usage: fs5 push <src> <dst>"
    return 1
  fi

  adb_check_device
  adb_push "${src}" "${dst}" "${force_flag}"
}

# ── Command: pull ─────────────────────────────────────────────────────────────
cmd_pull() {
  local src="" dst="" show_help=false force_flag=""

  for arg in "$@"; do
    case "${arg}" in
      --help)  show_help=true ;;
      --force) force_flag="--force" ;;
      *)
        if [[ -z "${src}" ]]; then src="${arg}"
        elif [[ -z "${dst}" ]]; then dst="${arg}"
        fi
        ;;
    esac
  done

  if [[ "${FORCE}" == "true" ]]; then force_flag="--force"; fi

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 pull <device-path> <local-path> [--force]

Pull a file or directory from the device to local filesystem.
Use --force to overwrite existing local files.
EOF
    return 0
  fi

  if [[ -z "${src}" ]] || [[ -z "${dst}" ]]; then
    out_error "Usage: fs5 pull <src> <dst>"
    return 1
  fi

  adb_check_device
  adb_pull "${src}" "${dst}" "${force_flag}"
}

# ── Command: app ──────────────────────────────────────────────────────────────
cmd_app() {
  local subcmd="${1:-}"
  shift || true

  case "${subcmd}" in
    list)      cmd_app_list "$@" ;;
    install)   cmd_app_install "$@" ;;
    uninstall) cmd_app_uninstall "$@" ;;
    --help|-h)
      cat <<EOF
Usage: fs5 app <subcommand> [options]

Subcommands:
  list [--all] [--json]       List installed apps
  install <apk>               Install APK
  uninstall <package> --force Uninstall package
EOF
      ;;
    *)
      out_error "Unknown app subcommand: '${subcmd}'. Use: list, install, uninstall"
      return 1
      ;;
  esac
}

cmd_app_list() {
  local show_all=false show_help=false

  for arg in "$@"; do
    case "${arg}" in
      --all)  show_all=true ;;
      --help) show_help=true ;;
      --json) JSON_OUTPUT=true ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 app list [--all] [--json]"
    return 0
  fi

  adb_check_device

  local pm_args="-3"
  [[ "${show_all}" == "true" ]] && pm_args=""

  local raw_packages
  # shellcheck disable=SC2086
  raw_packages=$(adb_cmd shell pm list packages ${pm_args} 2>/dev/null | tr -d '\r')

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    echo "${raw_packages}" | python3 -c "
import sys, json
packages = []
for line in sys.stdin:
    line = line.strip()
    if line.startswith('package:'):
        packages.append({'package': line[8:]})
print(json.dumps(packages, indent=2))
"
  else
    echo "${raw_packages}"
  fi
}

cmd_app_install() {
  local apk="" show_help=false

  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *) [[ -z "${apk}" ]] && apk="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 app install <apk-path>"
    return 0
  fi

  if [[ -z "${apk}" ]]; then
    out_error "Usage: fs5 app install <apk-path>"
    return 1
  fi

  if [[ ! -f "${apk}" ]]; then
    out_error "APK not found: ${apk}"
    return 1
  fi

  adb_check_device

  local result
  result=$(adb_cmd install "${apk}" 2>&1)
  if echo "${result}" | grep -q "Success"; then
    adb_log_op "INSTALL" "${apk}"
    out_success "Installed: ${apk}"
  else
    out_error "Install failed: ${result}"
    return 1
  fi
}

cmd_app_uninstall() {
  local pkg="" show_help=false force_flag=false

  for arg in "$@"; do
    case "${arg}" in
      --help)  show_help=true ;;
      --force) force_flag=true ;;
      *) [[ -z "${pkg}" ]] && pkg="${arg}" ;;
    esac
  done

  if [[ "${FORCE}" == "true" ]]; then force_flag=true; fi

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 app uninstall <package> --force"
    return 0
  fi

  if [[ -z "${pkg}" ]]; then
    out_error "Usage: fs5 app uninstall <package> --force"
    return 1
  fi

  if [[ "${force_flag}" != "true" ]]; then
    out_error "Uninstall requires --force flag to prevent accidental removal"
    return 1
  fi

  adb_check_device

  local result
  result=$(adb_cmd shell pm uninstall "${pkg}" 2>&1 | tr -d '\r')
  if [[ "${result}" == "Success" ]]; then
    adb_log_op "UNINSTALL" "${pkg}"
    out_success "Uninstalled: ${pkg}"
  else
    out_error "Uninstall failed (package may not be installed): ${pkg}"
    return 1
  fi
}

# ── Command: shell ────────────────────────────────────────────────────────────
cmd_shell() {
  local show_help=false
  local -a cmd_args=()

  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *) cmd_args+=("${arg}") ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 shell [command]

Execute a shell command on the device. If no command is given,
opens an interactive shell session.
EOF
    return 0
  fi

  adb_check_device

  if [[ ${#cmd_args[@]} -eq 0 ]]; then
    adb_cmd shell
  else
    adb_cmd shell "${cmd_args[@]}"
  fi
}

# ── Command: logs ─────────────────────────────────────────────────────────────
cmd_logs() {
  local lines="" filter="" output_file="" show_help=false

  local i=0
  local -a args=("$@")
  while [[ ${i} -lt ${#args[@]} ]]; do
    case "${args[${i}]}" in
      --help)   show_help=true ;;
      --lines)  i=$(( i + 1 )); lines="${args[${i}]}" ;;
      --filter) i=$(( i + 1 )); filter="${args[${i}]}" ;;
      --output) i=$(( i + 1 )); output_file="${args[${i}]}" ;;
    esac
    i=$(( i + 1 ))
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 logs [--lines N] [--filter TAG] [--output FILE]

Stream or capture Android logcat output.

Options:
  --lines N      Print last N lines and exit (non-streaming)
  --filter TAG   Filter output by log tag
  --output FILE  Write output to file instead of stdout
EOF
    return 0
  fi

  adb_check_device

  local -a logcat_args=()

  if [[ -n "${lines}" ]]; then
    logcat_args+=("-d" "-t" "${lines}")
  fi

  if [[ -n "${filter}" ]]; then
    logcat_args+=("${filter}:V" "*:S")
  fi

  if [[ -n "${output_file}" ]]; then
    adb_cmd logcat "${logcat_args[@]+"${logcat_args[@]}"}" > "${output_file}"
    out_success "Logs written to: ${output_file}"
  else
    adb_cmd logcat "${logcat_args[@]+"${logcat_args[@]}"}"
  fi
}

# ── Command: screen ──────────────────────────────────────────────────────────
cmd_screen() {
  local headless=false show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --headless) headless=true ;;
      --help)     show_help=true ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 screen [--headless]

Launch scrcpy to mirror and control the device screen.
--headless: run without display window (for automation/OCR only).
EOF
    return 0
  fi

  adb_check_device

  if [[ "${headless}" == "true" ]]; then
    scrcpy -s "${DEVICE_SERIAL}" --no-display &
    out_success "scrcpy running headless (PID $!)"
  else
    scrcpy -s "${DEVICE_SERIAL}"
  fi
}

# ── Command: screenshot ───────────────────────────────────────────────────────
cmd_screenshot() {
  local output="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      [[ -z "${output}" ]] && output="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 screenshot [output-path.png]"
    return 0
  fi

  adb_check_device

  local path
  if [[ -n "${output}" ]]; then
    path=$(adb_screenshot "${output}")
  else
    path=$(adb_screenshot)
  fi

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    out_json "path=${path}" "timestamp=$(date -Iseconds)"
  else
    out_success "Screenshot saved: ${path}"
    echo "${path}"
  fi
}

# ── Command: tap ─────────────────────────────────────────────────────────────
cmd_tap() {
  local x="" y="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)
        if [[ -z "${x}" ]]; then x="${arg}"
        elif [[ -z "${y}" ]]; then y="${arg}"
        fi
        ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 tap <x> <y>"
    return 0
  fi

  if [[ -z "${x}" ]] || [[ -z "${y}" ]]; then
    out_error "Usage: fs5 tap <x> <y>"
    return 1
  fi

  adb_check_device
  adb_tap "${x}" "${y}"
  out_success "Tapped: (${x}, ${y})"
}

# ── Command: swipe ────────────────────────────────────────────────────────────
cmd_swipe() {
  local x1="" y1="" x2="" y2="" duration="300" show_help=false
  local -a pos=()
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      pos+=("${arg}") ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 swipe <x1> <y1> <x2> <y2> [duration_ms]"
    return 0
  fi

  x1="${pos[0]:-}" y1="${pos[1]:-}" x2="${pos[2]:-}" y2="${pos[3]:-}"
  [[ ${#pos[@]} -ge 5 ]] && duration="${pos[4]}"

  if [[ -z "${x1}" ]] || [[ -z "${y1}" ]] || [[ -z "${x2}" ]] || [[ -z "${y2}" ]]; then
    out_error "Usage: fs5 swipe <x1> <y1> <x2> <y2> [duration_ms]"
    return 1
  fi

  adb_check_device
  adb_swipe "${x1}" "${y1}" "${x2}" "${y2}" "${duration}"
  out_success "Swiped: (${x1},${y1}) → (${x2},${y2}) in ${duration}ms"
}

# ── Command: type ─────────────────────────────────────────────────────────────
cmd_type() {
  local text="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      text="${text}${text:+ }${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 type <text>"
    return 0
  fi

  if [[ -z "${text}" ]]; then
    out_error "Usage: fs5 type <text>"
    return 1
  fi

  adb_check_device
  adb_type "${text}"
  out_success "Typed: ${text}"
}

# ── Command: key ──────────────────────────────────────────────────────────────
cmd_key() {
  local keycode="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      [[ -z "${keycode}" ]] && keycode="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 key <keycode>

Send Android key event. Common keycodes:
  KEYCODE_HOME   KEYCODE_BACK   KEYCODE_POWER
  KEYCODE_ENTER  KEYCODE_TAB    KEYCODE_DEL
  3 (HOME)       4 (BACK)       26 (POWER)
EOF
    return 0
  fi

  if [[ -z "${keycode}" ]]; then
    out_error "Usage: fs5 key <keycode>"
    return 1
  fi

  adb_check_device
  adb_key "${keycode}"
  out_success "Key sent: ${keycode}"
}

# ── Command: launch ───────────────────────────────────────────────────────────
cmd_launch() {
  local pkg="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      [[ -z "${pkg}" ]] && pkg="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 launch <package-name>"
    return 0
  fi

  if [[ -z "${pkg}" ]]; then
    out_error "Usage: fs5 launch <package-name>"
    return 1
  fi

  adb_check_device
  if adb_launch "${pkg}"; then
    out_success "Launched: ${pkg}"
  else
    return 1
  fi
}

# ── Command: ui ───────────────────────────────────────────────────────────────
cmd_ui() {
  local subcmd="${1:-}"
  shift || true

  case "${subcmd}" in
    dump)     cmd_ui_dump     "$@" ;;
    elements) cmd_ui_elements "$@" ;;
    find)     cmd_ui_find     "$@" ;;
    tap)      cmd_ui_tap      "$@" ;;
    --help|-h)
      cat <<EOF
Usage: fs5 ui <subcommand> [options]

Inspect and interact with the device UI via UIAutomator.

Subcommands:
  dump [path]           Dump UI hierarchy XML to file
  elements [--json]     List all interactive UI elements
  find <text>           Find element by text/desc, print coordinates
  tap <text>            Find element by text/desc and tap it
EOF
      ;;
    *)
      out_error "Unknown ui subcommand: '${subcmd}'. Use: dump, elements, find, tap"
      return 1
      ;;
  esac
}

cmd_ui_dump() {
  local output="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)      [[ -z "${output}" ]] && output="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 ui dump [output-path.xml]

Dump the current UI hierarchy to an XML file via UIAutomator.
If no path given, saves to /tmp/fs5-ui-<timestamp>.xml.
EOF
    return 0
  fi

  adb_check_device

  local path
  if [[ -n "${output}" ]]; then
    path=$(adb_ui_dump "${output}")
  else
    path=$(adb_ui_dump)
  fi

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    out_json "path=${path}" "timestamp=$(date -Iseconds)"
  else
    out_success "UI dump saved: ${path}"
    echo "${path}"
  fi
}

cmd_ui_elements() {
  local xml_path="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      --json) JSON_OUTPUT=true ;;
      *)      [[ -z "${xml_path}" ]] && xml_path="${arg}" ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 ui elements [xml-path] [--json]

List all interactive UI elements on the current screen.
Optionally provide a previously dumped XML file.
EOF
    return 0
  fi

  adb_check_device

  local json_out
  if [[ -n "${xml_path}" ]]; then
    json_out=$(adb_ui_elements "${xml_path}")
  else
    json_out=$(adb_ui_elements)
  fi

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    echo "${json_out}"
  else
    # Human-readable table
    echo "${json_out}" | python3 -c "
import sys, json
elements = json.load(sys.stdin)
print(f'Found {len(elements)} elements:')
for i, e in enumerate(elements):
    label = e['text'] or e['content_desc'] or e['resource_id'] or e['class']
    click = '✓' if e['clickable'] else ' '
    cx, cy = e.get('center_x'), e.get('center_y')
    coord = f'({cx},{cy})' if cx is not None else '(?)'
    print(f'  [{click}] {coord:>12}  {label}')
"
  fi
}

cmd_ui_find() {
  local search_text="" xml_path="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)
        if [[ -z "${search_text}" ]]; then search_text="${arg}"
        elif [[ -z "${xml_path}" ]]; then xml_path="${arg}"
        fi
        ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 ui find <text> [xml-path]

Find a UI element by text or content-desc and print its center coordinates.
Returns "x y" on success, exits 1 if not found.
EOF
    return 0
  fi

  if [[ -z "${search_text}" ]]; then
    out_error "Usage: fs5 ui find <text>"
    return 1
  fi

  adb_check_device

  local coords
  if [[ -n "${xml_path}" ]]; then
    coords=$(adb_ui_find "${search_text}" "${xml_path}")
  else
    coords=$(adb_ui_find "${search_text}")
  fi

  if [[ -z "${coords}" ]]; then
    out_error "Element not found: '${search_text}'"
    return 1
  fi

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    local x y
    x="${coords%% *}"
    y="${coords##* }"
    out_json "text=${search_text}" "x=${x}" "y=${y}"
  else
    echo "${coords}"
  fi
}

cmd_ui_tap() {
  local search_text="" xml_path="" show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      *)
        if [[ -z "${search_text}" ]]; then search_text="${arg}"
        elif [[ -z "${xml_path}" ]]; then xml_path="${arg}"
        fi
        ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    cat <<EOF
Usage: fs5 ui tap <text> [xml-path]

Find a UI element by text or content-desc and tap its center.
Combines ui find + tap in one step.
EOF
    return 0
  fi

  if [[ -z "${search_text}" ]]; then
    out_error "Usage: fs5 ui tap <text>"
    return 1
  fi

  adb_check_device

  local coords
  if [[ -n "${xml_path}" ]]; then
    coords=$(adb_ui_find "${search_text}" "${xml_path}")
  else
    coords=$(adb_ui_find "${search_text}")
  fi

  if [[ -z "${coords}" ]]; then
    out_error "Element not found: '${search_text}'"
    return 1
  fi

  local x y
  x="${coords%% *}"
  y="${coords##* }"
  adb_tap "${x}" "${y}"

  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    out_json "text=${search_text}" "x=${x}" "y=${y}" "action=tap"
  else
    out_success "Tapped '${search_text}' at (${x}, ${y})"
  fi
}

# ── Command: ocr ──────────────────────────────────────────────────────────────
cmd_ocr() {
  local show_help=false
  for arg in "$@"; do
    [[ "${arg}" == "--help" ]] && show_help=true
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 ocr"
    echo "Take a screenshot and extract all visible text via OCR (tesseract)."
    return 0
  fi

  adb_check_device
  local text
  text=$(adb_ocr)
  if [[ "${JSON_OUTPUT}" == "true" ]]; then
    python3 -c "import json,sys; print(json.dumps({'text': sys.stdin.read()}))" <<< "${text}"
  else
    echo "${text}"
  fi
}

# ── Command: sms ──────────────────────────────────────────────────────────────
cmd_sms() {
  local subcmd="${1:-}"
  shift || true

  case "${subcmd}" in
    list)  cmd_sms_list  "$@" ;;
    otp)   cmd_sms_otp   "$@" ;;
    watch) cmd_sms_watch "$@" ;;
    --help|-h)
      cat <<EOF
Usage: fs5 sms <subcommand>

Subcommands:
  list [--limit N] [--json]   List recent SMS messages
  otp [--wait]                Extract latest OTP code from SMS
  watch                       Watch for new SMS messages (Ctrl+C to stop)
EOF
      ;;
    *)
      out_error "Unknown sms subcommand: '${subcmd}'. Use: list, otp, watch"
      return 1
      ;;
  esac
}

cmd_sms_list() {
  local limit=10 show_help=false
  local i=0
  local -a args=("$@")
  while [[ ${i} -lt ${#args[@]} ]]; do
    case "${args[${i}]}" in
      --help)  show_help=true ;;
      --json)  JSON_OUTPUT=true ;;
      --limit) i=$(( i + 1 )); limit="${args[${i}]}" ;;
    esac
    i=$(( i + 1 ))
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 sms list [--limit N] [--json]"
    return 0
  fi

  adb_check_device
  adb_sms_list "${limit}"
}

cmd_sms_otp() {
  local wait_secs=0 show_help=false
  for arg in "$@"; do
    case "${arg}" in
      --help) show_help=true ;;
      --wait) wait_secs=60 ;;
    esac
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 sms otp [--wait]"
    echo "Extract latest OTP/2FA code from SMS inbox."
    echo "--wait: poll up to 60 seconds for a new OTP."
    return 0
  fi

  adb_check_device
  local otp
  otp=$(adb_sms_otp "${wait_secs}")
  local exit_code=$?

  if [[ ${exit_code} -eq 0 ]]; then
    if [[ "${JSON_OUTPUT}" == "true" ]]; then
      out_json "otp=${otp}" "timestamp=$(date -Iseconds)"
    else
      echo "${otp}"
    fi
  fi
  return ${exit_code}
}

cmd_sms_watch() {
  local show_help=false
  for arg in "$@"; do
    [[ "${arg}" == "--help" ]] && show_help=true
  done

  if [[ "${show_help}" == "true" ]]; then
    echo "Usage: fs5 sms watch"
    echo "Watch for new SMS/notification messages via KDE Connect. Press Ctrl+C to stop."
    return 0
  fi

  local dev_id
  dev_id=$(_kdeconnect_check) || return 1

  out_success "Watching for SMS via KDE Connect notifications (Ctrl+C to stop)..."
  local notif_base="/modules/kdeconnect/devices/${dev_id}/notifications"
  local seen_ids=""

  while true; do
    local notif_ids
    notif_ids=$(gdbus call --session --dest org.kde.kdeconnect \
      --object-path "${notif_base}" \
      --method org.kde.kdeconnect.device.notifications.activeNotifications \
      2>/dev/null | grep -oE "'[^']+'" | tr -d "'" | tr ',' '\n' | tr -d ' []')

    for nid in ${notif_ids}; do
      # Skip already-seen notifications
      if echo "${seen_ids}" | grep -qw "${nid}"; then
        continue
      fi
      seen_ids="${seen_ids} ${nid}"

      local props app title text ts
      props=$(gdbus call --session --dest org.kde.kdeconnect \
        --object-path "${notif_base}/${nid}" \
        --method org.freedesktop.DBus.Properties.GetAll \
        "org.kde.kdeconnect.device.notifications.notification" 2>/dev/null)
      ts=$(date -Iseconds)
      app=$(echo "${props}" | grep -oP "'appName': <'\K[^']+")
      title=$(echo "${props}" | grep -oP "'title': <'\K[^']+")
      text=$(echo "${props}" | grep -oP "'text': <'\K[^']+" | head -c 200)
      echo "[${ts}] APP: ${app} | ${title}"
      echo "  ${text}"
      echo ""
    done
    sleep 5
  done
}

# ── Main dispatcher ───────────────────────────────────────────────────────────
main() {
  if [[ $# -eq 0 ]]; then
    usage
    exit 1
  fi

  # Extract command (first arg)
  local command="${1}"
  shift

  # Handle global --help before command dispatch
  if [[ "${command}" == "--help" ]] || [[ "${command}" == "-h" ]]; then
    usage
    exit 0
  fi

  # Parse remaining args for global flags; remaining_args gets non-flag args
  local -a remaining_args=()
  parse_global_flags remaining_args "$@"

  case "${command}" in
    status)     cmd_status     "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    push)       cmd_push       "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    pull)       cmd_pull       "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    app)        cmd_app        "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    shell)      cmd_shell      "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    logs)       cmd_logs       "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    screen)     cmd_screen     "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    screenshot) cmd_screenshot "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    tap)        cmd_tap        "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    swipe)      cmd_swipe      "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    type)       cmd_type       "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    key)        cmd_key        "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    launch)     cmd_launch     "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    ui)         cmd_ui         "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    ocr)        cmd_ocr        "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    sms)        cmd_sms        "${remaining_args[@]+"${remaining_args[@]}"}" ;;
    *)
      out_error "Unknown command: ${command}"
      usage
      exit 1
      ;;
  esac
}

main "$@"
