#!/usr/bin/env bash

set -u
set -o pipefail

PROGRAM="$(basename "$0")"
WORK_ROOT="$(pwd -P)"
VERSION=""
TARGETS=()

usage() {
  cat <<EOF
Usage: $PROGRAM [OPTIONS] [PATH ...]

Restore and clean extension paths before regenerating patch overrides.

With no PATH arguments, cleans the parent directory of each patches-[version]
directory found under the current directory.

Options:
  -v, --version VERSION   Patch version to use when discovering paths.
                          Defaults to the latest patches-[version] directory.
  -h, --help              Show this help.
EOF
}

log() {
  printf '%s\n' "$*"
}

warn() {
  printf 'Warning: %s\n' "$*" >&2
}

die() {
  printf 'Error: %s\n' "$*" >&2
  exit 1
}

abs_path() {
  local path="$1"
  local dir
  local base

  dir="$(dirname "$path")" || return 1
  base="$(basename "$path")" || return 1
  (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$base")
}

find_patch_dirs() {
  local pattern="$1"

  find "$WORK_ROOT" \
    \( -type d \( -name .git -o -name vendor \) \) -prune \
    -o -type d -name "$pattern" -print | sort
}

latest_patch_version() {
  local versions

  versions="$(find_patch_dirs 'patches-*' | sed 's#.*/patches-##')"
  [ -n "$versions" ] || return 1

  if command -v php >/dev/null 2>&1; then
    printf '%s\n' "$versions" | php -r '
      $versions = array_values(array_filter(array_map("trim", file("php://stdin"))));
      usort($versions, "version_compare");
      echo end($versions), PHP_EOL;
    '
  else
    printf '%s\n' "$versions" | sort | tail -n 1
  fi
}

clean_path() {
  local target="$1"
  local destination_root
  local git_root
  local target_path

  destination_root="$(abs_path "$target")" || {
    warn "Could not resolve $target"
    return 1
  }

  git_root="$(git -C "$destination_root" rev-parse --show-toplevel 2>/dev/null)" || {
    warn "$destination_root is not inside a git checkout"
    return 1
  }

  case "$destination_root" in
    "$git_root")
      target_path="."
      ;;
    "$git_root"/*)
      target_path="${destination_root#"$git_root"/}"
      ;;
    *)
      warn "Could not resolve $destination_root relative to $git_root"
      return 1
      ;;
  esac

  log "Cleaning $target_path"
  git -C "$git_root" restore -- "$target_path" || return 1
  git -C "$git_root" clean -fdx -- "$target_path" || return 1
}

discovered_targets() {
  local patch_dirs
  local patch_dir

  if [ -z "$VERSION" ]; then
    VERSION="$(latest_patch_version)" || die "No patches-[version] directories found"
  fi

  patch_dirs="$(find_patch_dirs "patches-$VERSION")"
  [ -n "$patch_dirs" ] || die "No patches-$VERSION directories found under $WORK_ROOT"

  while IFS= read -r patch_dir; do
    [ -n "$patch_dir" ] || continue
    dirname "$(abs_path "$patch_dir")"
  done <<EOF | sort -u
$patch_dirs
EOF
}

parse_args() {
  while [ "$#" -gt 0 ]; do
    case "$1" in
      -v|--version)
        [ "$#" -ge 2 ] || die "$1 requires a value"
        VERSION="$2"
        shift 2
        ;;
      -h|--help)
        usage
        exit 0
        ;;
      --)
        shift
        while [ "$#" -gt 0 ]; do
          TARGETS+=("$1")
          shift
        done
        ;;
      -*)
        die "Unknown option: $1"
        ;;
      *)
        TARGETS+=("$1")
        shift
        ;;
    esac
  done
}

main() {
  local target
  local targets
  local had_failure=0

  parse_args "$@"

  command -v git >/dev/null 2>&1 || die "git is required"
  command -v sed >/dev/null 2>&1 || die "sed is required"

  if [ "${#TARGETS[@]}" -gt 0 ]; then
    targets="$(printf '%s\n' "${TARGETS[@]}")"
  else
    targets="$(discovered_targets)"
  fi

  while IFS= read -r target; do
    [ -n "$target" ] || continue
    clean_path "$target" || had_failure=1
  done <<EOF
$targets
EOF

  if [ "$had_failure" -ne 0 ]; then
    exit 1
  fi
}

main "$@"
