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

let
  cfg = config.programs.sway;

  wayland-lib = import ./lib.nix { inherit lib; };
in
{
  options.programs.sway = {
    enable = lib.mkEnableOption ''
      Sway, the i3-compatible tiling Wayland compositor. You can manually launch
      Sway by executing "exec sway" on a TTY. Copy /etc/sway/config to
      ~/.config/sway/config to modify the default configuration. See
      <https://github.com/swaywm/sway/wiki> and
      "man 5 sway" for more information'';

    package =
      lib.mkPackageOption pkgs "sway" {
        nullable = true;
        extraDescription = ''
          If the package is not overridable with `extraSessionCommands`, `extraOptions`,
          `withBaseWrapper`, `withGtkWrapper`, `enableXWayland` and `isNixOS`,
          then the module options {option}`wrapperFeatures`, {option}`extraSessionCommands`,
          {option}`extraOptions` and {option}`xwayland` will have no effect.

          Set to `null` to not add any Sway package to your path.
          This should be done if you want to use the Home Manager Sway module to install Sway.
        '';
      }
      // {
        apply =
          p:
          if p == null then
            null
          else
            wayland-lib.genFinalPackage p {
              extraSessionCommands = cfg.extraSessionCommands;
              extraOptions = cfg.extraOptions;
              withBaseWrapper = cfg.wrapperFeatures.base;
              withGtkWrapper = cfg.wrapperFeatures.gtk;
              enableXWayland = cfg.xwayland.enable;
              isNixOS = true;
            };
      };

    wrapperFeatures = {
      base =
        lib.mkEnableOption ''
          the base wrapper to execute extra session commands and prepend a
          dbus-run-session to the sway command''
        // {
          default = true;
        };
      gtk = lib.mkEnableOption ''
        the wrapGAppsHook wrapper to execute sway with required environment
        variables for GTK applications'';
    };

    extraSessionCommands = lib.mkOption {
      type = lib.types.lines;
      default = "";
      example = ''
        # SDL:
        export SDL_VIDEODRIVER=wayland
        # QT (needs qt5.qtwayland in systemPackages):
        export QT_QPA_PLATFORM=wayland-egl
        export QT_WAYLAND_DISABLE_WINDOWDECORATION="1"
        # Fix for some Java AWT applications (e.g. Android Studio),
        # use this if they aren't displayed properly:
        export _JAVA_AWT_WM_NONREPARENTING=1
      '';
      description = ''
        Shell commands executed just before Sway is started. See
        <https://github.com/swaywm/sway/wiki/Running-programs-natively-under-wayland>
        and <https://github.com/swaywm/wlroots/blob/master/docs/env_vars.md>
        for some useful environment variables.
      '';
    };

    extraOptions = lib.mkOption {
      type = lib.types.listOf lib.types.str;
      default = [ ];
      example = [
        "--verbose"
        "--debug"
        "--unsupported-gpu"
      ];
      description = ''
        Command line arguments passed to launch Sway. Please DO NOT report
        issues if you use an unsupported GPU (proprietary drivers).
      '';
    };

    xwayland.enable = lib.mkEnableOption "XWayland" // {
      default = true;
    };

    extraPackages = lib.mkOption {
      type = with lib.types; listOf package;
      # Packages used in default config
      default = with pkgs; [
        brightnessctl
        foot
        grim
        pulseaudio
        swayidle
        swaylock
        wmenu
      ];
      defaultText = lib.literalExpression ''
        with pkgs; [ brightnessctl foot grim pulseaudio swayidle swaylock wmenu ];
      '';
      example = lib.literalExpression ''
        with pkgs; [ i3status i3status-rust termite rofi light ]
      '';
      description = ''
        Extra packages to be installed system wide. See
        <https://github.com/swaywm/sway/wiki/Useful-add-ons-for-sway> and
        <https://github.com/swaywm/sway/wiki/i3-Migration-Guide#common-x11-apps-used-on-i3-with-wayland-alternatives>
        for a list of useful software.
      '';
    };
  };

  config = lib.mkIf cfg.enable (
    lib.mkMerge [
      {
        assertions = [
          {
            assertion = cfg.extraSessionCommands != "" -> cfg.wrapperFeatures.base;
            message = ''
              The extraSessionCommands for Sway will not be run if wrapperFeatures.base is disabled.
            '';
          }
        ];

        warnings =
          lib.mkIf
            (
              (lib.elem "nvidia" config.services.xserver.videoDrivers)
              && (lib.versionOlder (lib.versions.major (lib.getVersion config.hardware.nvidia.package)) "551")
            )
            [
              "Using Sway with Nvidia driver version <= 550 may result in a broken system. Configure hardware.nvidia.package to use a newer version."
            ];

        environment = {
          systemPackages = lib.optional (cfg.package != null) cfg.package ++ cfg.extraPackages;

          # Needed for the default wallpaper:
          pathsToLink = lib.optional (cfg.package != null) "/share/backgrounds/sway";

          etc = {
            "sway/config.d/nixos.conf".source = pkgs.writeText "nixos.conf" ''
              # Import the most important environment variables into the D-Bus and systemd
              # user environments (e.g. required for screen sharing and Pinentry prompts):
              exec dbus-update-activation-environment --systemd DISPLAY WAYLAND_DISPLAY SWAYSOCK XDG_CURRENT_DESKTOP
              # enable systemd-integration
              exec "systemctl --user import-environment {,WAYLAND_}DISPLAY SWAYSOCK; systemctl --user start sway-session.target"
              exec swaymsg -t subscribe '["shutdown"]' && systemctl --user stop sway-session.target
            '';
          }
          // lib.optionalAttrs (cfg.package != null) {
            "sway/config".source = lib.mkOptionDefault "${cfg.package}/etc/sway/config";
          };
        };

        systemd.user.targets.sway-session = {
          description = "sway compositor session";
          documentation = [ "man:systemd.special(7)" ];
          bindsTo = [ "graphical-session.target" ];
          wants = [ "graphical-session-pre.target" ];
          after = [ "graphical-session-pre.target" ];
        };

        # To make a Sway session available if a display manager like SDDM is enabled:
        services.displayManager.sessionPackages = lib.optional (cfg.package != null) cfg.package;

        # https://bugs.debian.org/cgi-bin/bugreport.cgi?bug=1050913
        # https://github.com/emersion/xdg-desktop-portal-wlr/blob/master/contrib/wlroots-portals.conf
        # https://github.com/emersion/xdg-desktop-portal-wlr/pull/315
        xdg.portal.config.sway = {
          # Use xdg-desktop-portal-gtk for every portal interface...
          default = [ "gtk" ];
          # ... except for the ScreenCast, Screenshot and Secret
          "org.freedesktop.impl.portal.ScreenCast" = "wlr";
          "org.freedesktop.impl.portal.Screenshot" = "wlr";
          # ignore inhibit bc gtk portal always returns as success,
          # despite sway/the wlr portal not having an implementation,
          # stopping firefox from using wayland idle-inhibit
          "org.freedesktop.impl.portal.Inhibit" = "none";
        };
      }

      (import ./wayland-session.nix {
        inherit lib pkgs;
        enableXWayland = cfg.xwayland.enable;
      })
    ]
  );

  meta.maintainers = [ ];
}
