#!/bin/sh
set -eu

RELEASE_ROOT="https://jackscurrie.com/downloads"
MANIFEST_URL="$RELEASE_ROOT/icy-lyrics-update.json"
MINIMUM_SPICETIFY_VERSION="2.44.0"

MANIFEST_FILE=""
TEMP_EXTENSION=""
TEMP_BACKUP=""
SPICETIFY_INSTALLER=""
SPOTIFY_KEY_FILE=""
SPOTIFY_TEMP_DIR=""

cleanup() {
  [ -n "$MANIFEST_FILE" ] && rm -f "$MANIFEST_FILE"
  [ -n "$TEMP_EXTENSION" ] && rm -f "$TEMP_EXTENSION"
  [ -n "$TEMP_BACKUP" ] && rm -f "$TEMP_BACKUP"
  [ -n "$SPICETIFY_INSTALLER" ] && rm -f "$SPICETIFY_INSTALLER"
  [ -n "$SPOTIFY_KEY_FILE" ] && rm -f "$SPOTIFY_KEY_FILE"
  if [ -n "$SPOTIFY_TEMP_DIR" ] && [ -d "$SPOTIFY_TEMP_DIR" ]; then
    rm -f "$SPOTIFY_TEMP_DIR/Spotify.dmg"
    rmdir "$SPOTIFY_TEMP_DIR" 2>/dev/null || true
  fi
}
trap cleanup EXIT
trap 'exit 129' HUP
trap 'exit 130' INT
trap 'exit 143' TERM

section() {
  printf '\n\033[36m== %s ==\033[0m\n%s\n' "$1" "$2"
}

confirm() {
  question=$1
  default=${2:-yes}
  if [ "$default" = "yes" ]; then suffix="[Y/n]"; else suffix="[y/N]"; fi
  while :; do
    printf '%s %s ' "$question" "$suffix" > /dev/tty
    IFS= read -r answer < /dev/tty || return 1
    case "$answer" in
      "") [ "$default" = "yes" ] && return 0 || return 1 ;;
      y|Y|yes|YES|Yes) return 0 ;;
      n|N|no|NO|No) return 1 ;;
      *) printf 'Please answer yes or no.\n' > /dev/tty ;;
    esac
  done
}

download() {
  if command -v curl >/dev/null 2>&1; then
    curl -fL --retry 2 --connect-timeout 15 "$1" -o "$2"
  elif command -v wget >/dev/null 2>&1; then
    wget --timeout=30 --tries=3 -O "$2" "$1"
  else
    printf 'curl or wget is required to download %s.\n' "$1" >&2
    exit 1
  fi
}

find_spicetify() {
  if command -v spicetify >/dev/null 2>&1; then
    command -v spicetify
  elif [ -x "$HOME/.spicetify/spicetify" ]; then
    printf '%s\n' "$HOME/.spicetify/spicetify"
  else
    return 1
  fi
}

get_spicetify_version() {
  if ! version_output=$("$1" --version 2>&1); then return 1; fi
  printf '%s\n' "$version_output" |
    sed -n 's/^[^0-9]*\([0-9][0-9]*\.[0-9][0-9]*\.[0-9][0-9]*\).*$/\1/p' |
    head -n 1
}

version_at_least() {
  awk -v current="$1" -v minimum="$2" 'BEGIN {
    split(current, c, "."); split(minimum, m, ".");
    for (i = 1; i <= 3; i++) {
      if ((c[i] + 0) > (m[i] + 0)) exit 0;
      if ((c[i] + 0) < (m[i] + 0)) exit 1;
    }
    exit 0;
  }'
}

valid_semver() {
  value=$1
  if ! printf '%s\n' "$value" | grep -Eq '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$'; then
    return 1
  fi
  case "$value" in
    *-*)
      prerelease=${value#*-}
      prerelease=${prerelease%%+*}
      old_ifs=$IFS
      IFS=.
      for identifier in $prerelease; do
        case "$identifier" in
          *[!0-9]*|"") ;;
          0|[1-9][0-9]*) ;;
          *) IFS=$old_ifs; return 1 ;;
        esac
      done
      IFS=$old_ifs
      ;;
  esac
  return 0
}

sha256_file() {
  if command -v sha256sum >/dev/null 2>&1; then
    sha256sum "$1" | awk '{print tolower($1)}'
  elif command -v shasum >/dev/null 2>&1; then
    shasum -a 256 "$1" | awk '{print tolower($1)}'
  elif command -v openssl >/dev/null 2>&1; then
    openssl dgst -sha256 "$1" | awk '{print tolower($NF)}'
  else
    printf 'A SHA-256 tool (sha256sum, shasum, or openssl) is required.\n' >&2
    exit 1
  fi
}

detect_spotify() {
  SPOTIFY_FOUND=no
  SPOTIFY_ROOT=""
  SPOTIFY_INSTALL_KIND=""
  SPOTIFY_PREFS_PATH=""
  SNAP_FOUND=no
  case "$OS" in
    Darwin)
      if [ -d "/Applications/Spotify.app" ]; then SPOTIFY_FOUND=yes; fi
      ;;
    Linux)
      if command -v snap >/dev/null 2>&1 && snap list spotify >/dev/null 2>&1; then SNAP_FOUND=yes; fi
      for candidate in "/usr/share/spotify" "/opt/spotify" "$HOME/.local/share/spotify-launcher/install/usr/share/spotify"; do
        if [ -d "$candidate" ]; then
          SPOTIFY_FOUND=yes
          SPOTIFY_ROOT=$candidate
          SPOTIFY_INSTALL_KIND=native
          break
        fi
      done
      if [ "$SPOTIFY_FOUND" = no ]; then
        for candidate in \
          /var/lib/flatpak/app/com.spotify.Client/*/*/active/files/extra/share/spotify \
          "$HOME"/.local/share/flatpak/app/com.spotify.Client/*/*/active/files/extra/share/spotify; do
          if [ -d "$candidate" ]; then
            SPOTIFY_FOUND=yes
            SPOTIFY_ROOT=$candidate
            SPOTIFY_INSTALL_KIND=flatpak
            SPOTIFY_PREFS_PATH="$HOME/.var/app/com.spotify.Client/config/spotify/prefs"
            break
          fi
        done
      fi
      if [ "$SPOTIFY_FOUND" = no ] && command -v spotify >/dev/null 2>&1; then
        spotify_command=$(command -v spotify)
        spotify_resolved=$spotify_command
        if command -v readlink >/dev/null 2>&1; then spotify_resolved=$(readlink -f "$spotify_command" 2>/dev/null || printf '%s' "$spotify_command"); fi
        case "$spotify_command:$spotify_resolved" in
          *"/snap/"*) ;;
          *) SPOTIFY_FOUND=yes; SPOTIFY_INSTALL_KIND=native ;;
        esac
      fi
      ;;
  esac
}

if [ "$(id -u)" -eq 0 ]; then
  printf 'Do not run this installer with sudo or as root. Spicetify and Icy Lyrics must belong to your normal desktop user.\n' >&2
  exit 1
fi

printf '\033[1mIcy Lyrics guided installer\033[0m\n'
printf 'This installer checks each required component and asks before it installs anything.\n'

OS=$(uname -s)
case "$OS" in Darwin|Linux) ;; *) printf 'This installer supports macOS and Linux. Detected: %s\n' "$OS" >&2; exit 1 ;; esac

section "1. Spotify" "Spotify is the desktop music player that Icy Lyrics appears inside. The web player and Linux Snap package cannot load this Spicetify extension; native packages and supported Flatpak installs can."
detect_spotify
SPOTIFY_WAS_INSTALLED=no

if [ "$SPOTIFY_FOUND" = yes ]; then
  printf '\033[32mA compatible Spotify desktop installation is installed.\033[0m\n'
  if [ "$SNAP_FOUND" = yes ]; then
    printf '\033[33mA Spotify Snap installation was also found. It cannot be customized, so Icy Lyrics will use the compatible native installation.\033[0m\n'
  fi
else
  if [ "$SNAP_FOUND" = yes ]; then
    printf '\033[33mSpotify Snap was found, but Snap applications cannot be modified by Spicetify. This installer will not remove it automatically.\033[0m\n'
  fi
  printf 'A compatible Spotify desktop installation was not found.\n'
  if ! confirm "Download and install Spotify from its official distribution?" yes; then
    printf 'Spotify is required, so the Icy Lyrics installation has been stopped.\n'
    exit 0
  fi

  if [ "$OS" = Darwin ]; then
    SPOTIFY_TEMP_DIR=$(mktemp -d "${TMPDIR:-/tmp}/icy-spotify.XXXXXX")
    download "https://download.scdn.co/Spotify.dmg" "$SPOTIFY_TEMP_DIR/Spotify.dmg"
    printf 'Spotify.dmg was downloaded and will now open. Drag Spotify into Applications, then return here.\n'
    open "$SPOTIFY_TEMP_DIR/Spotify.dmg"
    printf 'Press Enter after Spotify is present in Applications. ' > /dev/tty
    IFS= read -r _answer < /dev/tty || true
  elif command -v apt-get >/dev/null 2>&1; then
    if ! command -v gpg >/dev/null 2>&1; then
      printf 'The GnuPG package is needed to verify Spotify\047s package repository and will be installed first.\n'
      sudo apt-get update
      sudo apt-get install gnupg
    fi
    SPOTIFY_KEY_FILE=$(mktemp "${TMPDIR:-/tmp}/spotify-key.XXXXXX")
    download "https://download.spotify.com/debian/pubkey_C85668DF69375001.gpg" "$SPOTIFY_KEY_FILE"
    sudo gpg --dearmor --yes -o /etc/apt/trusted.gpg.d/spotify.gpg "$SPOTIFY_KEY_FILE"
    printf '%s\n' "deb https://repository.spotify.com stable non-free" | sudo tee /etc/apt/sources.list.d/spotify.list >/dev/null
    sudo apt-get update
    sudo apt-get install spotify-client
  else
    printf 'Spotify does not publish a universal installer for this Linux distribution. Open https://www.spotify.com/download/linux/, install a non-Snap desktop package, then run this installer again.\n' >&2
    if command -v xdg-open >/dev/null 2>&1; then xdg-open "https://www.spotify.com/download/linux/" >/dev/null 2>&1 || true; fi
    exit 1
  fi

  detect_spotify
  if [ "$SPOTIFY_FOUND" != yes ]; then
    printf 'Spotify installation was not completed or could not be detected. Finish or repair Spotify, then run this installer again.\n' >&2
    exit 1
  fi
  SPOTIFY_WAS_INSTALLED=yes
  printf '\033[32mSpotify was installed successfully.\033[0m\n'
fi

if [ "$OS" = Linux ] && [ -n "$SPOTIFY_ROOT" ] && { [ ! -w "$SPOTIFY_ROOT" ] || [ ! -w "$SPOTIFY_ROOT/Apps" ]; }; then
  printf 'Spicetify needs write access to Spotify\047s application files. The official setup grants that access to local users; it does not run Spotify as root.\n'
  if ! confirm "Grant the required write permission to $SPOTIFY_ROOT?" yes; then
    printf 'Spicetify cannot safely apply without those permissions, so installation has stopped.\n'
    exit 0
  fi
  if ! command -v sudo >/dev/null 2>&1; then
    printf 'sudo is required to update Spotify\047s application permissions.\n' >&2
    exit 1
  fi
  sudo chmod a+wr "$SPOTIFY_ROOT"
  if [ -d "$SPOTIFY_ROOT/Apps" ]; then sudo chmod -R a+wr "$SPOTIFY_ROOT/Apps"; fi
fi

if [ "$SPOTIFY_WAS_INSTALLED" = yes ]; then
  printf '\033[33mBefore Spicetify can customize a new Spotify installation, open Spotify, sign in, and leave it open for at least 60 seconds so it creates its local files.\033[0m\n'
  if ! confirm "Have you completed that first Spotify run?" no; then
    printf 'No further components were changed. Complete Spotify\047s first run, then start this installer again.\n'
    exit 0
  fi
fi

if [ "$OS" = Linux ] && [ "$SPOTIFY_INSTALL_KIND" = flatpak ] && [ ! -f "$SPOTIFY_PREFS_PATH" ]; then
  printf '\033[33mFlatpak Spotify is installed, but its preferences file does not exist yet. Open Spotify, sign in, and leave it open for at least 60 seconds, then run this installer again.\033[0m\n'
  exit 0
fi

section "2. Spicetify" "Spicetify is the open-source customization layer that lets Spotify load Icy Lyrics. It modifies your local Spotify client and can be removed later with 'spicetify restore'. Icy Lyrics requires Spicetify 2.44.0 or newer."
SPICETIFY=$(find_spicetify 2>/dev/null || true)
if [ -n "$SPICETIFY" ]; then
  SPICETIFY_VERSION=$(get_spicetify_version "$SPICETIFY" || true)
  if [ -z "$SPICETIFY_VERSION" ]; then
    printf 'Spicetify was found, but its version could not be read. Repair it, then run this installer again.\n' >&2
    exit 1
  fi
  if ! version_at_least "$SPICETIFY_VERSION" "$MINIMUM_SPICETIFY_VERSION"; then
    printf '\033[33mSpicetify %s is installed, but %s or newer is required.\033[0m\n' "$SPICETIFY_VERSION" "$MINIMUM_SPICETIFY_VERSION"
    if ! confirm "Update Spicetify now?" yes; then
      printf 'A compatible Spicetify version is required, so installation has stopped.\n'
      exit 0
    fi
    printf 'Spicetify\047s updater may reapply customizations and restart Spotify.\n'
    if ! "$SPICETIFY" update; then
      if [ "$OS" = Darwin ] && command -v brew >/dev/null 2>&1; then
        brew upgrade spicetify-cli
      else
        printf 'Spicetify could not update itself. Update it with its original package manager, then run this installer again.\n' >&2
        exit 1
      fi
    fi
    SPICETIFY=$(find_spicetify 2>/dev/null || true)
    SPICETIFY_VERSION=$(get_spicetify_version "$SPICETIFY" || true)
    if [ -z "$SPICETIFY_VERSION" ] || ! version_at_least "$SPICETIFY_VERSION" "$MINIMUM_SPICETIFY_VERSION"; then
      printf 'Spicetify is still incompatible after updating. Open a new terminal and run this installer again.\n' >&2
      exit 1
    fi
  fi
  printf '\033[32mSpicetify %s is installed.\033[0m\n' "$SPICETIFY_VERSION"
else
  printf 'Spicetify was not found.\n'
  if ! confirm "Install Spicetify for your user account?" yes; then
    printf 'Spicetify is required, so the Icy Lyrics installation has been stopped.\n'
    exit 0
  fi
  if [ "$OS" = Darwin ] && command -v brew >/dev/null 2>&1; then
    brew install spicetify-cli
  else
    printf 'The official Spicetify install script will be used. It may separately offer the optional Spicetify Marketplace.\n'
    SPICETIFY_INSTALLER=$(mktemp "${TMPDIR:-/tmp}/install-spicetify.XXXXXX")
    download "https://raw.githubusercontent.com/spicetify/cli/main/install.sh" "$SPICETIFY_INSTALLER"
    sh "$SPICETIFY_INSTALLER"
  fi
  SPICETIFY=$(find_spicetify 2>/dev/null || true)
  if [ -z "$SPICETIFY" ]; then
    printf 'Spicetify installed, but its executable could not be found. Open a new terminal and run this installer again.\n' >&2
    exit 1
  fi
  SPICETIFY_VERSION=$(get_spicetify_version "$SPICETIFY" || true)
  if [ -z "$SPICETIFY_VERSION" ] || ! version_at_least "$SPICETIFY_VERSION" "$MINIMUM_SPICETIFY_VERSION"; then
    printf 'Spicetify %s or newer is required, but the installed version is incompatible.\n' "$MINIMUM_SPICETIFY_VERSION" >&2
    exit 1
  fi
  printf '\033[32mSpicetify %s was installed successfully.\033[0m\n' "$SPICETIFY_VERSION"
fi

if [ "$OS" = Darwin ]; then
  "$SPICETIFY" config spotify_path "/Applications/Spotify.app/Contents/Resources" >/dev/null || {
    printf 'Spicetify could not configure the detected macOS Spotify installation.\n' >&2
    exit 1
  }
elif [ -n "$SPOTIFY_ROOT" ]; then
  "$SPICETIFY" config spotify_path "$SPOTIFY_ROOT" >/dev/null || {
    printf 'Spicetify could not configure the detected Spotify installation.\n' >&2
    exit 1
  }
  if [ "$SPOTIFY_INSTALL_KIND" = flatpak ]; then
    "$SPICETIFY" config prefs_path "$SPOTIFY_PREFS_PATH" >/dev/null || {
      printf 'Spicetify could not configure Flatpak Spotify preferences.\n' >&2
      exit 1
    }
  fi
fi

section "3. Icy Lyrics" "Icy Lyrics is the synchronized-lyrics extension itself. It adds animated lyrics, fullscreen views, persistent local TTML, and the Lyric Creator. The installed bootstrap checks jackscurrie.com for a newer verified runtime when Spotify starts."
MANIFEST_FILE=$(mktemp "${TMPDIR:-/tmp}/icy-manifest.XXXXXX")
download "$MANIFEST_URL" "$MANIFEST_FILE"
MANIFEST_JSON=$(tr -d '[:space:]' < "$MANIFEST_FILE")
SCHEMA_VERSION=$(printf '%s\n' "$MANIFEST_JSON" | sed -n 's/^{"schemaVersion":\([0-9][0-9]*\),.*$/\1/p')
VERSION=$(printf '%s\n' "$MANIFEST_JSON" | sed -n 's/^.*,"version":"\([^"]*\)",.*$/\1/p')
RUNTIME_URL=$(printf '%s\n' "$MANIFEST_JSON" | sed -n 's/^.*,"runtimeUrl":"\([^"]*\)",.*$/\1/p')
EXPECTED_HASH=$(printf '%s\n' "$MANIFEST_JSON" | sed -n 's/^.*,"sha256":"\([a-fA-F0-9]*\)","size".*$/\1/p' | tr 'A-F' 'a-f')
SIZE=$(printf '%s\n' "$MANIFEST_JSON" | sed -n 's/^.*,"size":\([0-9][0-9]*\)}$/\1/p')
EXPECTED_MANIFEST=$(printf '{"schemaVersion":%s,"version":"%s","runtimeUrl":"%s","sha256":"%s","size":%s}' "$SCHEMA_VERSION" "$VERSION" "$RUNTIME_URL" "$EXPECTED_HASH" "$SIZE")
if [ "$MANIFEST_JSON" != "$EXPECTED_MANIFEST" ] || [ "$SCHEMA_VERSION" != 1 ] || [ -z "$SIZE" ] || [ "$SIZE" -le 0 ]; then
  printf 'The Icy Lyrics release manifest schema or runtime URL is invalid. Nothing was changed.\n' >&2
  exit 1
fi
if ! valid_semver "$VERSION"; then
  printf 'The Icy Lyrics release version is not valid semantic versioning. Nothing was changed.\n' >&2
  exit 1
fi
if [ "$RUNTIME_URL" != "https://jackscurrie.com/downloads/icy-lyrics-$VERSION.js" ]; then
  printf 'The Icy Lyrics release URL does not match its versioned runtime. Nothing was changed.\n' >&2
  exit 1
fi
case "$EXPECTED_HASH" in ""|*[!a-f0-9]*) printf 'The Icy Lyrics release checksum is invalid. Nothing was changed.\n' >&2; exit 1 ;; esac
if [ "${#EXPECTED_HASH}" -ne 64 ]; then
  printf 'The Icy Lyrics release checksum is invalid. Nothing was changed.\n' >&2
  exit 1
fi

if ! USER_DATA_OUTPUT=$("$SPICETIFY" path userdata); then
  printf 'Spicetify could not report its user-data folder.\n' >&2
  exit 1
fi
USER_DATA=$(printf '%s\n' "$USER_DATA_OUTPUT" | tail -n 1 | tr -d '\r')
if [ -z "$USER_DATA" ]; then
  printf 'Spicetify did not report its user-data folder.\n' >&2
  exit 1
fi
EXTENSIONS_DIRECTORY="$USER_DATA/Extensions"
EXTENSION_PATH="$EXTENSIONS_DIRECTORY/icy-lyrics.js"
INSTALLED_HASH=""
[ -f "$EXTENSION_PATH" ] && INSTALLED_HASH=$(sha256_file "$EXTENSION_PATH")

if [ "$INSTALLED_HASH" = "$EXPECTED_HASH" ]; then
  printf '\033[32mIcy Lyrics %s is already installed and matches the website release.\033[0m\n' "$VERSION"
else
  if [ -f "$EXTENSION_PATH" ]; then
    printf 'A different Icy Lyrics build is installed. It will be preserved as icy-lyrics.js.backup before replacement.\n'
  else
    printf 'Icy Lyrics is not installed.\n'
  fi
  if ! confirm "Download and install Icy Lyrics $VERSION from jackscurrie.com?" yes; then
    printf 'Icy Lyrics was not changed.\n'
    exit 0
  fi

  mkdir -p "$EXTENSIONS_DIRECTORY"
  TEMP_EXTENSION=$(mktemp "$EXTENSIONS_DIRECTORY/.icy-lyrics.XXXXXX")
  download "$RUNTIME_URL" "$TEMP_EXTENSION"
  DOWNLOADED_HASH=$(sha256_file "$TEMP_EXTENSION")
  if [ "$DOWNLOADED_HASH" != "$EXPECTED_HASH" ]; then
    printf 'The downloaded Icy Lyrics file did not match the website checksum. The installed file was left untouched.\n' >&2
    exit 1
  fi
  if [ "$(wc -c < "$TEMP_EXTENSION" | tr -d ' ')" -ne "$SIZE" ]; then
    printf 'The downloaded Icy Lyrics file did not match the website size. The installed file was left untouched.\n' >&2
    exit 1
  fi
  if [ -f "$EXTENSION_PATH" ]; then
    TEMP_BACKUP=$(mktemp "$EXTENSIONS_DIRECTORY/.icy-lyrics-backup.XXXXXX")
    cp -p "$EXTENSION_PATH" "$TEMP_BACKUP"
    mv -f "$TEMP_BACKUP" "$EXTENSION_PATH.backup"
    TEMP_BACKUP=""
  fi
  mv -f "$TEMP_EXTENSION" "$EXTENSION_PATH"
  TEMP_EXTENSION=""
  printf '\033[32mIcy Lyrics %s was installed.\033[0m\n' "$VERSION"
fi

if ! CONFIGURED_EXTENSIONS=$("$SPICETIFY" config extensions); then
  printf 'Spicetify could not read its configured extensions.\n' >&2
  exit 1
fi
CONFIGURED_EXTENSIONS=$(printf '%s' "$CONFIGURED_EXTENSIONS" | tr -d '\r\n')
case "|$CONFIGURED_EXTENSIONS|" in
  *"|icy-lyrics.js|"*) ;;
  *) "$SPICETIFY" config extensions icy-lyrics.js ;;
esac

printf '\n\033[33mSpicetify now needs to apply the extension. Spotify will close and restart, so save any Lyric Creator draft first.\033[0m\n'
if confirm "Apply Icy Lyrics and restart Spotify now?" yes; then
  if ! "$SPICETIFY" backup apply; then
    printf 'Spicetify could not apply Icy Lyrics.\n' >&2
    exit 1
  fi
  printf '\033[32mIcy Lyrics is ready. Open its microphone button in Spotify to begin.\033[0m\n'
else
  printf 'Installation is complete but not applied. When ready, run: spicetify backup apply\n'
fi
