#!/usr/bin/bash
# SPDX-FileCopyrightText: 2026-present Setup Tooling Project
# SPDX-License-Identifier: MPL-2.0

set -euo pipefail

APP_NAME="Setup Tool Plugin Manager"

# Set this later, for example:
# PLUGIN_DOMAIN="https://plugins.setup-tooling.example"
PLUGIN_DOMAIN=""

CONFIG_DIR="${XDG_CONFIG_HOME:-$HOME/.config}/setup-tool"
CONFIG_FILE="$CONFIG_DIR/plugin-manager.conf"
INSTALL_DIR="${SETUP_TOOL_PLUGIN_DIR:-$HOME/.local/share/setup-tool/plugins}"
STATE_DIR="${XDG_STATE_HOME:-$HOME/.local/state}/setup-tool/plugin-manager"
INDEX_CACHE="$STATE_DIR/index.tsv"

# Override with either:
#   ST_PLUGIN_INDEX_URL=https://example.test/plugins/index.tsv
# or:
#   ST_PLUGIN_DOMAIN=https://example.test
# The domain form fetches: $ST_PLUGIN_DOMAIN/plugins/index.tsv
DEFAULT_DOMAIN="${PLUGIN_DOMAIN:-https://plugins.example.invalid}"

need_cmd() {
    command -v "$1" >/dev/null 2>&1 || {
        printf '%s\n' "Missing required command: $1" >&2
        exit 1
    }
}

ensure_dirs() {
    mkdir -p "$CONFIG_DIR" "$INSTALL_DIR" "$STATE_DIR"
}

load_config() {
    if [ -f "$CONFIG_FILE" ]; then
        # shellcheck disable=SC1090
        . "$CONFIG_FILE"
    fi
}

index_url() {
    if [ -n "${ST_PLUGIN_INDEX_URL:-}" ]; then
        printf '%s\n' "$ST_PLUGIN_INDEX_URL"
    elif [ -n "${ST_PLUGIN_DOMAIN:-}" ]; then
        printf '%s/plugins/index.tsv\n' "${ST_PLUGIN_DOMAIN%/}"
    elif [ -n "${plugin_index_url:-}" ]; then
        printf '%s\n' "$plugin_index_url"
    elif [ -n "${plugin_domain:-}" ]; then
        printf '%s/plugins/index.tsv\n' "${plugin_domain%/}"
    else
        printf '%s/plugins/index.tsv\n' "$DEFAULT_DOMAIN"
    fi
}

write_default_config() {
    ensure_dirs
    if [ ! -f "$CONFIG_FILE" ]; then
        cat >"$CONFIG_FILE" <<EOF
# Setup Tool Plugin Manager
# Set one of these once the plugin domain is chosen:
# plugin_domain="https://plugins.example.invalid"
# plugin_index_url="https://plugins.example.invalid/plugins/index.tsv"
EOF
    fi
}

msg() {
    whiptail --title "$APP_NAME" --msgbox "$1" 12 72
}

error_box() {
    whiptail --title "$APP_NAME" --msgbox "Error:\n\n$1" 14 72
}

confirm() {
    whiptail --title "$APP_NAME" --yesno "$1" 12 72
}

fetch_index() {
    local url
    url=$(index_url)
    ensure_dirs

    if ! curl -fsSL "$url" -o "$INDEX_CACHE.tmp"; then
        rm -f "$INDEX_CACHE.tmp"
        error_box "Could not fetch plugin index:\n$url\n\nSet ST_PLUGIN_DOMAIN, ST_PLUGIN_INDEX_URL, or edit:\n$CONFIG_FILE"
        return 1
    fi

    mv "$INDEX_CACHE.tmp" "$INDEX_CACHE"
}

ensure_index() {
    if [ ! -s "$INDEX_CACHE" ]; then
        fetch_index
    fi
}

index_rows() {
    ensure_index || return 1
    awk -F '\t' '
        NF >= 6 && $1 !~ /^#/ && $1 != "" {
            print
        }
    ' "$INDEX_CACHE"
}

plugin_field() {
    local wanted_id=$1
    local field=$2
    awk -F '\t' -v id="$wanted_id" -v field="$field" '
        $1 == id { print $field; exit }
    ' "$INDEX_CACHE"
}

plugin_installed() {
    local id=$1
    [ -f "$INSTALL_DIR/$id/setup-tool-plugin.yml" ]
}

installed_version() {
    local id=$1
    local manifest="$INSTALL_DIR/$id/setup-tool-plugin.yml"
    if [ ! -f "$manifest" ]; then
        printf '%s\n' "-"
        return
    fi
    awk -F ':' '
        /^[[:space:]]*version[[:space:]]*:/ {
            sub(/^[[:space:]]+/, "", $2);
            gsub(/^["'\'']|["'\'']$/, "", $2);
            print $2;
            exit;
        }
    ' "$manifest"
}

choose_plugin() {
    local title=$1
    local rows menu_args id name version spec desc installed marker
    rows=$(index_rows) || return 1

    if [ -z "$rows" ]; then
        msg "No plugins were found in the current index."
        return 1
    fi

    menu_args=()
    while IFS=$'\t' read -r id name version spec archive_url desc; do
        (void_archive_url="$archive_url")
        marker=""
        if plugin_installed "$id"; then
            marker="[installed]"
        fi
        menu_args+=("$id" "$name $version $marker - $desc")
    done <<< "$rows"

    whiptail --title "$APP_NAME" --menu "$title" 24 100 14 "${menu_args[@]}" 3>&1 1>&2 2>&3
}

validate_plugin_dir() {
    local dir=$1
    [ -f "$dir/setup-tool-plugin.yml" ] || return 1
}

install_plugin() {
    local id=$1
    local name version spec archive_url desc tmp extract_root source_dir
    name=$(plugin_field "$id" 2)
    version=$(plugin_field "$id" 3)
    spec=$(plugin_field "$id" 4)
    archive_url=$(plugin_field "$id" 5)
    desc=$(plugin_field "$id" 6)

    if [ -z "$archive_url" ]; then
        error_box "Plugin '$id' has no archive URL in the index."
        return 1
    fi

    if plugin_installed "$id"; then
        if ! confirm "'$name' is already installed.\n\nReplace it with version $version?"; then
            return 0
        fi
    else
        if ! confirm "Install '$name'?\n\nVersion: $version\nSpec: $spec\n\n$desc"; then
            return 0
        fi
    fi

    tmp=$(mktemp -d)
    trap 'rm -rf "$tmp"' RETURN
    curl -fL "$archive_url" -o "$tmp/plugin.tar.gz"
    mkdir -p "$tmp/extract"
    tar -xzf "$tmp/plugin.tar.gz" -C "$tmp/extract"

    extract_root="$tmp/extract"
    source_dir=""
    if validate_plugin_dir "$extract_root"; then
        source_dir="$extract_root"
    elif validate_plugin_dir "$extract_root/$id"; then
        source_dir="$extract_root/$id"
    else
        source_dir=$(find "$extract_root" -mindepth 1 -maxdepth 2 -type f -name setup-tool-plugin.yml -print -quit)
        if [ -n "$source_dir" ]; then
            source_dir=$(dirname "$source_dir")
        fi
    fi

    if [ -z "$source_dir" ] || ! validate_plugin_dir "$source_dir"; then
        error_box "Downloaded archive does not contain setup-tool-plugin.yml."
        return 1
    fi

    rm -rf "$INSTALL_DIR/$id.tmp" "$INSTALL_DIR/$id"
    mkdir -p "$INSTALL_DIR/$id.tmp"
    cp -R "$source_dir"/. "$INSTALL_DIR/$id.tmp/"
    mv "$INSTALL_DIR/$id.tmp" "$INSTALL_DIR/$id"
    msg "Installed '$name' to:\n$INSTALL_DIR/$id"
}

remove_plugin() {
    local id=$1
    if ! plugin_installed "$id"; then
        msg "Plugin '$id' is not installed."
        return 0
    fi

    if confirm "Remove installed plugin '$id'?\n\n$INSTALL_DIR/$id"; then
        rm -rf "$INSTALL_DIR/$id"
        msg "Removed '$id'."
    fi
}

install_flow() {
    local id
    id=$(choose_plugin "Choose a plugin to install or update") || return 0
    install_plugin "$id"
}

remove_flow() {
    local entries=()
    local dir id version

    shopt -s nullglob
    for dir in "$INSTALL_DIR"/*; do
        [ -d "$dir" ] || continue
        id=$(basename "$dir")
        version=$(installed_version "$id")
        entries+=("$id" "installed version: $version")
    done
    shopt -u nullglob

    if [ ${#entries[@]} -eq 0 ]; then
        msg "No v1/v2 plugins are installed in:\n$INSTALL_DIR"
        return 0
    fi

    id=$(whiptail --title "$APP_NAME" --menu "Choose a plugin to remove" 22 80 12 "${entries[@]}" 3>&1 1>&2 2>&3) || return 0
    remove_plugin "$id"
}

list_installed() {
    local text="" dir id version spec manifest
    shopt -s nullglob
    for dir in "$INSTALL_DIR"/*; do
        [ -d "$dir" ] || continue
        id=$(basename "$dir")
        manifest="$dir/setup-tool-plugin.yml"
        version=$(installed_version "$id")
        spec="-"
        if [ -f "$manifest" ]; then
            spec=$(awk -F ':' '/^[[:space:]]*spec[[:space:]]*:/ { sub(/^[[:space:]]+/, "", $2); print $2; exit }' "$manifest")
            [ -n "$spec" ] || spec="legacy-script"
        fi
        text="${text}${id}  ${version}  ${spec}\n"
    done
    shopt -u nullglob

    if [ -z "$text" ]; then
        text="No v1/v2 plugins are installed in:\n$INSTALL_DIR"
    fi

    whiptail --title "$APP_NAME" --msgbox "$text" 22 90
}

show_source() {
    msg "Current index:\n$(index_url)\n\nInstall directory:\n$INSTALL_DIR\n\nConfig file:\n$CONFIG_FILE"
}

main_menu() {
    while true; do
        local choice
        choice=$(whiptail --title "$APP_NAME" --menu "Manage Setup Tool v1/v2 plugins" 22 80 10 \
            "install" "Install or update a plugin from the remote index" \
            "remove" "Remove an installed plugin" \
            "installed" "Show installed plugins" \
            "refresh" "Refresh the remote plugin list" \
            "source" "Show configured plugin source" \
            "quit" "Exit" \
            3>&1 1>&2 2>&3) || break

        case "$choice" in
            install) install_flow ;;
            remove) remove_flow ;;
            installed) list_installed ;;
            refresh) fetch_index && msg "Plugin index refreshed." ;;
            source) show_source ;;
            quit) break ;;
        esac
    done
}

usage() {
    cat <<EOF
Usage: st-plugin-manager [OPTION]

Whiptail interface for installing Setup Tool v1/v2 plugins.

Options:
  --help, -h        Show this help
  --refresh         Refresh the remote plugin index
  --install ID      Install plugin ID from the index
  --remove ID       Remove installed plugin ID
  --list-installed  Print installed plugins

Configuration:
  ST_PLUGIN_DOMAIN      Base domain; fetches /plugins/index.tsv
  ST_PLUGIN_INDEX_URL   Full index URL
  SETUP_TOOL_PLUGIN_DIR Install directory override

Default install directory:
  ~/.local/share/setup-tool/plugins/
EOF
}

print_installed() {
    local dir id version
    shopt -s nullglob
    for dir in "$INSTALL_DIR"/*; do
        [ -d "$dir" ] || continue
        id=$(basename "$dir")
        version=$(installed_version "$id")
        printf '%s\t%s\t%s\n' "$id" "$version" "$dir"
    done
    shopt -u nullglob
}

main() {
    need_cmd whiptail
    need_cmd curl
    need_cmd tar
    ensure_dirs
    write_default_config
    load_config

    case "${1:-}" in
        --help|-h)
            usage
            ;;
        --refresh)
            fetch_index
            ;;
        --install)
            ensure_index
            install_plugin "${2:?missing plugin id}"
            ;;
        --remove)
            remove_plugin "${2:?missing plugin id}"
            ;;
        --list-installed)
            print_installed
            ;;
        "")
            main_menu
            ;;
        *)
            printf 'Unknown option: %s\n\n' "$1" >&2
            usage >&2
            exit 1
            ;;
    esac
}

main "$@"
