#!/bin/bash
# ==============================================================================
# karch - common library
# Shared helpers for all modules: dialog wrappers, detection, env management
# ==============================================================================

# --- Paths -------------------------------------------------------------------
# Only set SCRIPT_DIR if not already set by install.sh
if [[ -z "${SCRIPT_DIR:-}" ]]; then
    SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")/.." && pwd)"
fi
ENV_FILE="${ENV_FILE:-/root/karch/karch.env}"

# --- Colors ------------------------------------------------------------------
RED='\033[0;31m'
GREEN='\033[0;32m'
YELLOW='\033[1;33m'
CYAN='\033[0;36m'
NC='\033[0m' # No Color

# --- Logging -----------------------------------------------------------------

msg_info() {
    echo -e "${CYAN}[karch]${NC} $*"
}

msg_ok() {
    echo -e "${GREEN}[karch]${NC} $*"
}

msg_warn() {
    echo -e "${YELLOW}[karch]${NC} $*"
}

msg_error() {
    echo -e "${RED}[karch ERROR]${NC} $*" >&2
}

# --- Dialog wrappers ---------------------------------------------------------
# All wrappers write the user's choice to stdout. The caller captures it with
# result=$(dialog_xxx ...).  dialog itself draws to stderr (the terminal).
#
# If dialog is not installed (common on some live ISOs), all functions
# gracefully degrade to plain terminal read/echo prompts.

DIALOG_BACKTITLE="karch - Arch Linux Installer"

_have_dialog() {
    command -v dialog &>/dev/null
}

dialog_menu() {
    local title="$1" text="$2"
    shift 2
    # remaining args are tag/item pairs

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --menu "$text" 20 70 12 "$@" \
               3>&1 1>&2 2>&3
        return $?
    fi

    # --- Fallback: plain terminal ---
    echo "" >&2
    echo "=== $title ===" >&2
    echo "$text" >&2
    echo "" >&2
    local i=1
    local -a tags=()
    while [[ $# -ge 2 ]]; do
        local tag="$1" desc="$2"
        shift 2
        tags+=("$tag")
        printf "  %d) %s — %s\n" "$i" "$tag" "$desc" >&2
        i=$((i + 1))
    done
    echo "" >&2
    while true; do
        read -rp "Enter choice [1-${#tags[@]}]: " choice </dev/tty
        if [[ "$choice" =~ ^[0-9]+$ ]] && [[ "$choice" -ge 1 && "$choice" -le ${#tags[@]} ]]; then
            echo "${tags[$((choice - 1))]}"
            return 0
        fi
        echo "Invalid choice. Try again." >&2
    done
}

dialog_input() {
    local title="$1" text="$2" default="${3:-}"

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --inputbox "$text" 0 60 "$default" \
               3>&1 1>&2 2>&3
        return $?
    fi

    # --- Fallback ---
    echo "" >&2
    echo "=== $title ===" >&2
    echo "$text" >&2
    local prompt="Enter value"
    [[ -n "$default" ]] && prompt="Enter value [$default]"
    read -rp "$prompt: " value </dev/tty
    [[ -z "$value" && -n "$default" ]] && value="$default"
    echo "$value"
}

dialog_password() {
    local title="$1" text="$2"

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --insecure --passwordbox "$text" 0 60 \
               3>&1 1>&2 2>&3
        return $?
    fi

    # --- Fallback ---
    echo "" >&2
    echo "=== $title ===" >&2
    echo "$text" >&2
    read -rsp "Password: " value </dev/tty
    echo "" >&2
    echo "$value"
}

dialog_yesno() {
    local title="$1" text="$2"

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --yesno "$text" 0 0
        return $?
    fi

    # --- Fallback ---
    echo "" >&2
    echo "=== $title ===" >&2
    echo "$text" >&2
    while true; do
        read -rp "Continue? [y/n]: " yn </dev/tty
        case "$yn" in
            [Yy]*) return 0 ;;
            [Nn]*) return 1 ;;
            *) echo "Please enter y or n." >&2 ;;
        esac
    done
}

dialog_msgbox() {
    local title="$1" text="$2"

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --msgbox "$text" 0 0
        return $?
    fi

    # --- Fallback ---
    echo "" >&2
    echo "=== $title ===" >&2
    echo -e "$text" >&2
    read -rp "Press Enter to continue..." _ </dev/tty
}

dialog_gauge() {
    local title="$1" text="$2"

    if _have_dialog; then
        dialog --backtitle "$DIALOG_BACKTITLE" \
               --title "$title" \
               --gauge "$text" 8 60 0
        return $?
    fi

    # --- Fallback: just show message ---
    echo "=== $title ===" >&2
    echo "$text" >&2
}

# --- Environment file management --------------------------------------------

write_env() {
    local key="$1" value="$2"
    mkdir -p "$(dirname "$ENV_FILE")"

    # Remove existing key (if any) then append new value
    # This avoids sed delimiter conflicts with arbitrary values
    if [[ -f "$ENV_FILE" ]]; then
        grep -v "^${key}=" "$ENV_FILE" > "${ENV_FILE}.tmp" || true
        mv "${ENV_FILE}.tmp" "$ENV_FILE"
    fi
    # Quote the value so space-containing strings (e.g. GPU_VENDORS="nvidia intel")
    # survive sourcing without word-splitting.
    echo "${key}=\"${value}\"" >> "$ENV_FILE"
}

source_env() {
    if [[ -f "$ENV_FILE" ]]; then
        # shellcheck disable=SC1090
        source "$ENV_FILE"
    fi
}

# --- Module progress tracking ------------------------------------------------

mark_module_completed() {
    local module="$1"
    local current=""
    if [[ -f "$ENV_FILE" ]] && grep -q "^COMPLETED_MODULES=" "$ENV_FILE"; then
        current="$(grep "^COMPLETED_MODULES=" "$ENV_FILE" | cut -d= -f2)"
    fi

    if [[ -z "$current" ]]; then
        write_env "COMPLETED_MODULES" "$module"
    else
        write_env "COMPLETED_MODULES" "${current},${module}"
    fi
}

is_module_completed() {
    local module="$1"
    if [[ -f "$ENV_FILE" ]] && grep -q "^COMPLETED_MODULES=" "$ENV_FILE"; then
        local completed
        completed="$(grep "^COMPLETED_MODULES=" "$ENV_FILE" | cut -d= -f2)"
        echo ",$completed," | grep -q ",$module,"
        return $?
    fi
    return 1
}

# --- run_module: retry / skip / abort wrapper --------------------------------

run_module() {
    local name="$1"
    local resume="${2:-false}"

    # Skip if already completed and we're in resume mode
    if [[ "$resume" == "true" ]] && is_module_completed "$name"; then
        msg_info "Skipping already completed module: $name"
        return 0
    fi

    # Source the module file
    local module_file="${SCRIPT_DIR}/modules/${name}.sh"
    if [[ ! -f "$module_file" ]]; then
        msg_error "Module file not found: $module_file"
        return 1
    fi

    # shellcheck disable=SC1090
    source "$module_file"

    # Run the module function in a retry loop
    while true; do
        msg_info "Running module: $name"

        # Execute the module's run function
        local exit_code=0
        "run_${name}" || exit_code=$?

        if [[ "$exit_code" -eq 0 ]]; then
            msg_ok "Module completed: $name"
            mark_module_completed "$name"
            return 0
        else
            msg_error "Module failed: $name (exit code: $exit_code)"

            local choice
            choice=$(dialog_menu "Module Failed: $name" \
                "The module '$name' failed. What would you like to do?" \
                "retry" "Retry this module" \
                "skip"  "Skip and continue" \
                "abort" "Abort installation") || choice="abort"

            case "$choice" in
                retry)
                    msg_info "Retrying module: $name"
                    continue
                    ;;
                skip)
                    msg_warn "Skipping module: $name (will NOT be marked complete)"
                    return 0
                    ;;
                abort)
                    msg_error "Installation aborted by user at module: $name"
                    exit 1
                    ;;
            esac
        fi
    done
}

# --- Hardware detection ------------------------------------------------------

detect_boot_mode() {
    local platform_size
    platform_size="$(cat /sys/firmware/efi/fw_platform_size 2>/dev/null)" || true

    if [[ "$platform_size" == "64" || "$platform_size" == "32" ]]; then
        echo "uefi"
    else
        # Show notice — use wrapper which handles missing dialog gracefully.
        # Write directly to /dev/tty so it doesn't contaminate stdout
        # when this function is captured via $()
        if _have_dialog; then
            dialog --backtitle "$DIALOG_BACKTITLE" \
                   --title "Boot Mode" \
                   --msgbox "UEFI firmware was not detected.\n\nThe installer will proceed in BIOS/Legacy mode.\nIf this is incorrect, please reboot in UEFI mode." 0 0 \
                   2>/dev/tty </dev/tty
        else
            echo "" >/dev/tty
            echo "[karch] UEFI firmware was not detected. Proceeding in BIOS/Legacy mode." >/dev/tty
            echo "[karch] If this is incorrect, please reboot in UEFI mode." >/dev/tty
        fi
        echo "bios"
    fi
}

detect_cpu_vendor() {
    local vendor
    vendor="$(grep -m1 'vendor_id' /proc/cpuinfo | awk '{print $3}')"

    case "$vendor" in
        GenuineIntel) echo "intel-ucode" ;;
        AuthenticAMD) echo "amd-ucode" ;;
        *)            echo "" ;;
    esac
}

detect_intel_wifi() {
    lspci -knnd ::0280 2>/dev/null | grep -qi intel
}

detect_bluetooth() {
    # Kernel exposes Bluetooth controllers in sysfs even without bluez
    ls /sys/class/bluetooth/ 2>/dev/null | grep -q .
}

detect_gpu() {
    # Returns space-separated list of detected GPU vendors: nvidia amd intel
    # Uses lspci class-code filtering with a fallback to broad grep.
    local gpus=""
    local pci_output=""

    if ! command -v lspci &>/dev/null; then
        msg_warn "lspci not found — cannot auto-detect GPU"
        return 0
    fi

    # Primary: filter by PCI class (0300 = VGA, 0302 = 3D controller)
    pci_output="$(lspci -d ::0300 2>/dev/null)"$'\n'"$(lspci -d ::0302 2>/dev/null)"

    # Strip whitespace-only output to test for real content
    local stripped
    stripped="${pci_output//[$'\n\r\t ']}"

    # Fallback: if class-code filter returned nothing, try broad keyword grep
    if [[ -z "$stripped" ]]; then
        msg_info "lspci class-code filter returned nothing — trying keyword fallback..."
        pci_output="$(lspci 2>/dev/null | grep -iE 'vga|3d controller|display' 2>/dev/null)" || true
    fi

    [[ -z "$pci_output" ]] && return 0

    if echo "$pci_output" | grep -qi nvidia; then
        gpus+="nvidia "
    fi
    if echo "$pci_output" | grep -qiE 'amd|radeon|\bati\b'; then
        gpus+="amd "
    fi
    if echo "$pci_output" | grep -qi intel; then
        gpus+="intel "
    fi

    echo "${gpus% }"  # trim trailing space
}

detect_nvidia_generation() {
    # Determines whether an NVIDIA GPU is Turing+ (supported by nvidia-open 590+)
    # or legacy Maxwell/Pascal/Volta (needs nvidia-580xx-dkms from AUR).
    # Returns: "turing-plus", "legacy", or "unknown"
    #
    # Since Dec 2025, the main nvidia/nvidia-open packages (590+) dropped support
    # for Maxwell, Pascal, and Volta GPUs.  Those generations now require the
    # nvidia-580xx-dkms AUR package.
    local nvidia_lines
    nvidia_lines="$(lspci -d 10de::0300 2>/dev/null)"$'\n'"$(lspci -d 10de::0302 2>/dev/null)"

    # Strip whitespace-only content
    local stripped="${nvidia_lines//[$'\n\r\t ']}"
    [[ -z "$stripped" ]] && { echo "unknown"; return; }

    # Turing or newer: chip codes TU/GA/AD/GB, or model names RTX / GTX 16xx
    if echo "$nvidia_lines" | grep -qiE 'TU[0-9]|GA[0-9]|AD[0-9]|GB[0-9]|RTX [2-9]|RTX [0-9]{4}|GTX 1[6-9][0-9]{2}'; then
        echo "turing-plus"
        return
    fi

    # Legacy: Maxwell (GM), Pascal (GP), Volta (GV), or GTX 7xx/9xx/10xx models
    if echo "$nvidia_lines" | grep -qiE 'GM[0-9]|GP[0-9]|GV[0-9]|GTX [789][0-9]{2}|GTX 10[0-9]{2}|Titan X|Titan V'; then
        echo "legacy"
        return
    fi

    echo "unknown"
}

# --- Partition naming helper -------------------------------------------------

part_suffix() {
    local disk="$1"
    # NVMe and MMC devices use 'p' prefix for partition numbers
    if [[ "$disk" == *"nvme"* || "$disk" == *"mmcblk"* ]]; then
        echo "p"
    else
        echo ""
    fi
}
