#!/usr/bin/env zsh

# I don't believe the interface for Nix's utility scripts are very good. Guix's,
# on the other hand, is excellent. Management of your OS is done by a single
# program with simpler, more intuitive commands. This script is a rudimentary
# attempt at giving me that.
#
#   hey cd|check      -> nix flake check $DOTFILES
#   hey sh|show       -> nix flake show $DOTFILES
#   hey u|update      -> nix flake update --recreate-lock-file $DOTFILES
#   hey re|rebuild    -> nixos-rebuild --flake $DOTFILES#$HOSTNAME ${1:-build}
#   hey t|test        -> nixos-rebuild --flake $DOTFILES#$HOSTNAME --fast test
#   hey rollback      -> hey rebuild --rollback switch
#   hey s|search      -> nix search nixpkgs $@
#   hey vm            -> hey rebuild vm
#   hey up|upgrade    -> hey update && hey rebuild switch
#   hey gc            -> GC user profile, optimise nix store
#   sudo hey gc       -> GC, optimise nix store, & delete old boot entries
#   hey push REMOTE   -> push flake to remote (over SSH) and rebuild remotely
#   hey gen|generations -> explore, manage, diff across generations (WIP)
#
# hey (with no sub-command) can also be used as an alternative to nix-env:
#
#   hey -q
#   hey -iA nixos.htop
#   hey -e htop

set -e
REMOTE=${REMOTE:-"~/.config/dotfiles"}
DOTFILES="${0:a:h}/.."

#
function main {
  function _do { [[ $DRYRUN ]] && echo "$@" || "$@" }

  ### Helpers
  function _usage {
    echo "Usage: hey COMMAND [FLAGS...] [ARGS...]"
    echo
    echo "Available commands:"
    echo "  check (ch)         Same as 'nix flake check \$DOTFILES'"
    echo "  gc                 Garbage collect and optimize store"
    echo "  generations (gen)  TODO"
    echo "  push               Push thie config to a remote NixOS system and rebuild it"
    echo "  rebuild (re)       Rebuild current system flake"
    echo "  rollback           Roll back to last generation"
    echo "  search (s)         Searches nixpkgs for a package"
    echo "  show (sh)          Same as 'nix flake show \$DOTFILES'"
    echo "  swap               Swap nix-store link (or directory thereof) for tweaking"
    echo "  test (t)           Quickly rebuild, for quick iteration"
    echo "  upgrade (up)       Update flake lockfile and rebuild"
    echo "  vm                 Build flake into a VM image"
  }

  function _rebuild {
    # Move to $DOTFILES to prevent polluting the system with result links
    pushd $DOTFILES >/dev/null
    _do sudo nixos-rebuild --flake ".#$HOSTNAME" ${@:-build}
    popd >/dev/null
  }

  function _update {
    echo "Updating NixOS flake"
    if [[ "$@" ]]; then
      for flake in $@; do
        _do nix flake update --update-input "$flake" "$DOTFILES"
      done
    else
      _do nix flake update --recreate-lock-file "$DOTFILES"
    fi
  }

  function _gc {
    if [[ $EUID == 0 ]]; then
      echo "Cleaning up your system..."
      _do sudo nix-collect-garbage ${@:--d}
      _do nix-store --optimise
      # nix-collect-garbage is a Nix tool, not a NixOS tool. It won't delete old
      # boot entries until you do a nixos-rebuild (which means we'll always have
      # 2 boot entries at any time). Instead, we properly delete them by
      # reloading the current environment.
      _do sudo nix-env --delete-generations old --profile /nix/var/nix/profiles/system
      _do sudo /nix/var/nix/profiles/system/bin/switch-to-configuration switch
    else
      echo "Cleaning up your user profile..."
      _do nix-collect-garbage ${@:--d}
    fi
  }

  function _swap {
    for target in $@; do
      if [ -d "$target" ]; then
        files=( "$target"/**/*.nix-store-backup )
        links=( $(find "$target" -type l) )
        if [[ "$files" ]]; then
          echo "There are swapped files here. Unswapping!"
          for file in $files[@]; do
            _swap "$link"
          done
        else
          echo "Swapping files in $target"
          for link in $links[@]; do
            if [[ "$(readlink $link)" == /nix/store/* ]]; then
              _swap "$link"
            fi
          done
        fi
      elif [ -f "$target.nix-store-backup" ]; then
        echo "Unswapping $target"
        _do mv -i "$target.nix-store-backup" "$target"
      elif [ -f "$target" ]; then
        echo "Swapping $target"
        _do mv "$target" "$target.nix-store-backup"
        _do cp "$target.nix-store-backup" "$target"
      else
        >&2 echo "Not a valid target: $target"
        exit 1
      fi
    done
  }

  ###
  cmd="$1"
  [[ $# > 0 ]] && shift
  case "$cmd" in
    # TODO Repl/shell for nixos+dotfiles
    # sh|shell)    _do nix-shell "$@"             ;;
    # repl)        _do nix develop "$@"           ;;

    ch|check)    _do nix flake check "$DOTFILES";;
    sh|show)     _do nix flake show "$DOTFILES" ;;

    t|test)      _rebuild --fast test "$@"      ;;
    re|rebuild)  _rebuild "${@:-switch}"        ;;
    rollback)    _rebuild --rollback switch     ;;
    vm)          _rebuild "$@" vm               ;;

    u|update)    _update "$@"                   ;;
    up|upgrade)  _update && _rebuild switch     ;;
    s|search)    nix search nixpkgs "$@"        ;;

    gc)          _gc "$@"                       ;;
    push)
      rsync -avzh ./ "$1:$REMOTE/"
      ssh -t -l "$USERNAME" "$1" "$REMOTE/hey re" "${@[2,-1]}"
      ;;

    gen|generations)
      profile=/nix/var/nix/profiles/system
      case "$1" in
        switch) >&2 echo "Targeted rollback not supported yet" ;;  # TODO Is this true?
        diff)
          to=/nix/var/nix/profiles/system-${2}-link
          diff \
            <(nix-store -q --references $profile) \
            <(nix-store -q --references $to)
          ;;
        remove)
          shift
          _do sudo nix-env --delete-generations --profile $profile $@
          ;;
        "")
          _do sudo nix-env --list-generations --profile $profile
          ;;
      esac
      ;;

    h|help|-h|--help) _usage ;;
    -*) _do nix-env "$cmd" "$@" ;;
    "") _usage ;;
    *)  echo "Couldn't find command: $cmd" 1>&2
        exit 1
        ;;
  esac
}

main "$@"
