#!/usr/bin/env bash
set -euo pipefail
source "$SHELLOPS_DIR/.envrc"

USAGE="$(cat <<EOF
Runs shellcheck against all bash files in the project.

Files are discovered automatically by shebang (#!/*bash) and extension
(.bats). .git and submodule paths are excluded automatically.
Additional include/exclude paths are configured in \$PROJ/shellcheck.json.

Usage: ops shellcheck [-h,--help] [-- shellcheck-args...]

Flags:
  -h, --help    Show this help text
  --            Pass remaining args to shellcheck (e.g. -- --severity=warning)
EOF
)"

CONFIG="$PROJ/shellcheck.json"

function main() {
  local sc_args=()
  parse_args sc_args "$@"
  require_shellcheck
  validate_config

  local files
  files="$(discover_files)"

  if [[ -z "$files" ]]; then
    log "No files to lint."
    exit 0
  fi

  echo "$files" | xargs shellcheck "${sc_args[@]}"
}

function require_shellcheck() {
  if command -v shellcheck &>/dev/null; then
    return
  fi

  error "shellcheck is not installed."
  log "Install it with: ops install shellcheck"
  exit 1
}

function validate_config() {
  if [[ ! -f "$CONFIG" ]]; then
    error "shellcheck.json not found at $CONFIG"
    exit 1
  fi
}

function discover_files() {
  local find_args
  find_args="$(build_find_args)"

  {
    eval "find '$PROJ' $find_args -type f -print0" | xargs -0 grep -l '#!/.*bash' 2>/dev/null
    eval "find '$PROJ' $find_args -name '*.bats' -print"
    read_config_array '.include'
  } | sort -u
}

function build_find_args() {
  local excludes
  mapfile -t excludes < <(default_excludes)
  mapfile -t -O "${#excludes[@]}" excludes < <(read_config_array '.exclude')

  for dir in "${excludes[@]}"; do
    [[ -n "$dir" ]] && printf "%s " "-path '$PROJ/$dir' -prune -o"
  done
}

function default_excludes() {
  echo ".git"
  submodule_paths
}

function submodule_paths() {
  [[ -f "$PROJ/.gitmodules" ]] || return 0
  git -C "$PROJ" config --file .gitmodules --get-regexp 'submodule\..*\.path' \
    | awk '{print $2}'
}

function read_config_array() {
  local key="$1"
  jq -r "$key // [] | .[]" "$CONFIG"
}

function parse_args() {
  local -n _sc_args=$1
  shift

  while [[ $# -gt 0 ]]; do
    case "$1" in
      -h|--help) log "$USAGE"; exit 0 ;;
      --)        shift; _sc_args+=("$@"); return ;;
      -*)
        error "Unknown option: $1"
        exit 1
        ;;
    esac
    shift
  done
}

if [[ "${BASH_SOURCE[0]}" == "$0" ]]; then
  main "$@"
fi
