#!/bin/bash
set -euo pipefail
#  Groundwire Installer for macOS and Linux
# Usage: curl -fsSL https://groundwire.io/install.sh | bash

# ─── Colors ───────────────────────────────────────────────────────────────────
BOLD='\033[1m'
ACCENT='\033[38;2;72;199;142m'
INFO='\033[38;2;136;146;176m'
SUCCESS='\033[38;2;0;229;204m'
WARN='\033[38;2;255;176;32m'
ERROR='\033[38;2;230;57;70m'
MUTED='\033[38;2;90;100;128m'
NC='\033[0m'

# ─── Config ──────────────────────────────────────────────────────────────────
REPO="gwbtc/urbit"
INSTALL_DIR="${URBIT_INSTALL_DIR:-${XDG_DATA_HOME:-$HOME/.local/share}/groundwire-alpha}"
VERBOSE="${URBIT_VERBOSE:-0}"
DRY_RUN="${URBIT_DRY_RUN:-0}"
SKIP_DOWNLOAD="${URBIT_SKIP_DOWNLOAD:-0}"
SKIP_ATTESTATION="${URBIT_SKIP_ATTESTATION:-0}"
DEVELOPER_BUILD="${URBIT_DEVELOPER_BUILD:-0}"
DEFAULT_SNAPSHOT_URL="https://groundwire.nyc3.cdn.digitaloceanspaces.com/snapshot/urb-snapshot.jam"
# Pin a specific tag instead of latest:
PINNED_TAG="${URBIT_VERSION:-}"

GITHUB_API="https://api.github.com"
GITHUB_RELEASES="https://github.com/${REPO}/releases"

# ─── Temp file management ────────────────────────────────────────────────────
TMPFILES=()
cleanup_tmpfiles() {
    local f
    for f in "${TMPFILES[@]:-}"; do
        rm -rf "$f" 2>/dev/null || true
    done
}
trap cleanup_tmpfiles EXIT

mktempfile() {
    local f
    f="$(mktemp)"
    TMPFILES+=("$f")
    echo "$f"
}

mktempdir() {
    local d
    d="$(mktemp -d)"
    TMPFILES+=("$d")
    echo "$d"
}

# ─── UI helpers ──────────────────────────────────────────────────────────────
ui_info()    { echo -e "${MUTED}·${NC} $*"; }
ui_success() { echo -e "${SUCCESS}✓${NC} $*"; }
ui_warn()    { echo -e "${WARN}!${NC} $*"; }
ui_error()   { echo -e "${ERROR}✗${NC} $*"; }

ui_section() {
    echo ""
    echo -e "${ACCENT}${BOLD}$1${NC}"
}

ui_kv() {
    echo -e "${MUTED}$1:${NC} $2"
}

print_banner() {
    echo -e "${ACCENT}${BOLD}"
    echo "🌀 Groundwire Installer"
}

# ─── OS / Arch detection ────────────────────────────────────────────────────
OS=""
ARCH=""

detect_os() {
    case "$(uname -s 2>/dev/null)" in
        Darwin)  OS="darwin" ;;
        Linux)   OS="linux" ;;
        MINGW*|MSYS*|CYGWIN*)
            ui_error "Windows is not supported by this installer."
            echo ""
            echo "Options:"
            echo "  1) Use WSL (Windows Subsystem for Linux) and re-run this installer inside it."
            echo "     https://learn.microsoft.com/en-us/windows/wsl/install"
            echo ""
            echo "  2) Download the release manually:"
            echo "     ${GITHUB_RELEASES}"
            exit 1
            ;;
        *)
            ui_error "Unsupported operating system: $(uname -s 2>/dev/null || echo unknown)"
            echo "This installer supports macOS and Linux (including WSL)."
            exit 1
            ;;
    esac

    if [[ "$OS" == "linux" && -n "${WSL_DISTRO_NAME:-}" ]]; then
        ui_info "WSL detected (${WSL_DISTRO_NAME})"
    fi

    ui_success "Detected OS:   ${OS}"
}

detect_arch() {
    local raw_arch
    raw_arch="$(uname -m 2>/dev/null || echo unknown)"

    if [[ "$OS" == "darwin" ]]; then
        # On macOS, only Apple Silicon is supported.
        # Check via sysctl, which reports correctly even under Rosetta 2
        # (where uname -m would wrongly say x86_64).
        if sysctl -n hw.optional.arm64 2>/dev/null | grep -q '1'; then
            ARCH="aarch64"
        else
            ui_error "Unsupported Mac architecture: ${raw_arch}"
            echo "This installer only supports Apple Silicon (M-series) Macs."
            echo "Intel Macs are not supported."
            exit 1
        fi
    else
        case "$raw_arch" in
            x86_64|amd64)   ARCH="x86_64" ;;
            arm64|aarch64)  ARCH="aarch64" ;;
            *)
                ui_error "Unsupported architecture: ${raw_arch}"
                echo "This installer supports x86_64 and arm64/aarch64."
                exit 1
                ;;
        esac
    fi
    ui_success "Detected arch: ${ARCH}"
}

# ─── Downloader ──────────────────────────────────────────────────────────────
DOWNLOADER=""

detect_downloader() {
    if command -v curl &>/dev/null; then
        DOWNLOADER="curl"
    elif command -v wget &>/dev/null; then
        DOWNLOADER="wget"
    else
        ui_error "Neither curl nor wget found. Please install one and retry."
        exit 1
    fi
}

download_file() {
    local url="$1" output="$2"
    [[ -z "$DOWNLOADER" ]] && detect_downloader

    if [[ "$DOWNLOADER" == "curl" ]]; then
        curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 2 --retry-connrefused \
            -o "$output" "$url"
    else
        wget -q --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=30 \
            -O "$output" "$url"
    fi
}

# Fetch a URL and print the body to stdout (for API calls)
fetch_url() {
    local url="$1"
    [[ -z "$DOWNLOADER" ]] && detect_downloader

    if [[ "$DOWNLOADER" == "curl" ]]; then
        curl -fsSL --proto '=https' --tlsv1.2 --retry 3 --retry-delay 2 --retry-connrefused "$url"
    else
        wget -qO- --https-only --secure-protocol=TLSv1_2 --tries=3 --timeout=30 "$url"
    fi
}

# ─── Release resolution ─────────────────────────────────────────────────────
RELEASE_TAG=""
RELEASE_CHANNEL="daily"
RELEASE_TAG_PREFIX="groundwire-daily-"

set_release_channel() {
    if [[ "$DEVELOPER_BUILD" == "1" ]]; then
        RELEASE_CHANNEL="developer-build"
        RELEASE_TAG_PREFIX="groundwire-alpha-"
    else
        RELEASE_CHANNEL="daily"
        RELEASE_TAG_PREFIX="groundwire-daily-"
    fi
}

resolve_latest_release() {
    set_release_channel
    ui_info "Querying GitHub releases for latest ${RELEASE_CHANNEL} release..."

    local api_url="${GITHUB_API}/repos/${REPO}/releases?per_page=100"
    local response=""

    response="$(fetch_url "$api_url" 2>/dev/null || true)"

    if [[ -z "$response" ]]; then
        ui_error "Failed to fetch releases from GitHub API"
        echo "  URL: ${api_url}"
        echo ""
        echo "This can happen if:"
        echo "  - You have no internet connection"
        echo "  - GitHub API rate limit is exceeded (60 requests/hour for unauthenticated)"
        echo "  - The repository has no published releases"
        echo ""
        echo "You can pin a version manually:"
        echo "  curl -fsSL https://your-domain.com/install.sh | URBIT_VERSION=build-0a489791 bash"
        exit 1
    fi

    # Extract release fields from JSON without requiring jq.
    # GitHub returns releases in reverse chronological order,
    # so the first match is the latest.
    local tags prereleases drafts records tag
    tags="$(echo "$response" | grep -o '"tag_name"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')"
    prereleases="$(echo "$response" | grep -o '"prerelease"[[:space:]]*:[[:space:]]*\(true\|false\)' | sed 's/.*: *//')"
    drafts="$(echo "$response" | grep -o '"draft"[[:space:]]*:[[:space:]]*\(true\|false\)' | sed 's/.*: *//')"
    records="$(paste <(printf "%s\n" "$tags") <(printf "%s\n" "$prereleases") <(printf "%s\n" "$drafts"))"

    tag="$(echo "$records" | awk -v prefix="$RELEASE_TAG_PREFIX" -v dev="$DEVELOPER_BUILD" '
        {
            t=$1; pre=$2; dr=$3
            if (index(t, prefix) != 1) next
            if (dr != "false") next
            if (dev == "1" && pre != "true") next
            if (dev != "1" && pre != "false") next
            print t
            exit
        }
    ')"

    if [[ -z "$tag" ]]; then
        if [[ "$DEVELOPER_BUILD" == "1" ]]; then
            ui_error "Could not find a ${RELEASE_CHANNEL} prerelease (${RELEASE_TAG_PREFIX}*) from GitHub API response"
        else
            ui_error "Could not find a ${RELEASE_CHANNEL} release (${RELEASE_TAG_PREFIX}*) from GitHub API response"
        fi
        echo "  API URL: ${api_url}"
        echo "  Check available releases at:"
        echo "    ${GITHUB_RELEASES}"
        echo ""
        echo "You can pin a version manually:"
        echo "  curl -fsSL https://your-domain.com/install.sh | URBIT_VERSION=build-0a489791 bash"
        exit 1
    fi

    RELEASE_TAG="$tag"
    ui_success "Latest ${RELEASE_CHANNEL} release: ${RELEASE_TAG}"
}

resolve_release_tag() {
    if [[ -n "$PINNED_TAG" ]]; then
        RELEASE_TAG="$PINNED_TAG"
        ui_info "Using pinned version: ${RELEASE_TAG}"

        # Verify the tag actually exists
        local check_url="${GITHUB_API}/repos/${REPO}/releases/tags/${RELEASE_TAG}"
        local check_response=""
        check_response="$(fetch_url "$check_url" 2>/dev/null || true)"

        if [[ -z "$check_response" ]] || echo "$check_response" | grep -q '"message"[[:space:]]*:[[:space:]]*"Not Found"'; then
            ui_error "Release tag '${RELEASE_TAG}' not found"
            echo "  Check available releases at:"
            echo "    ${GITHUB_RELEASES}"
            exit 1
        fi

        ui_success "Verified release: ${RELEASE_TAG}"
    else
        resolve_latest_release
    fi
}

# ─── Asset resolution ───────────────────────────────────────────────────────
ASSET_URL=""

resolve_asset_url() {
    # Strategy:
    #   1. Build expected asset name from OS/ARCH and try a direct URL.
    #   2. If that fails, query the release API and fuzzy-match from the asset list.

    # ── Attempt 1: direct URL with expected naming convention ────────────
    # Adjust this pattern to match your actual release asset filenames.
    # Common patterns:
    #   urbit-<tag>-<os>-<arch>.tar.gz
    #   urbit-<os>-<arch>.tar.gz
    #   <tag>-<os>-<arch>.tar.gz
    # Map OS to common asset naming variants
    local asset_os="$OS"
    if [[ "$OS" == "darwin" ]]; then
        asset_os="macos"
    fi

    local candidates=(
        "groundwire-${asset_os}-${ARCH}.tar.gz"
        "groundwire-${RELEASE_TAG}-${asset_os}-${ARCH}.tar.gz"
        "urbit-${asset_os}-${ARCH}.tar.gz"
        "urbit-${RELEASE_TAG}-${asset_os}-${ARCH}.tar.gz"
        "${RELEASE_TAG}-${asset_os}-${ARCH}.tar.gz"
        "groundwire-${asset_os}-${ARCH}.tgz"
    )

    local base_url="${GITHUB_RELEASES}/download/${RELEASE_TAG}"

    for candidate in "${candidates[@]}"; do
        local test_url="${base_url}/${candidate}"
        # Quick HEAD check to see if the asset exists
        if command -v curl &>/dev/null; then
            if curl -fsSL --proto '=https' --tlsv1.2 --head "$test_url" >/dev/null 2>&1; then
                ASSET_URL="$test_url"
                ui_success "Found asset: ${candidate}"
                return 0
            fi
        fi
    done

    # ── Attempt 2: query release API and match by OS/ARCH ───────────────
    ui_info "Searching release assets via API..."

    local api_url="${GITHUB_API}/repos/${REPO}/releases/tags/${RELEASE_TAG}"
    local response=""
    response="$(fetch_url "$api_url" 2>/dev/null || true)"

    if [[ -z "$response" ]]; then
        ui_error "Failed to fetch release details from GitHub API"
        exit 1
    fi

    # Extract all browser_download_url values
    local urls=""
    urls="$(echo "$response" | grep -o '"browser_download_url"[[:space:]]*:[[:space:]]*"[^"]*"' | sed 's/.*: *"//;s/"//')"

    if [[ -z "$urls" ]]; then
        ui_error "No downloadable assets found for release ${RELEASE_TAG}"
        echo "  Check: ${GITHUB_RELEASES}/tag/${RELEASE_TAG}"
        exit 1
    fi

    if [[ "$VERBOSE" == "1" ]]; then
        ui_info "Available assets:"
        echo "$urls" | while IFS= read -r u; do
            echo "    ${u}"
        done
    fi

    # Try to find an asset matching our OS and ARCH (case-insensitive)
    local os_lower arch_lower
    os_lower="$(echo "$OS" | tr '[:upper:]' '[:lower:]')"
    arch_lower="$(echo "$ARCH" | tr '[:upper:]' '[:lower:]')"

    # Handle common OS aliases (e.g. darwin <-> macos)
    local os_patterns=("$os_lower")
    case "$os_lower" in
        darwin)  os_patterns+=("macos" "osx") ;;
        linux)   os_patterns+=("linux") ;;
    esac

    # Also handle common arch aliases
    local arch_patterns=("$arch_lower")
    case "$arch_lower" in
        x86_64)   arch_patterns+=("amd64" "x64") ;;
        aarch64)  arch_patterns+=("arm64") ;;
    esac

    local url=""
    for os_pat in "${os_patterns[@]}"; do
        for arch_pat in "${arch_patterns[@]}"; do
            url="$(echo "$urls" | grep -i "${os_pat}" | grep -i "${arch_pat}" | grep -iE '\.(tar\.gz|tgz)$' | head -n1 || true)"
            if [[ -n "$url" ]]; then
                break 2
            fi
        done
    done

    # If no tar.gz, try any archive format
    if [[ -z "$url" ]]; then
        for os_pat in "${os_patterns[@]}"; do
            for arch_pat in "${arch_patterns[@]}"; do
                url="$(echo "$urls" | grep -i "${os_pat}" | grep -i "${arch_pat}" | head -n1 || true)"
                if [[ -n "$url" ]]; then
                    break 2
                fi
            done
        done
    fi

    if [[ -z "$url" ]]; then
        ui_error "No matching asset found for ${OS}/${ARCH}"
        echo ""
        echo "Available assets for ${RELEASE_TAG}:"
        echo "$urls" | while IFS= read -r u; do
            echo "  $(basename "$u")"
        done
        echo ""
        echo "If your platform is listed above under a different name,"
        echo "please open an issue: https://github.com/${REPO}/issues"
        exit 1
    fi

    ASSET_URL="$url"
    ui_success "Found asset: $(basename "$url")"
}

# ─── Dependency checks ──────────────────────────────────────────────────────
check_dependencies() {
    ui_section "Checking dependencies"

    if ! command -v tar &>/dev/null; then
        ui_error "tar is required but not found"
        exit 1
    fi

    if ! command -v curl &>/dev/null && ! command -v wget &>/dev/null; then
        ui_error "curl or wget is required"
        exit 1
    fi

    ui_success "All dependencies satisfied"
}

# ─── Installation ────────────────────────────────────────────────────────────
install_release() {
    resolve_asset_url

    local asset_filename
    asset_filename="$(basename "$ASSET_URL")"

    ui_section "Downloading release"
    ui_kv "Release"  "$RELEASE_TAG"
    ui_kv "Asset"    "$asset_filename"
    ui_kv "URL"      "$ASSET_URL"

    if [[ "$DRY_RUN" == "1" ]]; then
        ui_info "[dry-run] Would download ${ASSET_URL}"
        ui_info "[dry-run] Would extract to ${INSTALL_DIR}"
        return 0
    fi

    local tmp_dir archive_path
    tmp_dir="$(mktempdir)"
    archive_path="${tmp_dir}/${asset_filename}"

    download_file "$ASSET_URL" "$archive_path"
    ui_success "Download complete ($(du -h "$archive_path" | cut -f1 | xargs))"

    # ── Extract ──────────────────────────────────────────────────────────
    ui_section "Extracting"

    mkdir -p "$INSTALL_DIR"

    # Detect archive type and extract accordingly
    case "$asset_filename" in
        *.tar.gz|*.tgz)
            if [[ "$VERBOSE" == "1" ]]; then
                tar -xzvf "$archive_path" -C "$INSTALL_DIR"
            else
                tar -xzf "$archive_path" -C "$INSTALL_DIR"
            fi
            ;;
        *.tar.bz2)
            tar -xjf "$archive_path" -C "$INSTALL_DIR"
            ;;
        *.tar.xz)
            tar -xJf "$archive_path" -C "$INSTALL_DIR"
            ;;
        *.zip)
            if ! command -v unzip &>/dev/null; then
                ui_error "unzip is required for .zip archives but not found"
                exit 1
            fi
            unzip -o "$archive_path" -d "$INSTALL_DIR"
            ;;
        *)
            ui_warn "Unknown archive format: ${asset_filename}"
            ui_info "Attempting tar -xzf..."
            tar -xzf "$archive_path" -C "$INSTALL_DIR"
            ;;
    esac

    ui_success "Extracted to ${INSTALL_DIR}"

    # Show what was extracted
    if [[ "$VERBOSE" == "1" ]]; then
        ui_info "Contents:"
        ls -la "$INSTALL_DIR" | head -n 20
    fi
}

# ─── Post-install setup (placeholder) ───────────────────────────────────────
run_onboarding() {
    ui_info "Beginning onboarding..."
    cd "$INSTALL_DIR"
    local onboard_args=()
    if [[ -n "$INVITE_CODE" ]]; then
        onboard_args+=(--invite "$INVITE_CODE")
    fi
    if [[ -n "$FIEF" ]]; then
        onboard_args+=(--fief "$FIEF")
    fi
    if [[ "$SKIP_ATTESTATION" == "1" ]]; then
        onboard_args+=(--skip-attestation)
    fi
    local snapshot_url="${SNAPSHOT_URL:-$DEFAULT_SNAPSHOT_URL}"
    if [[ -n "$snapshot_url" ]]; then
        onboard_args+=(--snapshot-url "$snapshot_url")
    fi
    if [[ -n "$PILL" ]]; then
        onboard_args+=(--pill "$PILL")
    fi
    ./gw-onboard "${onboard_args[@]}" </dev/tty
}

# ─── PATH integration ───────────────────────────────────────────────────────
ensure_on_path() {
    local bin_dir="${INSTALL_DIR}/bin"

    if [[ ! -d "$bin_dir" ]]; then
        bin_dir="$INSTALL_DIR"
    fi

    case ":${PATH}:" in
        *":${bin_dir}:"*)
            return 0
            ;;
    esac

    export PATH="${bin_dir}:$PATH"

    # shellcheck disable=SC2016
    local path_line="export PATH=\"${bin_dir}:\$PATH\""
    local wrote_to=""

    for rc in "$HOME/.bashrc" "$HOME/.zshrc" "$HOME/.profile"; do
        if [[ -f "$rc" ]]; then
            if ! grep -qF "$bin_dir" "$rc" 2>/dev/null; then
                echo "" >> "$rc"
                echo "# Added by urbit installer" >> "$rc"
                echo "$path_line" >> "$rc"
                wrote_to="${wrote_to} ${rc}"
            fi
        fi
    done
}

# ─── Usage / help ────────────────────────────────────────────────────────────
print_usage() {
    cat <<EOF
Groundwire installer (macOS + Linux)

Usage:
  curl -fsSL https://groundwire.io/install.sh | bash
  curl -fsSL https://groundwire.io/install.sh | bash -s -- [options]

Options:
  --invite <code>        Invite code for faucet funding
  --fief <ip:port>       Static IP and port for direct routing (skips sponsor)
  --install-dir <path>   Installation directory (default: ~/.local/share/groundwire-alpha)
  --version <tag>        Install a specific release tag instead of latest
  --developer-build      Install developer prerelease channel
  --snapshot-url <url>   Snapshot URL to pass to gw-onboard (overrides default)
  --pill <path>          Path to a pill file to pass to gw-onboard
  --skip-download        Skip download and go straight to onboarding (use with --install-dir)
  --skip-attestation     Pass --skip-attestation to gw-onboard (mines and boots without Bitcoin tx)
  --dry-run              Print what would happen without making changes
  --verbose              Show detailed output
  --help, -h             Show this help

Environment variables:
  URBIT_INSTALL_DIR      Same as --install-dir
  URBIT_VERSION          Same as --version (pin a specific release tag)
  URBIT_DEVELOPER_BUILD  Set to 1 to use developer prerelease channel
  URBIT_VERBOSE          Set to 1 for verbose output
  URBIT_DRY_RUN          Set to 1 for dry run
  URBIT_SKIP_DOWNLOAD    Set to 1 to skip download
  URBIT_SKIP_ATTESTATION Set to 1 to skip Bitcoin attestation

Examples:
  # Install latest daily release:
  curl -fsSL https://groundwire.io/install.sh | bash

  # Install latest developer prerelease:
  curl -fsSL https://groundwire.io/install.sh | bash -s -- --developer-build

  # Install a specific version:
  curl -fsSL https://groundwire.io/install.sh | bash -s -- --version build-0a489791

  # Verbose + custom directory:
  curl -fsSL https://groundwire.io/install.sh | bash -s -- --verbose --install-dir /opt/urbit
EOF
}

# ─── Argument parsing ───────────────────────────────────────────────────────
HELP=0
INVITE_CODE=""
FIEF=""
PILL=""
SNAPSHOT_URL=""

parse_args() {
    while [[ $# -gt 0 ]]; do
        case "$1" in
            --help|-h)
                HELP=1
                shift
                ;;
            --skip-download)
                SKIP_DOWNLOAD=1
                shift
                ;;
            --skip-attestation)
                SKIP_ATTESTATION=1
                shift
                ;;
            --dry-run)
                DRY_RUN=1
                shift
                ;;
            --verbose)
                VERBOSE=1
                shift
                ;;
            --install-dir)
                INSTALL_DIR="$2"
                shift 2
                ;;
            --version)
                PINNED_TAG="$2"
                shift 2
                ;;
            --developer-build)
                DEVELOPER_BUILD=1
                shift
                ;;
            --invite)
                INVITE_CODE="$2"
                shift 2
                ;;
            --fief)
                FIEF="$2"
                shift 2
                ;;
            --pill)
                PILL="$2"
                shift 2
                ;;
            --snapshot-url)
                SNAPSHOT_URL="$2"
                shift 2
                ;;
            *)
                ui_warn "Unknown option: $1 (ignoring)"
                shift
                ;;
        esac
    done
}

# ─── Main ────────────────────────────────────────────────────────────────────
main() {
    if [[ "$HELP" == "1" ]]; then
        print_usage
        return 0
    fi

    if [[ "$VERBOSE" == "1" ]]; then
        set -x
    fi

    print_banner

    # Detect environment
    ui_section "Detecting environment"
    detect_os
    detect_arch

    # Check deps
    check_dependencies

    if [[ "$SKIP_DOWNLOAD" == "1" ]]; then
        ui_section "Skipping download"
        ui_kv "Install dir"  "$INSTALL_DIR"
        ui_info "Using existing installation at ${INSTALL_DIR}"
    else
        # Resolve which release to install
        ui_section "Resolving release"
        resolve_release_tag

        # Show plan
        ui_section "Install plan"
        ui_kv "Release"      "$RELEASE_TAG"
        ui_kv "OS / Arch"    "${OS} / ${ARCH}"
        ui_kv "Install dir"  "$INSTALL_DIR"
        if [[ -n "$PINNED_TAG" ]]; then
            ui_kv "Pinned"   "yes"
        else
            set_release_channel
            ui_kv "Channel"  "$RELEASE_CHANNEL (${RELEASE_TAG_PREFIX}*)"
        fi
        if [[ "$DRY_RUN" == "1" ]]; then
            ui_kv "Dry run"  "yes"
        fi

        # Download & extract
        install_release
    fi

    # Onboarding commands
    run_onboarding

    # PATH
    if [[ "$DRY_RUN" != "1" ]]; then
        ensure_on_path
    fi

    # ── Done ─────────────────────────────────────────────────────────────
    echo ""
    if [[ "$DRY_RUN" == "1" ]]; then
        ui_success "Dry run complete (no changes made)"
    fi
}

parse_args "$@"
main
