#!/bin/bash

# ============================================================================
# Claude Code Statusline - Git Integration Module
# ============================================================================
#
# This module handles all git-related operations including repository status,
# commit counting, branch detection, and submodule management.
#
# Error Suppression Patterns (Issue #76):
# - git rev-parse 2>/dev/null: May fail in non-git directories (expected)
# - git branch/log/config 2>/dev/null: May fail in empty repos or missing configs
# - git submodule 2>/dev/null: May fail if no .gitmodules present
# - grep .gitmodules 2>/dev/null: File may not exist in repos without submodules
# - timeout/gtimeout 2>/dev/null: Cross-platform timeout (GNU vs BSD)
#
# Dependencies: core.sh, security.sh
# ============================================================================

# Prevent multiple includes
[[ "${STATUSLINE_GIT_LOADED:-}" == "true" ]] && return 0
export STATUSLINE_GIT_LOADED=true

# Dependencies will be checked during initialization

# ============================================================================
# GIT REPOSITORY DETECTION
# ============================================================================

# Check if current directory is inside a git repository (with intelligent caching)
is_git_repository() {
    # Use cached result if available (30 second cache - directories rarely change repo status)
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local current_dir=$(pwd)
        local result
        result=$(cache_git_operation "is_repo_${current_dir//\//_}" "$CACHE_DURATION_SHORT" git rev-parse --is-inside-work-tree)
        [[ -n "$result" ]] && [[ "$result" == "true" ]]
    else
        # Fallback to direct check
        git rev-parse --is-inside-work-tree >/dev/null 2>&1
    fi
}

# Get the root directory of the git repository (with intelligent caching)
get_git_root() {
    if is_git_repository; then
        # Cache git root per directory (medium duration - rarely changes)
        if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
            local current_dir=$(pwd)
            cache_git_operation "root_${current_dir//\//_}" "$CACHE_DURATION_MEDIUM" git rev-parse --show-toplevel
        else
            git rev-parse --show-toplevel 2>/dev/null
        fi
    else
        return 1
    fi
}

# Check if current directory is the root of the git repository
is_git_root() {
    local current_dir
    local git_root
    
    current_dir=$(pwd)
    git_root=$(get_git_root)
    
    [[ "$current_dir" == "$git_root" ]]
}

# ============================================================================
# WORKTREE DETECTION
# ============================================================================

# Check if current directory is a git worktree
# Worktrees have .git as a FILE (not directory) containing gitdir reference
is_git_worktree() {
    [[ -f ".git" ]] && grep -q "^gitdir:" ".git" 2>/dev/null
}

# Get the name of the current worktree
# Returns empty string if not in a worktree
get_git_worktree_name() {
    if ! is_git_worktree; then
        echo ""
        return
    fi

    # Extract worktree name from git dir path
    # .git file contains: gitdir: /path/to/repo/.git/worktrees/NAME
    local git_dir
    git_dir=$(git rev-parse --git-dir 2>/dev/null)

    if [[ "$git_dir" == *"/worktrees/"* ]]; then
        basename "$git_dir"
    else
        echo ""
    fi
}

# ============================================================================
# BRANCH INFORMATION
# ============================================================================

# Get current git branch name (with intelligent caching)
get_git_branch() {
    if ! is_git_repository; then
        return 1
    fi
    
    # Use cached result if available (30 second cache - branches change during development)
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local git_root
        git_root=$(get_git_root)
        cache_git_operation "branch_${git_root//\//_}" "$CACHE_DURATION_SHORT" _get_git_branch_direct
    else
        _get_git_branch_direct
    fi
}

# Internal function for direct branch detection (used by caching)
_get_git_branch_direct() {
    # Try multiple methods to get branch name
    local branch=""
    
    # Method 1: git branch (most reliable)
    branch=$(git branch 2>/dev/null | sed -e '/^[^*]/d' -e 's/* \(.*\)/\1/')
    
    # Method 2: git symbolic-ref (fallback for detached HEAD)
    if [[ -z "$branch" ]]; then
        branch=$(git symbolic-ref --short HEAD 2>/dev/null)
    fi
    
    # Method 3: git describe (for detached HEAD)
    if [[ -z "$branch" ]]; then
        branch=$(git describe --exact-match HEAD 2>/dev/null)
    fi
    
    # Method 4: short commit hash (last resort)
    if [[ -z "$branch" ]]; then
        branch=$(git rev-parse --short HEAD 2>/dev/null)
        [[ -n "$branch" ]] && branch="detached:$branch"
    fi
    
    if [[ -n "$branch" ]]; then
        echo "$branch"
        return 0
    else
        return 1
    fi
}

# Check if current branch is the main branch
is_main_branch() {
    local branch
    branch=$(get_git_branch)
    
    case "$branch" in
        "main"|"master"|"trunk"|"develop"|"development")
            return 0
            ;;
        *)
            return 1
            ;;
    esac
}

# ============================================================================
# REPOSITORY STATUS
# ============================================================================

# Get git repository status (clean/dirty)
get_git_status() {
    if ! is_git_repository; then
        echo "not_git"
        return 1
    fi
    
    # Use cached result if available (30 second cache - status changes frequently during development)
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local git_root
        git_root=$(get_git_root)
        cache_git_operation "status_${git_root//\//_}" "$CACHE_DURATION_SHORT" _get_git_status_direct
    else
        _get_git_status_direct
    fi
}

# Internal function for direct status detection (used by caching)
_get_git_status_direct() {
    # Check if repository has any changes
    if git diff --quiet && git diff --cached --quiet; then
        echo "clean"
        return 0
    else
        echo "dirty"
        return 0
    fi
}

# Check if repository has uncommitted changes
has_uncommitted_changes() {
    if ! is_git_repository; then
        return 1
    fi
    
    # Check working directory and staging area
    ! (git diff --quiet && git diff --cached --quiet)
}

# Check if repository has untracked files
has_untracked_files() {
    if ! is_git_repository; then
        return 1
    fi
    
    local untracked_count
    untracked_count=$(git ls-files --others --exclude-standard | wc -l | tr -d ' ')
    [[ "$untracked_count" -gt 0 ]]
}

# Get detailed status information
get_detailed_git_status() {
    if ! is_git_repository; then
        echo "not_git_repo"
        return 1
    fi
    
    local status=""
    local staged_count modified_count untracked_count
    
    # Get counts for different types of changes
    staged_count=$(git diff --cached --name-only | wc -l | tr -d ' ')
    modified_count=$(git diff --name-only | wc -l | tr -d ' ')
    untracked_count=$(git ls-files --others --exclude-standard | wc -l | tr -d ' ')
    
    # Build status string
    if [[ "$staged_count" -gt 0 ]]; then
        status="${status}+${staged_count}"
    fi
    
    if [[ "$modified_count" -gt 0 ]]; then
        [[ -n "$status" ]] && status="${status} "
        status="${status}~${modified_count}"
    fi
    
    if [[ "$untracked_count" -gt 0 ]]; then
        [[ -n "$status" ]] && status="${status} "
        status="${status}?${untracked_count}"
    fi
    
    if [[ -z "$status" ]]; then
        echo "clean"
    else
        echo "$status"
    fi
}

# ============================================================================
# COMMIT INFORMATION
# ============================================================================

# Get total commit count in current branch
get_total_commit_count() {
    if ! is_git_repository; then
        echo "0"
        return 1
    fi
    
    # Use caching for expensive commit counting operation
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local cache_key
        cache_key=$(generate_typed_cache_key "total_commit_count" "git")
        cache_git_operation "$cache_key" "$CACHE_DURATION_MEDIUM" git rev-list --count HEAD
    else
        git rev-list --count HEAD 2>/dev/null || echo "0"
    fi
}

# Get commits count since a specific date
get_commits_since() {
    local since_date="$1"
    
    if ! is_git_repository; then
        echo "0"
        return 1
    fi
    
    if [[ -z "$since_date" ]]; then
        echo "0"
        return 1
    fi
    
    # Use caching for date-based commit counting with date in cache key
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local cache_key
        local sanitized_date="${since_date// /_}"  # Replace spaces with underscores
        sanitized_date="${sanitized_date//:/_}"    # Replace colons with underscores
        cache_key=$(generate_typed_cache_key "commits_since_${sanitized_date}" "git")
        cache_git_operation "$cache_key" "$CACHE_DURATION_SHORT" bash -c "git log --since='$since_date' --oneline 2>/dev/null | wc -l | tr -d ' '"
    else
        git log --since="$since_date" --oneline 2>/dev/null | wc -l | tr -d ' '
    fi
}

# Get commits today
get_commits_today() {
    get_commits_since "today 00:00"
}

# Get commits this week
get_commits_this_week() {
    get_commits_since "1 week ago"
}

# Get last commit information
get_last_commit_info() {
    if ! is_git_repository; then
        return 1
    fi
    
    local commit_hash commit_date commit_message
    
    commit_hash=$(git rev-parse --short HEAD 2>/dev/null)
    commit_date=$(git log -1 --format=%cd --date=relative 2>/dev/null)
    commit_message=$(git log -1 --format=%s 2>/dev/null)
    
    if [[ -n "$commit_hash" ]]; then
        echo "${commit_hash} (${commit_date}): ${commit_message}"
        return 0
    else
        return 1
    fi
}

# ============================================================================
# SUBMODULE MANAGEMENT
# ============================================================================

# Check if repository has submodules
has_submodules() {
    # Use caching for submodule detection (rarely changes)
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local cache_key
        cache_key=$(generate_typed_cache_key "has_submodules" "git")
        local result
        result=$(cache_git_operation "$cache_key" "$CACHE_DURATION_LONG" bash -c '[[ -f .gitmodules ]] && echo "true" || echo "false"')
        [[ "$result" == "true" ]]
    else
        [[ -f .gitmodules ]]
    fi
}

# Get submodule count
get_submodule_count() {
    if ! is_git_repository || ! has_submodules; then
        echo "0"
        return 0
    fi
    
    # Use caching for submodule count (rarely changes)
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local cache_key
        cache_key=$(generate_typed_cache_key "submodule_count" "git")
        cache_git_operation "$cache_key" "$CACHE_DURATION_LONG" bash -c 'grep -c "^\[submodule " .gitmodules 2>/dev/null || echo "0"'
    else
        grep -c "^\[submodule " .gitmodules 2>/dev/null || echo "0"
    fi
}

# Get submodule status
get_submodule_status() {
    if ! is_git_repository; then
        echo "${CONFIG_SUBMODULE_LABEL}${CONFIG_NO_SUBMODULES}"
        return 1
    fi
    
    if ! has_submodules; then
        echo "${CONFIG_SUBMODULE_LABEL}${CONFIG_NO_SUBMODULES}"
        return 0
    fi
    
    local total_submodules
    total_submodules=$(get_submodule_count)
    
    if [[ "$total_submodules" == "0" ]]; then
        echo "${CONFIG_SUBMODULE_LABEL}${CONFIG_NO_SUBMODULES}"
    else
        echo "${CONFIG_SUBMODULE_LABEL}${total_submodules}"
    fi
}

# Get detailed submodule information
get_detailed_submodule_info() {
    if ! is_git_repository || ! has_submodules; then
        return 1
    fi
    
    # Use caching for expensive submodule status operation
    if [[ "${STATUSLINE_CACHE_LOADED:-}" == "true" ]]; then
        local cache_key
        cache_key=$(generate_typed_cache_key "submodule_info" "git")
        cache_git_operation "$cache_key" "$CACHE_DURATION_SHORT" git submodule status
    else
        git submodule status 2>/dev/null
    fi
}

# Check if submodules are up to date
are_submodules_updated() {
    if ! is_git_repository || ! has_submodules; then
        return 0 # No submodules means they're "updated"
    fi
    
    # Check if any submodule is out of sync
    local submodule_status
    submodule_status=$(git submodule status 2>/dev/null)
    
    # Look for submodules that are not up to date (indicated by '+', '-', or 'U' prefixes)
    if echo "$submodule_status" | grep -q '^[+-U]'; then
        return 1 # Submodules are not up to date
    else
        return 0 # All submodules are up to date
    fi
}

# ============================================================================
# REMOTE INFORMATION
# ============================================================================

# Get remote repository URL
get_remote_url() {
    if ! is_git_repository; then
        return 1
    fi
    
    git config --get remote.origin.url 2>/dev/null
}

# Check if local branch is ahead/behind remote
get_remote_status() {
    if ! is_git_repository; then
        return 1
    fi
    
    local branch
    branch=$(get_git_branch)
    
    if [[ -z "$branch" ]]; then
        return 1
    fi
    
    # Fetch remote information (silently)
    git fetch --quiet 2>/dev/null || return 1
    
    local ahead behind
    ahead=$(git rev-list --count HEAD ^origin/"$branch" 2>/dev/null || echo "0")
    behind=$(git rev-list --count ^HEAD origin/"$branch" 2>/dev/null || echo "0")
    
    if [[ "$ahead" -gt 0 && "$behind" -gt 0 ]]; then
        echo "diverged:+${ahead}/-${behind}"
    elif [[ "$ahead" -gt 0 ]]; then
        echo "ahead:+${ahead}"
    elif [[ "$behind" -gt 0 ]]; then
        echo "behind:-${behind}"
    else
        echo "up-to-date"
    fi
}

# ============================================================================
# GIT UTILITIES
# ============================================================================

# Safe git command execution with timeout
execute_git_command() {
    local timeout_duration="${1:-5s}"
    shift
    local git_command=("$@")
    
    if ! command_exists git; then
        handle_error "Git command not available" 1 "execute_git_command"
        return 1
    fi
    
    # Execute git command with timeout if available
    if command_exists timeout; then
        timeout "$timeout_duration" git "${git_command[@]}" 2>/dev/null
    elif command_exists gtimeout; then
        gtimeout "$timeout_duration" git "${git_command[@]}" 2>/dev/null
    else
        git "${git_command[@]}" 2>/dev/null
    fi
}

# Get git configuration value
get_git_config() {
    local config_key="$1"
    local default_value="${2:-}"
    
    if ! is_git_repository; then
        echo "$default_value"
        return 1
    fi
    
    local config_value
    config_value=$(git config --get "$config_key" 2>/dev/null)
    
    if [[ -n "$config_value" ]]; then
        echo "$config_value"
    else
        echo "$default_value"
    fi
}

# Check if git repository is bare
is_bare_repository() {
    [[ "$(git config --bool core.bare 2>/dev/null)" == "true" ]]
}

# ============================================================================
# DISPLAY FORMATTING
# ============================================================================

# Format git status for display
format_git_status_display() {
    local git_status
    git_status=$(get_git_status)
    
    case "$git_status" in
        "clean")
            echo "${CONFIG_CLEAN_STATUS_EMOJI}"
            ;;
        "dirty")
            echo "${CONFIG_DIRTY_STATUS_EMOJI}"
            ;;
        "not_git")
            echo ""
            ;;
        *)
            echo "${CONFIG_DIRTY_STATUS_EMOJI}"
            ;;
    esac
}

# Get git branch with color formatting
get_formatted_git_branch() {
    local branch
    branch=$(get_git_branch)
    
    if [[ -z "$branch" ]]; then
        return 1
    fi
    
    local git_status
    git_status=$(get_git_status)
    
    case "$git_status" in
        "clean")
            echo "${CONFIG_GREEN}${branch}${CONFIG_RESET}"
            ;;
        "dirty")
            echo "${CONFIG_YELLOW}${branch}${CONFIG_RESET}"
            ;;
        *)
            echo "${CONFIG_MAGENTA}${branch}${CONFIG_RESET}"
            ;;
    esac
}

# ============================================================================
# MODULE INITIALIZATION
# ============================================================================

# Initialize the git module
init_git_module() {
    debug_log "Git integration module initialized" "INFO"
    
    # Check if git is available
    if ! command_exists git; then
        handle_warning "Git command not available" "init_git_module"
        return 1
    fi
    
    # Log git version for debugging
    local git_version
    git_version=$(git --version 2>/dev/null)
    debug_log "Git version: $git_version" "INFO"
    
    return 0
}

# Initialize the module (skip during testing to allow sourcing without side effects)
if [[ "${STATUSLINE_TESTING:-}" != "true" ]]; then
    init_git_module
fi

# Issue #134: Only export functions used in cached subshells
# Internal functions called via cache_git_operation need export
export -f _get_git_branch_direct _get_git_status_direct