#!/usr/bin/env bash
set -euo pipefail

usage() {
  cat <<EOF >&2
Usage: $0 [-r] [TARGET] SRC DST

  TARGET  Widget target name (default: copyjob)
  SRC     Source file, or directory with -r
  DST     Destination file, directory, or new path

  -r      Recursively copy a directory with cp -R (progress from du polling)
EOF
  exit 1
}

widget_progress() {
  local pct=$1
  local label=$2
  terminal-widget --background --target "$TARGET" \
    --icon arrow.down.doc.fill \
    --text "${label} ${pct}%" \
    --progress "$pct" \
    --foreground "#eaf6ff" \
    --background "#1f2a44"
}

widget_start() {
  local label=$1
  terminal-widget --background --target "$TARGET" \
    --icon arrow.down.doc.fill \
    --text "${label} starting" \
    --progress 0
}

widget_done() {
  terminal-widget --background --target "$TARGET" \
    --icon checkmark.circle.fill \
    --text "Copy complete" \
    --progress 100 \
    --foreground "#d9ffe7" \
    --background "#1b3a2f"
}

dest_path_for_source() {
  local src=$1
  local dst=$2
  if [[ -d "$dst" ]]; then
    printf '%s/%s\n' "${dst%/}" "$(basename "$src")"
  else
    printf '%s\n' "$dst"
  fi
}

copy_file_with_pv() {
  local src=$1
  local dst=$2

  if [[ -d "$dst" ]]; then
    dst="$(dest_path_for_source "$src" "$dst")"
  fi

  local size
  if stat -f%z "$src" >/dev/null 2>&1; then
    size=$(stat -f%z "$src")
  else
    size=$(stat -c%s "$src")
  fi

  widget_start "Copy"

  # pv -n writes 0-100 on stderr (not stdout). Do not use 2>&1 before the pipe or
  # progress lines are written into the destination file and the loop never runs.
  pv -n -i 0.25 -s "$size" "$src" > "$dst" 2> >(
    while IFS= read -r pct; do
      [[ "$pct" =~ ^[0-9]+$ ]] || continue
      widget_progress "$pct" "Copying"
    done
  )
  wait
}

copy_dir_with_cp() {
  local src=$1
  local dst=$2
  local dest_path
  local total_kb
  local total_bytes
  local last_pct=-1

  dest_path="$(dest_path_for_source "$src" "$dst")"
  total_kb=$(du -sk "$src" | awk '{print $1}')
  total_bytes=$((total_kb * 1024))
  if (( total_bytes <= 0 )); then
    echo "Could not measure source size: $src" >&2
    exit 1
  fi

  widget_start "Copy"

  cp -R "$src" "$dst" &
  local cp_pid=$!

  while kill -0 "$cp_pid" 2>/dev/null; do
    local current_kb=0
    if [[ -e "$dest_path" ]]; then
      current_kb=$(du -sk "$dest_path" 2>/dev/null | awk '{print $1}')
    fi
    local pct=$((current_kb * 1024 * 100 / total_bytes))
    if (( pct > 100 )); then
      pct=100
    fi
    if (( pct != last_pct )); then
      widget_progress "$pct" "Copying"
      last_pct=$pct
    fi
    sleep 0.25
  done

  if ! wait "$cp_pid"; then
    echo "cp -R failed" >&2
    exit 1
  fi
}

RECURSIVE=0
while getopts ":r" opt; do
  case "$opt" in
    r) RECURSIVE=1 ;;
    *) usage ;;
  esac
done
shift $((OPTIND - 1))

TARGET="${1:-copyjob}"
SRC="${2:?Usage: $0 [-r] [TARGET] SRC DST}"
DST="${3:?Usage: $0 [-r] [TARGET] SRC DST (DST may be a directory)}"

if (( ! RECURSIVE )) && ! command -v pv >/dev/null 2>&1; then
  echo "pv is required for file copies (brew install pv)" >&2
  exit 1
fi

if (( RECURSIVE )); then
  if [[ ! -d "$SRC" ]]; then
    echo "Source must be a directory when using -r: $SRC" >&2
    exit 1
  fi
  copy_dir_with_cp "$SRC" "$DST"
else
  if [[ ! -f "$SRC" ]]; then
    if [[ -d "$SRC" ]]; then
      echo "Source is a directory; re-run with -r" >&2
    else
      echo "Source not found: $SRC" >&2
    fi
    exit 1
  fi
  copy_file_with_pv "$SRC" "$DST"
fi

widget_done