#!/usr/bin/env bash
# nomarchy-eval-matrix
#
# Evaluates the opt-in nomarchy.* surface that the per-output flake check
# never touches. flake check only evaluates the 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.
#
# Cost matters: this runs in CI on a small self-hosted runner, and the
# first cut (one full `extendModules` eval per individual toggle, plus one
# per palette) did ~39 whole-system evaluations and ran ~3h. So:
#
#   * Compatible toggles are COMBINED into a few configs (a failure means
#     re-run the offending toggle alone to pinpoint it — see the trace hint
#     at the end). laptop vs desktop formFactor conflict, so they split;
#     impermanence's multi-disk variant gets its own config for the alt
#     mainLuksName.
#   * Palettes are checked DIRECTLY via the lib — the per-palette risk is
#     the system-side Plymouth base00 → RGB math (fromHexString of 2-char
#     slices in themes/engine/plymouth.nix), which is pure and needs no
#     module-system instantiation. Forcing the whole palette attrset also
#     catches a missing/garbled palette.
#   * The standalone homeConfigurations aren't re-checked here — the
#     per-output flake-check step already forces both.
#
# Net: a handful of full evals instead of ~39. Results come from
# builtins.tryEval so one failure doesn't mask the rest.
#
#   nomarchy-eval-matrix          # run the matrix, table + exit code
#
# Add a new opt-in option's coverage by dropping it into the relevant
# combined scenario in the `scenarios` attrset 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;
  nl = import ./lib { inherit lib; };
  default = f.nixosConfigurations.default;

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

  # Combined opt-in scenarios. Each enables as many compatible toggles as
  # possible so CI does ~3 full-system evals, not one per toggle. Home-side
  # options go under home-manager.users.nomarchy.nomarchy.*.
  scenarios = {
    # laptop formFactor + every compatible system/home toggle + impermanence.
    "laptop-stack" = {
      nomarchy.system = {
        formFactor = "laptop";
        laptop.enable = true;
        accessibility.enable = true;
        gaming.enable = true;
        features.hybridGPU = true;
        hibernation.enable = true;
        virtualization.docker.enable = true;
        impermanence.enable = true;
      };
      nomarchy.hardware.fwupd = true;
      home-manager.users.nomarchy.nomarchy = {
        accessibility.enable = true;
        gaming.enable = true;
        overrides.enable = true;
        panelPosition = "bottom";
        keymap = { layout = "de"; variant = "nodeadkeys"; };
        toggles = { waybar = false; idle = false; nightlight = false; };
      };
    };

    # desktop preset — conflicts with the laptop formFactor, so its own eval.
    "desktop-stack" = {
      nomarchy.system = { formFactor = "desktop"; desktop.enable = true; };
    };

    # impermanence multi-disk variant (alternate mainLuksName).
    "impermanence-multi" = {
      nomarchy.system.impermanence = { enable = true; mainLuksName = "crypted_main"; };
    };
  };

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

  # base00 → the three byte values the Plymouth template substitutes; plus
  # the whole palette attrset, so a missing/garbled palette is caught.
  paletteOk = name:
    let
      p = nl.getPalette name;
      b = p.base00;
      bytes = [
        (lib.fromHexString (lib.substring 0 2 b))
        (lib.fromHexString (lib.substring 2 2 b))
        (lib.fromHexString (lib.substring 4 2 b))
      ];
    in forced (builtins.deepSeq [ p bytes ] true);
in {
  scenarios = builtins.mapAttrs (_: evalToplevel) scenarios;
  palettes  = builtins.listToAttrs (map (n: { name = n; value = paletteOk n; }) nl.themeNames);
}
NIXEOF

echo "Evaluating combined toggle scenarios + per-palette colour math..."
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 scenarios
render palettes

echo
if [[ $fails -gt 0 ]]; then
  echo "$fails scenario(s) failed to evaluate. A combined scenario bundles several"
  echo "toggles — re-run the suspects individually 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."
