#!/system/bin/sh
# RadioControl WebUI — HTTP server with hardware interface APIs

WEBROOT="/data/adb/modules/radiocontrol/webroot"
CONFIG_DIR="/data/adb/radiocontrol"
CONFIG_FILE="$CONFIG_DIR/config.sh"
PORT=8088

mkdir -p "$CONFIG_DIR"

log() { echo "[$(date '+%Y-%m-%d %H:%M:%S')] [webui] $1"; }

###########################
# Hardware interface helpers
###########################

# Send AT command to modem — tries rc_shannon first, then raw devices
send_at_cmd() {
  local cmd="$1"
  local timeout="${2:-3}"
  local result=""

  # Try our kernel module device first
  if [ -c /dev/rc_shannon ]; then
    echo -e "${cmd}\r" > /dev/rc_shannon
    result=$(timeout "$timeout" cat /dev/rc_shannon 2>/dev/null)
    [ -n "$result" ] && echo "$result" && return 0
  fi

  # Try available AT devices — umts_router is confirmed working on Tensor/Shannon 5400
  for dev in /dev/umts_router /dev/umts_atc0 /dev/nr_atc0 /dev/umts_atc1 /dev/smd7 /dev/ttyHS0; do
    if [ -c "$dev" ]; then
      result=$(
        exec 3<>"$dev" 2>/dev/null
        if [ $? -eq 0 ]; then
          printf "${cmd}\r\n" >&3
          sleep "$timeout"
          local buf=""
          while read -t 1 line <&3 2>/dev/null; do
            buf="${buf}${line}\n"
          done
          echo -e "$buf"
          exec 3>&-
        fi
      )
      [ -n "$result" ] && echo "$result" && return 0
    fi
  done

  echo "ERROR: No modem AT interface available"
  return 1
}

# Read a debugfs or sysfs path safely
read_sys_path() {
  local path="$1"
  local max_bytes="${2:-4096}"

  # Validate path — only allow /sys and /proc
  case "$path" in
    /sys/kernel/debug/*|/sys/class/*|/sys/devices/*|/sys/module/*|/sys/bus/*|/proc/*)
      ;;
    *)
      echo "ERROR: path not allowed"
      return 1
      ;;
  esac

  if [ -f "$path" ]; then
    head -c "$max_bytes" "$path" 2>/dev/null
  elif [ -d "$path" ]; then
    ls -la "$path" 2>/dev/null
  else
    echo "ERROR: not found"
    return 1
  fi
}

# Write to a sysfs path
write_sys_path() {
  local path="$1"
  local value="$2"

  case "$path" in
    /sys/class/*|/sys/devices/*|/sys/module/*)
      ;;
    *)
      echo "ERROR: write path not allowed"
      return 1
      ;;
  esac

  if [ -w "$path" ]; then
    echo "$value" > "$path" 2>/dev/null && echo "OK" || echo "ERROR: write failed"
  else
    echo "ERROR: not writable"
    return 1
  fi
}

# List directory contents as JSON
list_dir_json() {
  local dir="$1"
  local result="["
  local first=1

  if [ ! -d "$dir" ]; then
    echo "[]"
    return
  fi

  for entry in "$dir"/*; do
    [ ! -e "$entry" ] && continue
    local name=$(basename "$entry")
    local type="file"
    [ -d "$entry" ] && type="dir"
    local readable="false"
    [ -r "$entry" ] && readable="true"
    local writable="false"
    [ -w "$entry" ] && writable="true"

    if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
    result="$result{\"name\":\"$name\",\"type\":\"$type\",\"readable\":$readable,\"writable\":$writable}"
  done

  echo "${result}]"
}

# Get WiFi interface details
get_wifi_details() {
  cat "$CONFIG_DIR/wifi_info" 2>/dev/null
}

# Get loaded kernel modules status
get_kmod_status() {
  local result="{"
  local mon="false" shan="false" diag="false"

  lsmod 2>/dev/null | grep -q rc_wifi_mon && mon="true"
  lsmod 2>/dev/null | grep -q rc_shannon_cmd && shan="true"
  lsmod 2>/dev/null | grep -q rc_diag_bridge && diag="true"

  echo "{\"wifi_mon\":$mon,\"shannon_cmd\":$shan,\"diag_bridge\":$diag}"
}

# Get thermal zones related to radios
get_thermal_info() {
  local result="["
  local first=1

  for tz in /sys/class/thermal/thermal_zone*; do
    local type=$(cat "$tz/type" 2>/dev/null)
    case "$type" in
      *modem*|*pa*|*wlan*|*wifi*|*rf*|*mdm*|*qfe*|*battery*)
        local temp=$(cat "$tz/temp" 2>/dev/null)
        if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
        result="$result{\"zone\":\"$(basename $tz)\",\"type\":\"$type\",\"temp\":$temp}"
        ;;
    esac
  done

  echo "${result}]"
}

# Get current config
get_current_config() {
  source "$CONFIG_FILE" 2>/dev/null
  local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)
  cat << CONF
{
  "engineering_mode": "${ENGINEERING_MODE:-0}",
  "factory_test_mode": "${FACTORY_TEST_MODE:-0}",
  "usb_diag_mode": "${USB_DIAG_MODE:-0}",
  "hidden_menus": "${HIDDEN_MENUS:-0}",
  "modem_log": "${MODEM_LOG:-0}",
  "wifi_mode": "${WIFI_MODE:-managed}",
  "load_modules": "${LOAD_MODULES:-}",
  "detected_soc": "$soc"
}
CONF
}

# Get available modem interfaces
get_modem_interfaces() {
  local result="["
  local first=1

  for dev in /dev/umts_atc0 /dev/umts_atc1 /dev/umts_router0 /dev/umts_ipc0 \
             /dev/umts_dm0 /dev/umts_boot0 /dev/umts_ramdump0 /dev/umts_rfs0 \
             /dev/nr_atc0 /dev/nr_ipc0 \
             /dev/diag /dev/smd7 /dev/ttyHS0 /dev/ttyMSM0 /dev/at_mdm0 \
             /dev/rc_shannon /dev/rc_diag; do
    if [ -c "$dev" ]; then
      local perms=$(ls -la "$dev" 2>/dev/null | awk '{print $1}')
      if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
      result="$result{\"path\":\"$dev\",\"perms\":\"$perms\"}"
    fi
  done

  echo "${result}]"
}

# Get radio info from props and modem
get_radio_info() {
  local baseband=$(getprop gsm.version.baseband 2>/dev/null)
  local ril=$(getprop gsm.version.ril-impl 2>/dev/null)
  local nettype=$(getprop gsm.network.type 2>/dev/null)
  local simstate=$(getprop gsm.sim.state 2>/dev/null)
  local operator=$(getprop gsm.operator.alpha 2>/dev/null)
  local chipset=$(getprop ro.board.platform 2>/dev/null)
  local hardware=$(getprop ro.hardware 2>/dev/null)
  local soc=$(cat "$CONFIG_DIR/detected_soc" 2>/dev/null)

  cat << INFO
{
  "baseband":"$baseband",
  "ril":"$ril",
  "network_type":"$nettype",
  "sim_state":"$simstate",
  "operator":"$operator",
  "chipset":"$chipset",
  "hardware":"$hardware",
  "soc_family":"$soc"
}
INFO
}

# Get system flags
get_system_flags() {
  local result="["
  local first=1
  for prop in \
    ro.build.type ro.debuggable ro.secure ro.adb.secure \
    persist.sys.usb.config persist.sys.factorytest ro.factorytest \
    persist.radio.fieldtest ro.telephony.hidden_menu \
    persist.radio.hidden_menu persist.mtk.engineer \
    persist.vendor.radio.adb_log_on persist.radio.ramdump \
    persist.vendor.radio.enableadvanced persist.radio.field_test \
    persist.vendor.radio.ca_info persist.vendor.radio.nr5g \
    persist.vendor.radio.modem_log persist.cp.log persist.cp.rat \
    ro.board.platform ro.hardware ro.hardware.chipname \
    gsm.version.baseband gsm.version.ril-impl; do
    val=$(getprop "$prop" 2>/dev/null)
    if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
    result="$result{\"prop\":\"$prop\",\"value\":\"$val\"}"
  done
  echo "${result}]"
}

# Update config key
update_config() {
  local key="$1" val="$2"

  case "$key" in
    ENGINEERING_MODE|FACTORY_TEST_MODE|USB_DIAG_MODE|HIDDEN_MENUS|MODEM_LOG)
      case "$val" in 0|1) ;; *) echo '{"ok":false,"error":"invalid value"}'; return ;; esac ;;
    WIFI_MODE)
      case "$val" in managed|monitor|injection|mesh|ap) ;; *) echo '{"ok":false,"error":"invalid mode"}'; return ;; esac ;;
    LOAD_MODULES)
      ;; # freeform
    *) echo '{"ok":false,"error":"invalid key"}'; return ;;
  esac

  if grep -q "^${key}=" "$CONFIG_FILE" 2>/dev/null; then
    sed -i "s|^${key}=.*|${key}=${val}|" "$CONFIG_FILE"
  else
    echo "${key}=${val}" >> "$CONFIG_FILE"
  fi
  echo "{\"ok\":true}"
}

# Set WiFi mode immediately
set_wifi_mode() {
  local mode="$1"
  local iface=""

  for candidate in wlan0 wlan1 wifi0; do
    [ -d "/sys/class/net/$candidate" ] && iface="$candidate" && break
  done
  [ -z "$iface" ] && echo '{"ok":false,"error":"no wifi interface"}' && return

  ip link set "$iface" down 2>/dev/null
  case "$mode" in
    monitor)   iw dev "$iface" set type monitor 2>/dev/null ;;
    injection) iw dev "$iface" set type monitor 2>/dev/null
               iw dev "$iface" set monitor fcsfail otherbss 2>/dev/null ;;
    mesh)      iw dev "$iface" set type mesh 2>/dev/null ;;
    ap)        iw dev "$iface" set type __ap 2>/dev/null ;;
    managed|*) iw dev "$iface" set type managed 2>/dev/null; mode="managed" ;;
  esac
  ip link set "$iface" up 2>/dev/null

  sed -i "s/^WIFI_MODE=.*/WIFI_MODE=$mode/" "$CONFIG_FILE" 2>/dev/null
  echo "{\"ok\":true,\"mode\":\"$mode\",\"iface\":\"$iface\"}"
}

# Get BCM4390 driver parameters
get_wifi_params() {
  local result="["
  local first=1
  local pdir="/sys/module/bcmdhd4390/parameters"

  [ ! -d "$pdir" ] && echo "[]" && return

  for p in "$pdir"/*; do
    local name=$(basename "$p")
    local val=$(cat "$p" 2>/dev/null)
    local writable="false"
    [ -w "$p" ] && writable="true"
    # Escape val for JSON
    val=$(echo "$val" | sed 's/\\/\\\\/g; s/"/\\"/g' | tr '\n' ' ')
    if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
    result="$result{\"name\":\"$name\",\"value\":\"$val\",\"writable\":$writable}"
  done
  echo "${result}]"
}

# Set BCM4390 driver parameter
set_wifi_param() {
  local name="$1" val="$2"
  local path="/sys/module/bcmdhd4390/parameters/$name"

  # Validate name (no path traversal)
  case "$name" in */*|*..*) echo '{"ok":false,"error":"invalid param name"}'; return ;; esac

  if [ -w "$path" ]; then
    echo "$val" > "$path" 2>/dev/null
    local newval=$(cat "$path" 2>/dev/null)
    echo "{\"ok\":true,\"value\":\"$newval\"}"
  else
    echo "{\"ok\":false,\"error\":\"param not writable\"}"
  fi
}

# Get WiFi firmware info
get_wifi_firmware() {
  local result="["
  local first=1

  for fw in /vendor/firmware/fw_bcmdhd* /vendor/firmware/bcmdhd*; do
    [ ! -f "$fw" ] && continue
    local name=$(basename "$fw")
    local size=$(stat -c%s "$fw" 2>/dev/null || ls -la "$fw" | awk '{print $5}')
    if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
    result="$result{\"path\":\"$fw\",\"name\":\"$name\",\"size\":$size}"
  done

  # Current loaded firmware
  local cur_fw=$(cat /sys/module/bcmdhd4390/parameters/firmware_path 2>/dev/null)
  local cur_nv=$(cat /sys/module/bcmdhd4390/parameters/nvram_path 2>/dev/null)
  local info=$(cat /sys/module/bcmdhd4390/parameters/info_string 2>/dev/null | sed 's/"/\\"/g' | tr '\n' ' ')

  echo "{\"files\":${result}],\"current_fw\":\"$cur_fw\",\"current_nvram\":\"$cur_nv\",\"info\":\"$info\"}"
}

# Get CP (modem processor) debug info
get_cp_debug() {
  local state=$(cat /sys/devices/platform/cpif/modem_state 2>/dev/null)
  local pcie=""
  local sbb=""

  [ -f /sys/devices/platform/cpif/modem/pcie_event_stats ] && \
    pcie=$(cat /sys/devices/platform/cpif/modem/pcie_event_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
  [ -f /sys/devices/platform/cpif/modem/sbb_debug ] && \
    sbb=$(cat /sys/devices/platform/cpif/modem/sbb_debug 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')
  [ -f /sys/devices/platform/cpif/modem/power_stats ] && \
    power=$(cat /sys/devices/platform/cpif/modem/power_stats 2>/dev/null | sed 's/"/\\"/g' | tr '\n' '|' | sed 's/|/\\n/g')

  echo "{\"state\":\"$state\",\"pcie_stats\":\"$pcie\",\"sbb_debug\":\"$sbb\",\"power_stats\":\"$power\"}"
}

# Trigger CP crash dump
trigger_cp_crash() {
  if [ -f /sys/devices/platform/cpif/do_cp_crash ]; then
    echo 1 > /sys/devices/platform/cpif/do_cp_crash 2>/dev/null
    echo '{"ok":true,"msg":"CP crash triggered — ramdump will be saved to /data/vendor/log/cbd/"}'
  else
    echo '{"ok":false,"error":"do_cp_crash not available"}'
  fi
}

# Carrier config — read current settings
get_carrier_config() {
  local volte=$(settings get global enhanced_4g_mode_enabled 2>/dev/null)
  local vonr=$(settings get global vonr_enabled 2>/dev/null)
  local wfc=$(settings get global wifi_calling_enabled 2>/dev/null)
  local vt=$(settings get global vt_ims_enabled 2>/dev/null)
  local apn=$(settings get global allow_adding_apns 2>/dev/null)
  local nradv=$(getprop persist.vendor.radio.nr_advanced 2>/dev/null)
  local netsel=$(getprop persist.dbg.hide_preferred_network_type 2>/dev/null)

  echo "{\"volte\":\"$volte\",\"vonr\":\"$vonr\",\"wfc\":\"$wfc\",\"vt\":\"$vt\",\"apn\":\"$apn\",\"nradv\":\"$nradv\",\"hide_network_type\":\"$netsel\"}"
}

# Carrier config — set a carrier override
set_carrier_flag() {
  local flag="$1" val="$2"

  case "$flag" in
    volte)
      settings put global enhanced_4g_mode_enabled "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    vonr)
      settings put global vonr_enabled "$val" 2>/dev/null
      resetprop persist.vendor.radio.vonr_enabled "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    wfc)
      settings put global wifi_calling_enabled "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    vt)
      settings put global vt_ims_enabled "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    apn)
      resetprop persist.dbg.allow_adding_apns 1 2>/dev/null
      settings put global allow_adding_apns "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    nradv)
      resetprop persist.vendor.radio.nr_advanced "$val" 2>/dev/null
      echo '{"ok":true}'
      ;;
    nettype)
      # val=1 means SHOW (so hide=0)
      resetprop persist.dbg.hide_preferred_network_type "$( [ "$val" = "1" ] && echo 0 || echo 1)" 2>/dev/null
      echo '{"ok":true}'
      ;;
    *) echo '{"ok":false,"error":"unknown flag"}' ;;
  esac
}

# List carrier settings protobuf files
get_carrier_files() {
  local result="["
  local first=1
  local csdir="/product/etc/CarrierSettings"

  if [ -d "$csdir" ]; then
    for f in "$csdir"/*; do
      [ ! -f "$f" ] && continue
      local name=$(basename "$f")
      local size=$(stat -c%s "$f" 2>/dev/null || ls -la "$f" | awk '{print $5}')
      if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
      result="$result{\"name\":\"$name\",\"size\":$size}"
    done
  fi
  echo "${result}]"
}

# Dump carrier_config service
dump_carrier_config() {
  dumpsys carrier_config 2>/dev/null | head -500
}

# Thread radio (Wonder) info
get_thread_info() {
  local result="{"
  local has_wonder="false"

  if [ -d /sys/class/ieee80211/wonder ]; then
    has_wonder="true"
    local mac=$(cat /sys/class/ieee80211/wonder/macaddress 2>/dev/null)
    local name=$(cat /sys/class/ieee80211/wonder/name 2>/dev/null)
    local idx=$(cat /sys/class/ieee80211/wonder/index 2>/dev/null)
    result="$result\"present\":true,\"mac\":\"$mac\",\"name\":\"$name\",\"index\":\"$idx\""

    # debugfs
    local dbg_entries=""
    if [ -d /sys/kernel/debug/wonder ]; then
      dbg_entries=$(ls /sys/kernel/debug/wonder/ 2>/dev/null | tr '\n' ',' | sed 's/,$//')
    fi
    result="$result,\"debugfs_entries\":\"$dbg_entries\""

    # force_stop_tx status
    if [ -f /sys/kernel/debug/wonder/force_stop_tx ]; then
      local fstx=$(cat /sys/kernel/debug/wonder/force_stop_tx 2>/dev/null)
      result="$result,\"force_stop_tx\":\"$fstx\""
    fi

    # thread-wpan interface
    if [ -d /sys/class/net/thread-wpan ]; then
      result="$result,\"wpan_iface\":true"
    else
      result="$result,\"wpan_iface\":false"
    fi
  else
    result="$result\"present\":false"
  fi

  echo "${result}}"
}

# Load/unload kernel module
manage_kmod() {
  local action="$1" mod="$2"
  local kmod_dir="/data/adb/modules/radiocontrol/common/kmod"

  case "$mod" in
    rc_wifi_mon|rc_shannon_cmd|rc_diag_bridge) ;;
    *) echo '{"ok":false,"error":"unknown module"}'; return ;;
  esac

  if [ "$action" = "load" ]; then
    if lsmod 2>/dev/null | grep -q "$mod"; then
      echo '{"ok":true,"status":"already loaded"}'
    elif [ -f "$kmod_dir/${mod}.ko" ]; then
      insmod "$kmod_dir/${mod}.ko" 2>/dev/null
      if [ $? -eq 0 ]; then
        echo '{"ok":true,"status":"loaded"}'
      else
        echo '{"ok":false,"error":"insmod failed — kernel version mismatch?"}'
      fi
    else
      echo '{"ok":false,"error":"module .ko not found"}'
    fi
  elif [ "$action" = "unload" ]; then
    rmmod "$mod" 2>/dev/null
    echo '{"ok":true,"status":"unloaded"}'
  fi
}

###########################
# HTTP server
###########################

get_mime() {
  case "$1" in
    *.html) echo "text/html" ;; *.css) echo "text/css" ;;
    *.js) echo "text/javascript" ;; *.json) echo "application/json" ;;
    *) echo "text/plain" ;;
  esac
}

send_response() {
  local code="$1" ct="$2" body="$3"
  printf "HTTP/1.1 %s\r\nContent-Type: %s\r\nContent-Length: %d\r\nConnection: close\r\nAccess-Control-Allow-Origin: *\r\nAccess-Control-Allow-Methods: GET,POST,OPTIONS\r\nAccess-Control-Allow-Headers: Content-Type\r\n\r\n%s" \
    "$code" "$ct" "${#body}" "$body"
}

send_file() {
  local f="$1"
  if [ -f "$f" ]; then
    send_response "200 OK" "$(get_mime "$f")" "$(cat "$f")"
  else
    send_response "404 Not Found" "text/plain" "Not Found"
  fi
}

# JSON body parser — extract value for key
json_val() {
  echo "$1" | sed -n "s/.*\"$2\"[[:space:]]*:[[:space:]]*\"\([^\"]*\)\".*/\1/p"
}

handle_request() {
  local method path query body content_length

  read -r request_line
  method=$(echo "$request_line" | awk '{print $1}')
  local full_path=$(echo "$request_line" | awk '{print $2}')
  path=$(echo "$full_path" | cut -d'?' -f1)
  query=$(echo "$full_path" | grep -o '?.*' | cut -c2-)

  while read -r header; do
    header=$(echo "$header" | tr -d '\r')
    [ -z "$header" ] && break
    case "$header" in Content-Length:*) content_length=$(echo "$header" | awk '{print $2}') ;; esac
  done

  body=""
  if [ "$method" = "POST" ] && [ -n "$content_length" ] && [ "$content_length" -gt 0 ] 2>/dev/null; then
    body=$(dd bs=1 count="$content_length" 2>/dev/null)
  fi

  # Handle OPTIONS for CORS
  if [ "$method" = "OPTIONS" ]; then
    send_response "200 OK" "text/plain" ""
    return
  fi

  log "$method $path"

  case "$method $path" in
    # Static files
    "GET /")           send_file "$WEBROOT/index.html" ;;
    "GET /css/"*|"GET /js/"*) send_file "$WEBROOT$path" ;;

    # Status & info APIs
    "GET /api/status")    send_response "200 OK" "application/json" "$(get_current_config)" ;;
    "GET /api/radio")     send_response "200 OK" "application/json" "$(get_radio_info)" ;;
    "GET /api/flags")     send_response "200 OK" "application/json" "$(get_system_flags)" ;;
    "GET /api/thermal")   send_response "200 OK" "application/json" "$(get_thermal_info)" ;;
    "GET /api/kmod")      send_response "200 OK" "application/json" "$(get_kmod_status)" ;;
    "GET /api/modem/interfaces") send_response "200 OK" "application/json" "$(get_modem_interfaces)" ;;
    "GET /api/wifi/info") send_response "200 OK" "text/plain" "$(get_wifi_details)" ;;
    "GET /api/wifi/params") send_response "200 OK" "application/json" "$(get_wifi_params)" ;;
    "GET /api/wifi/firmware")
      send_response "200 OK" "application/json" "$(get_wifi_firmware)"
      ;;
    "GET /api/cp") send_response "200 OK" "application/json" "$(get_cp_debug)" ;;
    "GET /api/carrier/config") send_response "200 OK" "application/json" "$(get_carrier_config)" ;;
    "GET /api/carrier/files") send_response "200 OK" "application/json" "$(get_carrier_files)" ;;
    "GET /api/carrier/dump")
      local dump=$(dump_carrier_config | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
      send_response "200 OK" "application/json" "{\"dump\":\"$dump\"}"
      ;;
    "GET /api/thread") send_response "200 OK" "application/json" "$(get_thread_info)" ;;

    # AT command terminal
    "POST /api/at")
      local cmd=$(json_val "$body" "cmd")
      local timeout=$(json_val "$body" "timeout")
      [ -z "$timeout" ] && timeout=3
      local resp=$(send_at_cmd "$cmd" "$timeout")
      # Escape for JSON
      resp=$(echo "$resp" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
      send_response "200 OK" "application/json" "{\"ok\":true,\"response\":\"$resp\"}"
      ;;

    # debugfs/sysfs browser
    "POST /api/fs/read")
      local fspath=$(json_val "$body" "path")
      if [ -d "$fspath" ]; then
        local listing=$(list_dir_json "$fspath")
        send_response "200 OK" "application/json" "{\"type\":\"dir\",\"entries\":$listing}"
      else
        local content=$(read_sys_path "$fspath" 8192)
        content=$(echo "$content" | sed 's/\\/\\\\/g; s/"/\\"/g; s/\t/\\t/g' | tr '\n' '|' | sed 's/|/\\n/g')
        send_response "200 OK" "application/json" "{\"type\":\"file\",\"content\":\"$content\"}"
      fi
      ;;

    "POST /api/fs/write")
      local fspath=$(json_val "$body" "path")
      local fsval=$(json_val "$body" "value")
      local result=$(write_sys_path "$fspath" "$fsval")
      send_response "200 OK" "application/json" "{\"ok\":true,\"result\":\"$result\"}"
      ;;

    # debugfs paths list
    "GET /api/debugfs")
      local paths=$(cat "$CONFIG_DIR/debugfs_paths" 2>/dev/null)
      local result="["
      local first=1
      for p in $paths; do
        [ -z "$p" ] && continue
        if [ "$first" = "1" ]; then first=0; else result="$result,"; fi
        result="$result\"$p\""
      done
      send_response "200 OK" "application/json" "${result}]"
      ;;

    # Config update
    "POST /api/config")
      local key=$(json_val "$body" "key")
      local val=$(json_val "$body" "value")
      send_response "200 OK" "application/json" "$(update_config "$key" "$val")"
      ;;

    # WiFi mode switch
    "POST /api/wifi/mode")
      local mode=$(json_val "$body" "mode")
      send_response "200 OK" "application/json" "$(set_wifi_mode "$mode")"
      ;;

    # Kernel module management
    "POST /api/kmod/load")
      local mod=$(json_val "$body" "module")
      send_response "200 OK" "application/json" "$(manage_kmod load "$mod")"
      ;;
    "POST /api/kmod/unload")
      local mod=$(json_val "$body" "module")
      send_response "200 OK" "application/json" "$(manage_kmod unload "$mod")"
      ;;

    # Carrier config flag
    "POST /api/carrier/set")
      local flag=$(json_val "$body" "flag")
      local val=$(json_val "$body" "value")
      send_response "200 OK" "application/json" "$(set_carrier_flag "$flag" "$val")"
      ;;

    # WiFi driver param
    "POST /api/wifi/param")
      local pname=$(json_val "$body" "name")
      local pval=$(json_val "$body" "value")
      send_response "200 OK" "application/json" "$(set_wifi_param "$pname" "$pval")"
      ;;

    # CP crash dump
    "POST /api/cp/crash")
      send_response "200 OK" "application/json" "$(trigger_cp_crash)"
      ;;

    # Set prop at runtime
    "POST /api/prop")
      local prop=$(json_val "$body" "prop")
      local val=$(json_val "$body" "value")
      case "$prop" in
        persist.*|ro.debuggable|ro.build.type|ro.secure|ro.adb.secure|service.adb.root)
          resetprop "$prop" "$val" 2>/dev/null
          send_response "200 OK" "application/json" "{\"ok\":true}"
          ;;
        *) send_response "200 OK" "application/json" "{\"ok\":false,\"error\":\"not in allowlist\"}" ;;
      esac
      ;;

    # Reboot
    "POST /api/reboot")
      send_response "200 OK" "application/json" '{"ok":true}'
      sleep 1; reboot ;;

    *) send_response "404 Not Found" "application/json" '{"error":"not found"}' ;;
  esac
}

###########################
# Main
###########################

log "Starting HTTP server on port $PORT"

while true; do
  handle_request | busybox nc -ll -p "$PORT" -s 127.0.0.1 2>/dev/null \
    || handle_request | toybox nc -L -p "$PORT" -s 127.0.0.1 2>/dev/null \
    || { log "ERROR: No network listener available"; exit 1; }
done
