#!/bin/bash
# Enhanced dotfiles sync utility
# Purpose: Sync dotfiles with repository while supporting blacklists and flexible sync configurations

set -euo pipefail  # Exit on error, undefined vars, pipe failures

# Configuration
readonly REPO_DIR="${HOME}/repos/dotfiles"
readonly CONFIG_DIR="${HOME}/.config"
readonly SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)"

# Global blacklist patterns (regex)
# These patterns apply to ALL sync operations
declare -a GLOBAL_BLACKLIST_PATTERNS=(
    "\.git(/|$)"           # Git directories
    "\.DS_Store$"          # macOS metadata
    "\.swp$"               # Vim swap files
    "\.tmp$"               # Temporary files
    "\.bak$"               # Backup files
    "~$"                   # Temporary files ending with ~
)

# Sync configuration - enhanced approach
# Format: "source_path:target_path:sync_type:local_blacklist_patterns"
# sync_type: "dir" for directories, "file" for files
# local_blacklist_patterns: comma-separated regex patterns (optional)
declare -a SYNC_CONFIGS=(
    # Config directories with specific blacklist patterns
    "${CONFIG_DIR}/hypr:${REPO_DIR}/user-home/.config/hypr:dir:logs?(/|$),\.log$"
    "${CONFIG_DIR}/kitty:${REPO_DIR}/user-home/.config/kitty:dir:"
    "${CONFIG_DIR}/systemd:${REPO_DIR}/user-home/.config/systemd:dir:buildkit.*,containerd.*"
    "${CONFIG_DIR}/gtk-3.0:${REPO_DIR}/user-home/.config/gtk-3.0:dir:bookmarks"
    "${CONFIG_DIR}/gtk-4.0:${REPO_DIR}/user-home/.config/gtk-4.0:dir:"
    "${CONFIG_DIR}/xsettingsd:${REPO_DIR}/user-home/.config/xsettingsd:dir:"
    "${CONFIG_DIR}/uwsm:${REPO_DIR}/user-home/.config/uwsm:dir:"
    "${CONFIG_DIR}/waybar:${REPO_DIR}/user-home/.config/waybar:dir:"
    "${CONFIG_DIR}/wlogout:${REPO_DIR}/user-home/.config/wlogout:dir:"
    "${CONFIG_DIR}/nvim:${REPO_DIR}/user-home/.config/nvim:dir:session(/|$),\.session$,swap(/|$),undo(/|$),view(/|$)"
    
    # Individual files (blacklist patterns not applicable to single files)
    "${HOME}/.zshrc:${REPO_DIR}/user-home/.zshrc:file:"
    "${HOME}/.gtkrc-2.0:${REPO_DIR}/user-home/.gtkrc-2.0:file:"
    "${HOME}/.tmux.conf:${REPO_DIR}/user-home/.tmux.conf:file:"
    "${HOME}/.oh-my-zsh/custom/aliases.zsh:${REPO_DIR}/user-home/aliases.zsh:file:"
    "${HOME}/.oh-my-zsh/custom/functions.zsh:${REPO_DIR}/user-home/functions.zsh:file:"
    "${HOME}/.oh-my-zsh/custom/hooks.zsh:${REPO_DIR}/user-home/hooks.zsh:file:"
    "${HOME}/.oh-my-zsh/custom/environment.zsh:${REPO_DIR}/user-home/environment.zsh:file:"
    "${HOME}/wp.png:${REPO_DIR}/wp.png:file:"
    
    # System directories with specific patterns
    "/etc/greetd:${REPO_DIR}/greetd:dir:cache(/|$),run(/|$),pid$"
)

# Colors for output - only use if terminal supports colors
if [[ -t 1 ]] && command -v tput >/dev/null 2>&1 && tput colors >/dev/null 2>&1 && [[ $(tput colors) -ge 8 ]]; then
    readonly RED='\033[0;31m'
    readonly GREEN='\033[0;32m'
    readonly YELLOW='\033[1;33m'
    readonly BLUE='\033[0;34m'
    readonly PURPLE='\033[0;35m'
    readonly CYAN='\033[0;36m'
    readonly WHITE='\033[1;37m'
    readonly BOLD='\033[1m'
    readonly NC='\033[0m' # No Color
else
    # No color support
    readonly RED=''
    readonly GREEN=''
    readonly YELLOW=''
    readonly BLUE=''
    readonly PURPLE=''
    readonly CYAN=''
    readonly WHITE=''
    readonly BOLD=''
    readonly NC=''
fi

# Logging functions
log_info() {
    echo -e "${BLUE}[INFO]${NC} $*"
}

log_success() {
    echo -e "${GREEN}[SUCCESS]${NC} $*"
}

log_warning() {
    echo -e "${YELLOW}[WARNING]${NC} $*"
}

log_error() {
    echo -e "${RED}[ERROR]${NC} $*"
}

# Check if a file/path should be blacklisted
is_blacklisted() {
    local path="$1"
    local local_patterns="$2"  # Comma-separated local patterns
    local relative_path="${path#*/}"  # Remove leading path components for matching
    
    # Check global blacklist patterns
    for pattern in "${GLOBAL_BLACKLIST_PATTERNS[@]}"; do
        if [[ "$relative_path" =~ $pattern ]]; then
            return 0  # Is blacklisted
        fi
    done
    
    # Check local blacklist patterns if provided
    if [[ -n "$local_patterns" ]]; then
        IFS=',' read -ra local_pattern_array <<< "$local_patterns"
        for pattern in "${local_pattern_array[@]}"; do
            # Trim whitespace
            pattern=$(echo "$pattern" | xargs)
            if [[ -n "$pattern" && "$relative_path" =~ $pattern ]]; then
                return 0  # Is blacklisted
            fi
        done
    fi
    
    return 1  # Not blacklisted
}

# Enhanced copy function with blacklist support
safe_copy() {
    local src="$1"
    local dest="$2"
    local is_dir="$3"
    local local_patterns="$4"
    
    if [[ "$is_dir" == "true" ]]; then
        # For directories, use rsync with exclude patterns
        local rsync_excludes=()
        
        # Add global blacklist patterns
        for pattern in "${GLOBAL_BLACKLIST_PATTERNS[@]}"; do
            rsync_excludes+=("--exclude=$pattern")
        done
        
        # Add local blacklist patterns if provided
        if [[ -n "$local_patterns" ]]; then
            IFS=',' read -ra local_pattern_array <<< "$local_patterns"
            for pattern in "${local_pattern_array[@]}"; do
                # Trim whitespace
                pattern=$(echo "$pattern" | xargs)
                if [[ -n "$pattern" ]]; then
                    rsync_excludes+=("--exclude=$pattern")
                fi
            done
        fi
        
        mkdir -p "$(dirname "$dest")"
        rsync -av --delete "${rsync_excludes[@]}" "$src/" "$dest/"
    else
        # For files, check blacklist before copying
        if is_blacklisted "$src" "$local_patterns"; then
            log_warning "Skipping blacklisted file: $src"
            return 0
        fi
        
        mkdir -p "$(dirname "$dest")"
        cp "$src" "$dest"
    fi
}

# Enhanced dry-run function to show what files would be synced
show_dry_run_preview() {
    local config="$1"
    local temp_repo_dir="${2:-}"  # Use empty string as default if not provided
    local source_path target_path sync_type local_patterns
    
    IFS=':' read -r source_path target_path sync_type local_patterns <<< "$config"
    
    if [[ ! -e "$source_path" ]]; then
        if [[ -z "$temp_repo_dir" ]]; then
            printf "  ${YELLOW}⚠️  Source does not exist:${NC} %s\n" "$source_path"
        fi
        return 0
    fi
    
    # If temp_repo_dir is provided, actually sync to temp directory (for summary)
    if [[ -n "$temp_repo_dir" ]]; then
        # Adjust target path to use temp directory
        local temp_target_path="${target_path/$REPO_DIR/$temp_repo_dir}"
        
        # Remove target before copying
        if [[ -e "$temp_target_path" ]]; then
            rm -rf "$temp_target_path"
        fi
        
        case "$sync_type" in
            "dir")
                if [[ -d "$source_path" ]]; then
                    safe_copy "$source_path" "$temp_target_path" true "$local_patterns"
                fi
                ;;
            "file")
                if [[ -f "$source_path" ]]; then
                    safe_copy "$source_path" "$temp_target_path" false "$local_patterns"
                fi
                ;;
        esac
        return 0
    fi
    
    # Original preview logic (when temp_repo_dir is not provided)
    if [[ -n "$local_patterns" ]]; then
        printf "${CYAN}📁 %s${NC} -> ${PURPLE}%s${NC} ${WHITE}(%s)${NC} ${YELLOW}[local patterns: %s]${NC}\n" \
            "$source_path" "$target_path" "$sync_type" "$local_patterns"
    else
        printf "${CYAN}📁 %s${NC} -> ${PURPLE}%s${NC} ${WHITE}(%s)${NC}\n" \
            "$source_path" "$target_path" "$sync_type"
    fi
    
    if [[ "$sync_type" == "file" ]]; then
        if is_blacklisted "$source_path" "$local_patterns"; then
            printf "  ${RED}❌ Would skip (blacklisted):${NC} %s\n" "$(basename "$source_path")"
        else
            printf "  ${GREEN}✅ Would sync:${NC} %s\n" "$(basename "$source_path")"
        fi
    elif [[ "$sync_type" == "dir" && -d "$source_path" ]]; then
        # Simulate what rsync would do
        local temp_dir=$(mktemp -d)
        local rsync_excludes=()
        
        # Add global blacklist patterns
        for pattern in "${GLOBAL_BLACKLIST_PATTERNS[@]}"; do
            rsync_excludes+=("--exclude=$pattern")
        done
        
        # Add local blacklist patterns if provided
        if [[ -n "$local_patterns" ]]; then
            IFS=',' read -ra local_pattern_array <<< "$local_patterns"
            for pattern in "${local_pattern_array[@]}"; do
                pattern=$(echo "$pattern" | xargs)
                if [[ -n "$pattern" ]]; then
                    rsync_excludes+=("--exclude=$pattern")
                fi
            done
        fi
        
        # Use rsync with --dry-run to see what would be copied
        printf "  ${WHITE}Files that would be synced:${NC}\n"
        rsync -av --dry-run "${rsync_excludes[@]}" "$source_path/" "$temp_dir/" 2>/dev/null | \
        grep -E '^[^d]' | \
        head -20 | \
        while read -r line; do
            if [[ "$line" =~ ^[^d] ]]; then
                printf "    ${GREEN}✅${NC} %s\n" "$line"
            fi
        done
        
        # Show excluded files if any
        printf "  ${WHITE}Files that would be excluded:${NC}\n"
        local found_excluded=false
        while IFS= read -r -d '' file; do
            local relative_file="${file#$source_path/}"
            if is_blacklisted "$file" "$local_patterns"; then
                printf "    ${RED}❌${NC} %s\n" "$relative_file"
                found_excluded=true
            fi
        done < <(find "$source_path" -type f -print0 2>/dev/null | head -20)
        
        if [[ "$found_excluded" == false ]]; then
            printf "    ${YELLOW}(no files would be excluded with current patterns)${NC}\n"
        fi
        
        rm -rf "$temp_dir"
    fi
    printf "\n"
}

# Generate comprehensive dry-run summary using git diff
show_dry_run_summary() {
    log_info "Generating comprehensive change summary..."
    
    # Validate that we have git and the repo exists
    if [[ ! -d "$REPO_DIR" ]]; then
        log_error "Repository directory does not exist: $REPO_DIR"
        return 1
    fi
    
    if ! command -v git &> /dev/null; then
        log_error "git is required for summary generation"
        return 1
    fi
    
    # Create temporary directory and copy repository
    local temp_base_dir=$(mktemp -d)
    local temp_repo_dir="$temp_base_dir/dotfiles"
    
    log_info "Creating temporary repository copy..."
    cp -r "$REPO_DIR" "$temp_repo_dir" || {
        log_error "Failed to copy repository to temporary directory"
        rm -rf "$temp_base_dir"
        return 1
    }
    
    # Perform sync operations to temporary directory - just like real sync but to temp location
    log_info "Simulating sync operations..."
    local simulation_errors=0
    for config in "${SYNC_CONFIGS[@]}"; do
        local source_path target_path sync_type local_patterns
        IFS=':' read -r source_path target_path sync_type local_patterns <<< "$config"
        
        # Skip if source doesn't exist
        if [[ ! -e "$source_path" ]]; then
            continue
        fi
        
        # Replace original repo path with temp repo path
        local temp_target_path="${target_path/$REPO_DIR/$temp_repo_dir}"
        
        # Remove target before copying (like real sync)
        if [[ -e "$temp_target_path" ]]; then
            rm -rf "$temp_target_path"
        fi
        
        # Perform actual sync operation to temp location
        case "$sync_type" in
            "dir")
                if [[ -d "$source_path" ]]; then
                    if ! safe_copy "$source_path" "$temp_target_path" true "$local_patterns"; then
                        ((simulation_errors++))
                    fi
                fi
                ;;
            "file")
                if [[ -f "$source_path" ]]; then
                    if ! safe_copy "$source_path" "$temp_target_path" false "$local_patterns"; then
                        ((simulation_errors++))
                    fi
                fi
                ;;
        esac
    done
    
    if [[ $simulation_errors -gt 0 ]]; then
        log_warning "$simulation_errors simulation operation(s) had issues"
    fi
    
    # Change to temp repo directory and show git status
    cd "$temp_repo_dir" || {
        log_error "Failed to change to temporary directory"
        rm -rf "$temp_base_dir"
        return 1
    }
    
    # Check if it's a git repository
    if [[ ! -d ".git" ]]; then
        log_error "Temporary copy is not a git repository"
        rm -rf "$temp_base_dir"
        return 1
    fi
    
    printf "\n${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}\n"
    printf "${BOLD}${WHITE}                        CHANGE SUMMARY                         ${NC}\n"
    printf "${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}\n\n"
    
    # Show git status
    local git_status=$(git status --porcelain 2>/dev/null)
    
    if [[ -z "$git_status" ]]; then
        printf "${GREEN}✅ No changes detected${NC} - Repository is already up to date!\n\n"
    else
        printf "${BOLD}Git Status Output:${NC}\n"
        git status --short
        printf "\n"
        
        # Show some diff preview for modified files
        printf "${BOLD}Content Changes Preview:${NC}\n"
        local has_changes=false
        
        while IFS= read -r line; do
            local status="${line:0:2}"
            local filename="${line:3}"
            
            if [[ "$status" =~ M ]]; then
                has_changes=true
                printf "\n${CYAN}📄 %s:${NC}\n" "$filename"
                git diff --no-color "$filename" 2>/dev/null | head -10 | while IFS= read -r diff_line; do
                    if [[ "$diff_line" =~ ^- ]]; then
                        printf "  ${RED}%s${NC}\n" "$diff_line"
                    elif [[ "$diff_line" =~ ^+ ]]; then
                        printf "  ${GREEN}%s${NC}\n" "$diff_line"
                    elif [[ "$diff_line" =~ ^@@ ]]; then
                        printf "  ${BLUE}%s${NC}\n" "$diff_line"
                    fi
                done
            elif [[ "$status" =~ ^\?\? ]]; then
                has_changes=true
                printf "\n${GREEN}📄 %s (new file):${NC}\n" "$filename"
                printf "  ${GREEN}+ This is a new file that will be added${NC}\n"
            fi
        done <<< "$git_status"
        
        if [[ "$has_changes" == false ]]; then
            printf "  ${YELLOW}(Only deletions detected - no content preview available)${NC}\n"
        fi
    fi
    
    printf "\n${BOLD}${CYAN}═══════════════════════════════════════════════════════════════${NC}\n\n"
    
    # Cleanup - go back to original directory and remove temp
    cd "$REPO_DIR" || cd /
    rm -rf "$temp_base_dir"
    
    if [[ -n "$git_status" ]]; then
        printf "${BOLD}To apply these changes, run:${NC} %s\n" "$(basename "$0")"
        printf "${BOLD}To skip git operations:${NC} %s --no-git\n\n" "$(basename "$0")"
    fi
}

# Sync function with improved error handling
sync_item() {
    local config="$1"
    local source_path target_path sync_type local_patterns
    
    IFS=':' read -r source_path target_path sync_type local_patterns <<< "$config"
    
    if [[ ! -e "$source_path" ]]; then
        log_warning "Source path does not exist: $source_path"
        return 0
    fi
    
    if [[ -n "$local_patterns" ]]; then
        log_info "Syncing $source_path -> $target_path (with local patterns: $local_patterns)"
    else
        log_info "Syncing $source_path -> $target_path"
    fi
    
    # Remove target before copying
    if [[ -e "$target_path" ]]; then
        rm -rf "$target_path"
    fi
    
    case "$sync_type" in
        "dir")
            if [[ ! -d "$source_path" ]]; then
                log_error "$source_path is not a directory"
                return 1
            fi
            safe_copy "$source_path" "$target_path" true "$local_patterns"
            ;;
        "file")
            if [[ ! -f "$source_path" ]]; then
                log_error "$source_path is not a file"
                return 1
            fi
            safe_copy "$source_path" "$target_path" false "$local_patterns"
            ;;
        *)
            log_error "Unknown sync type: $sync_type"
            return 1
            ;;
    esac
    
    log_success "Synced $source_path"
}

# Enhanced git operations with better error handling
gitops() {
    log_info "Checking git repository status..."
    
    if [[ ! -d "$REPO_DIR" ]]; then
        log_error "Repository directory does not exist: $REPO_DIR"
        return 1
    fi
    
    cd "$REPO_DIR" || {
        log_error "Failed to change to repository directory"
        return 1
    }
    
    if [[ ! -d ".git" ]]; then
        log_error "Not a git repository: $REPO_DIR"
        return 1
    fi
    
    if [[ -z "$(git status --porcelain)" ]]; then
        log_info "No changes detected in repository"
        return 0
    fi
    
    log_info "Changes detected in repository:"
    git status --short
    
    echo
    read -p "Continue with committing? (Y/n) >> " confirm
    if [[ "$confirm" =~ ^[nN]([oO])?$ ]]; then
        log_info "Commit cancelled by user"
        return 0
    fi
    
    read -p "Commit message (leave empty for automatic) >> " commit_message
    if [[ -z "$commit_message" ]]; then
        commit_message="auto sync $(date '+%Y-%m-%d %H:%M')"
    fi
    
    git add -A
    git commit -m "$commit_message"
    log_success "Changes committed: $commit_message"
    
    echo
    read -p "Continue with pushing? (Y/n) >> " confirm
    if [[ "$confirm" =~ ^[nN]([oO])?$ ]]; then
        log_info "Push cancelled by user"
        return 0
    fi
    
    git push origin main
    log_success "Changes pushed to remote repository"
}

# Validation function
validate_environment() {
    local errors=0
    
    if [[ ! -d "$REPO_DIR" ]]; then
        log_error "Repository directory does not exist: $REPO_DIR"
        ((errors++))
    fi
    
    if ! command -v rsync &> /dev/null; then
        log_error "rsync is required but not installed"
        ((errors++))
    fi
    
    if ! command -v git &> /dev/null; then
        log_error "git is required but not installed"
        ((errors++))
    fi
    
    return $errors
}

# Show colorized usage information
show_usage() {
    printf "${BOLD}${CYAN}Enhanced Dotfiles Sync Utility${NC}\n\n"
    
    printf "${BOLD}USAGE:${NC}\n"
    printf "    ${WHITE}%s${NC} ${YELLOW}[OPTIONS]${NC}\n\n" "$(basename "$0")"
    
    printf "${BOLD}OPTIONS:${NC}\n"
    printf "    ${GREEN}-h, --help${NC}             Show this help message\n"
    printf "    ${GREEN}-n, --dry-run${NC}          Show what would be synced without actually doing it\n"
    printf "    ${GREEN}--no-git${NC}               Skip git operations\n"
    printf "    ${GREEN}--list-blacklist${NC}       Show current blacklist patterns\n\n"
    
    printf "${BOLD}EXAMPLES:${NC}\n"
    printf "    ${WHITE}%s${NC}                    ${YELLOW}# Normal sync with git operations${NC}\n" "$(basename "$0")"
    printf "    ${WHITE}%s${NC} ${GREEN}--dry-run${NC}          ${YELLOW}# Preview what would be synced (detailed)${NC}\n" "$(basename "$0")"
    printf "    ${WHITE}%s${NC} ${GREEN}--no-git${NC}           ${YELLOW}# Sync files but skip git operations${NC}\n" "$(basename "$0")"
    printf "    ${WHITE}%s${NC} ${GREEN}--list-blacklist${NC}   ${YELLOW}# Show current blacklist patterns${NC}\n\n" "$(basename "$0")"
    
    printf "${BOLD}DESCRIPTION:${NC}\n"
    printf "    This script syncs dotfiles between your local system and a git repository.\n"
    printf "    It supports flexible sync configurations with blacklist patterns to exclude\n"
    printf "    unwanted files like logs, temporary files, and system-specific data.\n\n"
    
    printf "${BOLD}CONFIGURATION:${NC}\n"
    printf "    ${CYAN}Repository Directory:${NC} ${PURPLE}%s${NC}\n" "$REPO_DIR"
    printf "    ${CYAN}Config Directory:${NC}     ${PURPLE}%s${NC}\n\n" "$CONFIG_DIR"
    
    printf "${BOLD}FEATURES:${NC}\n"
    printf "    • ${GREEN}✓${NC} Flexible sync configurations for files and directories\n"
    printf "    • ${GREEN}✓${NC} Global and per-directory blacklist patterns\n"
    printf "    • ${GREEN}✓${NC} Automatic git operations with user confirmation\n"
    printf "    • ${GREEN}✓${NC} Dry-run mode to preview changes\n"
    printf "    • ${GREEN}✓${NC} Colored output for better readability\n"
    printf "    • ${GREEN}✓${NC} Comprehensive error handling and validation\n\n"
    
    printf "For more information, run with ${GREEN}--list-blacklist${NC} to see current patterns.\n"
}

# Main function
main() {
    local dry_run=false
    local no_git=false
    
    # Parse command line arguments
    while [[ $# -gt 0 ]]; do
        case $1 in
            -h|--help)
                show_usage
                exit 0
                ;;
            -n|--dry-run)
                dry_run=true
                shift
                ;;
            --no-git)
                no_git=true
                shift
                ;;
            --list-blacklist)
                printf "${BOLD}${CYAN}Global blacklist patterns:${NC}\n"
                printf '  %s\n' "${GLOBAL_BLACKLIST_PATTERNS[@]}"
                printf "\n"
                printf "${BOLD}${CYAN}Per-directory blacklist patterns:${NC}\n"
                for config in "${SYNC_CONFIGS[@]}"; do
                    local source_path target_path sync_type local_patterns
                    IFS=':' read -r source_path target_path sync_type local_patterns <<< "$config"
                    if [[ -n "$local_patterns" && "$sync_type" == "dir" ]]; then
                        printf "  ${YELLOW}%s:${NC} %s\n" "$source_path" "$local_patterns"
                    fi
                done
                exit 0
                ;;
            *)
                log_error "Unknown option: $1"
                echo
                show_usage
                exit 1
                ;;
        esac
    done
    
    log_info "Starting dotfiles sync..."
    
    # Validate environment
    if ! validate_environment; then
        log_error "Environment validation failed"
        exit 1
    fi
    
    if [[ "$dry_run" == true ]]; then
        printf "${BOLD}${YELLOW}DRY RUN MODE${NC} - No files will be modified\n\n"
        
        # Show detailed preview first
        printf "${BOLD}${CYAN}Detailed Preview:${NC}\n\n"
        for config in "${SYNC_CONFIGS[@]}"; do
            show_dry_run_preview "$config"
        done
        
        # Generate comprehensive summary
        show_dry_run_summary
        
        exit 0
    fi
    
    # Perform sync operations
    local sync_errors=0
    for config in "${SYNC_CONFIGS[@]}"; do
        if ! sync_item "$config"; then
            ((sync_errors++))
        fi
    done
    
    if [[ $sync_errors -gt 0 ]]; then
        log_warning "$sync_errors sync operation(s) failed"
    else
        log_success "All sync operations completed successfully"
    fi
    
    # Git operations
    if [[ "$no_git" != true ]]; then
        if ! gitops; then
            log_error "Git operations failed"
            exit 1
        fi
    else
        log_info "Skipping git operations (--no-git specified)"
    fi
    
    log_success "Dotfiles sync completed!"
}

# Run main function with all arguments
main "$@"
