# Pure monitor-rule composition, split out of hyprland.nix so
# checks.display-profiles can exercise the overlay semantics directly —
# no Home Manager evaluation, no state-file fixture.
{ lib }:

rec {
  # Skeleton for an entry built outside the option type (mirrors the
  # monitorType defaults in options.nix).
  defaults = {
    resolution = "preferred"; position = "auto"; scale = 1;
    transform = null; mirror = null; bitdepth = null; vrr = null; extra = "";
  };

  # An entry -> a Hyprland `monitor` rule. Unset optional fields are
  # omitted; `resolution = "disable"` collapses to the short form.
  rule = m:
    if m.resolution == "disable" then "${m.name}, disable"
    else lib.concatStringsSep ", " (
      [ m.name m.resolution (toString m.position) (toString m.scale) ]
      ++ lib.optionals (m.transform != null) [ "transform" (toString m.transform) ]
      ++ lib.optionals (m.mirror != null) [ "mirror" m.mirror ]
      ++ lib.optionals (m.bitdepth != null) [ "bitdepth" (toString m.bitdepth) ]
      ++ lib.optionals (m.vrr != null) [ "vrr" (toString m.vrr) ]
      ++ lib.optional (m.extra != "") m.extra
    );

  # A profile's workspace pinning ({ "1" = "DP-3"; }) -> a Hyprland
  # `workspace` rule. Only the active profile's pins are rendered
  # (hyprland.nix); a workspace rule naming an absent output is inert.
  workspaceRule = ws: out: "${ws}, monitor:${out}";

  # The full overlay, three layers:
  #
  #  1. base (nomarchy.monitors) with the ACTIVE display profile's entries
  #     (settings.displayProfile naming a nomarchy.displayProfiles key)
  #     replacing base entries WHOLE, by name — a profile entry is a
  #     complete statement about that output. Outputs a profile doesn't
  #     name keep their base rules; a cleared/unknown/non-string value
  #     ("" after `nomarchy-display-profile base`, or hand-edited junk —
  #     the validator only warns) means base config only.
  #
  #  2. Menu-remembered per-output resolutions (settings.monitors:
  #     output-name -> "WxH@R", written instantly by the Display menu)
  #     overlaid field-level by name: a declared output keeps its
  #     position/scale/etc and only its resolution changes; an output
  #     covered solely by the `,preferred,auto,1` wildcard (e.g. the
  #     laptop's built-in panel) becomes a new rule with default
  #     position/scale. The menu pick wins over a hand-set resolution
  #     (it's the explicit live action) — EXCEPT onto disabled entries: a
  #     stale pick, made while the output was enabled, must not resurrect
  #     a panel the active profile (or the base config) disables.
  #
  #  3. Anything still uncovered falls to the wildcard (hyprland.nix
  #     prepends it).
  resolve = { base, profiles, active, resOverrides }:
    let
      activeName = if builtins.isString active then active else "";
      profileEntries = profiles.${activeName} or [ ];
      profileNames = map (m: m.name) profileEntries;
      baseMonitors =
        lib.filter (m: ! lib.elem m.name profileNames) base
        ++ profileEntries;
      declaredNames = map (m: m.name) baseMonitors;
    in
    map (m: if resOverrides ? ${m.name} && m.resolution != "disable"
            then m // { resolution = resOverrides.${m.name}; }
            else m)
        baseMonitors
    ++ lib.mapAttrsToList
         (name: res: defaults // { inherit name; resolution = res; })
         (lib.filterAttrs (name: _: ! lib.elem name declaredNames) resOverrides);
}
