#!/usr/bin/env bash
#
# build-settings-shortcut.sh
#
# Generates a minimal Android APK that opens a specific Settings screen directly.
# Icons are downloaded on demand from the Material Symbols repository (Apache 2.0)
# and cached locally — no hardcoded path data, no PNGs.
#
# Usage:
#   ./build-settings-shortcut.sh                  # interactive menu
#   ./build-settings-shortcut.sh <slug>           # e.g. bluetooth, wifi-hotspot
#   ./build-settings-shortcut.sh --list           # show available shortcuts as table
#   ./build-settings-shortcut.sh --help           # show usage
#
# Environment overrides:
#   BASE_DIR=/somewhere/toolchain  ./build-settings-shortcut.sh wifi-hotspot
#   PROJECT_DIR=/somewhere/app     ./build-settings-shortcut.sh wifi-hotspot
#   ASSUME_YES=1                   ./build-settings-shortcut.sh bluetooth
#
set -euo pipefail

# bash 5.2+ turns on patsub_replacement by default, which makes a literal '&' in
# the replacement text of ${var//pat/repl} expand to the matched substring. That
# would silently corrupt XML escaping (e.g. turning &amp; back into the match),
# so disable it explicitly. Harmless on older bash that lacks the option.
shopt -u patsub_replacement 2>/dev/null || true

# ==========================================================================
# Shortcut definitions
# Format: "slug|Display Name|Primary Activity Class|Fallback Settings Action|material_icon_name"
# icon name = filename stem used in the Material Symbols GitHub repo
# Leave fallback empty if none applies.
# ==========================================================================
SHORTCUTS=(
  "wifi-hotspot|WiFi Hotspot & Tethering|com.android.settings.TetherSettings|ACTION_WIRELESS_SETTINGS|wifi_tethering"
  "wifi|WiFi Settings|com.android.settings.wifi.WifiSettings|ACTION_WIFI_SETTINGS|wifi"
  "bluetooth|Bluetooth Settings|com.android.settings.bluetooth.BluetoothSettings|ACTION_BLUETOOTH_SETTINGS|bluetooth"
  "display|Display Settings|com.android.settings.DisplaySettings|ACTION_DISPLAY_SETTINGS|display_settings"
  "sound|Sound & Vibration|com.android.settings.SoundSettings|ACTION_SOUND_SETTINGS|volume_up"
  "battery|Battery Settings|com.android.settings.BatterySaverSettings|ACTION_BATTERY_SAVER_SETTINGS|battery_full"
  "location|Location Settings|com.android.settings.LocationSettings|ACTION_LOCATION_SOURCE_SETTINGS|location_on"
  "nfc|NFC Settings|com.android.settings.nfc.NfcSettings|ACTION_NFC_SETTINGS|nfc"
  "vpn|VPN Settings|com.android.settings.VpnSettings|ACTION_VPN_SETTINGS|vpn_lock"
  "airplane|Airplane Mode|com.android.settings.AirplaneModeSettings|ACTION_AIRPLANE_MODE_SETTINGS|airplanemode_active"
  "data-usage|Mobile Data Usage|com.android.settings.DataUsageSummary|ACTION_DATA_USAGE_SETTINGS|data_usage"
  "apps|App Manager|com.android.settings.applications.ManageApplications|ACTION_MANAGE_ALL_APPLICATIONS_SETTINGS|apps"
  "developer|Developer Options|com.android.settings.DevelopmentSettings||developer_mode"
  "accessibility|Accessibility Settings|com.android.settings.accessibility.AccessibilitySettings|ACTION_ACCESSIBILITY_SETTINGS|accessibility"
  "language|Language & Input|com.android.settings.LanguageSettings|ACTION_INPUT_METHOD_SETTINGS|language"
  "date-time|Date & Time|com.android.settings.DateTimeSettings|ACTION_DATE_SETTINGS|schedule"
  "security|Security Settings|com.android.settings.SecuritySettings|ACTION_SECURITY_SETTINGS|security"
  "notifications|Notification Settings|com.android.settings.notification.NotificationSettings|ACTION_APP_NOTIFICATION_SETTINGS|notifications"
  "storage|Storage Settings|com.android.settings.deviceinfo.StorageSettings|ACTION_INTERNAL_STORAGE_SETTINGS|storage"
  "about-phone|About Phone|com.android.settings.deviceinfo.AboutPhoneActivity|ACTION_DEVICE_INFO_SETTINGS|phone_android"
)

# ==========================================================================
# Icon download settings
# Material Symbols Outlined, Apache 2.0 licence
# https://github.com/google/material-design-icons
# ==========================================================================
ICON_BASE_URL="https://raw.githubusercontent.com/google/material-design-icons/master/symbols/web"
ICON_SUFFIX="materialsymbolsoutlined"
ICON_SIZE="24px"

# The SVGs use viewBox="0 -960 960 960".
# Group transform to fit the 960x960 coordinate space into the 72dp safe zone
# of a 108x108 adaptive icon canvas (centred, 18dp margin on each side):
#   x_out = 18 + x_in * 0.075       (maps 0..960  -> 18..90)
#   y_out = 90 + y_in * 0.075       (maps -960..0 -> 18..90)
ICON_SCALE="0.075"
ICON_TRANSLATE_X="18"
ICON_TRANSLATE_Y="90"

# ==========================================================================
# Configuration
# ==========================================================================
SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"
BASE_DIR="${BASE_DIR:-$SCRIPT_DIR/toolchain}"
ICON_CACHE_DIR="$BASE_DIR/icon-cache"

CMDLINE_TOOLS_VERSION="11076708"
GRADLE_VERSION="8.7"
BUILD_TOOLS_VERSION="34.0.0"
PLATFORM="android-34"

ADOPTIUM_URL="https://api.adoptium.net/v3/binary/latest/17/ga/linux/x64/jdk/hotspot/normal/eclipse"
CMDLINE_TOOLS_URL="https://dl.google.com/android/repository/commandlinetools-linux-${CMDLINE_TOOLS_VERSION}_latest.zip"
GRADLE_URL="https://services.gradle.org/distributions/gradle-${GRADLE_VERSION}-bin.zip"

NET_HOSTS=(
  "https://api.adoptium.net/"
  "https://dl.google.com/"
  "https://services.gradle.org/"
  "https://repo1.maven.org/maven2/"
  "https://raw.githubusercontent.com/"
)

MIN_DISK_KB=3145728

# ==========================================================================
# Output helpers
# ==========================================================================
if [ -t 1 ]; then
  BOLD=$'\033[1m'; RED=$'\033[31m'; GRN=$'\033[32m'
  YEL=$'\033[33m'; CYN=$'\033[36m'; RST=$'\033[0m'
else
  BOLD=""; RED=""; GRN=""; YEL=""; CYN=""; RST=""
fi

info() { printf '%s==>%s %s\n' "$GRN"  "$RST" "$*"; }
warn() { printf '%sWARNING:%s %s\n' "$YEL" "$RST" "$*" >&2; }
fail() {
  printf '\n%sERROR:%s %s\n' "$RED$BOLD" "$RST" "$1" >&2
  [ "${2:-}" != "" ] && printf '       %s\n' "$2" >&2
  printf '\nAborted. Nothing was changed on your system.\n' >&2
  exit 1
}

# --------------------------------------------------------------------------
# Escape a string for safe inclusion as XML text content.
# '&' MUST be replaced first (it is the lead byte of every other entity).
# Several shortcut display names contain '&' (e.g. "Sound & Vibration");
# without this, strings.xml is malformed XML and aapt2 fails the build.
# Relies on patsub_replacement being off (see top of file).
# --------------------------------------------------------------------------
xml_escape() {
  local s="$1"
  s="${s//&/&amp;}"
  s="${s//</&lt;}"
  s="${s//>/&gt;}"
  s="${s//\"/&quot;}"
  printf '%s' "$s"
}

# --------------------------------------------------------------------------
# Detect filesystems where chmod/utime fail with EPERM (Windows mounts under
# WSL: 9p/v9fs/drvfs, plus CIFS/SMB network shares). Gradle's native chmod and
# tar's permission/utime restore both fail there. Resolves to the nearest
# existing ancestor so it works for not-yet-created paths.
# --------------------------------------------------------------------------
is_windows_fs() {
  local p="$1" t
  while [ ! -e "$p" ] && [ "$p" != "/" ] && [ "$p" != "." ]; do
    p="$(dirname "$p")"
  done
  t="$(stat -f -c '%T' "$p" 2>/dev/null || true)"
  case "$t" in
    v9fs|9p|drvfs|cifs|smb*|smbfs) return 0 ;;
  esac
  if command -v findmnt >/dev/null 2>&1; then
    t="$(findmnt -no FSTYPE --target "$p" 2>/dev/null | head -1)"
    case "$t" in
      v9fs|9p|drvfs|cifs|smb*|smbfs) return 0 ;;
    esac
  fi
  return 1
}

# --------------------------------------------------------------------------
# Pick a writable directory on a native (non-Windows) filesystem, used to
# relocate the Gradle build off a Windows mount. Echoes the path or returns 1.
# --------------------------------------------------------------------------
pick_native_root() {
  local c
  for c in "${TMPDIR:-}" "/tmp" "$HOME/.cache" "$HOME"; do
    [ -n "$c" ] || continue
    mkdir -p "$c" 2>/dev/null || continue
    is_windows_fs "$c" && continue
    [ -w "$c" ] || continue
    printf '%s' "$c"
    return 0
  done
  return 1
}

# --------------------------------------------------------------------------
# Extraction helpers that tolerate chmod/utime EPERM on Windows mounts.
# tar exits 2 and unzip exits non-zero when they cannot restore permissions
# or timestamps there, even though the file contents extract fine. We ignore
# that exit status; every caller verifies the expected output explicitly.
# --------------------------------------------------------------------------
extract_tar() {
  tar -xzf "$1" -C "$2" 2>/dev/null || true
}
extract_zip() {
  unzip -q -o "$1" -d "$2" 2>/dev/null || true
}

# --------------------------------------------------------------------------
# Helper: look up a shortcut field by slug
# Fields: name | activity | fallback | icon
# --------------------------------------------------------------------------
shortcut_field() {
  local slug="$1" field="$2"
  for entry in "${SHORTCUTS[@]}"; do
    IFS='|' read -r s n a f i <<< "$entry"
    if [ "$s" = "$slug" ]; then
      case "$field" in
        name)     echo "$n" ;;
        activity) echo "$a" ;;
        fallback) echo "$f" ;;
        icon)     echo "$i" ;;
      esac
      return 0
    fi
  done
  return 1
}

# --------------------------------------------------------------------------
# Download an icon SVG and extract the path data.
# Caches the raw path string in $ICON_CACHE_DIR/<icon_name>.pathdata
# Returns the path data string on stdout.
# --------------------------------------------------------------------------
fetch_icon_path() {
  local icon_name="$1"
  local cache_file="$ICON_CACHE_DIR/${icon_name}.pathdata"

  if [ -s "$cache_file" ]; then
    cat "$cache_file"
    return 0
  fi

  local url="${ICON_BASE_URL}/${icon_name}/${ICON_SUFFIX}/${icon_name}_${ICON_SIZE}.svg"
  local svg

  # Progress goes to stderr: this function's stdout is captured as the path
  # data, so a status line on stdout would be embedded into the icon pathData.
  info "Downloading icon: $icon_name ..." >&2
  if [ "$DL_TOOL" = "curl" ]; then
    svg=$(curl -fsSL --retry 3 --connect-timeout 15 "$url") || \
      fail "Could not download icon '$icon_name'." "URL: $url"
  else
    svg=$(wget -qO- --tries=3 --timeout=15 "$url") || \
      fail "Could not download icon '$icon_name'." "URL: $url"
  fi

  # Extract the d="..." attribute from the single <path> element.
  # Flatten to one line first (some SVGs wrap), and anchor on '<path ... d="'
  # so we never match an unrelated attribute such as id="...".
  local path_data
  path_data=$(printf '%s' "$svg" | tr '\n' ' ' | \
              sed 's#.*<path[^>]* d="\([^"]*\)".*#\1#')

  # If the substitution did not match, sed echoes the whole SVG back; a real
  # path data string never contains '<'. Treat either as extraction failure.
  case "$path_data" in
    ""|*"<"*)
      fail "Could not extract path data from icon SVG '$icon_name'." \
           "The SVG format may have changed. URL: $url" ;;
  esac

  mkdir -p "$ICON_CACHE_DIR"
  echo "$path_data" > "$cache_file"
  echo "$path_data"
}

# --------------------------------------------------------------------------
# --help
# --------------------------------------------------------------------------
show_help() {
  cat <<EOF

${BOLD}build-settings-shortcut.sh${RST} — Android Settings Shortcut APK Builder

${BOLD}USAGE${RST}
  $0                    Interactive selection menu
  $0 <slug>             Build directly by shortcut slug
  $0 --list             Show all available shortcuts as a table
  $0 --help             Show this help

${BOLD}EXAMPLES${RST}
  $0 bluetooth
  $0 wifi-hotspot
  ASSUME_YES=1 $0 developer
  BASE_DIR=/opt/toolchain $0 display

${BOLD}ICONS${RST}
  Icons are downloaded on demand from the Material Symbols repository
  (github.com/google/material-design-icons, Apache 2.0) and cached in
  $ICON_CACHE_DIR
  Delete that directory to force a re-download.

${BOLD}REQUIREMENTS${RST}
  Linux x86_64 with glibc, curl or wget, tar, unzip, sed, ~3 GiB free disk.
  No root required. Nothing is installed system-wide.

EOF
}

# --------------------------------------------------------------------------
# --list  (formatted table)
# --------------------------------------------------------------------------
show_list() {
  local col1=14 col2=28 col3=22 col4=55
  printf '\n'
  printf '%s%-*s  %-*s  %-*s  %s%s\n' \
    "$BOLD" $col1 "SLUG" $col2 "DISPLAY NAME" $col3 "ICON" $col4 "PRIMARY ACTIVITY" "$RST"
  printf '%s\n' "$(printf '%0.s-' $(seq 1 $(( col1 + col2 + col3 + col4 + 6 ))))"
  for entry in "${SHORTCUTS[@]}"; do
    IFS='|' read -r slug name activity fallback icon_name <<< "$entry"
    printf '%s%-*s%s  %-*s  %s%-*s%s  %s\n' \
      "$CYN" $col1 "$slug"      "$RST" \
              $col2 "$name"     \
      "$YEL"  $col3 "$icon_name" "$RST" \
                    "$activity"
  done
  printf '\n'
  printf 'Icons: Material Symbols Outlined (github.com/google/material-design-icons, Apache 2.0)\n'
  printf 'Cache: %s\n\n' "$ICON_CACHE_DIR"
}

# --------------------------------------------------------------------------
# Interactive menu
# --------------------------------------------------------------------------
interactive_select() {
  printf '\n%sAvailable Settings Shortcuts:%s\n\n' "$BOLD" "$RST"
  local i=1
  for entry in "${SHORTCUTS[@]}"; do
    IFS='|' read -r slug name _ _ _ <<< "$entry"
    printf '  %s%2d)%s  %-30s  %s(%s)%s\n' \
      "$CYN" $i "$RST" "$name" "$YEL" "$slug" "$RST"
    (( i++ ))
  done
  printf '\n'

  TTY_SRC=""
  [ -t 0 ] && TTY_SRC="stdin"
  { [ -z "$TTY_SRC" ] && ( : < /dev/tty ) 2>/dev/null; } && TTY_SRC="/dev/tty"
  [ -z "$TTY_SRC" ] && \
    fail "No terminal available." "Run interactively or pass a slug as argument."

  local reply
  while true; do
    printf '%sEnter number (1-%d): %s' "$BOLD" "${#SHORTCUTS[@]}" "$RST"
    if [ "$TTY_SRC" = "stdin" ]; then read -r reply; else read -r reply < /dev/tty; fi
    if [[ "$reply" =~ ^[0-9]+$ ]] && [ "$reply" -ge 1 ] && \
       [ "$reply" -le "${#SHORTCUTS[@]}" ]; then
      IFS='|' read -r SELECTED_SLUG _ _ _ _ <<< "${SHORTCUTS[$(( reply - 1 ))]}"
      break
    fi
    printf 'Please enter a number between 1 and %d.\n' "${#SHORTCUTS[@]}"
  done
}

# ==========================================================================
# Argument parsing
# ==========================================================================
SELECTED_SLUG=""
case "${1:-}" in
  --help|-h)  show_help; exit 0 ;;
  --list|-l)  show_list; exit 0 ;;
  "")         interactive_select ;;
  *)
    SELECTED_SLUG="$1"
    if ! shortcut_field "$SELECTED_SLUG" name >/dev/null 2>&1; then
      printf '%sUnknown slug: "%s"%s\n\n' "$RED" "$SELECTED_SLUG" "$RST" >&2
      printf 'Run  %s --list  to see all available shortcuts.\n\n' "$0" >&2
      exit 1
    fi
    ;;
esac

DISPLAY_NAME="$(shortcut_field "$SELECTED_SLUG" name)"
PRIMARY_ACTIVITY="$(shortcut_field "$SELECTED_SLUG" activity)"
FALLBACK_ACTION="$(shortcut_field "$SELECTED_SLUG" fallback)"
ICON_NAME="$(shortcut_field "$SELECTED_SLUG" icon)"

PKG_SUFFIX="${SELECTED_SLUG//-/_}"
APP_PKG="com.example.${PKG_SUFFIX}_shortcut"
PROJECT_DIR="${PROJECT_DIR:-$SCRIPT_DIR/${SELECTED_SLUG}-shortcut}"

printf '\n%sBuilding shortcut for:%s %s\n' "$BOLD" "$RST" "$DISPLAY_NAME"
printf '  Slug:      %s\n'  "$SELECTED_SLUG"
printf '  Package:   %s\n'  "$APP_PKG"
printf '  Activity:  %s\n'  "$PRIMARY_ACTIVITY"
printf '  Fallback:  %s\n'  "${FALLBACK_ACTION:-none}"
printf '  Icon:      %s\n'  "$ICON_NAME"
printf '  Output:    %s\n'  "$PROJECT_DIR"

# ==========================================================================
# PHASE 0 — PREFLIGHT CHECKS
# ==========================================================================
info "Checking requirements..."

[ "$(uname -s)" = "Linux" ] || \
  fail "Linux only (detected: $(uname -s))." \
       "Use a Linux machine, WSL, or a Linux container/VM."

ARCH="$(uname -m)"
case "$ARCH" in
  x86_64|amd64) ;;
  *) fail "Unsupported CPU: '$ARCH'." \
          "The Android SDK build tools exist only for x86_64. ARM is not supported." ;;
esac

if command -v ldd >/dev/null 2>&1; then
  ldd --version 2>&1 | grep -qi musl && \
    fail "musl C library detected (e.g. Alpine)." \
         "Use a glibc-based distro (Ubuntu, Debian, Fedora, etc.)."
elif ls /lib/ld-musl-* >/dev/null 2>&1; then
  fail "musl C library detected." "Use a glibc-based distro."
fi

DL_TOOL=""
command -v curl >/dev/null 2>&1 && DL_TOOL="curl"
{ command -v wget >/dev/null 2>&1 && [ -z "$DL_TOOL" ]; } && DL_TOOL="wget"

MISSING=()
[ -n "$DL_TOOL" ] || MISSING+=("curl or wget")
for tool in tar unzip find awk df sed; do
  command -v "$tool" >/dev/null 2>&1 || MISSING+=("$tool")
done
[ "${#MISSING[@]}" -gt 0 ] && \
  fail "Missing tool(s): ${MISSING[*]}." \
       "Install with: sudo apt install ${MISSING[*]}"

mkdir -p "$BASE_DIR"
AVAIL_KB="$(df -Pk "$BASE_DIR" | awk 'NR==2 {print $4}')"
{ [ -z "$AVAIL_KB" ] || [ "$AVAIL_KB" -lt "$MIN_DISK_KB" ]; } && \
  fail "Not enough disk space (need ~3 GiB)." \
       "Free space or set BASE_DIR to another location."

# Reachability check with retries — a single transient blip on any one host
# (e.g. raw.githubusercontent.com rate-limiting) must not abort the whole run.
check_url() {
  if [ "$DL_TOOL" = "curl" ]; then
    curl -sSfL -I -m 15 --retry 2 --retry-delay 2 --retry-connrefused \
      -o /dev/null "$1" >/dev/null 2>&1
  else
    wget -q --spider --timeout=15 -t 3 --waitretry=2 "$1"
  fi
}
for host in "${NET_HOSTS[@]}"; do
  check_url "$host" || fail "Cannot reach $host" "Check network/proxy/firewall."
done

[ "$(id -u)" = "0" ] && warn "Running as root — not required but continuing."
info "All requirements satisfied."

# ==========================================================================
# PHASE 1 — Fetch icon (cached)
# ==========================================================================
# Check icon cache before showing the confirmation prompt so the user sees
# whether the icon will be downloaded or reused.
ICON_CACHE_FILE="$ICON_CACHE_DIR/${ICON_NAME}.pathdata"
[ -s "$ICON_CACHE_FILE" ] \
  && ICON_STAT="cached, will reuse" \
  || ICON_STAT="will download from github.com/google/material-design-icons"

# ==========================================================================
# Confirmation
# ==========================================================================
JDK_FOUND="$(find "$BASE_DIR" -maxdepth 1 -type d -name 'jdk-17*' 2>/dev/null | head -1 || true)"
{ [ -n "$JDK_FOUND" ] && [ -x "$JDK_FOUND/bin/java" ]; } \
  && JDK_STAT="already installed, will skip" || JDK_STAT="will download"
{ [ -x "$BASE_DIR/android-sdk/cmdline-tools/latest/bin/sdkmanager" ] &&
  [ -d "$BASE_DIR/android-sdk/platforms/$PLATFORM" ] &&
  [ -d "$BASE_DIR/android-sdk/build-tools/$BUILD_TOOLS_VERSION" ] &&
  [ -d "$BASE_DIR/android-sdk/platform-tools" ]; } \
  && SDK_STAT="already installed, will skip" || SDK_STAT="will download missing parts"
[ -x "$BASE_DIR/gradle-$GRADLE_VERSION/bin/gradle" ] \
  && GRADLE_STAT="already installed, will skip" || GRADLE_STAT="will download"

cat <<EOF

${BOLD}Toolchain and asset status for this run:${RST}
  JDK 17 (Eclipse Temurin)   [$JDK_STAT]
  Android SDK                [$SDK_STAT]
  Gradle $GRADLE_VERSION               [$GRADLE_STAT]
  Icon ($ICON_NAME)    [$ICON_STAT]

${YEL}${BOLD}WARNING:${RST} Project files in '$PROJECT_DIR' are regenerated every run.
Manual edits to those files will be overwritten.

EOF

if [ "${ASSUME_YES:-}" = "1" ]; then
  info "ASSUME_YES=1 — proceeding without prompt."
else
  TTY_SRC=""
  [ -t 0 ] && TTY_SRC="stdin"
  { [ -z "$TTY_SRC" ] && ( : < /dev/tty ) 2>/dev/null; } && TTY_SRC="/dev/tty"
  [ -z "$TTY_SRC" ] && \
    fail "No terminal for confirmation." "Set ASSUME_YES=1 or run interactively."

  printf '%sProceed? [y/N] %s' "$BOLD" "$RST"
  [ "$TTY_SRC" = "stdin" ] && read -r reply || read -r reply < /dev/tty
  case "$reply" in
    [yY]|[yY][eE][sS]) ;;
    *) printf '\nCancelled.\n'; exit 0 ;;
  esac
fi

# --------------------------------------------------------------------------
# Download helper (for toolchain components)
# --------------------------------------------------------------------------
download() {
  local url="$1" dest="$2"
  info "Downloading $(basename "$dest") ..."
  if [ "$DL_TOOL" = "curl" ]; then
    curl -fSL --retry 3 --connect-timeout 30 -o "$dest" "$url"
  else
    wget -q --tries=3 -O "$dest" "$url"
  fi
}

DL_DIR="$BASE_DIR/dl"
mkdir -p "$DL_DIR"

# ==========================================================================
# PHASE 2 — JDK 17
# ==========================================================================
JDK_DIR="$(find "$BASE_DIR" -maxdepth 1 -type d -name 'jdk-17*' 2>/dev/null | head -1 || true)"
if [ -n "$JDK_DIR" ] && [ -x "$JDK_DIR/bin/java" ]; then
  info "JDK 17 already present: $JDK_DIR"
else
  download "$ADOPTIUM_URL" "$DL_DIR/jdk17.tar.gz"
  info "Extracting JDK 17..."
  extract_tar "$DL_DIR/jdk17.tar.gz" "$BASE_DIR"
  JDK_DIR="$(find "$BASE_DIR" -maxdepth 1 -type d -name 'jdk-17*' | head -1)"
  { [ -z "$JDK_DIR" ] || [ ! -x "$JDK_DIR/bin/java" ]; } && \
    fail "JDK extraction failed." "Delete '$DL_DIR' and re-run."
fi
export JAVA_HOME="$JDK_DIR"
export PATH="$JAVA_HOME/bin:$PATH"
info "Java: $("$JAVA_HOME/bin/java" -version 2>&1 | head -1)"

# ==========================================================================
# PHASE 3 — Android SDK
# ==========================================================================
export ANDROID_HOME="$BASE_DIR/android-sdk"
SDKMANAGER="$ANDROID_HOME/cmdline-tools/latest/bin/sdkmanager"

if [ ! -x "$SDKMANAGER" ]; then
  download "$CMDLINE_TOOLS_URL" "$DL_DIR/cmdline-tools.zip"
  info "Installing Android command-line tools..."
  mkdir -p "$ANDROID_HOME/cmdline-tools"
  rm -rf "$ANDROID_HOME/cmdline-tools/latest" "$ANDROID_HOME/cmdline-tools/cmdline-tools"
  extract_zip "$DL_DIR/cmdline-tools.zip" "$ANDROID_HOME/cmdline-tools"
  mv "$ANDROID_HOME/cmdline-tools/cmdline-tools" "$ANDROID_HOME/cmdline-tools/latest"
  [ -x "$SDKMANAGER" ] || \
    fail "sdkmanager not found after unzip." "Delete '$DL_DIR' and re-run."
else
  info "Android command-line tools already present."
fi

info "Accepting SDK licenses..."
yes | "$SDKMANAGER" --sdk_root="$ANDROID_HOME" --licenses >/dev/null 2>&1 || true

info "Installing SDK packages..."
"$SDKMANAGER" --sdk_root="$ANDROID_HOME" \
  "platform-tools" "platforms;$PLATFORM" "build-tools;$BUILD_TOOLS_VERSION" \
  >/dev/null 2>&1 || \
  fail "Failed to install Android SDK packages." \
       "Re-run or delete '$ANDROID_HOME' and try again."

# ==========================================================================
# PHASE 4 — Gradle
# ==========================================================================
GRADLE_BIN="$BASE_DIR/gradle-$GRADLE_VERSION/bin/gradle"
if [ ! -x "$GRADLE_BIN" ]; then
  download "$GRADLE_URL" "$DL_DIR/gradle.zip"
  info "Extracting Gradle $GRADLE_VERSION..."
  extract_zip "$DL_DIR/gradle.zip" "$BASE_DIR"
  [ -x "$GRADLE_BIN" ] || \
    fail "Gradle binary not found after unzip." "Delete '$DL_DIR' and re-run."
else
  info "Gradle $GRADLE_VERSION already present."
fi

# ==========================================================================
# PHASE 5 — Fetch icon path data (cached)
# ==========================================================================
ICON_PATH_DATA="$(fetch_icon_path "$ICON_NAME")"
[ -z "$ICON_PATH_DATA" ] && \
  fail "Icon path data is empty for '$ICON_NAME'." \
       "Try deleting '$ICON_CACHE_DIR' and re-running."
info "Icon ready: $ICON_NAME"

# ==========================================================================
# PHASE 6 — Generate project files
# ==========================================================================
info "Creating project at $PROJECT_DIR ..."

APP_PKG_PATH="${APP_PKG//./\/}"
APP_PKG_DIR="$PROJECT_DIR/app/src/main/java/$APP_PKG_PATH"
RES_DIR="$PROJECT_DIR/app/src/main/res"

mkdir -p "$APP_PKG_DIR" \
         "$RES_DIR/values" \
         "$RES_DIR/drawable" \
         "$RES_DIR/mipmap-anydpi-v26"

# Build fallback Kotlin block
if [ -n "$FALLBACK_ACTION" ]; then
  FALLBACK_KOTLIN="        // Fallback: public Settings action, always available
        try {
            startActivity(
                Intent(Settings.$FALLBACK_ACTION)
                    .addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
            )
        } catch (e: ActivityNotFoundException) {
            Toast.makeText(this, \"Could not open settings\", Toast.LENGTH_LONG).show()
        }"
else
  FALLBACK_KOTLIN='        Toast.makeText(this, "Could not open settings", Toast.LENGTH_LONG).show()'
fi

# settings.gradle.kts
cat > "$PROJECT_DIR/settings.gradle.kts" <<GRADLE
pluginManagement {
    repositories { google(); mavenCentral(); gradlePluginPortal() }
}
dependencyResolutionManagement {
    repositories { google(); mavenCentral() }
}
rootProject.name = "${SELECTED_SLUG}-shortcut"
include(":app")
GRADLE

# root build.gradle.kts
cat > "$PROJECT_DIR/build.gradle.kts" <<'GRADLE'
plugins {
    id("com.android.application") version "8.5.0" apply false
    id("org.jetbrains.kotlin.android") version "1.9.24" apply false
}
GRADLE

cat > "$PROJECT_DIR/gradle.properties" <<'GRADLE'
org.gradle.jvmargs=-Xmx2048m
android.useAndroidX=true
kotlin.code.style=official
GRADLE

printf 'sdk.dir=%s\n' "$ANDROID_HOME" > "$PROJECT_DIR/local.properties"

# app/build.gradle.kts
cat > "$PROJECT_DIR/app/build.gradle.kts" <<GRADLE
plugins {
    id("com.android.application")
    id("org.jetbrains.kotlin.android")
}

android {
    namespace = "$APP_PKG"
    compileSdk = 34

    defaultConfig {
        applicationId = "$APP_PKG"
        minSdk = 26
        targetSdk = 34
        versionCode = 1
        versionName = "1.0"
    }

    signingConfigs {
        // minSdk >= 24 makes AGP drop v1 (JAR) signing by default, producing a
        // v2-only APK that some devices reject with "problem parsing the
        // package". Force both v1 and v2 on the debug keystore.
        getByName("debug") {
            enableV1Signing = true
            enableV2Signing = true
        }
    }

    buildTypes {
        debug { signingConfig = signingConfigs.getByName("debug") }
        release { isMinifyEnabled = false }
    }
    compileOptions {
        sourceCompatibility = JavaVersion.VERSION_17
        targetCompatibility = JavaVersion.VERSION_17
    }
    kotlinOptions { jvmTarget = "17" }
}

dependencies {
    implementation("androidx.core:core-ktx:1.13.1")
}
GRADLE

# AndroidManifest.xml
cat > "$PROJECT_DIR/app/src/main/AndroidManifest.xml" <<XML
<?xml version="1.0" encoding="utf-8"?>
<manifest xmlns:android="http://schemas.android.com/apk/res/android">
    <application
        android:label="@string/app_name"
        android:icon="@mipmap/ic_launcher"
        android:roundIcon="@mipmap/ic_launcher"
        android:allowBackup="true">
        <activity
            android:name=".MainActivity"
            android:exported="true"
            android:theme="@android:style/Theme.NoDisplay">
            <intent-filter>
                <action android:name="android.intent.action.MAIN" />
                <category android:name="android.intent.category.LAUNCHER" />
            </intent-filter>
        </activity>
    </application>
</manifest>
XML

# strings.xml — escape the display name; several contain '&' (invalid raw XML).
DISPLAY_NAME_XML="$(xml_escape "$DISPLAY_NAME")"
cat > "$RES_DIR/values/strings.xml" <<XML
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <string name="app_name">$DISPLAY_NAME_XML</string>
</resources>
XML

# Icon background colour
cat > "$RES_DIR/values/ic_launcher_background.xml" <<'XML'
<?xml version="1.0" encoding="utf-8"?>
<resources>
    <color name="ic_launcher_background">#1565C0</color>
</resources>
XML

# Adaptive icon wrapper
cat > "$RES_DIR/mipmap-anydpi-v26/ic_launcher.xml" <<'XML'
<?xml version="1.0" encoding="utf-8"?>
<adaptive-icon xmlns:android="http://schemas.android.com/apk/res/android">
    <background android:drawable="@color/ic_launcher_background" />
    <foreground android:drawable="@drawable/ic_launcher_foreground" />
</adaptive-icon>
XML

# Foreground vector icon — path data fetched from Material Symbols repo.
#
# The SVGs use viewBox="0 -960 960 960" (x: 0..960, y: -960..0).
# The group transform maps this into the 72dp safe zone of the 108dp canvas:
#   translateX=18  translateY=90  scaleX=0.075  scaleY=0.075
# which produces:  x: 0..960 -> 18..90   y: -960..0 -> 18..90
cat > "$RES_DIR/drawable/ic_launcher_foreground.xml" <<XML
<?xml version="1.0" encoding="utf-8"?>
<!--
  Icon: $ICON_NAME (Material Symbols Outlined)
  Source: github.com/google/material-design-icons  (Apache 2.0)
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
    android:width="108dp"
    android:height="108dp"
    android:viewportWidth="108"
    android:viewportHeight="108">
    <group
        android:scaleX="$ICON_SCALE"
        android:scaleY="$ICON_SCALE"
        android:translateX="$ICON_TRANSLATE_X"
        android:translateY="$ICON_TRANSLATE_Y">
        <path
            android:fillColor="#FFFFFF"
            android:pathData="$ICON_PATH_DATA" />
    </group>
</vector>
XML

# MainActivity.kt
cat > "$APP_PKG_DIR/MainActivity.kt" <<KOTLIN
package $APP_PKG

import android.app.Activity
import android.content.ActivityNotFoundException
import android.content.Intent
import android.os.Bundle
import android.provider.Settings
import android.widget.Toast

class MainActivity : Activity() {
    override fun onCreate(savedInstanceState: Bundle?) {
        super.onCreate(savedInstanceState)
        openTargetSettings()
        finish()
    }

    private fun openTargetSettings() {
        // Primary: internal settings activity (ROM-specific, may not exist everywhere)
        val primary = Intent().apply {
            setClassName(
                "com.android.settings",
                "$PRIMARY_ACTIVITY"
            )
            addFlags(Intent.FLAG_ACTIVITY_NEW_TASK)
        }
        try {
            startActivity(primary)
            return
        } catch (e: ActivityNotFoundException) {
            // ROM moved or renamed this activity — fall through to fallback
        }

$FALLBACK_KOTLIN
    }
}
KOTLIN

# ==========================================================================
# PHASE 7 — Gradle wrapper + build
# ==========================================================================
# Gradle performs native chmod() on its build outputs and user home. On a
# Windows-mounted filesystem (WSL 9p/drvfs, CIFS) that fails with EPERM and the
# build dies. When the project lives on such a mount, build in a native-fs
# staging copy and copy only the finished APK back to PROJECT_DIR.
BUILD_DIR="$PROJECT_DIR"
NATIVE_ROOT=""
if is_windows_fs "$PROJECT_DIR"; then
  NATIVE_ROOT="$(pick_native_root)" || \
    fail "Project dir is on a Windows-mounted filesystem and no native-fs" \
         "staging dir was found. Set PROJECT_DIR to a path under your Linux home."
  BUILD_DIR="$NATIVE_ROOT/build-settings-shortcut/${SELECTED_SLUG}-shortcut"
  warn "Project dir is on a Windows-mounted filesystem (Gradle's chmod fails there)."
  info "Building in native-fs staging dir: $BUILD_DIR"
  rm -rf "$BUILD_DIR"
  mkdir -p "$(dirname "$BUILD_DIR")"
  cp -r "$PROJECT_DIR" "$BUILD_DIR"
fi

# Relocate GRADLE_USER_HOME too if its default lands on a Windows mount.
GRADLE_HOME_DEFAULT="${GRADLE_USER_HOME:-$HOME/.gradle}"
if is_windows_fs "$GRADLE_HOME_DEFAULT"; then
  NATIVE_ROOT="${NATIVE_ROOT:-$(pick_native_root)}" || \
    fail "Gradle home is on a Windows-mounted filesystem and no native-fs" \
         "location was found. Set GRADLE_USER_HOME to a path under your Linux home."
  export GRADLE_USER_HOME="$NATIVE_ROOT/build-settings-shortcut/gradle-home"
  mkdir -p "$GRADLE_USER_HOME"
fi

cd "$BUILD_DIR"

if [ ! -x "$BUILD_DIR/gradlew" ]; then
  info "Generating Gradle wrapper..."
  "$GRADLE_BIN" wrapper --gradle-version "$GRADLE_VERSION" >/dev/null
fi

info "Building debug APK (first run downloads Android/Kotlin dependencies)..."
"$GRADLE_BIN" --no-daemon assembleDebug || \
  fail "Gradle build failed (see output above)." \
       "Check network/proxy and re-run."

# ==========================================================================
# PHASE 8 — Verify and report
# ==========================================================================
APK="$BUILD_DIR/app/build/outputs/apk/debug/app-debug.apk"
[ -s "$APK" ] || fail "Build succeeded but APK is missing." "Expected: $APK"

FINAL_APK="$PROJECT_DIR/${SELECTED_SLUG}-shortcut.apk"
cp -f "$APK" "$FINAL_APK"

printf '\n%s%sBUILD SUCCESSFUL%s\n' "$GRN" "$BOLD" "$RST"
printf '  Shortcut:  %s\n'   "$DISPLAY_NAME"
printf '  Icon:      %s (Material Symbols Outlined, Apache 2.0)\n' "$ICON_NAME"
printf '  APK:       %s\n'   "$FINAL_APK"
printf '  Size:      %s\n'   "$(du -h "$FINAL_APK" | cut -f1)"
printf '  Package:   %s\n'   "$APP_PKG"
printf '  Toolchain: %s\n'   "$BASE_DIR"
printf '  Icon cache:%s\n\n' "$ICON_CACHE_DIR"
printf 'Install on a connected phone:\n'
printf '  %s/android-sdk/platform-tools/adb install "%s"\n\n' \
  "$BASE_DIR" "$FINAL_APK"
printf 'Or copy the APK to the device and tap it\n'
printf '(enable "Install unknown apps" first).\n'
