#!/bin/bash

# Commit Message Amendment Script
# Intelligently analyze and amend git commit messages
#
# ARCHITECTURE AND MOTIVATION
# ===========================
#
# This script is designed to gather comprehensive git repository information 
# into a single, structured YAML document to assist AI assistants and automated
# tools in understanding the context needed for commit message amendment without
# requiring multiple individual git command executions.
#
# PRIMARY MOTIVATION:
# Instead of AI assistants running dozens of separate git commands like:
# - git status --porcelain
# - git remote -v  
# - git log --oneline
# - git show --name-status <commit>
# - git merge-base --is-ancestor <commit> <branch>
# - git rev-parse <commit>
# - git diff-tree --name-status <commit>
#
# This script aggregates all relevant information into one comprehensive output,
# reducing API calls, improving performance, and providing consistent data structure.
#
# ARCHITECTURAL DESIGN PRINCIPLES:
#
# 1. INFORMATION AGGREGATION
#    - Single command provides complete repository context
#    - Structured YAML output for easy parsing
#    - Self-documenting with field explanations
#
# 2. SUBCOMMAND ARCHITECTURE  
#    - 'view': Read-only analysis and information gathering
#    - 'amend': Write operations for commit message changes
#    - Clear separation between read and write operations
#
# 3. NON-INTERACTIVE DESIGN
#    - Fully automated operation suitable for CI/CD and scripts
#    - No user prompts or interactive elements
#    - Predictable behavior for programmatic usage
#
# 4. COMPREHENSIVE CONTEXT
#    - Working directory status (clean/dirty, untracked files)
#    - Remote repository information and main branch detection
#    - Per-commit analysis including file changes and conventional commit detection
#    - Cross-reference commits against remote main branches
#
# 5. VALIDATION AND SAFETY
#    - File-based commit message amendment with hash validation
#    - Detection of already-pushed commits
#    - Skip merge commits and empty commits
#    - Preserve commit metadata and trailers
#
# 6. EXTENSIBILITY
#    - Modular function design for easy enhancement
#    - JSON intermediate format for complex data structures
#    - Support for multiple remotes and GitHub API integration
#
# DATA FLOW:
# 1. Parse subcommand and arguments
# 2. Validate git repository and prerequisites  
# 3. Gather repository context (remotes, working directory status)
# 4. Process commit range and analyze each commit
# 5. Cross-reference commits against remote branches
# 6. Output structured YAML with comprehensive information
#
# This architecture enables AI assistants to make informed decisions about
# commit message amendments with a single command invocation rather than
# iterative information gathering through multiple git commands.
#
# YAML AMENDMENT FILE FORMAT:
# ==========================
#
# The amend subcommand accepts a single YAML file containing an array of amendments.
# Each amendment specifies a commit hash and the new message to apply.
#
# Required structure:
#   amendments:
#     - commit: "full-commit-hash"
#       message: "New commit message"
#     - commit: "another-full-commit-hash"  
#       message: "Another new commit message"
#
# Field specifications:
# - amendments: Root array containing all commit amendments
# - commit: Full SHA-1 commit hash (40 characters) - REQUIRED
# - message: New commit message text, can be multiline - REQUIRED
#
# Example YAML file (amendments.yml):
#   amendments:
#     - commit: "a1b2c3d4e5f6789012345678901234567890abcd"
#       message: "feat(api): add user authentication endpoint
#   
#   Implement OAuth 2.0 authentication with JWT tokens:
#   - Add login and logout endpoints
#   - Implement token validation middleware
#   - Add user session management"
#     - commit: "f9e8d7c6b5a4321098765432109876543210fedc"
#       message: "fix(ui): resolve responsive layout issues
#   
#   Fix mobile display problems in navigation bar."
#
# Safety notes:
# - All commit hashes must exist in the current repository
# - Commits already in remote main branches will be rejected
# - Merge commits and empty commits are automatically skipped
# - YAML must be valid and parseable by yq tool

set -euo pipefail

# Colors for output
readonly RED='\033[0;31m'
readonly GREEN='\033[0;32m'
readonly YELLOW='\033[1;33m'
readonly BLUE='\033[0;34m'
readonly NC='\033[0m' # No Color

# Function to print colored output
print_info() { echo -e "${BLUE}[INFO]${NC} $1" >&2; }
print_warn() { echo -e "${YELLOW}[WARN]${NC} $1" >&2; }
print_error() { echo -e "${RED}[ERROR]${NC} $1" >&2; }
print_success() { echo -e "${GREEN}[SUCCESS]${NC} $1" >&2; }

# Function to show usage
show_usage() {
    cat << EOF
Usage: $0 [SUBCOMMAND] [arguments...]

Intelligently analyze and amend git commit messages.

Subcommands:
  view [commit-range]              Output commit analysis in YAML format
  amend <yaml-file>                Amend commits with messages from YAML file

Arguments:
  commit-range    Commit range. Examples: HEAD~3..HEAD, abc123..def456
  yaml-file       YAML file with amendments array containing commit hashes and messages

YAML File Format:
  amendments:
    - commit: "full-commit-hash-1"
      message: "New commit message 1"
    - commit: "full-commit-hash-2"  
      message: "New commit message 2"

Examples:
  $0 view HEAD~3..HEAD             # View changes in YAML format  
  $0 amend amendments.yml          # Amend commits using YAML file

Options:
  -h, --help      Show this help message
EOF
}

# Function to check if we're in a git repository
check_git_repo() {
    if ! git rev-parse --git-dir >/dev/null 2>&1; then
        print_error "Not in a git repository"
        exit 1
    fi
}

# Function to check for untracked changes
check_working_directory() {
    local status_output
    status_output=$(git status --porcelain 2>/dev/null || echo "")
    
    if [[ -n "$status_output" ]]; then
        print_error "Working directory is not clean. Please commit or stash changes before amending commit messages."
        echo "Untracked/modified files:"
        echo "$status_output" | sed 's/^/  /'
        echo
        print_info "Use 'git add' and 'git commit' to save changes, or 'git stash' to temporarily store them."
        exit 1
    fi
}

# Function to check if commits are already pushed
check_if_pushed() {
    local commits=("$@")
    local pushed_commits=()
    
    for commit in "${commits[@]}"; do
        if git merge-base --is-ancestor "$commit" origin/HEAD 2>/dev/null || 
           git merge-base --is-ancestor "$commit" origin/main 2>/dev/null || 
           git merge-base --is-ancestor "$commit" origin/master 2>/dev/null; then
            pushed_commits+=("$commit")
        fi
    done
    
    if [ ${#pushed_commits[@]} -gt 0 ]; then
        print_warn "The following commits appear to be already pushed:"
        for commit in "${pushed_commits[@]}"; do
            echo "  $(git log --oneline -1 "$commit")"
        done
        echo
        print_warn "Rewriting history of pushed commits can cause issues for other developers."
        print_warn "Continuing automatically - use with caution!"
    fi
}

# Function to detect sed variant and set command
detect_sed() {
    if command -v gsed >/dev/null 2>&1; then
        echo "gsed"
    elif sed --version 2>/dev/null | grep -q "GNU"; then
        echo "sed"
    else
        # BSD sed (macOS default)
        echo "sed"
    fi
}

# Function to check required tools for --view mode
check_yaml_tools() {
    local missing_tools=()
    
    if ! command -v yq >/dev/null 2>&1; then
        missing_tools+=("yq")
    fi
    
    if ! command -v jq >/dev/null 2>&1; then
        missing_tools+=("jq")
    fi
    
    if [[ ${#missing_tools[@]} -gt 0 ]]; then
        print_error "Missing required tools for --view mode: ${missing_tools[*]}"
        echo "Please install the missing tools:"
        for tool in "${missing_tools[@]}"; do
            case "$tool" in
                "yq")
                    echo "  - yq: https://github.com/mikefarah/yq#install"
                    echo "    macOS: brew install yq"
                    echo "    Linux: see installation guide above"
                    ;;
                "jq")
                    echo "  - jq: https://jqlang.github.io/jq/download/"
                    echo "    macOS: brew install jq"
                    echo "    Linux: apt install jq / yum install jq"
                    ;;
            esac
        done
        exit 1
    fi
}

# Function to output commit analysis in JSON format (converted to YAML)
output_commit_yaml() {
    local commit="$1"
    local remotes_json="${2:-[]}"
    local commit_hash commit_author commit_date commit_msg
    local commit_type scope proposed_msg files_changed files_added files_deleted
    local files_status diff_stats file_list_json
    
    # Get commit metadata
    commit_hash=$(git rev-parse "$commit")
    commit_author=$(git log --format="%an <%ae>" -n 1 "$commit")
    commit_date=$(git log --format="%ci" -n 1 "$commit")
    commit_msg=$(git log --format=%B -n 1 "$commit")
    
    # Get file analysis
    commit_type=$(analyze_commit_type "$commit")
    scope=$(analyze_scope "$commit")
    proposed_msg=$(generate_commit_message "$commit")
    
    # Get file statistics
    files_status=$(git diff-tree --no-commit-id --name-status "$commit" 2>/dev/null || echo "")
    files_changed=$(echo "$files_status" | grep -v "^$" | wc -l | tr -d ' ')
    files_added=$(echo "$files_status" | grep "^A" | wc -l | tr -d ' ')
    files_deleted=$(echo "$files_status" | grep "^D" | wc -l | tr -d ' ')
    diff_stats=$(git show --stat=80 --pretty=format: "$commit" 2>/dev/null | grep -v "^$" || echo "")
    
    # Build file list as JSON array
    file_list_json="[]"
    if [[ -n "$files_status" ]]; then
        file_list_json=$(echo "$files_status" | jq -R -s 'split("\n") | map(select(. != "")) | map(split("\t")) | map({"status": .[0], "file": .[1]})')
    fi
    
    # Check if commit is in remote main branches
    local in_main_branches="[]"
    if [[ "$remotes_json" != "[]" ]]; then
        local remotes_array
        remotes_array=$(echo "$remotes_json" | jq -r '.[] | @base64')
        
        while IFS= read -r remote_data; do
            [[ -n "$remote_data" ]] || continue
            local remote_name remote_main_branch
            remote_name=$(echo "$remote_data" | base64 --decode | jq -r '.name')
            remote_main_branch=$(echo "$remote_data" | base64 --decode | jq -r '.main_branch')
            
            if [[ "$remote_main_branch" != "unknown" && "$remote_main_branch" != "null" ]]; then
                # Check if commit is an ancestor of the remote main branch
                if git merge-base --is-ancestor "$commit" "refs/remotes/$remote_name/$remote_main_branch" 2>/dev/null; then
                    local branch_ref="$remote_name/$remote_main_branch"
                    in_main_branches=$(echo "$in_main_branches" | jq ". + [\"$branch_ref\"]")
                fi
            fi
        done <<< "$remotes_array"
    fi
    
    # Create JSON object and convert to YAML
    jq -n \
        --arg hash "$commit_hash" \
        --arg author "$commit_author" \
        --arg date "$commit_date" \
        --arg original_msg "$commit_msg" \
        --arg detected_type "$commit_type" \
        --arg detected_scope "$scope" \
        --arg proposed_msg "$proposed_msg" \
        --argjson total_files "$files_changed" \
        --argjson files_added "$files_added" \
        --argjson files_deleted "$files_deleted" \
        --argjson file_list "$file_list_json" \
        --argjson in_main_branches "$in_main_branches" \
        --arg diff_summary "$diff_stats" \
        '{
            commit: {
                hash: $hash,
                author: $author,
                date: $date,
                original_message: $original_msg,
                in_main_branches: $in_main_branches,
                analysis: {
                    detected_type: $detected_type,
                    detected_scope: $detected_scope,
                    proposed_message: $proposed_msg,
                    file_changes: {
                        total_files: $total_files,
                        files_added: $files_added,
                        files_deleted: $files_deleted,
                        file_list: $file_list
                    },
                    diff_summary: $diff_summary
                }
            }
        }' | yq eval -P -
}

# Function to analyze file changes and determine commit type
analyze_commit_type() {
    local commit="$1"
    local files_status
    files_status=$(git show --name-status "$commit" 2>/dev/null || echo "")
    
    if [[ -z "$files_status" ]]; then
        echo "chore"
        return
    fi
    
    # Check for different patterns to determine commit type
    if echo "$files_status" | grep -q "test\|spec"; then
        echo "test"
    elif echo "$files_status" | grep -q "\.md$\|README\|CHANGELOG\|LICENSE\|docs/"; then
        echo "docs"
    elif echo "$files_status" | grep -q "Cargo\.toml\|package\.json\|\.toml$\|config"; then
        echo "config"
    elif echo "$files_status" | grep -q "^A"; then
        echo "feat"
    elif echo "$files_status" | grep -q "^M.*\.\(rs\|js\|ts\|py\|go\)$"; then
        # Check if it's a bug fix by looking at diff
        local diff_content
        diff_content=$(git show "$commit" 2>/dev/null || echo "")
        if echo "$diff_content" | grep -qi "fix\|bug\|error\|issue\|problem"; then
            echo "fix"
        else
            echo "refactor"
        fi
    elif echo "$files_status" | grep -q "^D"; then
        echo "refactor"
    else
        echo "chore"
    fi
}

# Function to determine scope from files changed
analyze_scope() {
    local commit="$1"
    local files_status
    files_status=$(git show --name-only "$commit" 2>/dev/null || echo "")
    
    if [[ -z "$files_status" ]]; then
        echo ""
        return
    fi
    
    # Analyze file paths to determine scope
    if echo "$files_status" | grep -q "^src/commands/"; then
        echo "commands"
    elif echo "$files_status" | grep -q "^src/secret_types/"; then
        echo "types"
    elif echo "$files_status" | grep -q "^src/config"; then
        echo "config"
    elif echo "$files_status" | grep -q "^tests/"; then
        echo "tests"
    elif echo "$files_status" | grep -q "^docs/"; then
        echo "docs"
    elif echo "$files_status" | grep -q "Cargo\.toml\|deny\.toml"; then
        echo "deps"
    elif echo "$files_status" | grep -q "\.md$"; then
        echo "docs"
    elif echo "$files_status" | grep -q "^scripts/\|\.sh$"; then
        echo "scripts"
    else
        echo ""
    fi
}

# Function to generate commit message based on changes
generate_commit_message() {
    local commit="$1"
    local commit_type scope description
    
    # Get current commit message (without metadata)
    local current_msg
    current_msg=$(git log --format=%B -n 1 "$commit" | head -n 1)
    
    # Check if current message is already well-formatted
    if [[ "$current_msg" =~ ^(feat|fix|docs|style|refactor|test|chore)(\(.+\))?:\ .+ ]]; then
        # Return the existing well-formatted message
        echo "$current_msg"
        return
    fi
    
    commit_type=$(analyze_commit_type "$commit")
    scope=$(analyze_scope "$commit")
    
    # Get file changes summary
    local files_changed files_added files_deleted
    files_changed=$(git show --name-only "$commit" | wc -l | tr -d ' ')
    files_added=$(git show --name-status "$commit" | grep "^A" | wc -l | tr -d ' ')
    files_deleted=$(git show --name-status "$commit" | grep "^D" | wc -l | tr -d ' ')
    
    # Analyze the diff to understand what was done
    local diff_content
    diff_content=$(git show "$commit" --stat=80 2>/dev/null || echo "")
    
    # Generate description based on changes
    case "$commit_type" in
        "feat")
            if [[ -n "$scope" ]]; then
                description="add new functionality to $scope"
            else
                description="add new functionality"
            fi
            ;;
        "fix")
            if [[ -n "$scope" ]]; then
                description="resolve issues in $scope"
            else
                description="resolve critical issues"
            fi
            ;;
        "refactor")
            if [[ -n "$scope" ]]; then
                description="improve $scope implementation"
            else
                description="improve code structure and organization"
            fi
            ;;
        "docs")
            description="update documentation and examples"
            ;;
        "test")
            if [[ -n "$scope" ]]; then
                description="enhance $scope test coverage"
            else
                description="improve test coverage and reliability"
            fi
            ;;
        "config"|"chore")
            if [[ -n "$scope" ]]; then
                description="update $scope configuration"
            else
                description="update project configuration"
            fi
            ;;
        *)
            description="update project files"
            ;;
    esac
    
    # Construct the commit message
    if [[ -n "$scope" ]]; then
        echo "${commit_type}(${scope}): ${description}"
    else
        echo "${commit_type}: ${description}"
    fi
}

# Function to amend a single commit
amend_single_commit() {
    local commit="$1"
    local is_head_commit="$2"
    
    print_info "Analyzing commit: $(git log --oneline -1 "$commit")"
    
    # Check if it's a merge commit
    local parent_count
    parent_count=$(git cat-file -p "$commit" | grep "^parent" | wc -l | tr -d ' ')
    if [[ "$parent_count" -gt 1 ]]; then
        print_info "Skipping merge commit: $commit"
        return 0
    fi
    
    # Check if it's an empty commit
    if git diff-tree --no-commit-id --name-only "$commit" | wc -l | grep -q "^0$"; then
        print_info "Skipping empty commit: $commit"
        return 0
    fi
    
    # Generate new commit message
    local new_message
    new_message=$(generate_commit_message "$commit")
    
    # Get current full message with metadata
    local current_full_msg
    current_full_msg=$(git log --format=%B -n 1 "$commit")
    
    # Extract metadata (everything after the first blank line)
    local metadata
    metadata=$(echo "$current_full_msg" | sed -n '/^$/,$p' | tail -n +2)
    
    # Construct full new message
    local full_new_message="$new_message"
    if [[ -n "$metadata" ]]; then
        full_new_message="$new_message

$metadata"
    fi
    
    echo
    print_info "Current message:"
    echo "$current_full_msg" | sed 's/^/  /'
    echo
    print_info "Proposed message:"
    echo "$full_new_message" | sed 's/^/  /'
    echo
    
    echo -n "Apply this message? (y/N): "
    read -r response
    
    if [[ "$response" =~ ^[Yy]$ ]]; then
        if [[ "$is_head_commit" == "true" ]]; then
            # Amend HEAD directly
            git commit --amend -m "$full_new_message"
            print_success "Amended HEAD commit"
        else
            print_error "Non-HEAD commit amendment requires interactive rebase - not implemented yet"
            return 1
        fi
    else
        print_info "Skipping commit $commit"
    fi
}

# Function to process commit range
process_commits() {
    local commit_range="${1:-HEAD}"
    local commits
    
    # Handle different range formats
    if [[ "$commit_range" == "HEAD" ]]; then
        commits=("HEAD")
    elif [[ "$commit_range" =~ \.\. ]]; then
        # Range format like HEAD~3..HEAD or abc123..def456
        local commit_list
        commit_list=$(git rev-list --reverse "$commit_range" 2>/dev/null || {
            print_error "Invalid commit range: $commit_range"
            exit 1
        })
        IFS=$'\n' read -r -d '' -a commits <<< "$commit_list" || true
    else
        # Single commit
        commits=("$commit_range")
    fi
    
    if [[ ${#commits[@]} -eq 0 ]]; then
        print_error "No commits found in range: $commit_range"
        exit 1
    fi
    
    print_info "Found ${#commits[@]} commit(s) to process"
    
    # Check if any commits are already pushed
    check_if_pushed "${commits[@]}"
    
    # Process each commit
    for commit in "${commits[@]}"; do
        local is_head_commit="false"
        if [[ "$commit" == "HEAD" ]] || [[ "$commit" == "$(git rev-parse HEAD)" ]]; then
            is_head_commit="true"
        fi
        
        if ! amend_single_commit "$commit" "$is_head_commit"; then
            print_error "Failed to process commit: $commit"
            exit 1
        fi
    done
    
    print_success "Finished processing commits"
}

# Function to get main branches for all remotes
get_remote_main_branches() {
    local remotes_json="[]"
    
    # Get all remotes
    local remotes
    remotes=$(git remote 2>/dev/null || echo "")
    
    if [[ -n "$remotes" ]]; then
        while IFS= read -r remote; do
            [[ -n "$remote" ]] || continue
            
            local main_branch=""
            local remote_url=""
            
            # Get remote URL
            remote_url=$(git remote get-url "$remote" 2>/dev/null || echo "")
            
            # First try git symbolic-ref
            main_branch=$(git symbolic-ref "refs/remotes/$remote/HEAD" 2>/dev/null | sed 's|refs/remotes/[^/]*/||' || echo "")
            
            # If that fails and it's a GitHub remote, try gh command
            if [[ -z "$main_branch" && "$remote_url" =~ github\.com ]]; then
                local repo
                repo=$(echo "$remote_url" | sed -E 's|.*github\.com[:/]([^/]+/[^/]+)(\.git)?.*|\1|')
                if [[ -n "$repo" ]]; then
                    main_branch=$(gh repo view "$repo" --json defaultBranchRef --jq '.defaultBranchRef.name' 2>/dev/null || echo "")
                fi
            fi
            
            # Final fallback: check common branch names
            if [[ -z "$main_branch" ]]; then
                for branch in main master develop; do
                    if git show-ref --verify --quiet "refs/remotes/$remote/$branch"; then
                        main_branch="$branch"
                        break
                    fi
                done
            fi
            
            # Add to JSON array
            local remote_info
            remote_info=$(jq -n \
                --arg name "$remote" \
                --arg url "$remote_url" \
                --arg main_branch "${main_branch:-unknown}" \
                '{name: $name, url: $url, main_branch: $main_branch}')
            
            remotes_json=$(echo "$remotes_json" | jq ". + [$remote_info]")
        done <<< "$remotes"
    fi
    
    echo "$remotes_json"
}

# Function to process commits in view mode (YAML output)
process_commits_view() {
    local commit_range="${1:-HEAD}"
    local commits commit_data_json
    
    # Handle different range formats
    if [[ "$commit_range" == "HEAD" ]]; then
        commits=("HEAD")
    elif [[ "$commit_range" =~ \.\. ]]; then
        # Range format like HEAD~3..HEAD or abc123..def456
        local commit_list
        commit_list=$(git rev-list --reverse "$commit_range" 2>/dev/null || {
            print_error "Invalid commit range: $commit_range"
            exit 1
        })
        IFS=$'\n' read -r -d '' -a commits <<< "$commit_list" || true
    else
        # Single commit
        commits=("$commit_range")
    fi
    
    if [[ ${#commits[@]} -eq 0 ]]; then
        print_error "No commits found in range: $commit_range"
        exit 1
    fi
    
    # Get remote main branches early for commit analysis  
    local remotes_json
    remotes_json=$(get_remote_main_branches)
    
    # Start building JSON array
    commit_data_json="[]"
    
    # Process each commit
    for commit in "${commits[@]}"; do
        # Skip merge commits in view mode too
        local parent_count
        parent_count=$(git cat-file -p "$commit" | grep "^parent" | wc -l | tr -d ' ')
        if [[ "$parent_count" -gt 1 ]]; then
            continue
        fi
        
        # Skip empty commits
        if git diff-tree --no-commit-id --name-only "$commit" | wc -l | grep -q "^0$"; then
            continue
        fi
        
        # Get commit data as JSON and append to array
        local commit_json
        commit_json=$(output_commit_yaml "$commit" "$remotes_json" | yq eval -o=json -)
        commit_data_json=$(echo "$commit_data_json" | jq ". + [$commit_json]")
    done
    
    # Get working directory status
    local status_output
    status_output=$(git status --porcelain 2>/dev/null || echo "")
    
    # Build final output with working directory status and remotes
    local final_json
    if [[ -n "$status_output" ]]; then
        local untracked_files clean_status
        untracked_files=$(echo "$status_output" | jq -R -s 'split("\n") | map(select(. != "")) | map({"status": .[0:2], "file": .[3:]})')
        clean_status="false"
    else
        untracked_files="[]"
        clean_status="true"
    fi
    
    # Create field explanations
    local explanation_json
    explanation_json=$(jq -n '{
        explanation: {
            text: "Field documentation for the YAML output format. Each entry describes the purpose and content of fields returned by the view command.",
            fields: [
                {
                    name: "working_directory.clean",
                    text: "Boolean indicating if the working directory has no uncommitted changes"
                },
                {
                    name: "working_directory.untracked_changes",
                    text: "Array of files with uncommitted changes, showing git status and file path"
                },
                {
                    name: "remotes",
                    text: "Array of git remotes with their URLs and detected main branch names"
                },
                {
                    name: "commits[].hash",
                    text: "Full SHA-1 hash of the commit"
                },
                {
                    name: "commits[].author",
                    text: "Commit author name and email address"
                },
                {
                    name: "commits[].date",
                    text: "Commit date in ISO format with timezone"
                },
                {
                    name: "commits[].original_message",
                    text: "The original commit message as written by the author"
                },
                {
                    name: "commits[].in_main_branches",
                    text: "Array of remote main branches that contain this commit (empty if not pushed)"
                },
                {
                    name: "commits[].analysis.detected_type",
                    text: "Automatically detected conventional commit type (feat, fix, docs, test, chore, etc.)"
                },
                {
                    name: "commits[].analysis.detected_scope",
                    text: "Automatically detected scope based on file paths (commands, config, tests, etc.)"
                },
                {
                    name: "commits[].analysis.proposed_message",
                    text: "AI-generated conventional commit message based on file changes"
                },
                {
                    name: "commits[].analysis.file_changes.total_files",
                    text: "Total number of files modified in this commit"
                },
                {
                    name: "commits[].analysis.file_changes.files_added",
                    text: "Number of new files added in this commit"
                },
                {
                    name: "commits[].analysis.file_changes.files_deleted",
                    text: "Number of files deleted in this commit"
                },
                {
                    name: "commits[].analysis.file_changes.file_list",
                    text: "Array of files changed with their git status (M=modified, A=added, D=deleted)"
                },
                {
                    name: "commits[].analysis.diff_summary",
                    text: "Git diff --stat output showing lines changed per file"
                }
            ]
        }
    }')
    
    final_json=$(echo "$commit_data_json" | jq --argjson untracked "$untracked_files" --argjson clean "$clean_status" --argjson remotes "$remotes_json" --argjson explanation "$explanation_json" '
        $explanation + {
            working_directory: {
                clean: $clean,
                untracked_changes: $untracked
            },
            remotes: $remotes,
            commits: .
        }')
    
    # Convert to YAML
    echo "$final_json" | yq eval -P -
}

# Function to apply commit messages using git rebase --exec
apply_commit_messages_with_rebase() {
    # Parse arguments: first half are commits, second half are message files
    local total_args=$#
    local num_commits=$((total_args / 2))
    local valid_commits=("${@:1:$num_commits}")
    local message_files=("${@:$((num_commits + 1)):$num_commits}")
    
    # Find the base commit (parent of the earliest commit in the range)
    local base_commit
    base_commit=$(git rev-parse "${valid_commits[0]}^")
    
    print_info "Using rebase to amend commits from $base_commit to HEAD"
    
    # Create temporary script for rebase
    local temp_script
    temp_script=$(mktemp)
    trap "rm -f '$temp_script'" EXIT
    
    # Make script executable
    chmod +x "$temp_script"
    
    # Build rebase script that checks original commit messages and applies new ones
    cat > "$temp_script" << 'REBASE_SCRIPT_EOF'
#!/bin/bash
set -e

# Get current commit message
CURRENT_MESSAGE=$(git log --format=%B -n 1 HEAD)
CURRENT_SUBJECT=$(echo "$CURRENT_MESSAGE" | head -n 1)

REBASE_SCRIPT_EOF
    
    # Add message mapping logic
    for i in "${!valid_commits[@]}"; do
        local commit="${valid_commits[$i]}"
        local msg_file="${message_files[$i]}"
        local original_subject
        original_subject=$(git log --format=%s -n 1 "$commit")
        
        # Read new message content (skip first line with hash)
        local new_message
        new_message=$(tail -n +2 "$msg_file")
        
        # Encode message in base64 for safe transport through heredoc
        local encoded_message
        encoded_message=$(echo "$new_message" | base64 -w 0)
        
        # Add condition to script
        cat >> "$temp_script" << REBASE_CONDITION_EOF

# Check for commit: $commit
if [[ "\$CURRENT_SUBJECT" == "$original_subject" ]]; then
    echo "Amending commit with original message: \$CURRENT_SUBJECT"
    echo '$encoded_message' | base64 -d | git commit --amend -F -
    exit 0
fi
REBASE_CONDITION_EOF
    done
    
    # Add fallback (should not happen if our logic is correct)
    cat >> "$temp_script" << 'REBASE_FALLBACK_EOF'

# If we get here, the commit wasn't in our list - leave it unchanged
echo "Commit not in amendment list, leaving unchanged: $CURRENT_SUBJECT"
REBASE_FALLBACK_EOF
    
    print_info "Generated rebase script: $temp_script"
    print_info "Starting rebase operation..."
    
    # Execute the rebase
    if git rebase --exec "$temp_script" "$base_commit"; then
        print_success "Successfully amended commits using rebase"
        return 0
    else
        print_error "Rebase failed. Repository state may need manual recovery."
        print_info "Run 'git rebase --abort' to cancel the rebase if needed"
        return 1
    fi
}

# Function to process YAML file and apply commit amendments
process_commits_from_yaml() {
    local yaml_file="$1"
    
    print_info "Processing amendments from YAML file: $yaml_file"
    
    # Parse YAML file to extract amendments
    local amendments_json
    amendments_json=$(yq eval '.amendments' "$yaml_file" -o=json)
    
    if [[ "$amendments_json" == "null" || "$amendments_json" == "[]" ]]; then
        print_error "No amendments found in YAML file"
        print_info "Expected YAML format:"
        echo "  amendments:"
        echo "    - commit: \"full-commit-hash\""
        echo "      message: \"New commit message\""
        exit 1
    fi
    
    # Extract commits and messages from YAML
    local valid_commits=()
    local commit_messages=()
    local amendment_count
    amendment_count=$(echo "$amendments_json" | jq length)
    
    print_info "Found $amendment_count amendments in YAML file"
    
    # Process each amendment
    for i in $(seq 0 $((amendment_count - 1))); do
        local commit_hash message
        commit_hash=$(echo "$amendments_json" | jq -r ".[$i].commit")
        message=$(echo "$amendments_json" | jq -r ".[$i].message")
        
        if [[ "$commit_hash" == "null" || -z "$commit_hash" ]]; then
            print_error "Amendment $((i + 1)) missing commit hash"
            exit 1
        fi
        
        if [[ "$message" == "null" || -z "$message" ]]; then
            print_error "Amendment $((i + 1)) missing message for commit $commit_hash"
            exit 1
        fi
        
        # Validate commit exists
        if ! git rev-parse --verify "$commit_hash" >/dev/null 2>&1; then
            print_error "Invalid commit hash in amendment $((i + 1)): $commit_hash"
            exit 1
        fi
        
        # Skip merge commits and empty commits (same logic as before)
        local parent_count
        parent_count=$(git cat-file -p "$commit_hash" | grep "^parent" | wc -l | tr -d ' ')
        if [[ "$parent_count" -gt 1 ]]; then
            print_info "Skipping merge commit in amendment $((i + 1)): $(git log --oneline -1 "$commit_hash")"
            continue
        fi
        
        if git diff-tree --no-commit-id --name-only "$commit_hash" | wc -l | grep -q "^0$"; then
            print_info "Skipping empty commit in amendment $((i + 1)): $(git log --oneline -1 "$commit_hash")"
            continue
        fi
        
        valid_commits+=("$commit_hash")
        commit_messages+=("$message")
        print_info "✓ Amendment $((i + 1)): $(git log --oneline -1 "$commit_hash")"
    done
    
    if [[ ${#valid_commits[@]} -eq 0 ]]; then
        print_error "No valid commits found to amend after filtering"
        exit 1
    fi
    
    # Check if any commits are in remote main branches (refuse to amend if so)
    local remotes_json
    remotes_json=$(get_remote_main_branches)
    local commits_in_main_branches=()
    
    if [[ "$remotes_json" != "[]" ]]; then
        for commit in "${valid_commits[@]}"; do
            local remotes_array
            remotes_array=$(echo "$remotes_json" | jq -r '.[] | @base64')
            
            while IFS= read -r remote_data; do
                [[ -n "$remote_data" ]] || continue
                local remote_name remote_main_branch
                remote_name=$(echo "$remote_data" | base64 --decode | jq -r '.name')
                remote_main_branch=$(echo "$remote_data" | base64 --decode | jq -r '.main_branch')
                
                if [[ "$remote_main_branch" != "unknown" && "$remote_main_branch" != "null" ]]; then
                    if git merge-base --is-ancestor "$commit" "refs/remotes/$remote_name/$remote_main_branch" 2>/dev/null; then
                        local branch_ref="$remote_name/$remote_main_branch"
                        commits_in_main_branches+=("$commit ($branch_ref)")
                        break
                    fi
                fi
            done <<< "$remotes_array"
        done
    fi
    
    # Refuse to amend if any commits are in remote main branches
    if [[ ${#commits_in_main_branches[@]} -gt 0 ]]; then
        print_error "Cannot amend commits that are already in remote main branches:"
        for commit_info in "${commits_in_main_branches[@]}"; do
            local commit_hash branch_info
            commit_hash=$(echo "$commit_info" | cut -d' ' -f1)
            branch_info=$(echo "$commit_info" | cut -d' ' -f2- | tr -d '()')
            echo "  $(git log --oneline -1 "$commit_hash") → in $branch_info"
        done
        echo
        print_error "Amending these commits would rewrite published history and cause issues for other developers."
        print_info "Only amend commits that haven't been pushed to remote main branches."
        exit 1
    fi
    
    # Apply amendments using the same logic as before
    apply_amendments "${valid_commits[@]}" "${commit_messages[@]}"
}

# Function to apply amendments (refactored from old process_commits_from_files)
apply_amendments() {
    # Parse arguments: first half are commits, second half are messages
    local total_args=$#
    local num_commits=$((total_args / 2))
    local valid_commits=("${@:1:$num_commits}")
    local commit_messages=("${@:$((num_commits + 1)):$num_commits}")
    
    # Check if we only need to amend HEAD or if we need rebase
    local head_commit_hash
    head_commit_hash=$(git rev-parse HEAD)
    local has_non_head_commits="false"
    
    for commit in "${valid_commits[@]}"; do
        local commit_hash
        commit_hash=$(git rev-parse "$commit")
        if [[ "$commit_hash" != "$head_commit_hash" ]]; then
            has_non_head_commits="true"
            break
        fi
    done
    
    if [[ "$has_non_head_commits" == "true" ]]; then
        # Use rebase for non-HEAD commits
        print_info "Detected non-HEAD commits, using rebase approach..."
        if ! apply_amendments_with_rebase "${valid_commits[@]}" "${commit_messages[@]}"; then
            return 1
        fi
    else
        # Simple HEAD-only amendment
        print_info "Amending HEAD commit only..."
        local new_message="${commit_messages[0]}"
        
        # Show what we're about to do
        print_info "Applying new message to HEAD commit"
        echo "New message:"
        echo "$new_message" | sed 's/^/  /'
        
        git commit --amend -m "$new_message"
        print_success "Amended HEAD commit"
    fi
    
    print_success "Finished processing amendments from YAML file"
}

# Function to apply amendments by processing each commit individually in reverse order
apply_amendments_with_rebase() {
    # Parse arguments: first half are commits, second half are messages
    local total_args=$#
    local num_commits=$((total_args / 2))
    local valid_commits=("${@:1:$num_commits}")
    local commit_messages=("${@:$((num_commits + 1)):$num_commits}")
    
    print_info "Amending commits individually in reverse order (newest to oldest)"
    
    # Process each commit individually, starting from the newest (HEAD)
    # We'll work backwards through the commit history
    
    # Sort commits by their chronological order (newest first)
    local sorted_indices=()
    for i in "${!valid_commits[@]}"; do
        sorted_indices+=("$i")
    done
    
    # Sort indices by commit position (newest first)
    # Use git rev-list to determine order
    IFS=$'\n' read -r -d '' -a sorted_indices <<< "$(
        for i in "${!valid_commits[@]}"; do
            local commit="${valid_commits[$i]}"
            local position
            position=$(git rev-list --count "$commit"..HEAD)
            echo "$position:$i"
        done | sort -n | cut -d: -f2
    )" || true
    
    # Process commits in reverse order (newest first) using individual git commit --amend
    for idx in "${sorted_indices[@]}"; do
        local commit="${valid_commits[$idx]}"
        local new_message="${commit_messages[$idx]}"
        local commit_num=$((idx + 1))
        
        print_info "Processing commit $commit_num/$num_commits: $(git log --oneline -1 "$commit")"
        
        # Get the position of this commit from HEAD
        local position
        position=$(git rev-list --count "$commit"..HEAD)
        
        if [[ $position -eq 0 ]]; then
            # This is HEAD - simple amendment
            echo "$new_message" | git commit --amend -F -
            print_success "Amended HEAD commit: $commit"
        else
            # This is an older commit - use interactive rebase to amend it
            local base_commit
            base_commit=$(git rev-parse "$commit^")
            
            # Create a temporary script for the rebase sequence
            local temp_sequence
            temp_sequence=$(mktemp)
            trap "rm -f '$temp_sequence'" EXIT
            
            # Generate rebase sequence: edit the target commit, pick the rest
            git rev-list --reverse "$base_commit"..HEAD | while read -r rev; do
                if [[ "$rev" == "$commit" ]]; then
                    echo "edit $rev $(git log --format=%s -n 1 "$rev")"
                else
                    echo "pick $rev $(git log --format=%s -n 1 "$rev")"
                fi
            done > "$temp_sequence"
            
            print_info "Starting interactive rebase to amend commit: $commit"
            
            # Set up environment for non-interactive rebase
            export GIT_SEQUENCE_EDITOR="cp '$temp_sequence'"
            export GIT_EDITOR="true"
            
            # Start the interactive rebase
            if git rebase -i "$base_commit"; then
                # We should now be stopped at the commit we want to edit
                current_commit=$(git rev-parse HEAD)
                if [[ "$current_commit" == "$commit" ]]; then
                    # Amend the commit with new message
                    echo "$new_message" | git commit --amend -F -
                    print_success "Amended commit: $commit"
                    
                    # Continue the rebase
                    if git rebase --continue; then
                        print_success "Rebase completed successfully"
                    else
                        print_error "Failed to continue rebase"
                        return 1
                    fi
                else
                    print_error "Unexpected commit during rebase. Expected $commit, got $current_commit"
                    git rebase --abort
                    return 1
                fi
            else
                print_error "Failed to start interactive rebase for commit: $commit"
                return 1
            fi
            
            # Clean up environment
            unset GIT_SEQUENCE_EDITOR GIT_EDITOR
        fi
    done
    
    print_success "Successfully amended all commits individually in reverse order"
    return 0
}

# Function to validate and apply commit messages from files (DEPRECATED - kept for compatibility)
process_commits_from_files() {
    local commit_range="$1"
    shift
    local message_files=("$@")
    local commits commit_list
    
    # Handle different range formats
    if [[ "$commit_range" == "HEAD" ]]; then
        commits=("HEAD")
    elif [[ "$commit_range" =~ \.\. ]]; then
        # Range format like HEAD~3..HEAD or abc123..def456
        commit_list=$(git rev-list --reverse "$commit_range" 2>/dev/null || {
            print_error "Invalid commit range: $commit_range"
            exit 1
        })
        IFS=$'\n' read -r -d '' -a commits <<< "$commit_list" || true
    else
        # Single commit
        commits=("$commit_range")
    fi
    
    if [[ ${#commits[@]} -eq 0 ]]; then
        print_error "No commits found in range: $commit_range"
        exit 1
    fi
    
    # Skip merge commits and empty commits, build filtered list
    local valid_commits=()
    for commit in "${commits[@]}"; do
        # Skip merge commits
        local parent_count
        parent_count=$(git cat-file -p "$commit" | grep "^parent" | wc -l | tr -d ' ')
        if [[ "$parent_count" -gt 1 ]]; then
            print_info "Skipping merge commit: $(git log --oneline -1 "$commit")"
            continue
        fi
        
        # Skip empty commits
        if git diff-tree --no-commit-id --name-only "$commit" | wc -l | grep -q "^0$"; then
            print_info "Skipping empty commit: $(git log --oneline -1 "$commit")"
            continue
        fi
        
        valid_commits+=("$commit")
    done
    
    # Check if any commits are in remote main branches (refuse to amend if so)
    local remotes_json
    remotes_json=$(get_remote_main_branches)
    local commits_in_main_branches=()
    
    if [[ "$remotes_json" != "[]" ]]; then
        for commit in "${valid_commits[@]}"; do
            local remotes_array
            remotes_array=$(echo "$remotes_json" | jq -r '.[] | @base64')
            
            while IFS= read -r remote_data; do
                [[ -n "$remote_data" ]] || continue
                local remote_name remote_main_branch
                remote_name=$(echo "$remote_data" | base64 --decode | jq -r '.name')
                remote_main_branch=$(echo "$remote_data" | base64 --decode | jq -r '.main_branch')
                
                if [[ "$remote_main_branch" != "unknown" && "$remote_main_branch" != "null" ]]; then
                    # Check if commit is an ancestor of the remote main branch
                    if git merge-base --is-ancestor "$commit" "refs/remotes/$remote_name/$remote_main_branch" 2>/dev/null; then
                        local branch_ref="$remote_name/$remote_main_branch"
                        commits_in_main_branches+=("$commit ($branch_ref)")
                        break  # Found in at least one main branch, no need to check others for this commit
                    fi
                fi
            done <<< "$remotes_array"
        done
    fi
    
    # Refuse to amend if any commits are in remote main branches
    if [[ ${#commits_in_main_branches[@]} -gt 0 ]]; then
        print_error "Cannot amend commits that are already in remote main branches:"
        for commit_info in "${commits_in_main_branches[@]}"; do
            local commit_hash branch_info
            commit_hash=$(echo "$commit_info" | cut -d' ' -f1)
            branch_info=$(echo "$commit_info" | cut -d' ' -f2- | tr -d '()')
            echo "  $(git log --oneline -1 "$commit_hash") → in $branch_info"
        done
        echo
        print_error "Amending these commits would rewrite published history and cause issues for other developers."
        print_info "Only amend commits that haven't been pushed to remote main branches."
        exit 1
    fi
    
    # Check that we have the same number of commits and message files
    if [[ ${#valid_commits[@]} -ne ${#message_files[@]} ]]; then
        print_error "Mismatch: Found ${#valid_commits[@]} commits but ${#message_files[@]} message files"
        echo "Valid commits:"
        for commit in "${valid_commits[@]}"; do
            echo "  $(git log --oneline -1 "$commit")"
        done
        echo "Message files: ${message_files[*]}"
        exit 1
    fi
    
    print_info "Found ${#valid_commits[@]} commits and ${#message_files[@]} message files"
    
    # Validate each message file has correct commit hash
    for i in "${!valid_commits[@]}"; do
        local commit="${valid_commits[$i]}"
        local msg_file="${message_files[$i]}"
        local full_hash expected_header actual_header
        
        # Get full commit hash
        full_hash=$(git rev-parse "$commit")
        expected_header="# $full_hash"
        
        # Check if file exists
        if [[ ! -f "$msg_file" ]]; then
            print_error "Message file does not exist: $msg_file"
            exit 1
        fi
        
        # Read first line of message file
        actual_header=$(head -n 1 "$msg_file")
        
        # Validate header format
        if [[ "$actual_header" != "$expected_header" ]]; then
            print_error "Invalid header in $msg_file"
            echo "Expected: $expected_header"
            echo "Actual:   $actual_header"
            exit 1
        fi
        
        print_info "✓ File $msg_file matches commit $(git log --oneline -1 "$commit")"
    done
    
    # Check if we only need to amend HEAD or if we need rebase
    local head_commit_hash
    head_commit_hash=$(git rev-parse HEAD)
    local has_non_head_commits="false"
    
    for commit in "${valid_commits[@]}"; do
        local commit_hash
        commit_hash=$(git rev-parse "$commit")
        if [[ "$commit_hash" != "$head_commit_hash" ]]; then
            has_non_head_commits="true"
            break
        fi
    done
    
    if [[ "$has_non_head_commits" == "true" ]]; then
        # Use rebase for non-HEAD commits
        print_info "Detected non-HEAD commits, using rebase approach..."
        if ! apply_commit_messages_with_rebase "${valid_commits[@]}" "${message_files[@]}"; then
            return 1
        fi
    else
        # Simple HEAD-only amendment
        print_info "Amending HEAD commit only..."
        local msg_file="${message_files[0]}"
        local new_message
        new_message=$(tail -n +2 "$msg_file")
        
        # Show what we're about to do
        print_info "Applying message from $msg_file to HEAD commit"
        echo "New message:"
        echo "$new_message" | sed 's/^/  /'
        
        git commit --amend -m "$new_message"
        print_success "Amended HEAD commit with message from $msg_file"
    fi
    
    print_success "Finished processing commits from files"
}

# Main function
main() {
    local commit_range=""
    local subcommand="amend"  # default subcommand
    
    local yaml_file=""
    
    # Parse arguments - require explicit subcommand
    if [[ $# -eq 0 ]]; then
        print_error "No subcommand specified"
        show_usage
        exit 1
    fi
    
    case $1 in
        -h|--help)
            show_usage
            exit 0
            ;;
        view)
            subcommand="view"
            shift
            ;;
        amend)
            subcommand="amend-yaml"
            shift
            ;;
        *)
            print_error "Unknown subcommand: $1"
            show_usage
            exit 1
            ;;
    esac
    
    # Parse remaining arguments based on subcommand
    if [[ "$subcommand" == "amend-yaml" ]]; then
        # Single argument after 'amend' should be YAML file
        if [[ $# -gt 0 ]]; then
            yaml_file="$1"
            shift
        else
            print_error "amend subcommand requires a YAML file"
            show_usage
            exit 1
        fi
        
        # Check for additional unexpected arguments
        if [[ $# -gt 0 ]]; then
            print_error "amend subcommand accepts only one YAML file argument"
            show_usage
            exit 1
        fi
        
        # Validate YAML file exists
        if [[ ! -f "$yaml_file" ]]; then
            print_error "YAML file does not exist: $yaml_file"
            exit 1
        fi
    else
        # For view subcommand, parse commit range
        while [[ $# -gt 0 ]]; do
            case $1 in
                -h|--help)
                    show_usage
                    exit 0
                    ;;
                *)
                    if [[ -n "$commit_range" ]]; then
                        print_error "Multiple commit ranges specified"
                        show_usage
                        exit 1
                    fi
                    commit_range="$1"
                    ;;
            esac
            shift
        done
    fi
    
    # Check prerequisites
    check_git_repo
    
    # Check for required tools in view mode
    if [[ "$subcommand" == "view" ]]; then
        check_yaml_tools
    fi
    
    # In view mode, we don't need a clean working directory
    if [[ "$subcommand" != "view" ]]; then
        check_working_directory
    fi
    
    # Process commits based on subcommand
    case "$subcommand" in
        "view")
            process_commits_view "${commit_range:-HEAD}"
            ;;
        "amend-yaml")
            process_commits_from_yaml "$yaml_file"
            ;;
        *)
            print_error "Unknown subcommand: $subcommand"
            show_usage
            exit 1
            ;;
    esac
}

# Run main function if script is executed directly
if [[ "${BASH_SOURCE[0]}" == "${0}" ]]; then
    main "$@"
fi