#!/usr/bin/env bash
# macOS Storage Fix - "Other" Storage Cleaner
# GPL 3.0+ License - Free forever, corporate-capture proof
#
# Reclaims space hidden in macOS "Other" storage:
# Time Machine snapshots, caches, logs, temp files
# (iOS backups are reported but not auto-deleted — require manual review)

set -euo pipefail

# ── Colours ──────────────────────────────────────────────────────────────────
RED='\033[0;31m'; GREEN='\033[0;32m'; YELLOW='\033[1;33m'
CYAN='\033[0;36m'; MAGENTA='\033[0;35m'; GRAY='\033[0;37m'
WHITE='\033[1;37m'; RESET='\033[0m'; BOLD='\033[1m'

# ── Helpers ───────────────────────────────────────────────────────────────────
bytes_to_human() {
    local bytes=$1
    if   (( bytes >= 1073741824 )); then printf "%.1f GB" "$(echo "scale=1; $bytes/1073741824" | bc)"
    elif (( bytes >= 1048576    )); then printf "%.1f MB" "$(echo "scale=1; $bytes/1048576"    | bc)"
    elif (( bytes >= 1024       )); then printf "%.1f KB" "$(echo "scale=1; $bytes/1024"       | bc)"
    else printf "%d B" "$bytes"
    fi
}

dir_size_bytes() {
    # Returns size in bytes or 0 if path doesn't exist / unreadable
    local path="$1"
    [[ -e "$path" ]] || { echo 0; return; }
    du -sk "$path" 2>/dev/null | awk '{print $1 * 1024}' || echo 0
}

free_space_before() {
    df -k / | awk 'NR==2 {print $4 * 1024}'
}

confirm() {
    local prompt="$1"
    printf "${YELLOW}%s [y/N]: ${RESET}" "$prompt"
    read -r answer
    [[ "${answer,,}" == "y" ]]
}

# ── Header ────────────────────────────────────────────────────────────────────
show_header() {
    clear
    echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${RESET}"
    echo -e "${CYAN}║${RESET}          ${BOLD}${WHITE}macOS Storage Fix - Other Cleaner${RESET}          ${CYAN}║${RESET}"
    echo -e "${CYAN}║${RESET}  ${GRAY}Reclaims space Apple quietly fills and won't show you${RESET}  ${CYAN}║${RESET}"
    echo -e "${CYAN}║${RESET}  ${GRAY}GPL 3.0+ - Free forever, corporate-capture proof${RESET}       ${CYAN}║${RESET}"
    echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${RESET}"
    echo
}

# ── Show current storage ──────────────────────────────────────────────────────
show_storage() {
    echo -e "${CYAN}── Current Storage ──────────────────────────────────────${RESET}"
    df -H / | awk 'NR==2 {
        printf "   Total: %s\n   Used:  %s\n   Free:  %s  (%.0f%% full)\n",
        $2, $3, $4, ($3/$2)*100
    }'
    echo
}

# ── Scan what can be cleared ──────────────────────────────────────────────────
scan() {
    echo -e "${CYAN}── Scanning for recoverable space ───────────────────────${RESET}"
    echo

    # Time Machine local snapshots
    TM_SIZE=0
    TM_SNAPS=$(tmutil listlocalsnapshots / 2>/dev/null | wc -l | tr -d ' ')
    if (( TM_SNAPS > 0 )); then
        # Each snapshot size is hard to sum directly; estimate via tmutil
        TM_SIZE_RAW=$(tmutil listlocalsnapshotdates 2>/dev/null | \
            grep -v "^$\|Snapshot" | \
            xargs -I{} tmutil listlocalsnapshots / 2>/dev/null | wc -c || echo 0)
        TM_SIZE=$(tmutil listlocalsnapshots / 2>/dev/null | \
            xargs -I{} sh -c 'tmutil listlocalsnapshots / 2>/dev/null' | wc -c || echo 0)
        echo -e "  ${RED}●${RESET} Time Machine snapshots:  ${WHITE}${TM_SNAPS} snapshot(s)${RESET} ${GRAY}(can be large — 10s of GB)${RESET}"
    else
        echo -e "  ${GREEN}●${RESET} Time Machine snapshots:  ${GRAY}none${RESET}"
    fi

    # User caches
    USER_CACHE_SIZE=$(dir_size_bytes "$HOME/Library/Caches")
    echo -e "  ${YELLOW}●${RESET} User caches:             ${WHITE}$(bytes_to_human $USER_CACHE_SIZE)${RESET}"

    # System caches (requires sudo)
    SYS_CACHE_SIZE=$(dir_size_bytes "/Library/Caches")
    echo -e "  ${YELLOW}●${RESET} System caches:           ${WHITE}$(bytes_to_human $SYS_CACHE_SIZE)${RESET} ${GRAY}(requires sudo)${RESET}"

    # User logs
    USER_LOG_SIZE=$(dir_size_bytes "$HOME/Library/Logs")
    echo -e "  ${YELLOW}●${RESET} User logs:               ${WHITE}$(bytes_to_human $USER_LOG_SIZE)${RESET}"

    # System logs
    SYS_LOG_SIZE=$(dir_size_bytes "/var/log")
    echo -e "  ${YELLOW}●${RESET} System logs:             ${WHITE}$(bytes_to_human $SYS_LOG_SIZE)${RESET} ${GRAY}(requires sudo)${RESET}"

    # Temp files
    TEMP_SIZE=$(dir_size_bytes "/private/var/folders")
    echo -e "  ${YELLOW}●${RESET} Temp files:              ${WHITE}$(bytes_to_human $TEMP_SIZE)${RESET}"

    # Xcode derived data (common space hog)
    XC_SIZE=$(dir_size_bytes "$HOME/Library/Developer/Xcode/DerivedData")
    if (( XC_SIZE > 0 )); then
        echo -e "  ${RED}●${RESET} Xcode DerivedData:       ${WHITE}$(bytes_to_human $XC_SIZE)${RESET}"
    fi

    # Xcode simulators
    XC_SIM_SIZE=$(dir_size_bytes "$HOME/Library/Developer/CoreSimulator/Caches")
    if (( XC_SIM_SIZE > 0 )); then
        echo -e "  ${RED}●${RESET} Xcode simulator caches:  ${WHITE}$(bytes_to_human $XC_SIM_SIZE)${RESET}"
    fi

    # iOS device backups
    BACKUP_SIZE=$(dir_size_bytes "$HOME/Library/Application Support/MobileSync/Backup")
    if (( BACKUP_SIZE > 0 )); then
        echo -e "  ${MAGENTA}●${RESET} iOS device backups:      ${WHITE}$(bytes_to_human $BACKUP_SIZE)${RESET}"
    fi

    # Mail attachments
    MAIL_SIZE=$(dir_size_bytes "$HOME/Library/Mail")
    if (( MAIL_SIZE > 0 )); then
        echo -e "  ${YELLOW}●${RESET} Mail downloads/cache:    ${WHITE}$(bytes_to_human $MAIL_SIZE)${RESET} ${GRAY}(metadata only — won't delete mail)${RESET}"
    fi

    # Trash
    TRASH_SIZE=$(dir_size_bytes "$HOME/.Trash")
    if (( TRASH_SIZE > 0 )); then
        echo -e "  ${RED}●${RESET} Trash:                   ${WHITE}$(bytes_to_human $TRASH_SIZE)${RESET}"
    fi

    echo
}

# ── Clean functions ───────────────────────────────────────────────────────────
clean_tm_snapshots() {
    echo -e "${CYAN}── Deleting Time Machine local snapshots ────────────────${RESET}"
    local snaps
    snaps=$(tmutil listlocalsnapshots / 2>/dev/null | grep "com.apple.TimeMachine" || true)
    if [[ -z "$snaps" ]]; then
        echo -e "  ${GREEN}No snapshots to remove.${RESET}"; echo; return
    fi
    while IFS= read -r snap; do
        local date
        date=$(echo "$snap" | grep -oE '[0-9]{4}-[0-9]{2}-[0-9]{2}-[0-9]{6}' || echo "$snap")
        echo -ne "  Removing $date ... "
        if tmutil deletelocalsnapshots "$date" 2>/dev/null; then
            echo -e "${GREEN}done${RESET}"
        else
            echo -e "${RED}failed (try sudo)${RESET}"
        fi
    done <<< "$snaps"
    echo
}

clean_user_caches() {
    echo -e "${CYAN}── Clearing user caches ─────────────────────────────────${RESET}"
    local cache_dir="$HOME/Library/Caches"
    local before
    before=$(dir_size_bytes "$cache_dir")
    # Remove contents but keep the directory itself
    find "$cache_dir" -mindepth 1 -maxdepth 1 -exec rm -rf {} + 2>/dev/null || true
    local after
    after=$(dir_size_bytes "$cache_dir")
    local saved=$(( before - after ))
    echo -e "  ${GREEN}Recovered: $(bytes_to_human $saved)${RESET}"
    echo
}

clean_user_logs() {
    echo -e "${CYAN}── Clearing user logs ───────────────────────────────────${RESET}"
    local log_dir="$HOME/Library/Logs"
    local before
    before=$(dir_size_bytes "$log_dir")
    find "$log_dir" -name "*.log" -delete 2>/dev/null || true
    find "$log_dir" -name "*.gz"  -delete 2>/dev/null || true
    local after
    after=$(dir_size_bytes "$log_dir")
    echo -e "  ${GREEN}Recovered: $(bytes_to_human $(( before - after )))${RESET}"
    echo
}

clean_xcode() {
    echo -e "${CYAN}── Clearing Xcode DerivedData ───────────────────────────${RESET}"
    local xc_dir="$HOME/Library/Developer/Xcode/DerivedData"
    local before
    before=$(dir_size_bytes "$xc_dir")
    rm -rf "$xc_dir"/* 2>/dev/null || true
    echo -e "  ${GREEN}Recovered: $(bytes_to_human $(( before - $(dir_size_bytes "$xc_dir") )))${RESET}"
    echo
}

clean_trash() {
    echo -e "${CYAN}── Emptying Trash ───────────────────────────────────────${RESET}"
    local before
    before=$(dir_size_bytes "$HOME/.Trash")
    rm -rf "$HOME/.Trash/"* 2>/dev/null || true
    echo -e "  ${GREEN}Recovered: $(bytes_to_human $before)${RESET}"
    echo
}

purge_inactive_memory() {
    echo -e "${CYAN}── Purging inactive memory cache ────────────────────────${RESET}"
    if sudo purge 2>/dev/null; then
        echo -e "  ${GREEN}Done.${RESET}"
    else
        echo -e "  ${RED}Failed — requires sudo.${RESET}"
    fi
    echo
}

# ── Show summary ──────────────────────────────────────────────────────────────
show_summary() {
    local free_before=$1
    local free_after
    free_after=$(free_space_before)
    local recovered=$(( free_after - free_before ))

    echo -e "${CYAN}╔══════════════════════════════════════════════════════════╗${RESET}"
    echo -e "${CYAN}║${RESET}                    ${BOLD}${GREEN}All done!${RESET}                          ${CYAN}║${RESET}"
    echo -e "${CYAN}╚══════════════════════════════════════════════════════════╝${RESET}"
    echo
    if (( recovered > 0 )); then
        echo -e "  ${GREEN}Total recovered: $(bytes_to_human $recovered)${RESET}"
    else
        echo -e "  ${YELLOW}Free space unchanged — macOS may defer reclaim until restart.${RESET}"
        echo -e "  ${GRAY}Tip: Restart to fully flush filesystem and memory caches.${RESET}"
    fi
    echo
    show_storage
}

# ── Help ──────────────────────────────────────────────────────────────────────
show_help() {
    show_header
    echo -e "${YELLOW}🎯 What this fixes:${RESET}"
    echo -e "   macOS hides junk in 'Other' storage and reclaims it on its own"
    echo -e "   schedule — sometimes never. This forces the cleanup Apple won't."
    echo
    echo -e "${YELLOW}⚡ Usage:${RESET}"
    echo -e "   ${WHITE}./MacStorageFix.sh${RESET}              Interactive mode (recommended)"
    echo -e "   ${WHITE}./MacStorageFix.sh --scan${RESET}       Scan only, make no changes"
    echo -e "   ${WHITE}./MacStorageFix.sh --all${RESET}        Clean everything without prompts"
    echo -e "   ${WHITE}./MacStorageFix.sh --snapshots${RESET}  Time Machine snapshots only"
    echo -e "   ${WHITE}./MacStorageFix.sh --caches${RESET}     Caches only"
    echo -e "   ${WHITE}./MacStorageFix.sh --help${RESET}       This help"
    echo
    echo -e "${YELLOW}💡 What each category does:${RESET}"
    echo -e "   ${RED}Snapshots${RESET}  — Local Time Machine backups macOS keeps 'just in case'"
    echo -e "   ${YELLOW}Caches${RESET}     — Rebuild automatically, safe to delete"
    echo -e "   ${YELLOW}Logs${RESET}       — Old log files, safe to delete"
    echo -e "   ${RED}Xcode${RESET}      — Build artefacts, huge and fully regenerable"
    echo -e "   ${RED}Trash${RESET}      — You already deleted these"
    echo
    echo -e "${GRAY}Note: iOS backups and Mail are shown in the scan but NOT auto-deleted${RESET}"
    echo -e "${GRAY}— those require manual review in Finder.${RESET}"
    echo
}

# ── Interactive mode ──────────────────────────────────────────────────────────
interactive() {
    show_header
    show_storage
    scan

    local free_before
    free_before=$(free_space_before)

    local TM_SNAPS
    TM_SNAPS=$(tmutil listlocalsnapshots / 2>/dev/null | grep -c "com.apple" || echo 0)

    if (( TM_SNAPS > 0 )); then
        confirm "Delete Time Machine local snapshots? (biggest win, safe to remove)" && clean_tm_snapshots
    fi

    confirm "Clear user caches? (safe — apps rebuild them)" && clean_user_caches
    confirm "Clear user logs?" && clean_user_logs

    if [[ -d "$HOME/Library/Developer/Xcode/DerivedData" ]]; then
        confirm "Clear Xcode DerivedData?" && clean_xcode
    fi

    if [[ -n "$(ls -A "$HOME/.Trash" 2>/dev/null)" ]]; then
        confirm "Empty Trash?" && clean_trash
    fi

    confirm "Purge inactive memory? (requires sudo)" && purge_inactive_memory

    show_summary "$free_before"
}

# ── Entry point ───────────────────────────────────────────────────────────────
case "${1:-}" in
    --help|-h)   show_help ;;
    --scan)      show_header; show_storage; scan ;;
    --snapshots) show_header; free_before=$(free_space_before); clean_tm_snapshots; show_summary "$free_before" ;;
    --caches)    show_header; free_before=$(free_space_before); clean_user_caches;  show_summary "$free_before" ;;
    --all)
        show_header
        free_before=$(free_space_before)
        clean_tm_snapshots
        clean_user_caches
        clean_user_logs
        [[ -d "$HOME/Library/Developer/Xcode/DerivedData" ]] && clean_xcode
        clean_trash
        purge_inactive_memory
        show_summary "$free_before"
        ;;
    *)
        interactive
        ;;
esac