#!/bin/sh
# NOT POSIX, AT ALL. TODO: Make this not suck. Create a bash fork that does not have any of the BASH exclusive "features" and is 100% POSIX and ISO C.
# UPDATE: POSIX syntax. Still relies on undefined behaviour/BASHisms/features the GNU might have accidentally implemented in their attempt to rework Thompson Shell.
# If you install "Gum" (https://github.com/charmbracelet/gum) you will get some TUI fanciness.
# UPDATE: Dismiss the TODO; Try: "https://github.com/xplshn/bigdl", which you can statically link, and no longer depend on anything but a Linux system.

INAME="$0"
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
NC='\033[0m' # No Color

# Directories, files, etc. NOTE: Not consistent. TODO: Enhance readability, simplify variables.
INSTALL_DIR="${HOME}/.local/bin" # You may change this to whatever dir you want, make sure to add it to your PATH.
TEMP_DIR="${TMPDIR:-/tmp}/bdl_cached"
BOOTSTRAP_DIR="$HOME/.cache/bdl_bootstraped"
CACHE_FILE="${TMPDIR:-${TEMP_DIR}}/bdl_install-less_bins_cached"
METADATA_URL="https://raw.githubusercontent.com/metis-os/hysp-pkgs/main/data/metadata.json"
SYSTEM_ARCH="$(uname -m)"
if [ -z "$BASH_VERSION" ]; then
    xPATH="$PATH"
    PATH="$PATH:$BOOTSTRAP_DIR"
    if command -v bash >/dev/null 2>&1; then
        exec bash "$0" "$@"
    else
        echo "Running under SH, some features might not work(run), unless bash is installed or bootstrapped, there will be undefined behaviour. If the command you want to run fails try one more time. It might be related to this."
    fi
    PATH="$xPATH"
fi

spinner() {
    if [ -z "$COOLSPINNER" ]; then
        COOLSPINNER='|/-\\'
    fi
    if [ -z "$COOLSPINNER_COLOR" ]; then
        COOLSPINNER_COLOR='\033[0m'
    fi
    if [ -z "$COOLSPINNER_DELAY" ]; then
        COOLSPINNER_DELAY=0.1
    fi
    len=$(printf "%s" "$COOLSPINNER" | wc -c | awk '{print $1}')
    trap 'printf "\033[?25h"; exit' INT
    while true; do
        i=1
        while [ "$i" -le "$len" ]; do
            char=$(printf "%s" "$COOLSPINNER" | cut -c "$i")
            if [ -n "$COOLSPINNER_COLOR" ]; then
                printf "%b%s%b" "$COOLSPINNER_COLOR" "$char" "\033[0m"
            else
                printf "%s" "$char"
            fi
            sleep "$COOLSPINNER_DELAY"
            printf "\r"
            i=$((i + 1))
        done
    done
}

# Function to display colored output
print_color() {
    printf "%b%s%b\\n" "$1" "$2" "$NC"
}

# Define a helper function to determine which fetcher tool (curl or wget) should be used for bootstrapping
determine_fetcher() {
    if command -v wget >/dev/null 2>&1; then
        FETCHER="wget"
    elif command -v curl >/dev/null 2>&1; then
        FETCHER="curl"
    else
        printf "Neither curl nor wget found. Cannot proceed with bootstrapping.\n"
        return 1
    fi
}

fetch_binary() {
    PACKAGE_NAME="$1"
    nINSTALL_DIR="$2"
    DESTINATION="${3:-$nINSTALL_DIR/$PACKAGE_NAME}"
    mkdir -p "$nINSTALL_DIR"
    determine_fetcher
    SOURCE_URL=$(find_url "$PACKAGE_NAME")
    if [ -z "$SOURCE_URL" ]; then
        print_color "$YELLOW" "Source URL not found for '$PACKAGE_NAME'."
        return 1
    fi
    COOLSPINNER_COLOR='\033[92m'
    spinner & spinner_pid=$!
    if [ "$FETCHER" = "curl" ]; then
        if ! "$FETCHER" -o "$DESTINATION" "$SOURCE_URL" >/dev/null 2>&1; then
            kill "$spinner_pid" >/dev/null 2>&1  # Stop the spinner
            printf "\033[2K\r"  # Clear the spinner character
            print_color "$YELLOW" "Failed to fetch $PACKAGE_NAME."
            return 1
        fi
        chmod +x "$DESTINATION" &&
        kill "$spinner_pid" >/dev/null 2>&1  # Stop the spinner
        printf "\033[2K\r"  # Clear the spinner character
        print_color "$GREEN" "Fetched binary successfully."
    else
        if ! "$FETCHER" -O "$DESTINATION" "$SOURCE_URL" >/dev/null 2>&1; then
            kill "$spinner_pid" >/dev/null 2>&1  # Stop the spinner
            printf "\033[2K\r"  # Clear the spinner character
            print_color "$YELLOW" "Failed to fetch $PACKAGE_NAME."
            return 1
        fi
        chmod +x "$DESTINATION" &&
        kill "$spinner_pid" >/dev/null 2>&1  # Stop the spinner
        printf "\033[2K\r"  # Clear the spinner character
        print_color "$GREEN" "Fetched binary successfully."
    fi
}

# Function to retrieve cached file location
return_cached_file() {
    PACKAGE_NAME="$1"

    if [ -f "$CACHE_FILE" ] && grep -q "^$PACKAGE_NAME " "$CACHE_FILE"; then
        CACHED_LOCATION=$(grep -E "^$PACKAGE_NAME " "$CACHE_FILE" | awk '{ print $2 }')
        if [ -n "$CACHED_LOCATION" ]; then
            printf "%s\n" "$CACHED_LOCATION"
            return 0
        fi
    fi

    return 1
}

# Function to clean the cache (limit 40 programs)
clean_cache() {
    if [ -f "$CACHE_FILE" ]; then
        # Remove duplicate entries in the cache
        awk '!seen[$0]++' "$CACHE_FILE" > temp.txt
        mv temp.txt "$CACHE_FILE"

        # Remove non-existent files from the cache
        while read -r line; do
            cached_package=$(echo "$line" | awk '{print $1}')
            cached_file=$(echo "$line" | awk '{print $2}')
            if [ ! -f "$TEMP_DIR/$cached_file" ] || [ "$cached_package" != "$(echo "$cached_file" | cut -d'-' -f 1 | cut -d'_' -f 2-)" ]; then
                sed -i "/$cached_file/d" "$CACHE_FILE"
            fi
        done < "$CACHE_FILE"
    fi
}

# Function to run from cache or fetch if not found
run_from_cache() {
    PACKAGE_NAME="$1"
    CACHED_LOCATION=$(return_cached_file "$PACKAGE_NAME")

    if [ -n "$CACHED_LOCATION" ] && [ -x "$TEMP_DIR/$CACHED_LOCATION" ]; then
        printf "%bRunning '%s' from cache...%b\n" "$GREEN" "$PACKAGE_NAME" "$NC"
        clean_cache
        "$TEMP_DIR/$CACHED_LOCATION" "${@:2}" # Pass additional parameters as flags
        return 0
    else
        NEW_CACHED_LOCATION="bdl_$PACKAGE_NAME-$(date +'%s')"
        if fetch_binary "$PACKAGE_NAME" "$TEMP_DIR" "$TEMP_DIR/$NEW_CACHED_LOCATION"; then
            echo "$PACKAGE_NAME $NEW_CACHED_LOCATION" >> "$CACHE_FILE" # Update cache with the fetched binary
            clean_cache # Clean the cache
            "$TEMP_DIR/$NEW_CACHED_LOCATION" "${@:2}" # Execute the fetched binary
            return 0
        else
            return 1
        fi
    fi
}

install_binary() {
    PACKAGE_NAME="$1"

    # Check if the package name is empty
    if [ -z "$PACKAGE_NAME" ]; then
        print_color "$YELLOW" "No package name provided."
        return
    fi

    # Check if the binary is already installed in the INSTALL_DIR
    if [ -f "$INSTALL_DIR/$PACKAGE_NAME" ]; then
        print_color "$YELLOW" "The requested binary is already at: $INSTALL_DIR/$PACKAGE_NAME."
        if command -v gum >/dev/null 2>&1; then
            gum confirm "Do you want to continue?" || return
        else
            return
        fi
    fi

    # Get the cached location of the package
    CACHED_LOCATION=$(return_cached_file "$PACKAGE_NAME")

    if [ -n "$CACHED_LOCATION" ] && [ -x "$TEMP_DIR/$CACHED_LOCATION" ]; then
        print_color "$GREEN" "Installing '$PACKAGE_NAME' from cache..."
        mv "$TEMP_DIR/$CACHED_LOCATION" "$INSTALL_DIR/$PACKAGE_NAME"
        clean_cache # Remove duplicates and non-existent files from the cache
        return 1
    else
        # Fetch the binary if it's not found in the installation directory or cache
        fetch_binary "$PACKAGE_NAME" "$INSTALL_DIR" && print_color "$GREEN" "OK: $INSTALL_DIR/$PACKAGE_NAME"
        clean_cache # Remove duplicates and non-existent files from the cache
    fi
}

# Function to remove an installed package
remove_package() {
    PACKAGE_NAME="$1"
    PACKAGE_LOCATION="$INSTALL_DIR/$PACKAGE_NAME"

    if [ -f "$PACKAGE_LOCATION" ]; then
        rm "$PACKAGE_LOCATION" && print_color "$GREEN" "Package '$PACKAGE_NAME' removed."
    else
        print_color "$YELLOW" "Package '$PACKAGE_NAME' is not installed."
    fi
}

# Function to retrieve package source URL
find_url() {
    PACKAGE_NAME="$1"

    if [ -n "$NO_METADATA" ]; then
        if [ -n "$BASE" ]; then
            echo "https://bin.ajam.dev/$SYSTEM_ARCH/Baseutils/$PACKAGE_NAME"
        else
            echo "https://bin.ajam.dev/$SYSTEM_ARCH/$PACKAGE_NAME"
        fi
        return
    fi
    SOURCE_URL=$(curl -s "$METADATA_URL" | jq -r --arg pname "$PACKAGE_NAME" --arg sysarch "$SYSTEM_ARCH" \
        '.packages[] | select(.name == $pname and .architecture == $sysarch) | .source')
    # Check if source URL is empty -> Find the URL manually if so. #NOTE:If the firt SOURCE_URL check works, it saves time, that's why we preffer extracting from metadata first and turning to "curl --fail" IFs/checks as a last resort
    if [ -z "$SOURCE_URL" ]; then
        SOURCE_URL="https://bin.ajam.dev/$SYSTEM_ARCH/$PACKAGE_NAME"
        if ! curl --location --output /dev/null --silent --fail "$SOURCE_URL"; then
            SOURCE_URL="https://bin.ajam.dev/$SYSTEM_ARCH/Baseutils/$PACKAGE_NAME"
            if ! curl --location --output /dev/null --silent --fail "$SOURCE_URL"; then
                SOURCE_URL="https://raw.githubusercontent.com/xplshn/Handyscripts/master/$PACKAGE_NAME"
                if ! curl --location --output /dev/null --silent --fail "$SOURCE_URL"; then
                    print_color "$YELLOW" "Source URL not found for '$PACKAGE_NAME'."
                    return 1
                fi
            fi
        fi
    fi
    printf '%s\n' "$SOURCE_URL"
}

# Function to show detailed package information with color
show_package_info() {
    PACKAGE_NAME="$1"

    # Start spinner in the background & capture its PID.
    spinner & spinner_pid=$!
    PACKAGE_INFO=$(curl -s "$METADATA_URL" | jq --arg pname "$PACKAGE_NAME" --arg sysarch "$SYSTEM_ARCH" \
        '.packages[] | select(.name == $pname and .architecture == $sysarch) | {description, name, version, updated, size, sha, source}')
    if [ -z "$PACKAGE_INFO" ]; then
        # Kill the spinner if download fails
        kill "$spinner_pid" >/dev/null 2>&1
        # Clear the line
        printf "\033[2K\r"
        # Print error.
        print_color "$YELLOW" "Info for package '$PACKAGE_NAME' not found in the metadata.json file. Please contribute to $METADATA_URL."
    else
        # Kill the spinner if download succeeds
        kill "$spinner_pid" >/dev/null 2>&1
        # Clear the line
        printf "\033[2K\r"
        # SUCCESS: Print info in JSON.
        print_color "$GREEN" "$PACKAGE_INFO"
    fi
}

fSearch() {
  SEARCH_TERM="$1"

  # Fetch metadata
  METADATA=$(curl -s "$METADATA_URL")

  # Check if metadata retrieval failed
  if [ -z "$METADATA" ]; then
    printf "Failed to fetch package information.\n"
    return 1
  fi

  # Filter packages based on the search term
  SEARCH_RESULTS=$(printf '%s\n' "$METADATA" | jq -r --arg sterm "$SEARCH_TERM" \
    '.packages[] | select(.name + .description | test($sterm; "i")) | "\(.name) - \(.description)"' | sort -u)

  # Check if no matching packages found
  if [ -z "$SEARCH_RESULTS" ]; then
      printf "No matching packages found for '%s'.\n" "$SEARCH_TERM"
      return 1
  elif [ "$(echo "$SEARCH_RESULTS" | wc -l)" -gt 10 ]; then # Maximum results length
      printf "Too many matching packages found for '%s'.\n" "$SEARCH_TERM"
      return 1
  elif ! echo "$SEARCH_RESULTS" | grep -w "$SEARCH_TERM" > /dev/null; then
      printf "No matching packages found for '%s'.\n" "$SEARCH_TERM"
      return 1
  fi

  # Determine the truncation length
  TRUNC_LENGTH=$(( $(tput cols 2>/dev/null || echo "80") - 3 ))

  # Check if the package binary exists in the INSTALL_DIR and print results with installation state indicators
  printf '%s\n' "$SEARCH_RESULTS" | while IFS= read -r line; do # Take out the "grep -w" line if you want ACTUAL fuzzy search
    NAME=$(echo "$line" | awk -F ' - ' '{print $1}')
    DESCRIPTION=$(echo "$line" | awk -F ' - ' '{$1=""; print substr($0,2)}')
    CACHED_LOCATION=$(return_cached_file "$NAME")
    if [ -f "$INSTALL_DIR/$NAME" ]; then
      PREFIX="[i]"
    elif [ -n "$CACHED_LOCATION" ] && [ -x "$TEMP_DIR/$CACHED_LOCATION" ]; then
      PREFIX="[c]"
    else
      PREFIX="[-]"
    fi

    # Calculate available space for description
    AVAILABLE_SPACE=$((TRUNC_LENGTH - ${#PREFIX} - ${#NAME} - 4))  # 4 accounts for space around ' - '

    # Truncate the description if it exceeds the available space
    if [ ${#DESCRIPTION} -gt "$AVAILABLE_SPACE" ]; then
      DESCRIPTION=$(printf '%s' "$DESCRIPTION" | cut -c 1-"$AVAILABLE_SPACE" | sed 's/[ ,]*$//;s/,$//') # Shrink to the maximum line size + Remove trailing spaces & commas.
      DESCRIPTION="${DESCRIPTION}..."
    fi

    printf "%s %s - %s\n" "$PREFIX" "$NAME" "$DESCRIPTION"
  done
}

# Function to list all possible packages
list_packages() {
    spinner & spinner_pid=$!
    packages=$(
        {
            curl -qfsSL "https://bin.ajam.dev/$SYSTEM_ARCH/METADATA.json" && \
            curl -qfsSL "https://bin.ajam.dev/$SYSTEM_ARCH/Baseutils/METADATA.json" && \
            curl -qfsSL "https://api.github.com/repos/xplshn/Handyscripts/contents"
        } | jq -r '.[] | (.Name // .name)' | grep -v "\.7z$\|\.bz2$\|\.json$\|\.gz$\|\.md$\|\.txt$\|\.tar$\|\.zip$\|_dir$" | sort -u)

    kill "$spinner_pid" >/dev/null 2>&1
    printf "\033[2K\r%s\n" "$packages"
}

bootstrap() {
    mkdir -p "$BOOTSTRAP_DIR"
    PATH="$PATH:$BOOTSTRAP_DIR"
    if [ -n "$FETCHER" ]; then
        export NO_METADATA=1
        if ! command -v bash >/dev/null 2>&1; then
            print_color "${YELLOW}" "bash is not available. Bootstrapping..."
            BASE="true" fetch_binary "bash" "$BOOTSTRAP_DIR"
        fi
        if ! command -v curl >/dev/null 2>&1; then
            print_color "${YELLOW}" "curl is not installed. Bootstrapping..."
            fetch_binary curl "$BOOTSTRAP_DIR"
        fi
        if ! command -v jq >/dev/null 2>&1; then
            print_color "${YELLOW}" "jq is not installed. Bootstrapping..."
            fetch_binary "jq" "$BOOTSTRAP_DIR"
        fi
        if ! command -v awk >/dev/null 2>&1; then
            print_color "${YELLOW}" "awk is not installed. Bootstrapping..."
            BASE="true" fetch_binary "awk" "$BOOTSTRAP_DIR"
        fi
        if ! command -v tput >/dev/null 2>&1; then
            print_color "${YELLOW}" "tput is not available (from the netcurses package). Bootstrapping..."
            BASE="true" fetch_binary "tput" "$BOOTSTRAP_DIR"
        fi
        unset NO_METADATA
    fi
}

stubdl() {
    DEST="/tmp/._bdlstub_bigdl.bin"

    ARCH="$(uname -m)"
    if [ "$ARCH" = "x86_64" ]; then
        ARCH_SUFFIX="amd64"
    elif [ "$ARCH" = "aarch64" ]; then
        ARCH_SUFFIX="arm64"
    else
        echo "Unsupported architecture: $ARCH"
        exit 1
    fi

    BIGDL="https://github.com/xplshn/bigdl/releases/latest/download/bigdl_fast_${ARCH_SUFFIX}"

    if [ "$1" = "--install" ]; then
        DEST="$2"
    fi

    if [ -e "$DEST" ] && [ ! "$1" = "--install" ]; then
        "$DEST" "$@"
    else
        mkdir -p "$(dirname "$DEST")"
        if command -v wget >/dev/null 2>&1; then
            wget -q "$BIGDL" -O "$DEST"
        elif command -v curl >/dev/null 2>&1; then
            curl -qfsSL "$BIGDL" -o "$DEST"
        else
            echo "Neither wget nor curl is available."
            exit 1
        fi

        [ -e "$DEST" ] && [ "$1" = "--install" ] && echo "BIGDL IS NOW AVAILABLE. ($DEST)"
        chmod +x "$DEST"
        [ ! "$1" = "--install" ] && "$DEST" "$@"
    fi
}

determine_fetcher && bootstrap && \
case $1 in
    run)
        run_from_cache "$2" "${@:3}"
        ;;
    install)
        install_binary "$2"
        ;;
    remove)
        remove_package "$2"
        ;;
    search)
        fSearch "$2"
        ;;
    info)
        show_package_info "$2"
        ;;
    list)
        list_packages
        ;;
    find_url)
        find_url "$2"
        ;;
    tldr)
        run_from_cache tlrc "${@:2}" # Official TL;DR pages client written in Rust.
        ;;
    bigdl)
        shift
        stubdl "${@}" # Run bigdl (A total rewrite of bdl, which corrects all of its downfalls and implements a new `update` feature. bigdl is designed to be pretty. bigdl releases weight no more than 2 megs)
        ;;
    *)

        if [ "$1" = "what" ] && [ "$2" = "is" ] && [ "$3" = "bigdl?" -o "$3" = "bigdl" ]; then
            run_from_cache glow "https://raw.githubusercontent.com/xplshn/bigdl/master/README.md"
            exit
        fi

        if [ "$1" = "put" ] && [ "$2" = "bigdl" ] && [ "$3" = "at" ] && [ -n "$4" ]; then
            stubdl --install "$4"
            exit
        fi

        printf 'Usage: %s [list|install|remove|run|info|search|tldr|bigdl] [args...]\n' "$INAME"
        printf 'Usage: %s bdl is deprecated, please read about its rewrite/replacement in Go using the following command: ' "$INAME"
        print_color "$YELLOW" "$INAME what is bigdl?"
        printf 'Usage: %s You may also simply try it using the following command: ' "$INAME"
        print_color "$YELLOW" "$INAME bigdl"
        printf 'Usage: %s If you wish to install bigdl, use the following command: ' "$INAME"
        print_color "$YELLOW" "$INAME put bigdl at $HOME/.local/bin/bigdl"
        exit 1
        ;;
esac

exit 0
