#!/usr/bin/env bash

set -euo pipefail
# Disable glob expansion to handle brackets in file paths
set -f
usage() {
  local exit_code=${1:-2}
  if [ "$exit_code" -eq 0 ]; then
    printf 'Usage: %s [--force] [--fast] "commit message" "file" ["file" ...]\n' "$(basename "$0")"
  else
    printf 'Usage: %s [--force] [--fast] "commit message" "file" ["file" ...]\n' "$(basename "$0")" >&2
  fi
  exit "$exit_code"
}

if [[ "${1:-}" == "--help" || "${1:-}" == "-h" ]]; then
  usage 0
fi

if [ "$#" -lt 2 ]; then
  usage
fi

force_delete_lock=false
fast_commit=false
while [[ "${1:-}" == --* ]]; do
  case "${1:-}" in
    --force)
      force_delete_lock=true
      shift
      ;;
    --fast)
      fast_commit=true
      shift
      ;;
    --help|-h)
      usage 0
      ;;
    *)
      usage
      ;;
  esac
done

if [ "$#" -lt 2 ]; then
  usage
fi

commit_message=$1
shift

if [[ "$commit_message" != *[![:space:]]* ]]; then
  printf 'Error: commit message must not be empty\n' >&2
  exit 1
fi

if [ -e "$commit_message" ]; then
  printf 'Error: first argument looks like a file path ("%s"); provide the commit message first\n' "$commit_message" >&2
  exit 1
fi

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

path_exists_or_tracked() {
  local candidate=$1
  [ -e "$candidate" ] || git ls-files --error-unmatch -- "$candidate" >/dev/null 2>&1
}

append_normalized_file_arg() {
  local raw=$1

  if path_exists_or_tracked "$raw"; then
    files+=("$raw")
    return
  fi

  if [[ "$raw" == *$'\n'* || "$raw" == *$'\r'* ]]; then
    local normalized=${raw//$'\r'/}
    while IFS= read -r line; do
      if [[ "$line" == *[![:space:]]* ]]; then
        files+=("$line")
      fi
    done <<< "$normalized"
    return
  fi

  if [[ "$raw" == *[[:space:]]* ]]; then
    local split_paths=()
    # Intentional IFS split for callers that pass a single shell-expanded path blob.
    # shellcheck disable=SC2206
    split_paths=($raw)
    if [ "${#split_paths[@]}" -gt 1 ]; then
      files+=("${split_paths[@]}")
      return
    fi
  fi

  files+=("$raw")
}

files=()
for raw_arg in "$@"; do
  append_normalized_file_arg "$raw_arg"
done

# Disallow "." because it stages the entire repository and defeats the helper's safety guardrails.
for file in "${files[@]}"; do
  if [ "$file" = "." ]; then
    printf 'Error: "." is not allowed; list specific paths instead\n' >&2
    exit 1
  fi
done

# Prevent staging node_modules even if a path is forced.
for file in "${files[@]}"; do
  case "$file" in
    *node_modules* | */node_modules | */node_modules/* | node_modules)
      printf 'Error: node_modules paths are not allowed: %s\n' "$file" >&2
      exit 1
      ;;
  esac
done

last_commit_error=''

run_git_command() {
  local stderr_log
  stderr_log=$(mktemp)
  if "$@" 2>"$stderr_log"; then
    if [ -s "$stderr_log" ]; then
      cat "$stderr_log" >&2
    fi
    rm -f "$stderr_log"
    last_commit_error=''
    return 0
  fi

  if [ -s "$stderr_log" ]; then
    cat "$stderr_log" >&2
  fi
  last_commit_error=$(cat "$stderr_log")
  rm -f "$stderr_log"
  return 1
}

is_git_lock_error() {
  printf '%s\n' "$last_commit_error" | grep -Eq \
    "Another git process seems to be running|Unable to create '.*\\.git/[^']+\\.lock'"
}

extract_git_lock_path() {
  printf '%s\n' "$last_commit_error" |
    sed -n "s/.*'\(.*\.git\/[^']*\.lock\)'.*/\1/p" |
    head -n 1
}

run_git_with_lock_retry() {
  local label=$1
  shift

  local deadline=$((SECONDS + 5))
  local announced_retry=false

  while true; do
    if run_git_command "$@"; then
      return 0
    fi

    if ! is_git_lock_error; then
      return 1
    fi

    if [ "$SECONDS" -ge "$deadline" ]; then
      break
    fi

    if [ "$announced_retry" = false ]; then
      printf 'Git lock during %s; retrying for up to 5 seconds...\n' "$label" >&2
      announced_retry=true
    fi

    sleep 0.5
  done

  if [ "$force_delete_lock" = true ]; then
    local lock_path
    lock_path=$(extract_git_lock_path)
    if [ -n "$lock_path" ] && [ -e "$lock_path" ]; then
      rm -f "$lock_path"
      printf 'Removed stale git lock: %s\n' "$lock_path" >&2
      run_git_command "$@"
      return $?
    fi
  fi

  return 1
}

for file in "${files[@]}"; do
  if ! path_exists_or_tracked "$file"; then
    printf 'Error: file not found: %s\n' "$file" >&2
    exit 1
  fi
done

run_git_with_lock_retry "unstaging files" git restore --staged :/
run_git_with_lock_retry "staging files" git add --force -- "${files[@]}"

if git diff --staged --quiet; then
  printf 'Warning: no staged changes detected for: %s\n' "${files[*]}" >&2
  exit 1
fi

committed=false
if [ "$fast_commit" = true ]; then
  declare -a commit_env=(FAST_COMMIT=1)
  if run_git_with_lock_retry "commit" env "${commit_env[@]}" git commit -m "$commit_message"; then
    committed=true
  fi
else
  if run_git_with_lock_retry "commit" git commit -m "$commit_message"; then
    committed=true
  fi
fi

if [ "$committed" = false ]; then
  exit 1
fi

printf 'Committed "%s" with %d files\n' "$commit_message" "${#files[@]}"
