# Capture window classes for BACKLOG #41 float candidates (hyprpolkitagent,
# pinentry-qt, optional portal). Headless softGL Hyprland; dumps hyprctl
# clients while dialogs are open into $out/classes.json + classes.txt.
#
#   nix build --impure -f tools/capture-float-classes.nix --no-link --print-out-paths
#
# Maintainer harness — not a checks.* gate (needs interactive dialogs;
# softGL may drop some Qt windows).
let
  flake = builtins.getFlake ("git+file://" + toString ../.);
  inherit (flake.inputs) nixpkgs home-manager;
  pkgs = import nixpkgs {
    system = "x86_64-linux";
    overlays = [ flake.overlays.default ];
    config.allowUnfree = true;
  };
in
pkgs.testers.runNixOSTest {
  name = "capture-float-classes";
  node.pkgsReadOnly = false;
  node.specialArgs = { username = "nomarchy"; };
  nodes.machine = { pkgs, lib, ... }: {
    imports = [ flake.nixosModules.nomarchy home-manager.nixosModules.home-manager ];
    virtualisation.memorySize = 4096;
    virtualisation.cores = 2;
    hardware.graphics.enable = true;
    environment.variables.LIBGL_ALWAYS_SOFTWARE = "1";
    boot.initrd.availableKernelModules = [ "virtio_gpu" ];
    virtualisation.qemu.options = [ "-vga none" "-device virtio-gpu-pci,xres=1280,yres=800" ];

    users.users.nomarchy = {
      isNormalUser = true;
      password = "test";
      extraGroups = [ "wheel" "video" ];
    };
    security.sudo.wheelNeedsPassword = false;
    # PolicyKit test action so pkexec can raise the agent.
    security.polkit.enable = true;

    nomarchy.system.stateFile = flake + "/theme-state.json";
    systemd.tmpfiles.rules = [
      "d /home/nomarchy/.nomarchy 0755 nomarchy users -"
      "C /home/nomarchy/.nomarchy/theme-state.json 0644 nomarchy users - ${flake + "/theme-state.json"}"
    ];
    services.greetd.settings.initial_session = {
      command = "start-hyprland";
      user = "nomarchy";
    };
    home-manager.useGlobalPkgs = true;
    home-manager.users.nomarchy = {
      imports = [ flake.homeModules.nomarchy ];
      nomarchy.stateFile = flake + "/theme-state.json";
      nomarchy.idle.enable = false;
      # Ensure pinentry-qt is the active pinentry (keys.nix default).
    };
  };
  testScript = ''
    machine.wait_for_unit("multi-user.target")
    machine.wait_for_file("/run/user/1000/hypr", 180)
    machine.sleep(15)

    hy_env = "XDG_RUNTIME_DIR=/run/user/1000 HYPRLAND_INSTANCE_SIGNATURE=$(ls /run/user/1000/hypr | head -1)"
    hyctl = f"su - nomarchy -c '{hy_env} hyprctl"

    def clients():
        return machine.succeed(f"{hyctl} clients -j'").strip()

    dumps = []

    # ── pinentry-qt (keys.nix default) ───────────────────────────────
    machine.execute(
        f"su - nomarchy -c '{hy_env} timeout 8 pinentry-qt' >/tmp/pinentry.log 2>&1 &"
    )
    machine.sleep(3)
    dumps.append(("pinentry", clients()))
    machine.execute("pkill -x pinentry-qt >/dev/null 2>&1 || true")
    machine.sleep(1)

    # ── hyprpolkitagent via pkexec (passwordless sudo wheel — may still
    #    open agent depending on polkit rules; best-effort) ────────────
    machine.execute(
        f"su - nomarchy -c '{hy_env} timeout 8 pkexec true' >/tmp/pkexec.log 2>&1 &"
    )
    machine.sleep(3)
    dumps.append(("pkexec", clients()))
    machine.execute("pkill -x hyprpolkitagent >/dev/null 2>&1 || true")
    machine.execute("pkill -f pkexec >/dev/null 2>&1 || true")
    machine.sleep(1)

    # ── Always dump current clients for residual ─────────────────────
    dumps.append(("idle", clients()))

    import json
    out_path = machine.succeed("mktemp -d").strip()
    summary = []
    for label, raw in dumps:
        path = f"{out_path}/{label}.json"
        machine.succeed(f"cat > {path} <<'EOF'\n{raw}\nEOF")
        try:
            data = json.loads(raw) if raw else []
        except Exception as e:
            data = []
            summary.append(f"{label}: parse error {e}")
        classes = sorted({
            c.get("class") or c.get("initialClass") or ""
            for c in (data if isinstance(data, list) else [])
        } - {""})
        titles = sorted({
            c.get("title") or c.get("initialTitle") or ""
            for c in (data if isinstance(data, list) else [])
        } - {""})
        summary.append(f"{label}: classes={classes} titles={titles}")

    machine.succeed(
        "cat > /tmp/float-class-summary.txt <<'EOF'\n"
        + "\n".join(summary)
        + "\nEOF"
    )
    print("\n".join(summary))
    # Copy into the test result via machine.copy_from_vm if available —
    # at minimum assert pinentry produced *some* client dump JSON.
    machine.succeed("test -s /tmp/float-class-summary.txt")
  '';
}
