#!/bin/bash
# Queue filename
QUEUE_FILENAME=".queue"

# --- Help Information ---
usage() {
    echo "Usage: $0 <command> [scan_directory]"
    echo ""
    echo "Commands:"
    echo "  init   : Scan the specified directory and generate a .queue file in the current working directory"
    echo "  status : Show remaining queue count in the current working directory"
    echo "  push <path1> [path2...]: Append specified files/directories to the queue (multiple paths supported)"
    echo "  pop    : Pop and output one file absolute path"
    echo "  diff [git diff arguments...] : Add git diff results to the queue"
    echo "  clean  : Clean up"
    echo ""
    echo "Environment Variables:"
    echo "  EXCLUDE_SCAN_FILES : Space-separated glob patterns to exclude files (e.g. '*.o *.log')"
    echo "  EXCLUDE_SCAN_DIRS  : Space-separated glob patterns to exclude directories (e.g. 'node_modules .git')"
    echo ""
    echo "Notes:"
    echo "  Scan directory supports relative paths (relative to current working directory)."
    echo "  Queue file (.queue) is always generated in the current working directory."
    echo "  File paths written to the queue are absolute paths."
    exit 1
}

# --- Global Variables ---
CMD=""
SCAN_DIR_INPUT=""      # User-provided scan directory
SCAN_DIR_ABS=""        # Resolved absolute scan path
QUEUE_FILE=""          # Queue file path (fixed to current directory)
EXCLUDE_FILE_ARGS=()   # File exclusion arguments
EXCLUDE_DIR_ARGS=()    # Directory exclusion arguments

# --- Parameter Parsing ---

if [ $# -eq 0 ]; then
    usage
fi

CMD="$1"
# Get scan directory parameter, default to current directory "."
SCAN_DIR_INPUT="${2:-.}"

# --- Path Processing ---

# 1. Determine queue file location: fixed in current working directory
QUEUE_FILE="${PWD}/${QUEUE_FILENAME}"

# 2. Determine absolute path of the scan directory
# readlink -f automatically resolves relative paths to absolute paths (based on current PWD)
if command -v readlink &> /dev/null; then
    SCAN_DIR_ABS=$(readlink -f "$SCAN_DIR_INPUT")
elif command -v realpath &> /dev/null; then
    SCAN_DIR_ABS=$(realpath "$SCAN_DIR_INPUT")
else
    # Fallback when readlink is unavailable
    if [[ "$SCAN_DIR_INPUT" = /* ]]; then
        SCAN_DIR_ABS="$SCAN_DIR_INPUT"
    else
        SCAN_DIR_ABS="$PWD/$SCAN_DIR_INPUT"
    fi
fi

# --- Parse Environment Variables ---

# Parse file exclusion rules
if [ -n "$EXCLUDE_SCAN_FILES" ]; then
    for pattern in $EXCLUDE_SCAN_FILES; do
        EXCLUDE_FILE_ARGS+=("--exclude=$pattern")
    done
fi

# Parse directory exclusion rules
if [ -n "$EXCLUDE_SCAN_DIRS" ]; then
    for pattern in $EXCLUDE_SCAN_DIRS; do
        EXCLUDE_DIR_ARGS+=("--exclude-dir=$pattern")
    done
fi

# --- Function Definitions ---

func_init() {
    echo "Current working directory: $PWD"
    echo "Scan target directory: $SCAN_DIR_ABS"

    # Validate that scan directory exists
    if [ ! -d "$SCAN_DIR_ABS" ]; then
        echo "Error: Scan directory [$SCAN_DIR_INPUT] does not exist"
        exit 1
    fi

    # Print exclusion rules
    if [ ${#EXCLUDE_FILE_ARGS[@]} -gt 0 ]; then
        echo "Exclude file patterns: ${EXCLUDE_FILE_ARGS[*]}"
    fi
    if [ ${#EXCLUDE_DIR_ARGS[@]} -gt 0 ]; then
        echo "Exclude directory patterns: ${EXCLUDE_DIR_ARGS[*]}"
    fi

    # Clear or create queue file
    > "$QUEUE_FILE"

    # Build grep arguments
    # 1. Base arguments: recursive, exclude .git directory (default), exclude queue file itself
    local base_opts=(-r --exclude-dir=.git --exclude="$QUEUE_FILENAME")

    # Execute search
    # -l: show file names
    # -L: show empty file names
    # Combined with user-defined exclusion arguments

    # Find non-empty files
    # Use realpath/readlink to ensure absolute path output
    if command -v realpath &> /dev/null; then
        grep -l "${base_opts[@]}" "${EXCLUDE_FILE_ARGS[@]}" "${EXCLUDE_DIR_ARGS[@]}" "" "$SCAN_DIR_ABS" 2>/dev/null | xargs realpath 2>/dev/null >> "$QUEUE_FILE"
    elif command -v readlink &> /dev/null; then
        grep -l "${base_opts[@]}" "${EXCLUDE_FILE_ARGS[@]}" "${EXCLUDE_DIR_ARGS[@]}" "" "$SCAN_DIR_ABS" 2>/dev/null | xargs readlink -f 2>/dev/null >> "$QUEUE_FILE"
    else
        # Fallback when realpath/readlink is unavailable, assuming grep output is relative paths
        grep -l "${base_opts[@]}" "${EXCLUDE_FILE_ARGS[@]}" "${EXCLUDE_DIR_ARGS[@]}" "" "$SCAN_DIR_ABS" 2>/dev/null | while read -r file; do
            if [[ "$file" = /* ]]; then
                echo "$file"
            else
                echo "$PWD/$file"
            fi
        done >> "$QUEUE_FILE"
    fi

    # Find empty files
    # grep -L "${base_opts[@]}" "${EXCLUDE_FILE_ARGS[@]}" "${EXCLUDE_DIR_ARGS[@]}" "" "$SCAN_DIR_ABS" 2>/dev/null >> "$QUEUE_FILE"

    # Sort and deduplicate
    sort -u "$QUEUE_FILE" -o "$QUEUE_FILE"

    # Statistics
    local total_count=$(wc -l < "$QUEUE_FILE")
    local suffix_stats=$(awk -F. '{if(NF>1) print$NF}' "$QUEUE_FILE" | sort | uniq -c | sort -rn | head -n 10)

    echo "----------------------------------------"
    echo "Total files: $total_count"
    echo ""
    echo "File extension statistics (Top 10):"
    echo "$suffix_stats"
    echo "----------------------------------------"
    echo "Queue file generated: $QUEUE_FILE"
}


# add: Manually add paths to the queue
func_add() {
    if [ $# -eq 0 ]; then
        echo "Error: add command requires at least one path"
        usage
    fi

    local added_total=0

    for item in "$@"; do
        # 1. Check if it is a path with line number range (format: <file_path>:<start_line>:<line_count>)
        # Use end matching to identify, ensuring the last two parts are both numbers
        if [[ "$item" =~ ^(.+):[0-9]+:[0-9]+$ ]]; then
            # --- Path with line number range ---
            # Use parameter expansion to extract the last two parts (line number and line count)
            local remainder="$item"
            local line_count="${remainder##*:}"
            remainder="${remainder%:*}"
            local start_line="${remainder##*:}"
            local file_path="${remainder%:*}"

            # Convert file path to absolute path
            local abs_path=""
            if command -v readlink &> /dev/null; then
                abs_path=$(readlink -f "$file_path")
            elif command -v realpath &> /dev/null; then
                abs_path=$(realpath "$file_path")
            else
                if [[ "$file_path" = /* ]]; then
                    abs_path="$file_path"
                else
                    abs_path="$PWD/$file_path"
                fi
            fi

            # Validate path exists
            if [ ! -e "$abs_path" ]; then
                echo "Warning: Path [$item] does not exist, skipped"
                continue
            fi

            # Write path with line number range to queue
            path_with_range="${abs_path}:${start_line}:${line_count}"
            echo "$path_with_range" >> "$QUEUE_FILE"
            ((added_total++))

        elif [ -f "$item" ] || [ -d "$item" ] || (echo "$item" | grep -q ':' && [ ! -e "$item" ]); then
            # --- Plain file or directory path ---
            # Note: Check if the argument itself is valid, rather than converting to absolute path first
            # Converting first would cause range-format arguments to be incorrectly treated as "non-existent" and skipped
            # So above we already used regex matching to capture the range format

            # 1. Convert to absolute path
            local abs_path=""
            if command -v readlink &> /dev/null; then
                abs_path=$(readlink -f "$item")
            elif command -v realpath &> /dev/null; then
                abs_path=$(realpath "$item")
            else
                if [[ "$item" = /* ]]; then
                    abs_path="$item"
                else
                    abs_path="$PWD/$item"
                fi
            fi

            # 2. Validate path exists
            if [ ! -e "$abs_path" ]; then
                echo "Warning: Path [$item] does not exist, skipped"
                continue
            fi

            # 3. Process by type
            if [ -f "$abs_path" ]; then
                # --- Is a file ---
                # Prevent adding the queue file itself to the queue
                if [ "$abs_path" == "$QUEUE_FILE" ]; then
                    continue
                fi
                echo "$abs_path" >> "$QUEUE_FILE"
                ((added_total++))

            elif [ -d "$abs_path" ]; then
                # --- Is a directory ---
                # Use find to recursively list all files under the directory (-type f)
                # Exclude the queue file itself
                # tee -a appends and counts
                # Use realpath/readlink to ensure absolute path output
                echo "Scanning directory [$item] ..."
                local count=$(find "$abs_path" -type f ! -path "$QUEUE_FILE" 2>/dev/null | while read -r file; do
                    if command -v realpath &> /dev/null; then
                        realpath "$file" 2>/dev/null || echo "$file"
                    elif command -v readlink &> /dev/null; then
                        readlink -f "$file" 2>/dev/null || echo "$file"
                    else
                        if [[ "$file" = /* ]]; then
                            echo "$file"
                        else
                            echo "$PWD/$file"
                        fi
                    fi
                done | tee -a "$QUEUE_FILE" | wc -l)
                if [ "$count" -gt 0 ]; then
                    echo "Added $count files"
                    ((added_total+=count))
                else
                    echo "Note: No files found in directory [$item]"
                fi
            else
                echo "Warning: [$item] is not a regular file or directory, skipped"
            fi
        else
            echo "Warning: Path [$item] does not exist, skipped"
        fi
    done

    # 4. Deduplicate
    if [ $added_total -gt 0 ]; then
        sort -u "$QUEUE_FILE" -o "$QUEUE_FILE"
        echo "----------------------------------------"
        echo "Total added: $added_total"
        echo "Queue total: $(wc -l < "$QUEUE_FILE")"
    fi
}

func_status() {
    if [ ! -f "$QUEUE_FILE" ]; then
        echo "Note: No queue file found in current directory ($PWD)"
        return 1
    fi

    local count=$(wc -l < "$QUEUE_FILE")
    echo "Current working directory: $PWD"
    echo "Remaining files: $count"
}

func_pop() {
    if [ ! -f "$QUEUE_FILE" ]; then
        echo "Error: Queue is empty or not initialized in current directory ($PWD)"
        return 1
    fi

    if [ $(wc -l < "$QUEUE_FILE") -eq 0 ]; then
        echo "Queue is empty, cannot pop"
        return 1
    fi

    # head to get the first line
    local entry=$(head -n 1 "$QUEUE_FILE")

    # Check if it is diff format (contains two colons)
    if [[ "$entry" =~ ^(.+):([0-9]+):([0-9]+)$ ]]; then
        # --- diff format: absolute_path:start_line:line_count ---
        local file_path="${BASH_REMATCH[1]}"
        local start_line="${BASH_REMATCH[2]}"
        local line_count="${BASH_REMATCH[3]}"

        echo "Next audit file path: \`$file_path\`"
        echo "> [Diff Range] Start line: ${start_line}, Line count: ${line_count}"

        if [ $line_count -eq 0 ]; then
            echo "> [Hint] Line count is 0, meaning only code near this line was deleted without new additions; check function logic carefully"
        fi

        # Calculate relative path
        local rel_path=""
        if [[ "$file_path" == "$SCAN_DIR_ABS"* ]]; then
            rel_path="${file_path#$SCAN_DIR_ABS/}"
        elif [[ "$file_path" == "$PWD"* ]]; then
            rel_path="${file_path#$PWD/}"
        else
            rel_path=$(basename "$file_path")
        fi
        rel_path="${rel_path#/}"
        rel_path="${rel_path#./}"

        echo "> [Hint] The intermediate report output path for this file: \`<output_dir>/${rel_path}-L${start_line}-report-0.json\`"
    else
        # --- Normal format: absolute path ---
        local file_path="$entry"

        echo "Next audit file path: \`$file_path\`"

        # Calculate relative path
        local rel_path=""
        if [[ "$file_path" == "$SCAN_DIR_ABS"* ]]; then
            rel_path="${file_path#"$SCAN_DIR_ABS"/}"
        elif [[ "$file_path" == "$PWD"* ]]; then
            rel_path="${file_path#$PWD/}"
        else
            rel_path=$(basename "$file_path")
        fi
        rel_path="${rel_path#/}"
        rel_path="${rel_path#./}"

        echo "> [Hint] The intermediate report output path for this file: \`<output_dir>/${rel_path}-report-0.json\`"
    fi

    # tail to delete the first line
    tail -n +2 "$QUEUE_FILE" > "${QUEUE_FILE}.tmp"
    mv "${QUEUE_FILE}.tmp" "$QUEUE_FILE"
}

# diff: Add git diff results (path + line range) to the queue
func_diff() {
    # 1. Check git environment
    if ! git rev-parse --is-inside-work-tree > /dev/null 2>&1; then
        echo "Error: Current directory is not in a git repository"
        exit 1
    fi

    # 2. Get Git root directory
    local git_root
    git_root=$(git rev-parse --show-toplevel)

    # 3. Execute git diff
    # --unified=0 : compact output, only changed lines, no context
    # --diff-filter=d : exclude deleted files (we want paths of modified files)
    # Note: --diff-filter=d is not used yet because some rename scenarios need judgment; we filter /dev/null in code
    local diff_output
    diff_output=$(git diff --unified=0 "$@" 2>/dev/null)

    if [ -z "$diff_output" ]; then
        echo "No changes detected."
        return 0
    fi

    # 4. Parse output
    local count=0
    local current_file=""

    # Clear queue
    > "$QUEUE_FILE"

    while IFS= read -r line; do
        # --- A. Parse file path ---
        # Match: +++ b/README.md
        # Extract: README.md
        if [[ "$line" =~ ^\+\+\+\ b/(.*) ]]; then
            local rel_path="${BASH_REMATCH[1]}"

            # If it is /dev/null (appears in file deletion scenarios, which we typically don't handle, ignore)
            if [[ "$rel_path" == "/dev/null" ]]; then
                current_file=""
                continue
            else
                # Concatenate absolute path
                current_file="${git_root}/${rel_path}"

                # Secondary path existence check
                if [ ! -f "$current_file" ]; then
                    current_file=""
                fi
            fi
        fi

        # --- B. Parse line range ---
        # Match: @@ -old_start,old_count +new_start,new_count @@
        # Regex explanation:
        # @@ [^@]*  : match @@ and possible old file info in between
        # \+([0-9]+) : match the start line number after the plus sign
        # (,([0-9]+))? : match optional comma and line count
        # \s*@@     : match the trailing @@
        if [[ -n "$current_file" && "$line" =~ @@\ [^@]*\ \+([0-9]+)(,([0-9]+))?\ @@ ]]; then
            local start_line="${BASH_REMATCH[1]}"
            local line_count="${BASH_REMATCH[3]}"

            # If no comma, line count is 1
            if [ -z "$line_count" ]; then
                line_count=1
            fi

            # Write to queue: absolute_path:start_line:line_count
            echo "${current_file}:${start_line}:${line_count}" >> "$QUEUE_FILE"
            ((count++))
        fi

    done <<< "$diff_output"

    echo "----------------------------------------"
    echo "Written $count change blocks to queue"
    echo "Queue file: $QUEUE_FILE"
    echo "----------------------------------------"

}

func_clean() {
    if [ ! -f "$QUEUE_FILE" ]; then
        echo "Error: Queue is empty or not initialized in current directory ($PWD)"
        return 1
    fi

    if [ $(wc -l < "$QUEUE_FILE") -eq 0 ]; then
        rm "$QUEUE_FILE" && echo "Queue cleaned up" || echo "Cleanup failed" && return 1
        return 1
    fi

    local count=$(wc -l < "$QUEUE_FILE")
    echo "Remaining files to audit: $count, cannot clean"
}

# --- Main Logic Dispatch ---
case "$CMD" in
    init)
        func_init
        ;;
    status)
        func_status
        ;;
    push)
        # Pass all arguments after "add" to func_add
        # shift removes the first argument ($1/cmd), remaining $@ is the file list
        shift
        func_add "$@"
        ;;
    pop)
        func_pop
        ;;
    diff)
        # shift removes the 'diff' command, pass remaining arguments to func_diff
        shift
        func_diff "$@"
        ;;
    clean)
        func_clean
        ;;
    *)
        echo "Error: Unknown command '$CMD'"
        usage
        ;;
esac
