#!/usr/bin/env bash
#
# Copyright 2009-2026 Joshua Bronson. All rights reserved.
#
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.

set -euo pipefail

log() {
  >&2 printf "> %s\n" "$@"
}


# prek auto-update moves the ruff pin in .pre-commit-config.yaml but knows nothing about
# pyproject.toml's required-version, which exists to reject a local ruff older than that pin.
# Left to drift, the floor keeps passing versions that lint with fewer rules than CI uses.
check_ruff_required_version() {
  local pinned required
  pinned=$(grep -A1 'astral-sh/ruff-pre-commit' .pre-commit-config.yaml | sed -n 's/^ *rev: v//p')
  required=$(sed -n 's/^required-version = ">=\(.*\)"$/\1/p' pyproject.toml)
  if [[ -z $pinned || -z $required ]]; then
    log "Warning: could not compare ruff's pinned version to pyproject.toml's required-version."
    log "         Check by hand that they agree, and fix this check if its parsing has gone stale."
    return
  fi
  if [[ $pinned != "$required" ]]; then
    log "Action needed: ruff is pinned to v$pinned, but required-version says >=$required."
    log "               Set required-version = \">=$pinned\" in pyproject.toml, so that a ruff"
    log "               older than the pinned one fails instead of linting with fewer rules."
    log
  fi
}


# Zero-pad a version into a fixed-width string, so that two of them compare correctly with the
# lexicographic [[ < ]], which is available everywhere `sort -V` is not (e.g. BSD/macOS sort).
version_key() {
  local major minor patch
  IFS=. read -r major minor patch <<<"$1"
  printf '%05d%05d%05d' "${major:-0}" "${minor:-0}" "${patch:-0}"
}


# The wheel's RECORD is written by uv_build, whose version comes from pyproject.toml's
# [build-system] requires -- not from the uv on PATH, and not from uv.lock. Nothing else here
# touches that pin, and left to drift it is what shipped 0.24.0 with hex RECORD hashes (#406).
# The pin's floor guards correctness on its own; what needs watching is its ceiling, which
# should keep covering the uv in uv.lock, so that building a release goes on using that uv's
# built-in uv_build rather than quietly downloading a different version to build with.
check_uv_build_pin() {
  local locked floor ceiling
  locked=$(sed -n '/^name = "uv"$/{n;s/^version = "\(.*\)"$/\1/p;}' uv.lock)
  floor=$(sed -n 's/^requires = \["uv_build>=\([^,"]*\),.*$/\1/p' pyproject.toml)
  ceiling=$(sed -n 's/^requires = \["uv_build>=[^,]*,<\([^"]*\)"\].*$/\1/p' pyproject.toml)
  if [[ -z $locked || -z $floor || -z $ceiling ]]; then
    log "Warning: could not compare uv.lock's uv version to pyproject.toml's uv_build pin."
    log "         Check by hand that they agree, and fix this check if its parsing has gone stale."
    return
  fi
  if [[ ! $(version_key "$locked") < $(version_key "$ceiling") ]]; then
    log "Action needed: uv.lock has uv $locked, past uv_build>=$floor,<$ceiling's ceiling."
    log "               Raise that ceiling in pyproject.toml to cover $locked, so that building a"
    log "               release keeps using uv's own uv_build instead of downloading another."
    log "               Leave the floor alone: it is what keeps #406 from recurring."
    log
  elif [[ $(version_key "$locked") < $(version_key "$floor") ]]; then
    log "Action needed: uv.lock has uv $locked, below uv_build>=$floor,<$ceiling's floor, so a"
    log "               release would be built by a downloaded uv_build rather than this uv's own."
    log "               Upgrade uv rather than lowering the floor, which guards against #406."
    log
  fi
}


main() {
  declare -r hint="Hint: Use 'nix develop' to bootstrap a development environment"

  for cmd in uv prek; do
    if ! command -v "$cmd" >/dev/null 2>&1; then
      log "Error: No '$cmd' on PATH. $hint"
      exit 1
    fi
  done

  log "Upgrading PyPI dependencies..."
  uv lock --upgrade && uv sync
  log "Upgrading PyPI dependencies: Done"
  log
  log "Upgrading pre-commit hooks..."
  prek auto-update
  log "Upgrading pre-commit hooks: Done"
  log
  check_ruff_required_version
  check_uv_build_pin
  log "Reminders:"
  log " - Check release notes of upgraded packages for anything that affects bidict."
  log " - Ensure tests pass for all supported Python versions."
  log " - Check output for any new warnings, not just test failures."
  log " - Ensure 'prek run --all-files' still succeeds."
}

main
