#!/usr/bin/env bash

source "$(dirname "$0")/vars"

cleanup() {
    [ -d "$tempDir" ] && rm -rf "$tempDir"
}

makeTempDir() {
    cleanup
    mkdir "$tempDir"
}

colorize() {
    local red="\033[31m"
    local green="\033[32m"
    local blue="\033[34m"
    local yellow="\033[33m"
    local white="\033[37m"
    local gray="\e[90m"
    local clear="\033[0m"
    local bold="\033[1m"

    local color="$1"; shift
    local style=""

    if [[ "$1" == "bold" ]]; then
        style="${!1}"
        shift
    fi

    echo -ne "${!color}${style}$*${clear}"
}

printDone() {
    echo -e "$(colorize green bold "$ICO_DONE $1")"
}

printError() {
    echo -e "$(colorize red bold "$ICO_ERR $1")"
}

printImportant() {
    echo -e "$(colorize yellow bold "$ICO_WARN $1") $2"
}

printQuestion() {
    echo -en "$(colorize yellow bold "$ICO_QUESTION $1") [y/N]: "
}

printExec() {
    echo -e "$(colorize blue bold "$ICO_EXEC $MNG_EXEC") $(colorize white $1) \n"
}

printMsg() {
    local text="$1"
    local padding=$(( (48 - ${#text} - 2) / 2 ))
    local l=$(printf ":%.0s" {1..48})
    local s=$(printf ":%.0s" $(seq 1 $padding))
    local p=${s}$( (( ${#text} % 2 )) && echo ":" )

    echo
    echo -e "$(colorize green bold $l)"
    echo -e "$(colorize green bold $s) $(colorize white bold $text) $(colorize green bold $p)"
    echo -e "$(colorize green bold $l)"
}

printClose() {
    tput civis
    printMsg "$UPGRADE_ENTER"
    read -r
    tput cnorm
    [[ $(basename "$terminal") = "yakuake" ]] && qdbusCMD sessions removeSession "$(qdbusCMD sessions activeSessionId)"
}

printWhile() {
    tput sc
    pid=$1
    spinner $pid "$2"
    wait $pid
    exitCode=$?
    tput rc
    tput ed

    [[ $exitCode -eq 0 ]] && printDone "$2" || printError "$2"

    if [[ "$2" = "$WIDGETS_FETCH" ]]; then
        case $exitCode in
            1) printError "$WIDGETS_ERR_API_FAIL" ;;
            2) printError "$WIDGETS_ERR_API" ;;
            3) printError "$WIDGETS_ERR_UNKNOWN" ;;
        esac
    elif [[ "$2" = "$WIDGETS_LINK" ]]; then
        case $exitCode in
            1) printError "$WIDGETS_ERR_NOFILES. $UPGRADE_SKIPPED" ;;
            2) printError "$WIDGETS_ERR_NOTAGGED $latestVer. $UPGRADE_SKIPPED" ;;
            3) printError "$WIDGETS_ERR_TAGGED $latestVer. $UPGRADE_SKIPPED" ;;
        esac
    elif [[ "$2" = "$WIDGETS_DOWNLOADING" ]]; then
        case $exitCode in
            1) printError "$WIDGETS_ERR_PACKAGE_FAIL" ;;
            2) printError "$WIDGETS_ERR_EXT" ;;
            3) printError "$WIDGETS_ERR_NO_JSON" ;;
            4) printError "$WIDGETS_ERR_JSON" ;;
        esac
    fi
}

qdbusCMD() {
    qdbus6 org.kde.yakuake /yakuake/$1 "${@:2}"
}

dependencies() {
    for cmd in ${1}; do
        if ! command -v "$cmd" >/dev/null; then
            printError "${CMD_ERR} ${cmd}"
            [ $2 ] && returnMenu || exit
        fi
    done
}

oneLine() {
    tr '\n' ' ' | sed 's/ $//'
}

spinner() {
    local spin="⠋⠙⠹⠸⠼⠴⠦⠧⠇⠏"
    while kill -0 $1 2>/dev/null; do
        i=$(( (i+1) %10 ))
        printf "\r$(colorize red ${spin:$i:1}) $(colorize blue bold "$2...")"
        sleep .2
    done
}

clearVer() {
    local ver="${1}"
    ver="${ver//[^0-9.]/.}"
    while [[ "$ver" == *".."* ]]; do
        ver="${ver//../.}"
    done
    ver="${ver#.}"
    ver="${ver%.}"
    echo "${ver}"
}

compareVer() {
    [[ $1 == $2 ]] && return 0
    local IFS=.; local i ver1=($1) ver2=($2)
    for ((i=${#ver1[@]}; i<${#ver2[@]}; i++)); do ver1[i]=0; done
    for ((i=0; i<${#ver1[@]}; i++)); do
        [[ -z ${ver2[i]} ]] && ver2[i]=0
        ((10#${ver1[i]} > 10#${ver2[i]})) && return 1
        ((10#${ver1[i]} < 10#${ver2[i]})) && return 2
    done
    return 0
}

getId() {
    [[ $1 =~ ^[0-9]+$ ]] && f=2 || f=1
    grep "$1" "$(dirname "$0")/widgets-id" | cut -d' ' -f$f
}

restartPlasmashell() {
    if [[ "$restartShell" = true ]]; then
        sleep 1
        while true; do
            printQuestion "$WIDGETS_RESTART"; read -r answer
            case "$answer" in
                    [Yy]*) break;;
                 [Nn]*|"") exit;;
                        *)  ;;
            esac
        done
        eval ${restartCommand}
    else
        printImportant "$WIDGETS_WARN"
    fi
}

combineFiles() {
    sleep 1
    [ -f "$1" ] && rm "$1"
    for cache in $(find "$cacheDir" -name "updates.json_*" 2>/dev/null | sort); do
        cat "$cache" >> "$1"; rm "$cache"
    done
}

getIcons() {
    while read -r pkg file; do
        [[ "$processed" == *"$pkg"* ]] && continue
        icon=$(awk -F= '/^Icon=/ {print $2; exit}' "$file" 2>/dev/null || true) && [ -n "$icon" ] || continue
        processed="$processed $pkg"
        echo "$pkg $icon"
    done < <(pacman -Ql "$@" | grep '/usr/share/applications/.*\.desktop$')
}

updateJson() {
    cat <<< "$2" > "$1.tmp" && mv "$1.tmp" "$1"
}

rss() {
    command -v jq >/dev/null || { echo "${CMD_ERR} jq" >&2; return 127; }

    # Wait for DNS to be ready (up to 30s) — avoids failures at boot/resume
    local host=$(echo "$1" | sed -e 's|^[^/]*//||' -e 's|/.*$||')
    for i in $(seq 1 30); do
        getent hosts "$host" >/dev/null 2>&1 && break
        sleep 1
    done

    makeTempDir
    trap cleanup EXIT

    local newsFile="$cacheDir/news.json"
    local errors=""

    if [[ ! -s "$newsFile" ]] \
    || ! jq -e '.' "$newsFile" >/dev/null 2>&1 \
    || ! jq -e 'all(.[]; .timestamp | numbers)' "$newsFile" >/dev/null 2>&1; then
        echo '[]' > "$newsFile"
    fi

    # Build per-URL output file list for parallel download
    local -a urls=("$@")
    local -a rssFiles=()
    local -a curlArgs=()
    for url in "${urls[@]}"; do
        local rssFile=$(mktemp "$tempDir/XXXXXX.xml")
        rssFiles+=("$rssFile")
        curlArgs+=(-o "$rssFile" --url "$url")
    done

    # Fetch all feeds simultaneously
    curl -s --parallel --parallel-immediate \
        --connect-timeout 5 --retry 3 --retry-delay 1 --retry-all-errors --max-time 15 \
        "${curlArgs[@]}" 2>/dev/null

    # Process each downloaded file
    for i in "${!urls[@]}"; do
        local url="${urls[$i]}"
        local rssFile="${rssFiles[$i]}"

        # Strip default XML namespace to avoid XPath 1.0 namespace issues
        local cleanFile=$(mktemp "$tempDir/XXXXXX.xml")
        sed 's/ xmlns="[^"]*"//g' "$rssFile" > "$cleanFile"

        xmllint --noout "$cleanFile" 2>/dev/null || {
            errors+="$url "
            continue
        }

        # Detect RSS 2.0 vs Atom 1.0 by root element name
        local root=$(xmllint --xpath "name(/*)" "$cleanFile" 2>/dev/null)

        # Count available items in feed (capped at feedsFetchCount)
        local count title
        if [[ "$root" == "feed" ]]; then
            count=$(xmllint --xpath "count(/feed/entry)" "$cleanFile" 2>/dev/null || echo 0)
            title=$(xmllint --xpath "string(/feed/title)" "$cleanFile")
        else
            count=$(xmllint --xpath "count(/rss/channel/item)" "$cleanFile" 2>/dev/null || echo 0)
            title=$(xmllint --xpath "string(/rss/channel/title)" "$cleanFile")
        fi

        [[ -z "$title" ]] && { errors+="$url "; continue; }

        local max=${feedsFetchCount:-3}
        (( count > max )) && count=$max

        local n
        for (( n=1; n<=count; n++ )); do
            local article timestamp link
            if [[ "$root" == "feed" ]]; then
                article=$(xmllint --xpath "string(/feed/entry[$n]/title)" "$cleanFile")
                timestamp=$(date -d "$(xmllint --xpath "string(/feed/entry[$n]/updated)" "$cleanFile")" +%s 2>/dev/null)
                link=$(xmllint --xpath "string(/feed/entry[$n]/link/@href)" "$cleanFile")
            else
                article=$(xmllint --xpath "string(/rss/channel/item[$n]/title)" "$cleanFile")
                timestamp=$(date -d "$(xmllint --xpath "string(/rss/channel/item[$n]/pubDate)" "$cleanFile")" +%s 2>/dev/null)
                link=$(xmllint --xpath "string(/rss/channel/item[$n]/link)" "$cleanFile")
            fi

            [[ -z "$article" || -z "$timestamp" || -z "$link" ]] && continue

            if ! jq -e --arg l "$link" '.[] | select(.link == $l)' "$newsFile" > /dev/null; then
                local item=$(jq -n --arg t "$title" --arg a "$article" --arg l "$link" --argjson ts "$timestamp" \
                    '{title: $t, article: $a, timestamp: $ts, link: $l, removed: false}')
                updateJson "$newsFile" "$(jq --argjson item "$item" '[$item] + .' "$newsFile")"
            fi
        done

        # Prune oldest entries for this feed down to feedsFetchCount
        while (( $(jq --arg t "$title" '[.[] | select(.title == $t)] | length' "$newsFile") > max )); do
            index=$(jq --arg t "$title" 'to_entries | reverse | map(select(.value.title == $t)) | .[0].key' "$newsFile")
            updateJson "$newsFile" "$(jq --argjson index "$index" 'del(.[$index])' "$newsFile")"
        done
    done

    updateJson "$newsFile" "$(jq 'sort_by(.timestamp) | reverse' "$newsFile")"

    jq . $newsFile

    if [[ -n "$errors" ]]; then
        errors="$NEWS_ERR: ${errors// /, }"
        errors="${errors%, }"
        echo "$errors" >&2
        exit 1
    fi
}

sync_dbs() {
    ! type -p fakeroot >/dev/null && {
        echo "fakeroot not found" >&2
        exit 127
    }

    ! mkdir -p "$dbPath" && {
        echo "Failed to create directory $dbPath" >&2
        exit 1
    }

    db_conf="$(pacman-conf DBPath)"
    db_conf="${db_conf%/}"
    ! ln -sf "${db_conf}/local" "$dbPath" &> /dev/null && {
        echo "Failed to create symlink to $db_conf/local" >&2
        exit 1
    }

    test -f "$dbPath/db.lck" && rm -f "$dbPath/db.lck"

    ! fakeroot -- pacman -Sy --disable-sandbox-filesystem --dbpath "$dbPath" --logfile /dev/null 2>/dev/null && {
        echo "Failed to synchronize pacman databases" >&2
        exit 1
    }
}

checkCurrentVersions() {
    local list

    if [ "$arch" = true ]; then
        list+=$(pacman -Q | awk '{print "{\"NM\": \"" $1 "\", \"VO\": \"" $2 "\"},"}')
    fi

    if [ "$flatpak" = true ]; then
        list+=$(flatpak list --app --columns=name,version,active | awk -F'\t' '{print "{\"NM\": \"" $1 "\", \"VO\": \"" $2 $3 "\"},"}')
    fi

    if [ "$widgets" = true ]; then
        list+=$("$scriptDir"/widgets list)
    fi

    if [ "$fwupd" = true ]; then
        if command -v jq >/dev/null 2>&1; then
            list+=$(fwupdmgr get-devices --json | jq -cr '.Devices | map({NM: ((.Vendor + " " + .Name)), VO: (.Version // "")}) | .[] | @json + ","')
        fi
    fi

    echo "[${list%,}]"
}

checkSetup() {
    if (( systemWide )); then
        printError "Operation not permitted - the widget is installed system-wide"
        printClose
        exit
    fi
}

install_devel() {
    checkSetup

    printMsg "Downloading devel"
    echo

    required="git jq"
    for cmd in ${required}; do command -v "$cmd" >/dev/null || { printError "Required installed ${cmd}"; printClose; exit; }; done;

    commit=$(curl -s https://api.github.com/repos/exequtic/apdatifier/commits/main | jq -r '.sha' | cut -c1-7)

    if [[ "github commit: $commit" = "$(jq -r '.KPlugin.Description' "$appletDir/metadata.json")" ]]; then
        printDone "Up to date"
        printClose
        exit
    fi

    savedir=$(pwd)
    clonedir=$(mktemp -d)
    cd "$clonedir"

    { git clone -n --depth=10 --filter=tree:0 -b main https://github.com/exequtic/apdatifier 2>/dev/null
    } & printWhile $! "Clone main branch"
    [[ $exitCode -ne 0 ]] && { printClose; exit; }

    cd apdatifier

    { git sparse-checkout set --no-cone package 2>/dev/null && git checkout 2>/dev/null
    } & printWhile $! "Checkout package directory"
    [[ $exitCode -ne 0 ]] && { printClose; exit; }

    if command -v less &>/dev/null; then
        echo
        printImportant "Last 10 commits:"
        git log --oneline
        sleep 2
    fi

    echo
    printMsg "Upgrading Apdatifier"
    echo
    cd package || exit 1
    updateJson metadata.json "$(jq --arg new_value "github commit: $commit" '.KPlugin.Description = $new_value' metadata.json)"
    [[ $trayEnabledByDefault == "true" ]] && updateJson $metadata "$(jq '.KPlugin.EnabledByDefault = true' $metadata)"
    while true; do
        printQuestion "Do you want build translations?"; read -r answer
        case "$answer" in
               [Yy]*) cd translate; bash build; cd ..; break;;
            [Nn]*|"") break;;
                   *)  ;;
        esac
    done
    echo
    tar --exclude=./apdatifier.tar -cf apdatifier.tar .
    kpackagetool6 -t Plasma/Applet -u apdatifier.tar 2>/dev/null
    echo

    cd "$savedir"
    [ ! -d "$clonedir" ] || rm -rf "$clonedir"

    restartShell=true
    restartPlasmashell

    printClose
    exit
}

install_stable() {
    checkSetup

    if [[ "$(jq -r '.KPlugin.Description' "$appletDir/metadata.json")" = "Arch Update Notifier" ]]; then
        while true; do
            printImportant "It looks like you already have the stable version."
            printQuestion "Do you still want to proceed?"; read -r answer
            case "$answer" in
                   [Yy]*) break;;
                [Nn]*|"") printClose; exit;;
                       *)  ;;
            esac
        done
    fi

    $scriptDir/upgrade widget "2135796" "apdatifier"
}

uninstall() {
    checkSetup

    iconsDir="$localDir/icons/breeze/status/22"
    notifDir="$localDir/knotifications6"
    notif="apdatifier.notifyrc"
    icons=(apdatifier-plasmoid.svg
           apdatifier-packages.svg
           apdatifier-package.svg
           apdatifier-flatpak.svg)

    printMsg "Uninstall Apdatifier"
    echo

    while true; do
        printQuestion "Continue?"; read -r answer
        case "$answer" in
               [Yy]*) break;;
            [Nn]*|"") printClose; exit;;
                   *)  ;;
        esac
    done

    echo

    showCommand() {
        echo -e "$(colorize yellow bold "$ICO_EXEC Command:") $1"
    }

    printImportant "Remove icons"
    for icon in "${icons[@]}"; do
        showCommand "rm $iconsDir/$icon"
        test -f "$iconsDir/$icon" && rm "$iconsDir/$icon"
    done
    showCommand "rmdir -p --ignore-fail-on-non-empty $iconsDir"
    test -d "$iconsDir" && rmdir -p --ignore-fail-on-non-empty "$iconsDir"
    sleep 1; echo

    printImportant "Remove notification"
    showCommand "rm $notifDir/$notif"
    test -f "$notifDir/$notif" && rm "$notifDir/$notif"
    showCommand "rmdir -p --ignore-fail-on-non-empty $notifDir"
    test -d "$notifDir" && rmdir -p --ignore-fail-on-non-empty "$notifDir"
    sleep 1; echo

    printImportant "Remove configuration"
    showCommand "rm -rf $configDir"
    test -d "$configDir" && rm -rf "$configDir"
    sleep 1; echo

    printImportant "Uninstall plasmoid"
    showCommand "kpackagetool6 -t Plasma/Applet -r com.github.exequtic.apdatifier"
    kpackagetool6 -t Plasma/Applet -r com.github.exequtic.apdatifier 2>/dev/null
    sleep 1; echo

    echo "Bye :("; sleep 1

    printClose
    exit
}

case "$1" in
          "syncdb") sync_dbs ;;
             "rss") shift; rss "$@" ;;
    "combineFiles") shift; combineFiles $1 ;;
 "currentVersions") checkCurrentVersions ;;
        "getIcons") shift; getIcons "$@" ;;
      "installDev") install_devel ;;
   "installStable") install_stable ;;
       "uninstall") uninstall ;;
esac
