{
  config,
  lib,
  pkgs,
  ...
}:

let
  cfg = config.features.swayDesktop;
  # Catppuccin Mocha (shared with the Sway desktop, see lyrathorpe/home/sway.nix).
  ctp = import ./catppuccin-mocha.nix;
in
{
  # The features.swayDesktop.enable option is declared in
  # system/modules/features.nix (so headless hosts can read/set it without
  # importing this module). This module only provides its implementation.
  config = lib.mkIf cfg.enable {
    programs.sway = {
      enable = true;
      wrapperFeatures.gtk = true;
      extraSessionCommands = ''
        # QT
        export QT_QPA_PLATFORM="wayland;xcb"
        export QT_QPA_PLATFORMTHEME=qt5ct
        # SDL
        export SDL_VIDEODRIVER=wayland
        # Java
        export _JAVA_AWT_WM_NONREPARENTING=1
        # Misc
        export CLUTTER_BACKEND=wayland
        export WINIT_UNIX_BACKEND=wayland
        export MOZ_ENABLE_WAYLAND=1
      '';
      # Core Wayland utilities. The lock screen, idle daemon, status bar and
      # notification daemon are configured per-user in home/sway.nix.
      extraPackages = with pkgs; [
        brightnessctl
        foot
        grim
        slurp # region selection for screenshots
        swappy # screenshot annotation/save
        jq # used by the focused-window screenshot bind
        playerctl # MPRIS media keys
        sway-launcher-desktop
        pavucontrol
      ];
    };
    fonts.packages = with pkgs; [
      noto-fonts
      noto-fonts-color-emoji
      font-awesome
    ];

    # Wayland login screen (replaces console/getty login on every Sway host).
    # greetd runs ReGreet inside the cage kiosk compositor; the Sway session is
    # offered automatically because programs.sway registers itself via
    # services.displayManager.sessionPackages. Hosts that turn off
    # features.swayDesktop (e.g. EDaaS) keep plain TTY login.
    programs.regreet.enable = true;
    # Theme the greeter to match the Sway desktop (Catppuccin Mocha). ReGreet is
    # GTK; recolour via CSS (covering both libadwaita named colours and plain
    # GTK node selectors) and use the same Noto Sans as the bar/notifications.
    programs.regreet.font = {
      name = "Noto Sans";
      package = pkgs.noto-fonts;
      size = 16;
    };
    programs.regreet.extraCss = ''
      /* GTK4 Adwaita legacy names (what plain GTK4 actually references). */
      @define-color theme_bg_color #${ctp.base};
      @define-color theme_fg_color #${ctp.text};
      @define-color theme_base_color #${ctp.mantle};
      @define-color theme_text_color #${ctp.text};
      @define-color theme_selected_bg_color #${ctp.blue};
      @define-color theme_selected_fg_color #${ctp.base};
      @define-color insensitive_bg_color #${ctp.mantle};
      @define-color insensitive_fg_color #${ctp.overlay0};
      @define-color borders #${ctp.surface1};
      @define-color warning_color #${ctp.peach};
      @define-color error_color #${ctp.red};
      @define-color success_color #${ctp.green};

      /* libadwaita names (inert on plain GTK4, kept for forward-compat). */
      @define-color window_bg_color #${ctp.base};
      @define-color window_fg_color #${ctp.text};
      @define-color view_bg_color #${ctp.mantle};
      @define-color view_fg_color #${ctp.text};
      @define-color card_bg_color #${ctp.surface0};
      @define-color card_fg_color #${ctp.text};
      @define-color accent_bg_color #${ctp.blue};
      @define-color accent_fg_color #${ctp.base};
      @define-color accent_color #${ctp.blue};
      @define-color destructive_bg_color #${ctp.red};
      @define-color destructive_fg_color #${ctp.base};

      window {
        background-color: #${ctp.base};
        color: #${ctp.text};
      }

      label {
        color: #${ctp.text};
      }

      entry {
        background-color: #${ctp.surface0};
        color: #${ctp.text};
        border: 1px solid #${ctp.surface1};
      }

      entry:focus-within {
        border-color: #${ctp.blue};
      }

      button,
      combobox button {
        background-color: #${ctp.surface0};
        color: #${ctp.text};
        border: 1px solid #${ctp.surface1};
      }

      button:hover {
        background-color: #${ctp.surface1};
      }

      button:active,
      button:checked {
        background-color: #${ctp.blue};
        color: #${ctp.base};
      }
    '';
    # cage reads the XKB_* environment at startup, so force the greeter onto the
    # same Dvorak layout as the Sway session (home/sway.nix) -- otherwise the
    # password field would be QWERTY. Dvorak is the "us" layout's variant, NOT a
    # layout of its own: "dvorak" alone has no symbols/ file, so the keymap
    # fails to compile and the greeter ends up with no keyboard at all (the
    # greeter has no fallback, unlike a running Sway session). This overrides the
    # greetd command regreet sets with mkDefault.
    services.greetd.settings.default_session.command =
      let
        greeter = pkgs.writeShellScript "regreet-cage" ''
          export XKB_DEFAULT_LAYOUT=us
          export XKB_DEFAULT_VARIANT=dvorak
          # ReGreet is plain GTK4 (no libadwaita); force the dark Adwaita variant
          # so the extraCss accents sit on a dark base instead of light Adwaita.
          export GTK_THEME=Adwaita:dark
          exec ${pkgs.dbus}/bin/dbus-run-session ${lib.getExe pkgs.cage} -s -- ${lib.getExe config.programs.regreet.package}
        '';
      in
      "${greeter}";

    # Desktop portals: enables screen sharing (wlroots) and native file pickers
    # for Wayland apps such as Element and Firefox.
    xdg.portal = {
      enable = true;
      wlr.enable = true;
      extraPortals = [ pkgs.xdg-desktop-portal-gtk ];
      config.common.default = "*";
    };
  };
}
