#!/bin/sh
# Cellular Modem Auto-Detector for OpenWrt
# Outputs discovered modems in JSON format and updates /tmp/run/modem-manager/devices.json

RUN_DIR="/tmp/run/modem-manager"
CACHE_FILE="$RUN_DIR/devices.json"
KNOWN_VENDORS="2c7c 2cb7 1e0e 12d1 19d2 1bbb 05c6 413c 0bdb 1199 2977 1508 1bc7 0e8d 1782 0489 1fac 2dee 33f8 3731 2df3 20a6"

get_sys_attr() {
	local path="$1"
	if [ -f "$path" ]; then
		tr -d '\r\n' < "$path" 2>/dev/null
	fi
}

detect_all() {
	echo "{"
	echo '  "devices": ['

	local FIRST=1

	for dev in /sys/bus/usb/devices/*; do
		[ -d "$dev" ] || continue
		[ -f "$dev/idVendor" ] || continue
		[ -f "$dev/idProduct" ] || continue

		local VID=$(get_sys_attr "$dev/idVendor")
		local PID=$(get_sys_attr "$dev/idProduct")
		local DEVCLASS=$(get_sys_attr "$dev/bDeviceClass")

		case "$VID" in
			1d6b|2109|1d50) continue ;;
		esac
		[ "$DEVCLASS" = "09" ] && continue

		local IS_MODEM=0
		for kv in $KNOWN_VENDORS; do
			if [ "$VID" = "$kv" ]; then
				IS_MODEM=1
				break
			fi
		done

		local DEVNAME=$(basename "$dev")

		# Direct sysfs tree discovery for serial ports under this USB device
		local TTYS=""
		for t in $(find "$dev" -maxdepth 5 \( -name "ttyUSB*" -o -name "ttyACM*" \) 2>/dev/null); do
			local b=$(basename "$t")
			case "$b" in
				ttyUSB*|ttyACM*)
					if [ -e "/dev/$b" ]; then
						case " $TTYS " in
							*" /dev/$b "*) ;;
							*) TTYS="$TTYS /dev/$b" ;;
						esac
					fi
					;;
			esac
		done

		# Fallback tty search via /sys/class/tty
		if [ -z "$TTYS" ]; then
			for t in /sys/class/tty/ttyUSB* /sys/class/tty/ttyACM*; do
				[ -e "$t" ] || continue
				local t_target=$(readlink "$t" 2>/dev/null || readlink -f "$t" 2>/dev/null)
				case "$t_target" in
					*"$DEVNAME"*)
						local b=$(basename "$t")
						[ -e "/dev/$b" ] && case " $TTYS " in *" /dev/$b "*) ;; *) TTYS="$TTYS /dev/$b" ;; esac
						;;
				esac
			done
		fi
		TTYS=$(echo "$TTYS" | sed 's/^ *//')

		# Network interfaces discovery under this USB device
		local NETS=""
		for n in $(find "$dev" -maxdepth 5 -name "net" 2>/dev/null); do
			[ -d "$n" ] || continue
			for iface in "$n"/*; do
				[ -d "$iface" ] || continue
				local b=$(basename "$iface")
				case "$b" in
					lo|br-*|eth*|lan*|wan*|sfp*) ;;
					*) case " $NETS " in *" $b "*) ;; *) NETS="$NETS $b" ;; esac ;;
				esac
			done
		done

		# Fallback net search via /sys/class/net
		if [ -z "$NETS" ]; then
			for n in /sys/class/net/*; do
				[ -d "$n" ] || continue
				local n_target=$(readlink "$n" 2>/dev/null || readlink -f "$n" 2>/dev/null)
				case "$n_target" in
					*"$DEVNAME"*)
						local b=$(basename "$n")
						case "$b" in
							lo|br-*|eth*|lan*|wan*|sfp*) ;;
							*) case " $NETS " in *" $b "*) ;; *) NETS="$NETS $b" ;; esac ;;
						esac
						;;
				esac
			done
		fi
		NETS=$(echo "$NETS" | sed 's/^ *//')

		if [ -n "$TTYS" ] || [ -n "$NETS" ] || [ $IS_MODEM -eq 1 ]; then
			IS_MODEM=1
		fi

		[ $IS_MODEM -eq 1 ] || continue

		local MFG=$(get_sys_attr "$dev/manufacturer")
		local PROD=$(get_sys_attr "$dev/product")
		local SER=$(get_sys_attr "$dev/serial")

		local DRIVER="option"
		for net_iface in $NETS; do
			if [ -e "/sys/class/net/$net_iface/device/driver" ]; then
				local drv_target=$(basename "$(readlink -f "/sys/class/net/$net_iface/device/driver" 2>/dev/null)")
				case "$drv_target" in
					cdc_ncm|cdc_ether|cdc_mbim|qmi_wwan)
						DRIVER="$drv_target"
						break
						;;
				esac
			fi
		done

		local PRIMARY_TTY=""
		for t in $TTYS; do
			case "$t" in
				*ttyUSB2|*ttyACM0)
					PRIMARY_TTY="$t"
					break
					;;
			esac
		done
		if [ -z "$PRIMARY_TTY" ]; then
			for t in $TTYS; do
				case "$t" in
					*ttyUSB3)
						PRIMARY_TTY="$t"
						break
						;;
				esac
			done
		fi
		[ -z "$PRIMARY_TTY" ] && PRIMARY_TTY=$(echo "$TTYS" | awk '{print $1}')
		local PRIMARY_NET=$(echo "$NETS" | awk '{print $1}')

		[ $FIRST -eq 1 ] || echo ","
		FIRST=0

		local TTY_JSON=""
		for t in $TTYS; do
			[ -n "$TTY_JSON" ] && TTY_JSON="$TTY_JSON, "
			TTY_JSON="$TTY_JSON\"$t\""
		done

		local NET_JSON=""
		for n in $NETS; do
			[ -n "$NET_JSON" ] && NET_JSON="$NET_JSON, "
			NET_JSON="$NET_JSON\"$n\""
		done

		local MODES_JSON="\"ncm\", \"ecm\""

		cat <<EOF
    {
      "id": "$DEVNAME",
      "vid": "$VID",
      "pid": "$PID",
      "vendor": "${MFG:-Generic}",
      "model": "${PROD:-Modem}",
      "serial": "${SER}",
      "driver": "$DRIVER",
      "primary_port": "$PRIMARY_TTY",
      "primary_net": "$PRIMARY_NET",
      "supported_modes": [ $MODES_JSON ],
      "ports": [ $TTY_JSON ],
      "nets": [ $NET_JSON ]
    }
EOF
	done

	echo "  ]"
	echo "}"
}

mkdir -p "$RUN_DIR"
OUTPUT=$(detect_all)

# Atomic update cache
echo "$OUTPUT" > "${CACHE_FILE}.tmp" && mv -f "${CACHE_FILE}.tmp" "$CACHE_FILE"

# Output to stdout if not purely --cache mode
if [ "$1" != "--cache" ]; then
	echo "$OUTPUT"
fi

exit 0
