{
  lib,
  config,
  inputs,
  pkgs,
  ...
}: let
  cfg = config.marleyos.wayland.hyprland;
in {
  options.marleyos.wayland.hyprland = {
    enable = lib.mkEnableOption "hyprland";

    monitors = lib.mkOption {
      description = "Monitor configuration.";
      type = with lib.types; attrsOf str;
    };

    mainMonitor = lib.mkOption {
      description = "Which monitor to treat as the main for workspace assignment";
      type = with lib.types; str;
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion =
          if cfg ? monitors
          then cfg ? mainMonitor
          else true;
        message = ''
          You have defined monitors but not selected the main monitor. Please
          define marleyos.wayland.hyprland.mainMonitor.
        '';
      }
    ];

    marleyos = {
      programs = {
        hyprlock.enable = true;
        waybar.enable = true;
        wlogout.enable = true;
        wofi.enable = true;
      };
      services = {
        swaync.enable = true;
      };
    };

    home.pointerCursor.hyprcursor.enable = true;

    home.packages =
      lib.optional
      config.rose-pine.enable
      inputs.rose-pine-hyprcursor.packages.${pkgs.system}.default;

    services.hyprpaper = {
      enable = true;

      settings = {
        preload = "${./wallpaper.png}";
        wallpaper = ", ${./wallpaper.png}";
      };
    };

    wayland.windowManager.hyprland = {
      enable = true;

      systemd.enable = true;
      xwayland.enable = true;

      settings = let
        numMonitors = lib.length (lib.attrNames (cfg.monitors or {}));
        workspaces = lib.range 1 10;
        wsPerMonitor = 10 / numMonitors;

        mainMonWs = lib.range 1 wsPerMonitor;
        secondaryWs = lib.subtractLists mainMonWs workspaces;

        monitors = lib.mapAttrs (mon: _:
          if (mon == (cfg.mainMonitor or ""))
          then ""
          else lib.replicate wsPerMonitor "${mon}")
        (cfg.monitors or {});

        secondaryMons = removeAttrs monitors [(cfg.mainMonitor or "")];
        monList = lib.concatLists (lib.attrValues secondaryMons);

        firstWsToMons =
          lib.zipListsWith (mon: ws: {
            name = toString ws;
            value = mon;
          })
          monList
          secondaryWs;

        leftover =
          lib.sublist (lib.length monList) (10 - (wsPerMonitor * numMonitors)) secondaryWs;

        wsToMons =
          firstWsToMons
          ++ (map (ws: {
              name = toString ws;
              value = lib.last monList;
            })
            leftover)
          ++ (map (ws: {
              name = toString ws;
              value = cfg.mainMonitor or "";
            })
            mainMonWs);

        wsMons = lib.listToAttrs wsToMons;
      in {
        monitor = lib.attrValues cfg.monitors;

        "$terminal" = lib.getExe config.marleyos.apps.terminal;
        "$launcher" = let
          inherit (config.marleyos.apps) launcher;
        in "${lib.getExe launcher.package} ${launcher.command}";
        "$browser" = lib.getExe config.marleyos.apps.browser;

        exec-once = let
          browserWs =
            if (cfg ? monitors)
            then wsPerMonitor + 1
            else 2;
        in [
          (lib.getExe config.programs.waybar.package)
          "[workspace 1 silent] $terminal -e tmux new -s main -A"
          "[workspace ${toString browserWs} silent] $browser"
        ];

        # Fix WM thinking the cursor is bigger than it is, causing the pointer
        # to be offset.
        env =
          [
            "XCURSOR_SIZE,24"
            "HYPRCURSOR_SIZE,24"
          ]
          ++ (lib.optional config.rose-pine.enable "HYPRCURSOR_THEME,rose-pine-hyprcursor");

        workspace =
          lib.mkIf (cfg ? monitors)
          (map
            (ws: "${toString ws}, monitor:${wsMons."${toString ws}"}")
            workspaces);

        "$main" = lib.mkIf config.rose-pine.enable "$love";
        "$inactive" = lib.mkIf config.rose-pine.enable "$muted";

        general = {
          gaps_in = 5;
          gaps_out = 20;
          border_size = 2;

          "col.inactive_border" = "$inactive";
          "col.active_border" =
            lib.mkIf config.rose-pine.enable "$rose $pine $love $iris 90deg";

          resize_on_border = false;

          layout = "dwindle";
        };

        decoration = {
          rounding = 10;

          shadow = {
            enabled = true;
            range = 4;
            render_power = 3;
            color = "rgba(1a1a1aee)";
          };

          blur = {
            enabled = true;
            size = 3;
            passes = 1;

            vibrancy = 0.1696;
          };
        };

        animations = {
          enabled = true;

          bezier = [
            "easeOutQuint,0.23,1,0.32,1"
            "easeInOutCubic,0.65,0.05,0.36,1"
            "linear,0,0,1,1"
            "almostLinear,0.5,0.5,0.75,1.0"
            "quick,0.15,0,0.1,1"
          ];

          animation = [
            "global, 1, 10, default"
            "border, 1, 5.39, easeOutQuint"
            "windows, 1, 4.79, easeOutQuint"
            "windowsIn, 1, 4.1, easeOutQuint, popin 87%"
            "windowsOut, 1, 1.49, linear, popin 87%"
            "fadeIn, 1, 1.73, almostLinear"
            "fadeOut, 1, 1.46, almostLinear"
            "fade, 1, 3.03, quick"
            "layers, 1, 3.81, easeOutQuint"
            "layersIn, 1, 4, easeOutQuint, fade"
            "layersOut, 1, 1.5, linear, fade"
            "fadeLayersIn, 1, 1.79, almostLinear"
            "fadeLayersOut, 1, 1.39, almostLinear"
            "workspaces, 1, 1.94, almostLinear, fade"
            "workspacesIn, 1, 1.21, almostLinear, fade"
            "workspacesOut, 1, 1.94, almostLinear, fade"
          ];
        };

        dwindle = {
          pseudotile = true;
          preserve_split = true;
        };

        input = {
          kb_layout = "us";
          numlock_by_default = true;
          follow_mouse = 2;
        };

        "$mod" = "SUPER";

        # When changing these, make sure to also update waybar's script, defined
        # below.
        bind = [
          "$mod, Q, killactive,"
          "$mod SHIFT, E, exit,"

          "$mod SHIFT, SPACE, togglefloating"
          "$mod, E, togglesplit"

          "$mod, RETURN, exec, $terminal"
          "$mod, R, exec, $launcher"

          "$mod, h, movefocus, l"
          "$mod, j, movefocus, d"
          "$mod, k, movefocus, u"
          "$mod, l, movefocus, r"

          "$mod, 1, workspace, 1"
          "$mod, 2, workspace, 2"
          "$mod, 3, workspace, 3"
          "$mod, 4, workspace, 4"
          "$mod, 5, workspace, 5"
          "$mod, 6, workspace, 6"
          "$mod, 7, workspace, 7"
          "$mod, 8, workspace, 8"
          "$mod, 9, workspace, 9"
          "$mod, 0, workspace, 10"

          "$mod SHIFT, 1, movetoworkspace, 1"
          "$mod SHIFT, 2, movetoworkspace, 2"
          "$mod SHIFT, 3, movetoworkspace, 3"
          "$mod SHIFT, 4, movetoworkspace, 4"
          "$mod SHIFT, 5, movetoworkspace, 5"
          "$mod SHIFT, 6, movetoworkspace, 6"
          "$mod SHIFT, 7, movetoworkspace, 7"
          "$mod SHIFT, 8, movetoworkspace, 8"
          "$mod SHIFT, 9, movetoworkspace, 9"
          "$mod SHIFT, 0, movetoworkspace, 10"

          "$mod, mouse_down, workspace, e+1"
          "$mod, mouse_up, workspace, e-1"
        ];

        bindm = [
          "$mod, mouse:272, movewindow"
          "$mod, mouse:273, resizewindow"
        ];

        windowrulev2 = [
          # Ignore maximize requests from apps.
          "suppressevent maximize, class:.*"

          # Fix some dragging issues with XWayland.
          "nofocus,class:^$,title:^$,xwayland:1,floating:1,fullscreen:0,pinned:0"
        ];
      };
    };

    marleyos.programs.waybar.keybindings-script = [
      ''" = SUPER" "modifier key" ""''
      ''" + ENTER" "launch terminal" ""''
      ''" + r" "launcher" ""''
      ''" + q" "kill focused window" "killactive"''
      ''" + SHIFT + E" "exit hyprland" "exit"''
      ''" + SHIFT + SPACE" "toggle floating" "togglefloating"''
      ''" + e" "toggle split direction" "togglesplit"''
      ''" + h" "move focus left" "movefocus, l"''
      ''" + j" "move focus down" "movefocus, d"''
      ''" + k" "move focus up" "movefocus, u"''
      ''" + l" "move focus right" "movefocus, r"''
      ''" + 1-0" "move to workspace 1-10" "workspace, #"''
      ''" + SHIFT + 1-0" "move window to workspace 1-10" "movetoworkspace, #"''
      ''" + MOUSE UP" "move to next workspace" "workspace, e+1"''
      ''" + MOUSE DOWN" "move to prev workspace" "workspace, e-1"''
      ''" + LMB" "move/drag window" "movewindow"''
      ''" + RMB" "resize window" "resizewindow"''
    ];
  };
}