#!/bin/sh
# Bananarchy Dual-SIM & QMI UIM Provisioning Helper for Qualcomm MSM8905/MSM8909
set -eu

CONF_FILE="/etc/conf.d/msm-modem-uim-selection"
if [ -r "$CONF_FILE" ]; then
  # shellcheck disable=SC1090
  . "$CONF_FILE"
fi

SIM_WAIT_TIME="${SIM_WAIT_TIME:-${sim_wait_time:-4}}"
PREFERRED_SIM_SLOT="${PREFERRED_SIM_SLOT:-${preferred_sim_slot:-1}}"
QMICLI_MODEM=""

ensure_usb_network_gadget() {
  CONFIGFS="/sys/kernel/config/usb_gadget"
  if ! [ -d "$CONFIGFS" ]; then
    modprobe libcomposite 2>/dev/null || true
    mount -t configfs configfs /sys/kernel/config 2>/dev/null || true
  fi
  udc_dev="$(ls /sys/class/udc 2>/dev/null | head -n 1 || true)"
  if [ -z "$udc_dev" ]; then
    modprobe pm8916-lbc 2>/dev/null || true
    w=0
    while [ -z "$udc_dev" ] && [ "$w" -lt 20 ]; do
      sleep 0.1
      udc_dev="$(ls /sys/class/udc 2>/dev/null | head -n 1 || true)"
      w=$((w + 1))
    done
  fi
  if [ -d "$CONFIGFS" ] && [ -n "$udc_dev" ]; then
    if ! [ -d "$CONFIGFS/g1" ]; then
      mkdir -p "$CONFIGFS/g1" 2>/dev/null || true
      echo "0x18D1" > "$CONFIGFS/g1/idVendor" 2>/dev/null || true
      echo "0xD001" > "$CONFIGFS/g1/idProduct" 2>/dev/null || true
      mkdir -p "$CONFIGFS/g1/strings/0x409" 2>/dev/null || true
      echo "Nokia" > "$CONFIGFS/g1/strings/0x409/manufacturer" 2>/dev/null || true
      echo "Bananarchy" > "$CONFIGFS/g1/strings/0x409/serialnumber" 2>/dev/null || true
      echo "Nokia 8110 4G" > "$CONFIGFS/g1/strings/0x409/product" 2>/dev/null || true
      func="ncm.usb0"
      if ! mkdir -p "$CONFIGFS/g1/functions/$func" 2>/dev/null; then
        func="rndis.usb0"
        mkdir -p "$CONFIGFS/g1/functions/$func" 2>/dev/null || true
      fi
      mkdir -p "$CONFIGFS/g1/configs/c.1/strings/0x409" 2>/dev/null || true
      echo "USB network" > "$CONFIGFS/g1/configs/c.1/strings/0x409/configuration" 2>/dev/null || true
      ln -sf "$CONFIGFS/g1/functions/$func" "$CONFIGFS/g1/configs/c.1/" 2>/dev/null || true
    fi
    cur_udc="$(cat "$CONFIGFS/g1/UDC" 2>/dev/null | tr -d '[:space:]' || true)"
    if [ -z "$cur_udc" ]; then
      echo "$udc_dev" > "$CONFIGFS/g1/UDC" 2>/dev/null || true
    fi
  fi
  if ip link show dev usb0 >/dev/null 2>&1; then
    ip link set usb0 up 2>/dev/null || true
    ip -4 addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || \
      ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
    if ! pidof unudhcpd >/dev/null 2>&1; then
      (unudhcpd -i usb0 -s 172.16.42.1 -c 172.16.42.2 >/dev/null 2>&1) &
    fi
  fi
}

detect_qmicli_modem() {
  if ! command -v qmicli >/dev/null 2>&1; then
    return 1
  fi
  if [ -e "/dev/wwan0qmi0" ]; then
    if timeout 3 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>&1; then
      QMICLI_MODEM="timeout 4 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi"
      return 0
    elif timeout 3 qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi --uim-noop >/dev/null 2>&1; then
      QMICLI_MODEM="timeout 4 qmicli --silent -d /dev/wwan0qmi0 --device-open-qmi"
      return 0
    fi
  fi
  if timeout 3 qmicli --silent -pd qrtr://0 --uim-noop >/dev/null 2>&1; then
    QMICLI_MODEM="timeout 4 qmicli --silent -pd qrtr://0"
    return 0
  elif timeout 3 qmicli --silent -d qrtr://0 --uim-noop >/dev/null 2>&1; then
    QMICLI_MODEM="timeout 4 qmicli --silent -d qrtr://0"
    return 0
  elif [ -e "/dev/modem" ]; then
    if timeout 3 qmicli --silent -pd /dev/modem --uim-noop >/dev/null 2>&1; then
      QMICLI_MODEM="timeout 4 qmicli --silent -pd /dev/modem"
      return 0
    elif timeout 3 qmicli --silent -d /dev/modem --uim-noop >/dev/null 2>&1; then
      QMICLI_MODEM="timeout 4 qmicli --silent -d /dev/modem"
      return 0
    fi
  fi
  return 1
}

wait_for_qmicli_modem() {
  max_wait="${1:-12}"
  count=0
  while [ "$count" -lt "$max_wait" ]; do
    if detect_qmicli_modem; then
      return 0
    fi
    sleep 1
    count=$((count + 1))
  done
  return 1
}

# Parse `qmicli --uim-get-card-status` into shell-safe KEY=VALUE lines
parse_uim_card_status() {
  raw_status="$1"
  pref_slot="$2"
  printf "%s\n" "$raw_status" | awk -v pref="$pref_slot" '
    BEGIN {
      prim_slot = 0
      prim_app = 0
      sec_slot = 0
      sec_app = 0
      cur_slot = 0
      in_app = 0
      for (s = 1; s <= 2; s++) {
        cstate[s] = "absent"
        rawstate[s] = "absent"
        upin_state[s] = "not-initialized"
        upin_ret[s] = 0
        upuk_ret[s] = 0
        app_type[s] = "none"
        app_state[s] = "none"
        aid[s] = ""
        pin1_state[s] = "not-initialized"
        pin1_ret[s] = 0
        puk1_ret[s] = 0
        want_aid[s] = 0
        need_aid_line[s] = 0
      }
    }
    /Primary GW:/ {
      if ($0 ~ /slot \x27[0-9]+\x27/) {
        line = $0
        sub(/.*slot \x27/, "", line)
        sub(/\x27.*/, "", line)
        prim_slot = line + 0
      }
      if ($0 ~ /application \x27[0-9]+\x27/) {
        line = $0
        sub(/.*application \x27/, "", line)
        sub(/\x27.*/, "", line)
        prim_app = line + 0
      }
    }
    /Secondary GW:/ {
      if ($0 ~ /slot \x27[0-9]+\x27/) {
        line = $0
        sub(/.*slot \x27/, "", line)
        sub(/\x27.*/, "", line)
        sec_slot = line + 0
      }
      if ($0 ~ /application \x27[0-9]+\x27/) {
        line = $0
        sub(/.*application \x27/, "", line)
        sub(/\x27.*/, "", line)
        sec_app = line + 0
      }
    }
    /^[[:space:]]*Slot \[[0-9]+\]:/ {
      line = $0
      sub(/.*Slot \[/, "", line)
      sub(/\].*/, "", line)
      cur_slot = line + 0
      in_app = 0
      next
    }
    cur_slot >= 1 && cur_slot <= 2 {
      if ($0 ~ /^[[:space:]]*Application \[[0-9]+\]:/) {
        in_app = 1
        next
      }
      if (!in_app && $0 ~ /Card state:/) {
        line = $0
        sub(/.*Card state:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        rawstate[cur_slot] = line
        if (line == "present") cstate[cur_slot] = "present"
        else if (line == "absent" || line ~ /no-atr-received/) cstate[cur_slot] = "absent"
        else cstate[cur_slot] = "error"
      }
      if (!in_app && $0 ~ /UPIN state:/) {
        line = $0
        sub(/.*UPIN state:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        upin_state[cur_slot] = line
      }
      if (!in_app && $0 ~ /UPIN retries:/) {
        line = $0
        sub(/.*UPIN retries:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        upin_ret[cur_slot] = line + 0
      }
      if (!in_app && $0 ~ /UPUK retries:/) {
        line = $0
        sub(/.*UPUK retries:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        upuk_ret[cur_slot] = line + 0
      }
      if (in_app && $0 ~ /Application type:/) {
        line = $0
        sub(/.*Application type:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        if (line ~ /^usim/) {
          app_type[cur_slot] = "usim"
          want_aid[cur_slot] = 2
        } else if (line ~ /^sim/ && app_type[cur_slot] != "usim") {
          app_type[cur_slot] = "sim"
          want_aid[cur_slot] = 1
        } else if (app_type[cur_slot] == "none") {
          app_type[cur_slot] = "sim"
          want_aid[cur_slot] = 1
        } else {
          want_aid[cur_slot] = 0
        }
      }
      if (in_app && $0 ~ /Application state:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*Application state:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        app_state[cur_slot] = line
      }
      if (in_app && $0 ~ /Application ID:/ && want_aid[cur_slot] > 0) {
        need_aid_line[cur_slot] = 1
        next
      }
      if (in_app && need_aid_line[cur_slot] == 1) {
        line = $0
        gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
        if (line != "" && line !~ /:.*\x27/) {
          aid[cur_slot] = line
        }
        need_aid_line[cur_slot] = 0
      }
      if (in_app && $0 ~ /PIN1 state:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PIN1 state:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        pin1_state[cur_slot] = line
      }
      if (in_app && $0 ~ /PIN1 retries:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PIN1 retries:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        pin1_ret[cur_slot] = line + 0
      }
      if (in_app && $0 ~ /PUK1 retries:/ && want_aid[cur_slot] > 0) {
        line = $0
        sub(/.*PUK1 retries:[[:space:]]*\x27/, "", line)
        sub(/\x27.*/, "", line)
        puk1_ret[cur_slot] = line + 0
      }
    }
    END {
      printf "PREFERRED_SLOT=%d\n", (pref == 2 ? 2 : 1)
      printf "PRIMARY_GW_SLOT=%d\n", prim_slot
      printf "PRIMARY_GW_APP=%d\n", prim_app
      printf "SECONDARY_GW_SLOT=%d\n", sec_slot
      printf "SECONDARY_GW_APP=%d\n", sec_app
      for (s = 1; s <= 2; s++) {
        gsub(/[^A-Za-z0-9_.:-]/, "_", cstate[s])
        gsub(/[^A-Za-z0-9_.:-]/, "_", rawstate[s])
        gsub(/[^A-Za-z0-9_.:-]/, "_", app_type[s])
        gsub(/[^A-Za-z0-9_.:-]/, "_", app_state[s])
        gsub(/[^A-Za-z0-9_.:-]/, "", aid[s])
        gsub(/[^A-Za-z0-9_.:-]/, "_", pin1_state[s])
        gsub(/[^A-Za-z0-9_.:-]/, "_", upin_state[s])
        printf "SLOT%d_STATE=%s\n", s, cstate[s]
        printf "SLOT%d_RAW_STATE=%s\n", s, rawstate[s]
        printf "SLOT%d_APP_TYPE=%s\n", s, app_type[s]
        printf "SLOT%d_APP_STATE=%s\n", s, app_state[s]
        printf "SLOT%d_AID=%s\n", s, aid[s]
        printf "SLOT%d_PIN1_STATE=%s\n", s, pin1_state[s]
        printf "SLOT%d_PIN1_RETRIES=%d\n", s, pin1_ret[s]
        printf "SLOT%d_PUK1_RETRIES=%d\n", s, puk1_ret[s]
        printf "SLOT%d_UPIN_STATE=%s\n", s, upin_state[s]
        printf "SLOT%d_UPIN_RETRIES=%d\n", s, upin_ret[s]
        printf "SLOT%d_UPUK_RETRIES=%d\n", s, upuk_ret[s]
      }
    }
  '
}

parse_nas_serving_system() {
  raw_nas="$1"
  printf "%s\n" "$raw_nas" | awk '
    BEGIN {
      reg_state = "unknown"
      cs_state = "unknown"
      ps_state = "unknown"
      rat = ""
      mcc = ""
      mnc = ""
      op_desc = ""
      roam = "off"
      in_rat = 0
      in_plmn = 0
      in_roam = 0
    }
    /^[[:space:]]*Registration state:/ {
      line = $0
      sub(/.*Registration state:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      reg_state = line
      in_rat = 0; in_plmn = 0; in_roam = 0
      next
    }
    /^[[:space:]]*CS:/ {
      line = $0
      sub(/.*CS:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      cs_state = line
      next
    }
    /^[[:space:]]*PS:/ {
      line = $0
      sub(/.*PS:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      ps_state = line
      next
    }
    /^[[:space:]]*Radio interfaces:/ {
      in_rat = 1; in_plmn = 0; in_roam = 0
      next
    }
    /^[[:space:]]*Data service capabilities:/ {
      in_rat = 0
      next
    }
    /^[[:space:]]*Current PLMN:/ {
      in_plmn = 1; in_rat = 0; in_roam = 0
      next
    }
    /^[[:space:]]*Roaming indicators:/ {
      in_roam = 1; in_plmn = 0; in_rat = 0
      next
    }
    /^[[:space:]]*Default roaming status:/ {
      line = $0
      sub(/.*Default roaming status:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      roam = line
      in_roam = 0
      next
    }
    in_rat && /^[[:space:]]*\[[0-9]+\]:/ {
      line = $0
      sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      if (line != "" && line != "none" && rat == "") rat = line
    }
    in_plmn && /MCC:/ {
      line = $0
      sub(/.*MCC:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      mcc = line
    }
    in_plmn && /MNC:/ {
      line = $0
      sub(/.*MNC:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      mnc = line
    }
    in_plmn && /Description:/ {
      line = $0
      sub(/.*Description:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      gsub(/[\r\n\t]/, " ", line)
      op_desc = line
      in_plmn = 0
    }
    in_roam && /^[[:space:]]*\[[0-9]+\]:/ {
      line = $0
      sub(/.*\[[0-9]+\]:[[:space:]]*\x27/, "", line)
      sub(/\x27.*/, "", line)
      if (line == "on") roam = "on"
    }
    END {
      printf "NAS_REG_STATE=%s\n", reg_state
      printf "NAS_CS_STATE=%s\n", cs_state
      printf "NAS_PS_STATE=%s\n", ps_state
      printf "NAS_RAT=%s\n", rat
      printf "NAS_RADIO=%s\n", rat
      printf "NAS_MCC=%s\n", mcc
      printf "NAS_MNC=%s\n", mnc
      printf "NAS_OPERATOR=%s\n", op_desc
      printf "NAS_ROAMING=%s\n", roam
    }
  '
}

save_preferred_slot() {
  target_slot="$1"
  PREFERRED_SIM_SLOT="$target_slot"
  if [ -f "$CONF_FILE" ]; then
    if grep -q "^preferred_sim_slot=" "$CONF_FILE"; then
      sed -i "s/^preferred_sim_slot=.*/preferred_sim_slot=$target_slot/" "$CONF_FILE"
    else
      printf "\npreferred_sim_slot=%s\n" "$target_slot" >> "$CONF_FILE"
    fi
  else
    printf "sim_wait_time=10\npreferred_sim_slot=%s\n" "$target_slot" > "$CONF_FILE"
  fi
}

provision_slot() {
  req_slot="$1"
  wait_secs="$2"

  if ! wait_for_qmicli_modem "$wait_secs"; then
    echo "No modem available." >&2
    return 2
  fi

  QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
  count=0
  while : ; do
    if [ -n "$QMI_CARDS" ]; then
      eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"
      if [ "$req_slot" = "2" ] && [ "$SLOT2_STATE" = "present" ] && [ "$SLOT2_APP_TYPE" != "none" ]; then
        break
      elif [ "$req_slot" != "2" ] && [ "$SLOT1_STATE" = "present" ] && [ "$SLOT1_APP_TYPE" != "none" ]; then
        break
      elif [ "$count" -ge 3 ] && { { [ "$SLOT1_STATE" = "present" ] && [ "$SLOT1_APP_TYPE" != "none" ]; } || { [ "$SLOT2_STATE" = "present" ] && [ "$SLOT2_APP_TYPE" != "none" ]; }; }; then
        break
      fi
    fi
    if [ "$count" -ge "$SIM_WAIT_TIME" ]; then
      break
    fi
    sleep 1
    count=$((count + 1))
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
  done

  if [ -z "$QMI_CARDS" ]; then
    echo "No SIM card status returned."
    return 0
  fi

  eval "$(parse_uim_card_status "$QMI_CARDS" "$req_slot")"

  chosen_slot=""
  chosen_aid=""
  if [ "$req_slot" = "2" ]; then
    if [ "$SLOT2_STATE" = "present" ]; then
      chosen_slot="2"
      chosen_aid="$SLOT2_AID"
    elif [ "$SLOT1_STATE" = "present" ]; then
      chosen_slot="1"
      chosen_aid="$SLOT1_AID"
    fi
  else
    if [ "$SLOT1_STATE" = "present" ]; then
      chosen_slot="1"
      chosen_aid="$SLOT1_AID"
    elif [ "$SLOT2_STATE" = "present" ]; then
      chosen_slot="2"
      chosen_aid="$SLOT2_AID"
    fi
  fi

  if [ -z "$chosen_slot" ]; then
    echo "No SIM detected after $SIM_WAIT_TIME seconds."
    return 0
  fi

  if [ "$chosen_slot" != "$PREFERRED_SIM_SLOT" ]; then
    save_preferred_slot "$chosen_slot"
  fi

  # If already provisioned on the chosen slot and application is ready or waiting for PIN, keep session intact
  eval "chosen_app_state=\$SLOT${chosen_slot}_APP_STATE"
  if [ "$PRIMARY_GW_SLOT" = "$chosen_slot" ] && { [ "$chosen_app_state" = "ready" ] || [ "$chosen_app_state" = "pin1-or-upin-pin-required" ] || [ "$chosen_app_state" = "puk1-or-upin-puk-required" ]; }; then
    echo "Slot $chosen_slot already bound to primary-gw-provisioning ($chosen_app_state)."
    return 0
  fi

  if [ "${SECONDARY_GW_SLOT:-0}" = "$chosen_slot" ]; then
    $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true
  fi

  if [ "$PRIMARY_GW_SLOT" != "0" ]; then
    $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
  fi

  if [ -n "$chosen_aid" ]; then
    echo "Selecting AID $chosen_aid on slot $chosen_slot"
    if ! $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning,aid=$chosen_aid" >/dev/null 2>&1; then
      $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
    fi
  else
    echo "Selecting slot $chosen_slot"
    $QMICLI_MODEM --uim-change-provisioning-session="slot=$chosen_slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
  fi
  return 0
}

prepare_slot_for_pin_op() {
  req_slot="${1:-1}"
  slot="$req_slot"
  target_aid=""
  if detect_qmicli_modem; then
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
    if [ -n "$QMI_CARDS" ]; then
      eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")"
      if [ "$slot" = "1" ] && [ "$SLOT1_STATE" != "present" ] && [ "$SLOT2_STATE" = "present" ]; then
        slot="2"
      elif [ "$slot" = "2" ] && [ "$SLOT2_STATE" != "present" ] && [ "$SLOT1_STATE" = "present" ]; then
        slot="1"
      fi
      eval "target_aid=\$SLOT${slot}_AID"
      if [ "$PRIMARY_GW_SLOT" != "$slot" ]; then
        if [ "${SECONDARY_GW_SLOT:-0}" = "$slot" ]; then
          $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=secondary-gw-provisioning' >/dev/null 2>&1 || true
        fi
        if [ "$PRIMARY_GW_SLOT" != "0" ]; then
          $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
        fi
        if [ -n "$target_aid" ]; then
          $QMICLI_MODEM --uim-change-provisioning-session="slot=$slot,activate=yes,session-type=primary-gw-provisioning,aid=$target_aid" >/dev/null 2>&1 || \
            $QMICLI_MODEM --uim-change-provisioning-session="slot=$slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
        else
          $QMICLI_MODEM --uim-change-provisioning-session="slot=$slot,activate=yes,session-type=primary-gw-provisioning" >/dev/null 2>&1 || true
        fi
        sleep 0.5
      fi
    fi
  fi
}

restart_modemmanager_clean() {
  if command -v rc-service >/dev/null 2>&1; then
    timeout 4 rc-service polkit start >/dev/null 2>&1 || true
    timeout 6 rc-service modemmanager restart >/dev/null 2>&1 || {
      killall -q ModemManager 2>/dev/null || true
      sleep 0.3
      timeout 6 rc-service modemmanager zap >/dev/null 2>&1 || true
      timeout 6 rc-service modemmanager start >/dev/null 2>&1 || true
    }
  elif command -v systemctl >/dev/null 2>&1; then
    timeout 6 systemctl restart ModemManager >/dev/null 2>&1 || true
  fi
}

enable_and_register_modem() {
  target_slot="${1:-$PREFERRED_SIM_SLOT}"
  force_unlock="${2:-0}"
  if detect_qmicli_modem; then
    $QMICLI_MODEM --dms-set-operating-mode=online >/dev/null 2>&1 || true
    $QMICLI_MODEM --nas-initiate-network-register >/dev/null 2>&1 || true
  fi

  i=0
  mm_state=""
  while [ "$i" -lt 8 ]; do
    mm_state="$(timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
    if [ -n "$mm_state" ]; then
      break
    fi
    sleep 1
    i=$((i + 1))
  done

  if [ -z "$mm_state" ] || [ "$mm_state" = "failed" ] || { [ "$force_unlock" = "1" ] && [ "$mm_state" = "locked" ]; }; then
    restart_modemmanager_clean
    i=0
    mm_state=""
    while [ "$i" -lt 10 ]; do
      mm_state="$(timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
      if [ -n "$mm_state" ] && [ "$mm_state" != "failed" ]; then
        break
      fi
      sleep 1
      i=$((i + 1))
    done
  fi

  if [ -z "$mm_state" ] || [ "$mm_state" = "locked" ]; then
    return 0
  fi

  i=0
  while [ "$i" -lt 8 ]; do
    mm_state="$(timeout 3 mmcli -m any -K 2>/dev/null | awk -F': ' '/^modem\.generic\.state / {print $2; exit}' || true)"
    case "$mm_state" in
      disabled)
        timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
        ;;
      enabled)
        timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
        break
        ;;
      searching|registered|connected|connecting)
        break
        ;;
      locked)
        break
        ;;
    esac
    sleep 1
    i=$((i + 1))
  done

  timeout 4 mmcli -m any --3gpp-register-home >/dev/null 2>&1 || true
  if command -v sxmo_hook_statusbar.sh >/dev/null 2>&1; then
    DISPLAY="${DISPLAY:-:0}" timeout 3 sxmo_hook_statusbar.sh modem >/dev/null 2>&1 || true
  fi
}

spawn_enable_and_register() {
  s_slot="${1:-$PREFERRED_SIM_SLOT}"
  s_force="${2:-0}"
  (
    exec >/dev/null 2>&1 </dev/null 3>&- 4>&- 5>&- 6>&- 7>&- 8>&- 9>&- 2>/dev/null || true
    enable_and_register_modem "$s_slot" "$s_force"
  ) &
}

cmd="${1:-boot}"

case "$cmd" in
  boot)
    ensure_usb_network_gadget
    if command -v systemd-notify >/dev/null 2>&1; then
      trap '[ $? -eq 0 ] && systemd-notify --ready' EXIT
    fi
    case "$(cat /sys/devices/soc0/machine 2>/dev/null || true)" in
      APQ*)
        echo "Skipping SIM configuration on APQ SoC."
        exit 0
        ;;
    esac
    provision_slot "$PREFERRED_SIM_SLOT" 10 || true
    exit 0
    ;;

  status)
    QMI_CARDS=""
    if command -v qmicli >/dev/null 2>&1; then
      if [ -e "/dev/wwan0qmi0" ]; then
        QMI_CARDS="$(timeout 3 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi --uim-get-card-status 2>/dev/null || true)"
        if [ -n "$QMI_CARDS" ]; then
          QMICLI_MODEM="timeout 3 qmicli --silent -pd /dev/wwan0qmi0 --device-open-qmi"
        fi
      fi
      if [ -z "$QMI_CARDS" ] && detect_qmicli_modem; then
        QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
      fi
    fi
    if [ -z "$QMI_CARDS" ]; then
      echo "MODEM_AVAILABLE=0"
      echo "PREFERRED_SLOT=$PREFERRED_SIM_SLOT"
      exit 0
    fi
    echo "MODEM_AVAILABLE=1"
    parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT"
    QMI_NAS="$($QMICLI_MODEM --nas-get-serving-system 2>/dev/null || true)"
    if [ -n "$QMI_NAS" ]; then
      parse_nas_serving_system "$QMI_NAS"
    fi
    exit 0
    ;;

  switch)
    target="${2:-1}"
    if [ "$target" != "1" ] && [ "$target" != "2" ]; then
      echo "Invalid slot: $target (must be 1 or 2)" >&2
      exit 1
    fi
    save_preferred_slot "$target"
    SIM_WAIT_TIME=3
    provision_slot "$target" 5
    restart_modemmanager_clean
    spawn_enable_and_register "$target" 0
    exit 0
    ;;

  verify-pin)
    slot="${2:-1}"
    pin="${3:-}"
    if [ -z "$pin" ]; then
      echo "Missing PIN" >&2
      exit 1
    fi
    prepare_slot_for_pin_op "$slot"
    eval "before_ret=\${SLOT${slot}_PIN1_RETRIES:-3}"
    verified=0
    wrong_pin=0
    sim_id="$(timeout 3 mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ]; then
      mm_pin_out="$(timeout 5 mmcli -i "$sim_id" --pin="$pin" 2>&1)" && verified=1 || {
        if printf "%s\n" "$mm_pin_out" | grep -qiE "IncorrectPin|SimPin|SimPuk|PinBlocked|incorrect"; then
          wrong_pin=1
        fi
      }
    fi
    if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ] && [ -n "$QMICLI_MODEM" ]; then
      qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=primary-gw-provisioning" 2>&1)" && verified=1 || {
        if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then
          wrong_pin=1
        fi
      }
      if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then
        qmi_pin_out="$($QMICLI_MODEM --uim-verify-pin="PIN1,$pin,session-type=card-slot-$slot" 2>&1)" && verified=1 || {
          if printf "%s\n" "$qmi_pin_out" | grep -qiE "IncorrectPin|PinBlocked|Blocked"; then
            wrong_pin=1
          fi
        }
      fi
    fi
    if [ "$verified" = "0" ] && [ -n "$QMICLI_MODEM" ]; then
      QMI_CARDS_AFTER="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
      if [ -n "$QMI_CARDS_AFTER" ]; then
        eval "$(parse_uim_card_status "$QMI_CARDS_AFTER" "$slot")"
        eval "after_pin1=\$SLOT${slot}_PIN1_STATE"
        eval "after_app=\$SLOT${slot}_APP_STATE"
        eval "after_ret=\$SLOT${slot}_PIN1_RETRIES"
        if [ "$after_pin1" = "enabled-verified" ] || [ "$after_pin1" = "disabled" ] || [ "$after_app" = "ready" ]; then
          verified=1
        elif [ "$after_ret" -lt "$before_ret" ] 2>/dev/null; then
          wrong_pin=1
        fi
      fi
    fi
    if [ "$verified" = "0" ] && [ "$wrong_pin" = "0" ]; then
      at_cpin="$("$0" at auto "AT+CPIN=\"$pin\"" 2>/dev/null || true)"
      if printf "%s\n" "$at_cpin" | grep -qE "(^|[[:space:]])OK([[:space:]]|$)"; then
        verified=1
      fi
    fi
    if [ "$verified" = "1" ]; then
      save_preferred_slot "$slot"
      SIM_WAIT_TIME=2
      provision_slot "$slot" 3 >/dev/null 2>&1 || true
      timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
      spawn_enable_and_register "$slot" 1
      exit 0
    fi
    exit 1
    ;;

  unblock-puk)
    slot="${2:-1}"
    puk="${3:-}"
    newpin="${4:-}"
    if [ -z "$puk" ] || [ -z "$newpin" ]; then
      echo "Missing PUK or new PIN" >&2
      exit 1
    fi
    prepare_slot_for_pin_op "$slot"
    unblocked=0
    sim_id="$(timeout 3 mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ] && timeout 4 mmcli -i "$sim_id" --puk="$puk" --pin="$newpin" >/dev/null 2>&1; then
      unblocked=1
    fi
    if [ "$unblocked" = "0" ] && [ -n "$QMICLI_MODEM" ]; then
      if $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        unblocked=1
      elif $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        unblocked=1
      elif $QMICLI_MODEM --uim-unblock-pin="PIN1,$puk,$newpin" >/dev/null 2>&1; then
        unblocked=1
      elif $QMICLI_MODEM --dms-uim-unblock-pin="PIN,$puk,$newpin" >/dev/null 2>&1; then
        unblocked=1
      fi
    fi
    if [ "$unblocked" = "0" ]; then
      at_cpin="$("$0" at auto "AT+CPIN=\"$puk\",\"$newpin\"" 2>/dev/null || true)"
      if printf "%s\n" "$at_cpin" | grep -qE "(^|[[:space:]])OK([[:space:]]|$)"; then
        unblocked=1
      fi
    fi
    if [ "$unblocked" = "1" ]; then
      save_preferred_slot "$slot"
      SIM_WAIT_TIME=2
      provision_slot "$slot" 3 >/dev/null 2>&1 || true
      timeout 4 mmcli -m any -e >/dev/null 2>&1 || true
      spawn_enable_and_register "$slot" 1
      exit 0
    fi
    exit 1
    ;;

  set-pin-lock)
    slot="${2:-1}"
    mode="${3:-enable}"
    pin="${4:-}"
    if [ -z "$pin" ] || { [ "$mode" != "enable" ] && [ "$mode" != "disable" ]; }; then
      echo "Usage: $0 set-pin-lock <1|2> <enable|disable> <pin>" >&2
      exit 1
    fi
    prepare_slot_for_pin_op "$slot"
    if [ -n "$QMICLI_MODEM" ]; then
      if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-set-pin-protection="PIN1,$mode,$pin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --dms-uim-set-pin-protection="PIN,$mode,$pin" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ]; then
      if [ "$mode" = "enable" ] && mmcli -i "$sim_id" --enable-pin --pin="$pin" >/dev/null 2>&1; then
        exit 0
      elif [ "$mode" = "disable" ] && mmcli -i "$sim_id" --disable-pin --pin="$pin" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    exit 1
    ;;

  change-pin)
    slot="${2:-1}"
    oldpin="${3:-}"
    newpin="${4:-}"
    if [ -z "$oldpin" ] || [ -z "$newpin" ]; then
      echo "Usage: $0 change-pin <1|2> <oldpin> <newpin>" >&2
      exit 1
    fi
    prepare_slot_for_pin_op "$slot"
    if [ -n "$QMICLI_MODEM" ]; then
      if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=primary-gw-provisioning" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --uim-change-pin="PIN1,$oldpin,$newpin,session-type=card-slot-$slot" >/dev/null 2>&1; then
        exit 0
      fi
      if $QMICLI_MODEM --dms-uim-change-pin="PIN,$oldpin,$newpin" >/dev/null 2>&1; then
        exit 0
      fi
    fi
    sim_id="$(mmcli -m any 2>/dev/null | grep -oE 'SIM/[0-9]+' | head -n1 | cut -d'/' -f2 || true)"
    if [ -n "$sim_id" ] && mmcli -i "$sim_id" --change-pin="$newpin" --pin="$oldpin" >/dev/null 2>&1; then
      exit 0
    fi
    exit 1
    ;;

  imei)
    imei1=""
    imei2=""
    imei_sv=""
    if detect_qmicli_modem; then
      dms_ids="$($QMICLI_MODEM --dms-get-ids 2>/dev/null || true)"
      imei1="$(printf "%s\n" "$dms_ids" | sed -n "s/.*IMEI: '\([0-9]\{14,16\}\)'/\1/p" | head -n1)"
      imei_sv="$(printf "%s\n" "$dms_ids" | sed -n "s/.*IMEI SV: '\([^']*\)'/\1/p" | head -n1)"
    fi
    if [ -z "$imei1" ]; then
      imei1="$(mmcli -m any -K 2>/dev/null | awk -F': ' '/modem\.3gpp\.imei/ {print $2; exit}')"
    fi
    # Query secondary AT channel (/dev/wwan0at1) if present
    if [ -c "/dev/wwan0at1" ]; then
      at_out="$(timeout 1 sh -c '
        stty -F /dev/wwan0at1 raw -echo 115200 2>/dev/null || true
        (cat /dev/wwan0at1 & pid=$!; printf "AT+CGSN\r\n" > /dev/wwan0at1; sleep 0.25; kill $pid 2>/dev/null) 2>/dev/null
      ' 2>/dev/null | tr -d "\r" | grep -oE '^[0-9]{15}$' | head -n1 || true)"
      if [ -n "$at_out" ] && [ "$at_out" != "$imei1" ]; then
        imei2="$at_out"
      fi
    fi
    # Scan factory partitions for Luhn-valid 15-digit IMEIs (TAC 35...)
    if [ -z "$imei2" ]; then
      for part in /dev/disk/by-partlabel/traceability /dev/disk/by-partlabel/tunning /dev/disk/by-partlabel/devinfo /dev/disk/by-partlabel/oem; do
        [ -e "$part" ] || continue
        cand_list="$(strings -n 14 "$part" 2>/dev/null | grep -oE '35[0-9]{13}' | awk '
          {
            val = $0
            sum = 0
            for (i = 1; i <= 14; i++) {
              d = substr(val, i, 1) + 0
              if (i % 2 == 0) {
                d *= 2
                if (d > 9) d -= 9
              }
              sum += d
            }
            chk = (10 - (sum % 10)) % 10
            if (chk == (substr(val, 15, 1) + 0)) print val
          }
        ' || true)"
        for c in $cand_list; do
          if [ -z "$imei1" ]; then
            imei1="$c"
          elif [ "$c" != "$imei1" ] && [ -z "$imei2" ]; then
            imei2="$c"
            break 2
          fi
        done
      done
    fi
    echo "IMEI1=${imei1}"
    echo "IMEI2=${imei2}"
    echo "IMEI_SV=${imei_sv}"
    ;;

  usb-tether)
    mode="${2:-status}"
    case "$mode" in
      on)
        ensure_usb_network_gadget
        sysctl -w net.ipv4.ip_forward=1 >/dev/null 2>&1 || true
        nft add table ip nat 2>/dev/null || true
        nft 'add chain ip nat postrouting { type nat hook postrouting priority 100 ; }' 2>/dev/null || true
        nft add rule ip nat postrouting oifname "wwan*" masquerade 2>/dev/null || true
        nft add rule ip nat postrouting oifname "wlan*" masquerade 2>/dev/null || true
        ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
        ip link set usb0 up 2>/dev/null || true
        echo "1" > /tmp/bananarchy_usb_tether.active
        ;;
      off)
        ensure_usb_network_gadget
        sysctl -w net.ipv4.ip_forward=0 >/dev/null 2>&1 || true
        nft delete table ip nat 2>/dev/null || true
        nmcli connection delete USBNetworkShare >/dev/null 2>&1 || true
        ip addr show dev usb0 2>/dev/null | grep -q "172.16.42.1" || ip addr add 172.16.42.1/16 dev usb0 2>/dev/null || true
        ip link set usb0 up 2>/dev/null || true
        rm -f /tmp/bananarchy_usb_tether.active
        ;;
      *)
        if [ -f /tmp/bananarchy_usb_tether.active ] && [ "$(sysctl -n net.ipv4.ip_forward 2>/dev/null || echo 0)" = "1" ]; then
          echo "ACTIVE=1"
        else
          echo "ACTIVE=0"
        fi
        ;;
    esac
    ;;

  at)
    port="${2:-auto}"
    shift 2 2>/dev/null || true
    at_cmd="$*"
    if [ -z "$at_cmd" ]; then
      echo "ERROR: Empty AT command"
      exit 1
    fi
    resp=""
    if [ "$PREFERRED_SIM_SLOT" = "2" ]; then
      at_candidates="$port /dev/wwan0at1 /dev/wwan0at0"
    else
      at_candidates="$port /dev/wwan0at0 /dev/wwan0at1"
    fi
    for candidate in $at_candidates; do
      case "$candidate" in
        wwan0at0|wwan0at1)
          dev="/dev/$candidate"
          ;;
        /dev/wwan0at0|/dev/wwan0at1)
          dev="$candidate"
          ;;
        *)
          dev=""
          ;;
      esac
      if [ -n "$dev" ] && [ -c "$dev" ]; then
        resp="$(timeout 3 sh -c '
          dev="$1"
          cmd="$2"
          tmpf="/tmp/at_resp_$$.txt"
          rm -f "$tmpf"
          cat "$dev" > "$tmpf" 2>/dev/null &
          rpid=$!
          sleep 0.04
          printf "%s\r\n" "$cmd" > "$dev" 2>/dev/null || true
          i=0
          while [ "$i" -lt 16 ]; do
            if grep -qE "(^|[[:space:]])(OK|ERROR|\+CME ERROR|\+CMS ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
              break
            fi
            sleep 0.08
            i=$((i + 1))
          done
          kill "$rpid" 2>/dev/null || true
          wait "$rpid" 2>/dev/null || true
          tr -d "\r" < "$tmpf" 2>/dev/null || true
          rm -f "$tmpf"
        ' _ "$dev" "$at_cmd" 2>/dev/null | sed '/^[[:space:]]*$/d' || true)"
        if [ -n "$resp" ]; then
          printf "%s\n" "$resp"
          exit 0
        fi
      fi
      if [ "$port" != "auto" ] && [ "$candidate" = "$port" ]; then
        break
      fi
    done
    mm_out="$(mmcli -m any --command="$at_cmd" 2>&1 || true)"
    if [ -n "$mm_out" ]; then
      printf "%s\n" "$mm_out" | sed "s/^response: '\(.*\)'$/\1/"
      exit 0
    fi
    echo "ERROR: No response from modem AT channel"
    exit 1
    ;;

  ussd)
    ussd_mode="${2:-initiate}"
    shift 2 2>/dev/null || true
    ussd_code="$*"
    if [ "$ussd_mode" = "cancel" ]; then
      mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1 || true
      "$0" at auto 'AT+CUSD=2' >/dev/null 2>&1 || true
      echo "USSD session cancelled."
      exit 0
    fi
    if [ -z "$ussd_code" ]; then
      echo "ERROR: Empty USSD code"
      exit 1
    fi
    mm_state="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.generic.state" {print $2; exit}' || true)"
    if [ "$mm_state" = "disabled" ] || [ -z "$mm_state" ]; then
      mmcli -m any -e >/dev/null 2>&1 || true
      sleep 1
    fi
    modem_dbus_path="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.dbus-path" {print $2; exit}' || true)"
    get_ussd_prop() {
      prop_name="$1"
      if [ -n "$modem_dbus_path" ] && command -v busctl >/dev/null 2>&1 && command -v jq >/dev/null 2>&1; then
        busctl --json=short call org.freedesktop.ModemManager1 "$modem_dbus_path" \
          org.freedesktop.DBus.Properties Get ss \
          org.freedesktop.ModemManager1.Modem.Modem3gpp.Ussd "$prop_name" 2>/dev/null | \
          jq -r '.data[].data // empty' 2>/dev/null || true
      fi
    }
    prev_notif="$(get_ussd_prop NetworkNotification)"
    prev_req="$(get_ussd_prop NetworkRequest)"
    ussd_st="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.3gpp.ussd.status" || $1 == "modem.3gpp.ussd.state" {print $2; exit}' || true)"
    if [ "$ussd_mode" = "respond" ] && [ "$ussd_st" != "user-response" ]; then
      case "$ussd_code" in
        \**\#|\#*\#)
          ussd_mode="initiate"
          ;;
      esac
    fi
    mm_rc=0
    mm_out=""
    if [ "$ussd_mode" = "respond" ]; then
      mm_out="$(mmcli --timeout=30 -m any --3gpp-ussd-respond="$ussd_code" 2>&1)" || mm_rc=$?
    else
      if [ "$ussd_st" = "active" ] || [ "$ussd_st" = "user-response" ]; then
        mmcli -m any --3gpp-ussd-cancel >/dev/null 2>&1 || true
      fi
      mm_out="$(mmcli --timeout=30 -m any --3gpp-ussd-initiate="$ussd_code" 2>&1)" || mm_rc=$?
    fi
    if [ "$mm_rc" -eq 0 ] && [ -n "$mm_out" ]; then
      reply="$(printf "%s\n" "$mm_out" | sed \
        -e "1s/^USSD session initiated; new reply from network: '//" \
        -e "1s/^response successfully sent in USSD session; new reply from network: '//" \
        -e "1s/^USSD response sent; new reply from network: '//" \
        -e "\$s/'$//")"
      if [ -n "$reply" ]; then
        printf "%s\n" "$reply"
        exit 0
      fi
    fi
    # Check if the USSD response arrived asynchronously via D-Bus properties
    w=0
    while [ "$w" -lt 25 ]; do
      cur_req="$(get_ussd_prop NetworkRequest)"
      cur_notif="$(get_ussd_prop NetworkNotification)"
      if [ -n "$cur_req" ] && [ "$cur_req" != "$prev_req" ]; then
        printf "%s\n" "$cur_req"
        exit 0
      fi
      if [ -n "$cur_notif" ] && [ "$cur_notif" != "$prev_notif" ]; then
        printf "%s\n" "$cur_notif"
        exit 0
      fi
      cur_st="$(mmcli -m any -K 2>/dev/null | awk -F': ' '$1 == "modem.3gpp.ussd.status" || $1 == "modem.3gpp.ussd.state" {print $2; exit}' || true)"
      if [ "$cur_st" != "active" ] && [ "$w" -ge 8 ]; then
        if [ -n "$cur_req" ]; then
          printf "%s\n" "$cur_req"
          exit 0
        fi
        if [ -n "$cur_notif" ]; then
          printf "%s\n" "$cur_notif"
          exit 0
        fi
        break
      fi
      sleep 0.2
      w=$((w + 1))
    done
    # Fallback to direct AT+CUSD on the active SIM AT port
    if [ "$PREFERRED_SIM_SLOT" = "2" ]; then
      at_candidates="/dev/wwan0at1 /dev/wwan0at0"
    else
      at_candidates="/dev/wwan0at0 /dev/wwan0at1"
    fi
    for dev in $at_candidates; do
      [ -c "$dev" ] || continue
      cusd_out="$(timeout 12 sh -c '
        dev="$1"
        code="$2"
        tmpf="/tmp/ussd_resp_$$.txt"
        rm -f "$tmpf"
        cat "$dev" > "$tmpf" 2>/dev/null &
        rpid=$!
        sleep 0.05
        printf "AT+CSCS=\"GSM\";+CUSD=1,\"%s\",15\r\n" "$code" > "$dev" 2>/dev/null || true
        i=0
        while [ "$i" -lt 80 ]; do
          if grep -qE "\+CUSD:[[:space:]]*[0-9]+" "$tmpf" 2>/dev/null; then
            break
          fi
          if grep -qE "(^|[[:space:]])(\+CME ERROR|\+CMS ERROR|ERROR)([[:space:]]|$)" "$tmpf" 2>/dev/null; then
            break
          fi
          sleep 0.1
          i=$((i + 1))
        done
        kill "$rpid" 2>/dev/null || true
        wait "$rpid" 2>/dev/null || true
        tr -d "\r" < "$tmpf" 2>/dev/null || true
        rm -f "$tmpf"
      ' _ "$dev" "$ussd_code" 2>/dev/null || true)"
      cusd_msg="$(printf "%s\n" "$cusd_out" | sed -n 's/.*+CUSD:[[:space:]]*[0-9]*,[[:space:]]*"\([^"]*\)".*/\1/p' | tail -n1)"
      if [ -n "$cusd_msg" ]; then
        printf "%s\n" "$cusd_msg"
        exit 0
      fi
    done
    if [ -n "$mm_out" ]; then
      printf "%s\n" "$mm_out"
    else
      echo "ERROR: USSD request failed"
    fi
    exit 1
    ;;

  sim-contacts-import)
    req_slot="${2:-auto}"
    if ! wait_for_qmicli_modem 3; then
      echo "ERROR: No modem available"
      exit 1
    fi
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
    if [ -z "$QMI_CARDS" ]; then
      echo "ERROR: Cannot read SIM card status"
      exit 1
    fi
    eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")"

    target_slot=""
    target_aid=""
    if [ "$req_slot" = "1" ]; then
      if [ "$SLOT1_STATE" != "present" ]; then
        echo "ERROR: SIM 1 is not inserted"
        exit 1
      fi
      target_slot="1"
      target_aid="$SLOT1_AID"
    elif [ "$req_slot" = "2" ]; then
      if [ "$SLOT2_STATE" != "present" ]; then
        echo "ERROR: SIM 2 is not inserted"
        exit 1
      fi
      target_slot="2"
      target_aid="$SLOT2_AID"
    else
      if [ "$PRIMARY_GW_SLOT" = "1" ] && [ "$SLOT1_STATE" = "present" ]; then
        target_slot="1"
        target_aid="$SLOT1_AID"
      elif [ "$PRIMARY_GW_SLOT" = "2" ] && [ "$SLOT2_STATE" = "present" ]; then
        target_slot="2"
        target_aid="$SLOT2_AID"
      elif [ "$SLOT1_STATE" = "present" ]; then
        target_slot="1"
        target_aid="$SLOT1_AID"
      elif [ "$SLOT2_STATE" = "present" ]; then
        target_slot="2"
        target_aid="$SLOT2_AID"
      else
        echo "ERROR: No SIM card detected"
        exit 1
      fi
    fi

    tmp_contacts="/tmp/sim_import_$$.tsv"
    rm -f "$tmp_contacts"
    : > "$tmp_contacts"

    # Method 1: Fast direct QRTR QMI UIM EF_ADN read (supports SIM 1 & SIM 2 without switching GW session)
    if command -v python3 >/dev/null 2>&1; then
      python3 - "$target_slot" > "$tmp_contacts" 2>/dev/null << 'PY' || true
import socket, struct, select, sys

slot = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 1
AF_QIPCRTR = 42
QRTR_PORT_CTRL = 0xfffffffe
QRTR_TYPE_NEW_SERVER = 4
QRTR_TYPE_NEW_LOOKUP = 10
QMI_SERVICE_UIM = 11

ls = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
node, _ = ls.getsockname()
ls.sendto(struct.pack("<IIIII", QRTR_TYPE_NEW_LOOKUP, QMI_SERVICE_UIM, 0, 0, 0), (node, QRTR_PORT_CTRL))
uim_addr = None
while True:
    r, _, _ = select.select([ls], [], [], 0.15)
    if not r: break
    data, _ = ls.recvfrom(256)
    if len(data) >= 20:
        cmd, srv, inst, srv_node, srv_port = struct.unpack("<IIIII", data[:20])
        if cmd == QRTR_TYPE_NEW_SERVER:
            if srv == QMI_SERVICE_UIM and srv_port != 0:
                uim_addr = (srv_node, srv_port)
            if srv == 0 and srv_node == 0 and srv_port == 0:
                break
ls.close()
if not uim_addr:
    sys.exit(1)

s = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
session_type = 7 if slot == 2 else 6
tlv_f = None
rec_size = 0
rec_count = 0
txn = 1

for stype in [session_type, 0]:
    for fid, path in [(0x6F3A, [0x3F00, 0x7F10]), (0x4F3A, [0x3F00, 0x7F10, 0x5F3A])]:
        pbytes = b"".join(struct.pack("<H", x) for x in path)
        fval = struct.pack("<HB", fid, len(pbytes)) + pbytes
        tf = struct.pack("<BH", 0x02, len(fval)) + fval
        ts = struct.pack("<BHB", 0x01, 2, stype) + b"\x00"
        tlvs = ts + tf
        s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0024, len(tlvs)) + tlvs, uim_addr)
        txn += 1
        r, _, _ = select.select([s], [], [], 0.15)
        if not r: continue
        resp, _ = s.recvfrom(512)
        if len(resp) < 14 or resp[0] != 0x02 or resp[3] != 0x24: continue
        pos = 7
        ok = False
        rs = rc = 0
        while pos + 3 <= len(resp):
            tt, tl = struct.unpack("<BH", resp[pos:pos+3])
            pos += 3
            if tt == 0x02 and tl >= 4:
                res, err = struct.unpack("<HH", resp[pos:pos+4])
                if res == 0 and err == 0: ok = True
            elif tt == 0x11 and tl >= 9:
                _, _, _, rs, rc = struct.unpack("<HHBHH", resp[pos:pos+9])
            pos += tl
        if ok and rs >= 14 and rc >= 1:
            session_type = stype
            tlv_f = tf
            rec_size = rs
            rec_count = min(rc, 500)
            break
    if tlv_f:
        break

if not tlv_f:
    s.close()
    sys.exit(1)

tlv_session = struct.pack("<BHB", 0x01, 2, session_type) + b"\x00"
bcd_map = "0123456789*#p"

for rnum in range(1, rec_count + 1):
    tlv_rec = struct.pack("<BHHH", 0x03, 4, rnum, rec_size)
    tlvs = tlv_session + tlv_f + tlv_rec
    s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0021, len(tlvs)) + tlvs, uim_addr)
    txn += 1
    r, _, _ = select.select([s], [], [], 0.15)
    if not r: continue
    resp, _ = s.recvfrom(512)
    pos = 7
    raw = None
    ok = False
    while pos + 3 <= len(resp):
        tt, tl = struct.unpack("<BH", resp[pos:pos+3])
        pos += 3
        if tt == 0x02 and tl >= 4:
            res, err = struct.unpack("<HH", resp[pos:pos+4])
            if res == 0 and err == 0: ok = True
        elif tt == 0x11 and tl >= 2:
            clen = struct.unpack("<H", resp[pos:pos+2])[0]
            raw = resp[pos+2:pos+2+clen]
        pos += tl
    if not (ok and raw and len(raw) >= 14):
        continue
    alpha_len = len(raw) - 14
    alpha = raw[:alpha_len]
    bcd_len = raw[alpha_len]
    ton_npi = raw[alpha_len + 1]
    if bcd_len == 0 or bcd_len > 11 or bcd_len == 0xFF:
        continue
    num = "+" if ((ton_npi >> 4) & 0x07) == 1 else ""
    for b in raw[alpha_len + 2 : alpha_len + 2 + min(10, bcd_len - 1)]:
        if b == 0xFF: break
        lo = b & 0x0F
        hi = (b >> 4) & 0x0F
        if lo < len(bcd_map): num += bcd_map[lo]
        else: break
        if hi == 0x0F: break
        if hi < len(bcd_map): num += bcd_map[hi]
        else: break
    if not num or num == "+":
        continue
    while alpha and alpha[-1] == 0xFF:
        alpha = alpha[:-1]
    if not alpha:
        name = num
    elif alpha[0] == 0x80:
        u16 = alpha[1:]
        if len(u16) % 2 == 1: u16 = u16[:-1]
        name = u16.decode("utf-16-be", errors="replace").replace("\t", " ").replace("\n", " ").strip()
    else:
        name = "".join(chr(b) for b in alpha if 32 <= b < 127 and b != 9).strip()
    print(f"{num}\t{name or num}")
s.close()
PY
    fi

    if [ -s "$tmp_contacts" ]; then
      cat "$tmp_contacts"
      rm -f "$tmp_contacts"
      exit 0
    fi

    orig_gw_slot="$PRIMARY_GW_SLOT"
    orig_gw_aid=""
    if [ "$orig_gw_slot" = "1" ]; then
      orig_gw_aid="$SLOT1_AID"
    elif [ "$orig_gw_slot" = "2" ]; then
      orig_gw_aid="$SLOT2_AID"
    fi
    switched_gw=0
    restore_gw_slot() {
      if [ "$switched_gw" = "1" ]; then
        $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
        if [ -n "$orig_gw_slot" ] && [ "$orig_gw_slot" != "0" ] && [ -n "$orig_gw_aid" ]; then
          $QMICLI_MODEM --uim-change-provisioning-session="slot=$orig_gw_slot,activate=yes,session-type=primary-gw-provisioning,aid=$orig_gw_aid" >/dev/null 2>&1 || true
        fi
        switched_gw=0
      fi
    }
    trap restore_gw_slot EXIT INT TERM

    if [ "$PRIMARY_GW_SLOT" != "$target_slot" ] && [ -n "$target_aid" ]; then
      if [ "$PRIMARY_GW_SLOT" != "0" ]; then
        $QMICLI_MODEM --uim-change-provisioning-session='activate=no,session-type=primary-gw-provisioning' >/dev/null 2>&1 || true
      fi
      if $QMICLI_MODEM --uim-change-provisioning-session="slot=$target_slot,activate=yes,session-type=primary-gw-provisioning,aid=$target_aid" >/dev/null 2>&1; then
        switched_gw=1
        sleep 1
      fi
    fi

    # Method 2: Direct qmicli UIM EF_ADN (3F00-7F10-6F3A) record fallback
    if [ ! -s "$tmp_contacts" ]; then
      adn_attr="$($QMICLI_MODEM --uim-get-file-attributes=0x3F00,0x7F10,0x6F3A 2>/dev/null || true)"
      reclen="$(printf "%s\n" "$adn_attr" | awk '/Record (size|length):/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}')"
      reccount="$(printf "%s\n" "$adn_attr" | awk '/Record count:/ {gsub(/[^0-9]/, "", $NF); print $NF + 0; exit}')"
      if [ -n "$reclen" ] && [ -n "$reccount" ] && [ "$reclen" -ge 14 ] 2>/dev/null && [ "$reccount" -ge 1 ] 2>/dev/null; then
        if [ "$reccount" -gt 250 ]; then
          reccount=250
        fi
        rec=1
        while [ "$rec" -le "$reccount" ]; do
          raw_rec="$($QMICLI_MODEM --uim-read-record="file=0x3F00-0x7F10-0x6F3A,record-number=${rec},record-length=${reclen}" 2>/dev/null || true)"
          if [ -n "$raw_rec" ]; then
            printf "%s\n" "$raw_rec" | awk '
              function hex2val(h,   i, c, v, res) {
                res = 0
                h = toupper(h)
                for (i = 1; i <= length(h); i++) {
                  c = substr(h, i, 1)
                  v = index("0123456789ABCDEF", c) - 1
                  if (v < 0) return -1
                  res = res * 16 + v
                }
                return res
              }
              function bcd_digit(n) {
                if (n >= 0 && n <= 9) return "" n
                if (n == 10) return "*"
                if (n == 11) return "#"
                return ""
              }
              /Read result:/ { in_res = 1; full = ""; next }
              in_res && /^[[:space:]]*[0-9A-Fa-f]{2}(:[0-9A-Fa-f]{2})*:?[[:space:]]*$/ {
                line = $0
                gsub(/^[[:space:]]+|[[:space:]]+$/, "", line)
                sub(/:$/, "", line)
                full = (full == "") ? line : (full ":" line)
              }
              END {
                if (full == "") exit
                n = split(full, hex, ":")
                if (n < 14) exit
                alpha_len = n - 14
                bcd_len = hex2val(hex[alpha_len + 1])
                ton_npi = hex2val(hex[alpha_len + 2])
                if (bcd_len <= 0 || bcd_len > 11 || bcd_len == 255) exit
                num = ""
                if (int(ton_npi / 16) % 8 == 1) {
                  num = "+"
                }
                bcd_bytes = bcd_len - 1
                if (bcd_bytes > 10) bcd_bytes = 10
                for (i = 1; i <= bcd_bytes; i++) {
                  b = hex2val(hex[alpha_len + 2 + i])
                  if (b < 0 || b == 255) break
                  lo = b % 16
                  hi = int(b / 16)
                  d1 = bcd_digit(lo)
                  if (d1 == "") break
                  num = num d1
                  d2 = bcd_digit(hi)
                  if (d2 == "") break
                  num = num d2
                }
                if (num == "" || num == "+") exit
                name = ""
                for (i = 1; i <= alpha_len; i++) {
                  b = hex2val(hex[i])
                  if (b < 0 || b == 255) break
                  if (b >= 32 && b < 127 && b != 9) {
                    name = name sprintf("%c", b)
                  }
                }
                gsub(/^[[:space:]]+|[[:space:]]+$/, "", name)
                if (name == "") name = num
                printf "%s\t%s\n", num, name
              }
            ' >> "$tmp_contacts"
          fi
          rec=$((rec + 1))
        done
      fi
    fi

    cat "$tmp_contacts"
    rm -f "$tmp_contacts"
    exit 0
    ;;

  sim-contacts-export)
    req_slot="${2:-auto}"
    src_tsv="${3:-/home/user/.config/sxmo/contacts.tsv}"
    if [ ! -r "$src_tsv" ]; then
      echo "ERROR: Contacts file not found"
      exit 1
    fi
    if ! wait_for_qmicli_modem 3; then
      echo "ERROR: No modem available"
      exit 1
    fi
    QMI_CARDS="$($QMICLI_MODEM --uim-get-card-status 2>/dev/null || true)"
    if [ -z "$QMI_CARDS" ]; then
      echo "ERROR: Cannot read SIM card status"
      exit 1
    fi
    eval "$(parse_uim_card_status "$QMI_CARDS" "$PREFERRED_SIM_SLOT")"

    target_slot=""
    target_aid=""
    if [ "$req_slot" = "1" ]; then
      if [ "$SLOT1_STATE" != "present" ]; then
        echo "ERROR: SIM 1 is not inserted"
        exit 1
      fi
      target_slot="1"
      target_aid="$SLOT1_AID"
    elif [ "$req_slot" = "2" ]; then
      if [ "$SLOT2_STATE" != "present" ]; then
        echo "ERROR: SIM 2 is not inserted"
        exit 1
      fi
      target_slot="2"
      target_aid="$SLOT2_AID"
    else
      if [ "$PRIMARY_GW_SLOT" = "1" ] && [ "$SLOT1_STATE" = "present" ]; then
        target_slot="1"
        target_aid="$SLOT1_AID"
      elif [ "$PRIMARY_GW_SLOT" = "2" ] && [ "$SLOT2_STATE" = "present" ]; then
        target_slot="2"
        target_aid="$SLOT2_AID"
      elif [ "$SLOT1_STATE" = "present" ]; then
        target_slot="1"
        target_aid="$SLOT1_AID"
      elif [ "$SLOT2_STATE" = "present" ]; then
        target_slot="2"
        target_aid="$SLOT2_AID"
      else
        echo "ERROR: No SIM card detected"
        exit 1
      fi
    fi

    if command -v python3 >/dev/null 2>&1; then
      exp_out="$(python3 - "$target_slot" "$src_tsv" << 'PY'
import socket, struct, select, sys

slot = int(sys.argv[1]) if len(sys.argv) > 1 and sys.argv[1].isdigit() else 1
tsv_path = sys.argv[2]

rows = []
with open(tsv_path, "r", encoding="utf-8", errors="replace") as f:
    for line in f:
        line = line.rstrip("\r\n")
        if "\t" not in line: continue
        num, name = line.split("\t", 1)
        num = "".join(c for c in num if c in "+0123456789*#")
        name = name.strip()
        if num:
            rows.append((num, name or num))

if not rows:
    print("ERROR: No contacts to export")
    sys.exit(1)

AF_QIPCRTR = 42
QRTR_PORT_CTRL = 0xfffffffe
QRTR_TYPE_NEW_SERVER = 4
QRTR_TYPE_NEW_LOOKUP = 10
QMI_SERVICE_UIM = 11

ls = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
node, _ = ls.getsockname()
ls.sendto(struct.pack("<IIIII", QRTR_TYPE_NEW_LOOKUP, QMI_SERVICE_UIM, 0, 0, 0), (node, QRTR_PORT_CTRL))
uim_addr = None
while True:
    r, _, _ = select.select([ls], [], [], 0.15)
    if not r: break
    data, _ = ls.recvfrom(256)
    if len(data) >= 20:
        cmd, srv, inst, srv_node, srv_port = struct.unpack("<IIIII", data[:20])
        if cmd == QRTR_TYPE_NEW_SERVER:
            if srv == QMI_SERVICE_UIM and srv_port != 0:
                uim_addr = (srv_node, srv_port)
            if srv == 0 and srv_node == 0 and srv_port == 0:
                break
ls.close()
if not uim_addr:
    print("ERROR: Modem UIM service unavailable")
    sys.exit(1)

s = socket.socket(AF_QIPCRTR, socket.SOCK_DGRAM)
session_type = 7 if slot == 2 else 6
tlv_f = None
rec_size = 0
rec_count = 0
txn = 1

for stype in [session_type, 0]:
    for fid, path in [(0x6F3A, [0x3F00, 0x7F10]), (0x4F3A, [0x3F00, 0x7F10, 0x5F3A])]:
        pbytes = b"".join(struct.pack("<H", x) for x in path)
        fval = struct.pack("<HB", fid, len(pbytes)) + pbytes
        tf = struct.pack("<BH", 0x02, len(fval)) + fval
        ts = struct.pack("<BHB", 0x01, 2, stype) + b"\x00"
        tlvs = ts + tf
        s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0024, len(tlvs)) + tlvs, uim_addr)
        txn += 1
        r, _, _ = select.select([s], [], [], 0.15)
        if not r: continue
        resp, _ = s.recvfrom(512)
        if len(resp) < 14 or resp[0] != 0x02 or resp[3] != 0x24: continue
        pos = 7
        ok = False
        rs = rc = 0
        while pos + 3 <= len(resp):
            tt, tl = struct.unpack("<BH", resp[pos:pos+3])
            pos += 3
            if tt == 0x02 and tl >= 4:
                res, err = struct.unpack("<HH", resp[pos:pos+4])
                if res == 0 and err == 0: ok = True
            elif tt == 0x11 and tl >= 9:
                _, _, _, rs, rc = struct.unpack("<HHBHH", resp[pos:pos+9])
            pos += tl
        if ok and rs >= 14 and rc >= 1:
            session_type = stype
            tlv_f = tf
            rec_size = rs
            rec_count = min(rc, 500)
            break
    if tlv_f:
        break

if not tlv_f:
    s.close()
    print(f"ERROR: SIM {slot} phonebook not ready")
    sys.exit(1)

tlv_session = struct.pack("<BHB", 0x01, 2, session_type) + b"\x00"
bcd_map_str = "0123456789*#p"
existing_by_norm = {}
existing_by_sfx = {}
empty_slots = []

def sfx_key(norm):
    d = "".join(c for c in norm if c.isdigit())
    return d[-9:] if len(d) >= 9 else norm

for rnum in range(1, rec_count + 1):
    tlv_rec = struct.pack("<BHHH", 0x03, 4, rnum, rec_size)
    tlvs = tlv_session + tlv_f + tlv_rec
    s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0021, len(tlvs)) + tlvs, uim_addr)
    txn += 1
    r, _, _ = select.select([s], [], [], 0.15)
    if not r: continue
    resp, _ = s.recvfrom(512)
    pos = 7
    raw = None
    ok = False
    while pos + 3 <= len(resp):
        tt, tl = struct.unpack("<BH", resp[pos:pos+3])
        pos += 3
        if tt == 0x02 and tl >= 4:
            res, err = struct.unpack("<HH", resp[pos:pos+4])
            if res == 0 and err == 0: ok = True
        elif tt == 0x11 and tl >= 2:
            clen = struct.unpack("<H", resp[pos:pos+2])[0]
            raw = resp[pos+2:pos+2+clen]
        pos += tl
    if not (ok and raw and len(raw) >= 14):
        continue
    alpha_len = len(raw) - 14
    bcd_len = raw[alpha_len]
    ton_npi = raw[alpha_len + 1]
    if bcd_len == 0 or bcd_len > 11 or bcd_len == 0xFF:
        empty_slots.append(rnum)
        continue
    num = "+" if ((ton_npi >> 4) & 0x07) == 1 else ""
    for b in raw[alpha_len + 2 : alpha_len + 2 + min(10, bcd_len - 1)]:
        if b == 0xFF: break
        lo = b & 0x0F
        hi = (b >> 4) & 0x0F
        if lo < len(bcd_map_str): num += bcd_map_str[lo]
        else: break
        if hi == 0x0F: break
        if hi < len(bcd_map_str): num += bcd_map_str[hi]
        else: break
    if not num or num == "+":
        empty_slots.append(rnum)
        continue
    existing_by_norm[num] = rnum
    existing_by_sfx[sfx_key(num)] = rnum

def encode_adn(name, num, rsize):
    alpha_len = rsize - 14
    try:
        nb = name.encode("ascii")[:alpha_len]
        alpha = nb + b"\xFF" * (alpha_len - len(nb))
    except UnicodeEncodeError:
        u16 = name.encode("utf-16-be")[:max(0, (alpha_len - 1) // 2 * 2)]
        alpha = b"\x80" + u16 + b"\xFF" * (alpha_len - 1 - len(u16))
    ton_npi = 0x91 if num.startswith("+") else 0x81
    digits = [c for c in num if c != "+"][:20]
    if not digits: return None
    bcd = bytearray([0xFF] * 10)
    bmap = {str(i): i for i in range(10)}
    bmap["*"] = 0x0A
    bmap["#"] = 0x0B
    for idx, ch in enumerate(digits):
        nib = bmap.get(ch, 0x0F)
        bi = idx // 2
        if idx % 2 == 0:
            bcd[bi] = (bcd[bi] & 0xF0) | nib
        else:
            bcd[bi] = (bcd[bi] & 0x0F) | (nib << 4)
    bcd_len = 1 + (len(digits) + 1) // 2
    return alpha + bytes([bcd_len, ton_npi]) + bytes(bcd) + b"\xFF\xFF"

exported = 0
empty_idx = 0
for num, name in rows:
    trec = existing_by_norm.get(num) or existing_by_sfx.get(sfx_key(num))
    if not trec:
        if empty_idx < len(empty_slots):
            trec = empty_slots[empty_idx]
            empty_idx += 1
        else:
            break
    rdata = encode_adn(name, num, rec_size)
    if not rdata: continue
    wval = struct.pack("<HH", trec, len(rdata)) + rdata
    tlv_w = struct.pack("<BH", 0x03, len(wval)) + wval
    tlvs = tlv_session + tlv_f + tlv_w
    s.sendto(struct.pack("<BHHH", 0x00, txn, 0x0023, len(tlvs)) + tlvs, uim_addr)
    txn += 1
    r, _, _ = select.select([s], [], [], 0.2)
    if not r: continue
    resp, _ = s.recvfrom(512)
    pos = 7
    ok = False
    while pos + 3 <= len(resp):
        tt, tl = struct.unpack("<BH", resp[pos:pos+3])
        pos += 3
        if tt == 0x02 and tl >= 4:
            res, err = struct.unpack("<HH", resp[pos:pos+4])
            if res == 0 and err == 0: ok = True
        pos += tl
    if ok:
        exported += 1
        existing_by_norm[num] = trec
        existing_by_sfx[sfx_key(num)] = trec

s.close()
if exported == 0 and empty_idx >= len(empty_slots):
    print(f"ERROR: SIM {slot} phonebook is full")
    sys.exit(1)
print(f"EXPORTED={exported}")
print(f"SLOT={slot}")
PY
)"
      if printf "%s\n" "$exp_out" | grep -q "^EXPORTED="; then
        printf "%s\n" "$exp_out"
        exit 0
      fi
      if printf "%s\n" "$exp_out" | grep -q "^ERROR:"; then
        printf "%s\n" "$exp_out"
        exit 1
      fi
    fi

    echo "ERROR: SIM $target_slot phonebook not ready"
    exit 1
    ;;

  *)
    echo "Usage: $0 [boot|status|switch <1|2>|verify-pin <slot> <pin>|unblock-puk <slot> <puk> <newpin>|set-pin-lock <slot> <enable|disable> <pin>|change-pin <slot> <oldpin> <newpin>|imei|usb-tether <on|off|status>|at <port> <cmd>|sim-contacts-import [1|2|auto]|sim-contacts-export [1|2|auto] [tsv_file]]" >&2
    exit 1
    ;;
esac
