#!/usr/bin/env bash
# fast-check: run only the minimal checks for the current jj change.
# Run from anywhere inside a jj workspace of the repository. Never commits, never pushes.
# See tools/README.md.
set -uo pipefail

S="${FAST_CHECK_SCRATCH:-${XDG_CACHE_HOME:-$HOME/.cache}/komorebi-fast-check}"
HERE="$(dirname "$(readlink -f "${BASH_SOURCE[0]}")")"
FROM=""
LIST=0
SELF="$(readlink -f "${BASH_SOURCE[0]}")"

usage() {
  cat <<'USAGE'
usage: fast-check [--list] [--range | --since REV] [--help]
  --list        dry run: print the checks that would run
  --range       diff trunk()..@ instead of just @
  --since REV   diff REV..@ (REV is a jj revset, e.g. wip-a3b)
Env: FAST_CHECK_SCRATCH  base dir for the persistent target dirs (default: ${XDG_CACHE_HOME:-~/.cache}/komorebi-fast-check)
USAGE
}

# Evaluation-only nix check, attribute level: `nix flake show` (about 9 s, 0.4 GB) and the drvPath of one check
# and one package of each platform (the Windows, Linux and macOS groups of the one root flake). `nix flake check
# --no-build` forces every check of every system (up to 28 GB RSS for 5 minutes with --all-systems) and fails
# without .git anyway (decision D9). Not caught here: an error inside a check's derivation body; the
# pre-landing `nix build` of the check still catches that.
nix_eval() {
  systemd-run --user --scope -q -p MemoryMax=6G nice -n 19 nix flake show >/dev/null || return 1
  for a in checks.x86_64-linux.windows-clippy checks.x86_64-linux.linux-clippy checks.aarch64-darwin.macos-clippy \
    packages.x86_64-linux.windows-komorebic packages.x86_64-linux.komorebic packages.aarch64-darwin.komorebic; do
    systemd-run --user --scope -q -p MemoryMax=6G nice -n 19 nix eval --raw ".#$a.drvPath" >/dev/null || return 1
  done
}

while [ $# -gt 0 ]; do
  case "$1" in
    --list) LIST=1 ;;
    --range) FROM='trunk()' ;;
    --nix-eval) nix_eval; exit $? ;;
    --since) shift; FROM="${1:?--since needs a revset}" ;;
    -h | --help) usage; exit 0 ;;
    *) usage >&2; exit 2 ;;
  esac
  shift
done

ROOT="$(jj root 2>/dev/null)" || { echo "fast-check: not in a jj workspace" >&2; exit 2; }
cd "$ROOT" || exit 2

if [ -n "$FROM" ]; then
  FILES="$(jj diff --name-only --from "$FROM" --to @)" || exit 2
else
  FILES="$(jj diff --name-only -r @)" || exit 2
fi

want_kde=0 want_mac=0 want_win=0 want_native=0 want_nix=0
unclassified=()
while IFS= read -r f; do
  [ -z "$f" ] && continue
  case "$f" in
    flake.nix | flake.lock | nix/* | *.nix) want_nix=1 ;;
    # the files the Rust sources include or read, before the docs are ignored
    docs/linux/komorebi.example.json | docs/linux/skhdrc.sample | schemas/linux/*) want_kde=1 ;;
    docs/macos/*.json | docs/macos/*.sample | docs/macos/cli/* | schemas/macos/*) want_mac=1 ;;
    docs/*.json | docs/*.sample | schemas/windows/*) want_win=1 ;;
    Cargo.toml | Cargo.lock | .cargo/* | rust-toolchain.toml | rustfmt.toml | deny.toml) want_win=1; want_kde=1; want_mac=1 ;;
    # the platform crates and the crates of one set, before the `crates/*` catch-all
    crates/komorebi-layouts/* | crates/komorebi-scrolling/*) want_native=1; want_win=1 ;;
    crates/komorebi-linux/* | crates/komorebi-hotkeys/* | scripts/linux/*) want_kde=1 ;;
    crates/komorebi-macos/*) want_mac=1 ;;
    crates/komorebi-windows/* | crates/komorebi-themes/* | crates/komorebi-e2e-agent/* | crates/komorebi-testwin/*) want_win=1 ;;
    komorebic/src/linux* | komorebic/tests/linux_* | crates/komorebi-client/src/linux.rs | crates/komorebi-client/tests/linux_* | komorebi/tests/linux_*) want_kde=1 ;;
    komorebic/src/macos* | crates/komorebi-client/src/macos.rs | komorebi-bar/src/macos*) want_mac=1 ;;
    crates/komorebi-client/*) want_win=1; want_kde=1; want_mac=1 ;;
    crates/*) want_native=1; want_kde=1; want_mac=1; want_win=1 ;;
    komorebi-bar/*) want_win=1; want_mac=1 ;;
    komorebi/* | komorebic/*) want_win=1; want_kde=1; want_mac=1 ;;
    *.md | docs/* | tools/* | LICENSE* | .github/* | *.png | *.jpg | *.gif) ;;
    komorebi-gui/* | komorebi-shortcuts/* | komorebic-no-console/* | e2e/*)
      want_win=1 ;;
    *) unclassified+=("$f") ;;
  esac
done <<<"$FILES"

NAMES=() DIRS=() CMDS=()
add() { NAMES+=("$1"); DIRS+=("$2"); CMDS+=("$3"); }

if [ "$want_native" = 1 ]; then
  add "native: shared crates tests" "." \
    "CARGO_TARGET_DIR=$S/target-fast-native env -u CARGO_BUILD_TARGET nix shell nixpkgs#gcc -c cargo test --locked -p komorebi-layouts -p komorebi-scrolling"
fi
if [ "$want_win" = 1 ]; then
  # a bare cargo command builds default-members (the Windows crates) of the one workspace
  add "windows: clippy --all-targets -D warnings (dev shell, cross)" "." \
    "CARGO_TARGET_DIR=$S/target-fast-win nix develop --command cargo clippy --locked --all-targets -- -D warnings"
fi
if [ "$want_kde" = 1 ]; then
  add "kde: cargo check + lib tests (komorebi-linux)" "." \
    "export CARGO_TARGET_DIR=$S/kde-target COMMIT_HASH=\${COMMIT_HASH:-dev}; unset CARGO_BUILD_TARGET; nix shell nixpkgs#gcc -c bash -c 'cargo check --locked -p komorebi-linux && cargo check --locked -p komorebi && cargo test --locked -p komorebi-linux --lib && cargo test --locked -p komorebi-linux --test schemas -- --test-threads=1'"
fi
if [ "$want_mac" = 1 ]; then
  add "macos: type-check only (fake CC, cannot link)" "." \
    "export CARGO_TARGET_DIR=$S/target-fast-mac COMMIT_HASH=\${COMMIT_HASH:-dev} CC_aarch64_apple_darwin=$HERE/cc-fake.sh AR_aarch64_apple_darwin=ar; PKG_CONFIG_PATH=\$(nix build --no-link --print-out-paths nixpkgs#openssl.dev)/lib/pkgconfig; export PKG_CONFIG_PATH; nix shell nixpkgs#gcc nixpkgs#pkg-config nixpkgs#openssl.dev -c cargo check --locked --target aarch64-apple-darwin -p komorebi -p komorebi-macos -p komorebic -p komorebi-client -p komorebi-bar --all-targets"
fi
if [ "$want_nix" = 1 ]; then
  add "nix: root flake evaluation only (flake show and the drvPath of one check and package per platform)" "." "$SELF --nix-eval"
fi

if [ "${#unclassified[@]}" -gt 0 ]; then
  echo "fast-check: unclassified (no check): ${unclassified[*]}"
fi
if [ "${#NAMES[@]}" -eq 0 ]; then
  echo "fast-check: nothing to run for this diff"
  exit 0
fi

fail=0
for i in "${!NAMES[@]}"; do
  if [ "$LIST" = 1 ]; then
    echo "[list] ${NAMES[$i]}  (cd ${DIRS[$i]}; ${CMDS[$i]})"
    continue
  fi
  echo "==> ${NAMES[$i]}"
  start=$SECONDS
  (cd "${DIRS[$i]}" && bash -c "${CMDS[$i]}")
  rc=$?
  el=$((SECONDS - start))
  if [ $rc -eq 0 ]; then echo "<== PASS ${NAMES[$i]} (${el}s)"; else echo "<== FAIL ${NAMES[$i]} (${el}s, exit $rc)"; fail=1; fi
done
exit $fail
