#!/usr/bin/env bash
# nomarchy-eval-matrix
#
# Evaluates the opt-in nomarchy.* surface that `nix flake check` never
# touches. flake check only evaluates the four default configs, so a bug
# that only fires when a toggle is flipped — a renamed option, a failed
# assertion, a stale reference — sails straight through. Two such bugs
# shipped to main before this existed (the vscode option rename and the
# impermanence systemd-stage-1 assertion); both would have been caught
# here.
#
# Each toggle scenario is layered onto nixosConfigurations.default via
# extendModules and its system.build.toplevel.drvPath is forced (which
# also forces the assertion checks). Every palette is forced through the
# system-side Plymouth colour math (which allThemeVariants, being
# home-only, doesn't exercise). Both standalone homeConfigurations are
# forced too.
#
# Per-scenario results come from builtins.tryEval so one failure doesn't
# mask the rest — the whole matrix runs, then the script exits non-zero
# if anything failed.
#
#   nomarchy-eval-matrix          # run the full matrix, table + exit code
#
# Add a scenario by editing the `toggleScenarios` attrset in the embedded
# Nix expression below.

set -u

repo_root="$(cd "$(dirname "${BASH_SOURCE[0]}")/../.." && pwd)"
cd "$repo_root"

NIX=(nix --extra-experimental-features 'nix-command flakes')

read -r -d '' EXPR <<'NIXEOF' || true
let
  f = builtins.getFlake (toString ./.);
  lib = f.inputs.nixpkgs.lib;
  themeNames = (import ./lib { inherit lib; }).themeNames;
  default = f.nixosConfigurations.default;

  # Opt-in toggles layered onto the default config. Home-side options go
  # under home-manager.users.nomarchy.nomarchy.*; system-side directly.
  toggleScenarios = {
    "impermanence-single"  = { nomarchy.system.impermanence.enable = true; };
    "impermanence-multi"   = { nomarchy.system.impermanence = { enable = true; mainLuksName = "crypted_main"; }; };
    "laptop-preset"        = { nomarchy.system = { formFactor = "laptop"; laptop.enable = true; }; };
    "desktop-preset"       = { nomarchy.system = { formFactor = "desktop"; desktop.enable = true; }; };
    "accessibility-system" = { nomarchy.system.accessibility.enable = true; };
    "gaming-system"        = { nomarchy.system.gaming.enable = true; };
    "hybrid-gpu"           = { nomarchy.system.features.hybridGPU = true; };
    "hibernation"          = { nomarchy.system.hibernation.enable = true; };
    "docker"               = { nomarchy.system.virtualization.docker.enable = true; };
    "fwupd"                = { nomarchy.hardware.fwupd = true; };
    "accessibility-home"   = { home-manager.users.nomarchy.nomarchy.accessibility.enable = true; };
    "gaming-home"          = { home-manager.users.nomarchy.nomarchy.gaming.enable = true; };
    "overrides"            = { home-manager.users.nomarchy.nomarchy.overrides.enable = true; };
    "panel-bottom"         = { home-manager.users.nomarchy.nomarchy.panelPosition = "bottom"; };
    "keymap-variant"       = { home-manager.users.nomarchy.nomarchy.keymap = { layout = "de"; variant = "nodeadkeys"; }; };
    "toggles-off"          = { home-manager.users.nomarchy.nomarchy.toggles = { waybar = false; idle = false; nightlight = false; }; };
  };

  forced = x: (builtins.tryEval (builtins.seq x true)).success;

  tryToplevel = mod:
    forced (default.extendModules { modules = [ mod ]; }).config.system.build.toplevel.drvPath;

  # Palettes only need the system-side Plymouth path forced — that's the
  # per-palette base00 → RGB float math in themes/engine/plymouth.nix that
  # the home-only allThemeVariants never reaches.
  tryPalette = name:
    forced (default.extendModules { modules = [ { nomarchy.system.theme = name; } ]; }).config.boot.plymouth.themePackages;

  tryHome = name: forced f.homeConfigurations.${name}.activationPackage.drvPath;
in {
  toggles  = builtins.mapAttrs (_: tryToplevel) toggleScenarios;
  palettes = builtins.listToAttrs (map (n: { name = n; value = tryPalette n; }) themeNames);
  home     = builtins.listToAttrs (map (n: { name = n; value = tryHome n; }) [ "nomarchy" "nixos" ]);
}
NIXEOF

echo "Evaluating opt-in toggle + palette + home matrix (this takes a few minutes)..."
json="$("${NIX[@]}" eval --impure --json --expr "$EXPR" 2>/tmp/eval-matrix.err)"
status=$?
if [[ $status -ne 0 || -z "$json" ]]; then
  echo "ERROR: the matrix evaluation aborted before producing results." >&2
  echo "This usually means an uncatchable error (abort/infinite recursion) in one" >&2
  echo "scenario, or a syntax error in the expression. Full output:" >&2
  cat /tmp/eval-matrix.err >&2
  exit 1
fi

# Render each section and tally failures.
fails=0
render() {
  local section="$1"
  echo
  echo "=== $section ==="
  while IFS=$'\t' read -r name ok; do
    if [[ "$ok" == "true" ]]; then
      printf '  ok    %s\n' "$name"
    else
      printf '  FAIL  %s\n' "$name"
      fails=$((fails + 1))
    fi
  done < <(echo "$json" | jq -r --arg s "$section" '.[$s] | to_entries[] | "\(.key)\t\(.value)"' | sort)
}

render toggles
render palettes
render home

echo
if [[ $fails -gt 0 ]]; then
  echo "$fails scenario(s) failed to evaluate. Re-run the failing one directly for the trace, e.g.:"
  echo "  nix eval --impure --show-trace --expr 'let f = builtins.getFlake (toString ./.); in (f.nixosConfigurations.default.extendModules { modules = [ { <the toggle> } ]; }).config.system.build.toplevel.drvPath'"
  exit 1
fi
echo "All scenarios evaluated cleanly."
