#!/bin/bash

set -euo pipefail

ROOT=$(cd -- "$(dirname -- "${BASH_SOURCE[0]}")/.." && pwd)
TEST_DIR="$ROOT/test/shell.d"

shopt -s nullglob
tests=()
for test in "$TEST_DIR"/*-test.sh; do
  [[ $(basename "$test") == "base-test.sh" ]] && continue
  tests+=("$test")
done
shopt -u nullglob

if (( ${#tests[@]} == 0 )); then
  echo "No shell tests found in $TEST_DIR" >&2
  exit 1
fi

# Files that need the live compositor or desktop to themselves run one at
# a time after the parallel batch; everything else runs through a job
# pool sized to the machine. Timing-sensitive files stay out of the pool
# too: the budget guards calibrate against the machine's speed, which a
# busy pool distorts, and sleep-lock asserts tight unscaled timings.
# A file can also opt out of the pool with an `# omarchy:exclusive`
# marker near its top.
is_exclusive() {
  local name
  name=$(basename "$1")
  [[ $name == *budget* || $name == "sleep-lock-test.sh" || $name == "sleep-monitor-test.sh" || $name == "hyprland-window-close-all-test.sh" ]] && return 0
  grep -q 'require_compositor\|require_quickshell\|# omarchy:exclusive' "$1"
}

# The idle-spawn guard spends most of its time passively watching its own
# shell's process tree through an exec logger, so pool load cannot distort
# what it counts. It starts alongside the pool instead of blocking the
# serial compositor chain with its idle window.
is_overlap() {
  [[ $(basename "$1") == "shell-idle-spawn-test.sh" ]]
}

parallel_tests=()
exclusive_tests=()
overlap_tests=()
for test in "${tests[@]}"; do
  if is_overlap "$test"; then
    overlap_tests+=("$test")
  elif is_exclusive "$test"; then
    exclusive_tests+=("$test")
  else
    parallel_tests+=("$test")
  fi
done

# Every file's output is kept after the run, so a failure can be read from
# its log rather than found by running the suite again.
workdir=$(mktemp -d -t omarchy-test-shell.XXXXXX)

run_one() {
  local test="$1"
  local name
  name=$(basename "$test")
  local status=0
  bash "$test" >"$workdir/$name.out" 2>&1 || status=$?
  echo "$status" >"$workdir/$name.code"
}
export -f run_one
export workdir

# Keep going after a failing file. A test file exits at its first failed
# assertion, so aborting the run there too would hide every file after it --
# one packaging failure was masking 114 of 134 files.
overlap_pids=()
for test in "${overlap_tests[@]}"; do
  run_one "$test" &
  overlap_pids+=("$!")
done
if (( ${#parallel_tests[@]} > 0 )); then
  printf '%s\0' "${parallel_tests[@]}" |
    xargs -0 -P "$(nproc)" -n 1 bash -c 'run_one "$1"' _
fi
# The overlap jobs must be done before the exclusive chain starts: those
# files launch full shells on the live compositor, and a shell starting
# beside the idle guard (the clipboard plugin's watcher pkill, say) can
# make the guard's own shell spawn processes it would then count.
if (( ${#overlap_pids[@]} > 0 )); then
  wait "${overlap_pids[@]}"
fi
for test in "${exclusive_tests[@]}"; do
  run_one "$test"
done

failed=()
skipped=()
for test in "${tests[@]}"; do
  name=$(basename "$test")
  printf '==> %s\n' "${test#$ROOT/}"
  cat "$workdir/$name.out"
  if (( $(cat "$workdir/$name.code") != 0 )); then
    failed+=("${test#$ROOT/}")
  fi
  if grep -q '^ok - .* # SKIP$' "$workdir/$name.out"; then
    skipped+=("${test#$ROOT/}")
  fi
done

if (( ${#skipped[@]} > 0 )); then
  printf '\nSkipped checks in %d of %d test files:\n' "${#skipped[@]}" "${#tests[@]}"
  printf '  %s\n' "${skipped[@]}"
fi

if (( ${#failed[@]} > 0 )); then
  printf '\n%d of %d test files failed:\n' "${#failed[@]}" "${#tests[@]}" >&2
  for test in "${failed[@]}"; do
    printf '  %s (log: %s/%s.out)\n' "$test" "$workdir" "$(basename "$test")" >&2
  done
  printf 'Logs for every file: %s\n' "$workdir" >&2
  exit 1
fi

if (( ${#skipped[@]} == 0 )); then
  printf '\nAll %d test files passed.\n' "${#tests[@]}"
else
  printf '\n%d test files completed without failures; %d had skipped checks.\n' "${#tests[@]}" "${#skipped[@]}"
fi
printf 'Logs for every file: %s\n' "$workdir"
