{ config
, lib
, pkgs
, inputs
, ...
} @ args:

let
  inherit (lib) mkOption mkEnableOption mkIf types getExe genAttrs optionalAttrs optional;
  cfg = config.my.programs.hyprland;

  hyprland = inputs.hyprland.packages.${pkgs.system}.default;
in
{
  options.my.programs.hyprland = {
    enable = mkEnableOption "hyprland";
    nvidiaSupport = mkEnableOption "enable nvidia Support";
    blur = mkOption {
      type = types.bool;
      description = "enable window blurring";
      default = true;
    };
    shadows = mkOption {
      type = types.bool;
      description = "enable window shadows";
      default = true;
    };
    keyboardLayouts = mkOption {
      type = types.listOf types.str;
      description = "list of keyboard layouts";
      default = [ "de" "us" ];
    };
    monitors = mkOption {
      type = types.attrsOf (
        let
          sub = types.submodule {
            options = {
              resolution = mkOption {
                type = types.strMatching "\(preferred\)|\([[:digit:]]+x[[:digit:]]+\(@[[:digit:]]+\)\?\)";
                default = "preferred";
              };
              position = mkOption {
                type = types.strMatching "\(auto\)|\(-\?[[:digit:]]+x-?[[:digit:]]+\)";
                default = "auto";
              };
              disabled = mkEnableOption "disabled";
              scale = mkOption {
                type = types.oneOf [ types.float (types.strMatching "auto") ];
                default = 1.0;
                apply = x: if lib.isFloat x then lib.strings.floatToString x else x;
              };
              extra = mkOption {
                type = types.listOf types.str;
                default = [ ];
                apply = lib.concatStringsSep ",";
              };
            };
          };
        in
        sub
      );
      description = "monitor setting";
    };
    extraConfig = mkOption {
      type = types.str;
      default = "";
    };
  };

  config = mkIf cfg.enable {
    my = {
      nixpkgs.overlays = [
        inputs.hypr-contrib.overlays.default
      ];
      programs = {
        wallpaper.enable = true;
        # foot.enable = true;
        kitty.enable = true;
        rofi.enable = true;
        hyprland.monitors."" = lib.mkDefault { };
        hyprland.monitors."Unknown-1" = { disabled = true; };
      };
      wallpapers.enable = true;
      services = {
        dunst.enable = true;
        wallpaper = {
          enable = true;
          target = "hyprland-session.target";
        };
        spotify-player.target = "hyprland-session.target";
      };
    };

    programs.hyprland = {
      enable = true;
      package = hyprland;
      portalPackage = inputs.hyprland.packages.${pkgs.system}.xdg-desktop-portal-hyprland;
    };

    home-manager.users.moritz = {
      # enable home-manager module
      wayland.windowManager.hyprland = {
        enable = true;
        extraConfig = import ./_config.nix args;
        systemd.extraCommands = [
          "systemctl --user stop hyprland-session.target"
          "sleep 1.5"
          "systemctl --user start hyprland-session.target"
          "systemctl --user start xdg-desktop-portal-hyprland.service"
        ];
      };

      # add waybar as a status bar
      programs.waybar = {
        enable = true;
        package = pkgs.waybar.overrideAttrs (old: {
          patches = old.patches or [ ] ++ [
            (pkgs.fetchpatch {
              url = "https://gitlab.archlinux.org/archlinux/packaging/packages/waybar/-/raw/0306af03fcb6de6aee1e288f42b0bf1b223513bd/a544f4b2cdcf632f1a4424b89f6e3d85ef5aaa85.patch";
              sha256 = "sha256-S/1oUj9Aj6BElNTsDY8CTcKtS1j7Gl54JFgCywH05pg=";
            })
          ];
        });

        # start using systemd service
        systemd = {
          enable = true;
          target = "hyprland-session.target";
        };

        settings = {
          mainBar = {
            start_hidden = true;
            layer = "top";
            position = "top";
            height = 24;
            modules-left = [ "hyprland/workspaces" ];
            modules-center = [ "hyprland/window" ];
            modules-right = [ "hyprland/language" "network" "memory" "cpu" "battery" "clock" ];
          };
        };
      };

      # lock screen after timeout
      programs.swaylock = {
        enable = true;
        settings = {
          color = "000000";
        };
      };
      services.swayidle = {
        enable = true;
        events = [
          {
            event = "before-sleep";
            command = "${getExe pkgs.swaylock} -fF";
          }
          {
            event = "lock";
            command = "${getExe pkgs.swaylock} -fF";
          }
        ];
        timeouts =
          let
            lockTimeout = 10;
          in
          [
            {
              timeout = lockTimeout * 60 - 10;
              command = "${pkgs.libnotify}/bin/notify-send 'Locking screen!'";
            }
            {
              timeout = lockTimeout * 60;
              command = "${hyprland}/bin/hyprctl dispatch dpms off";
              resumeCommand = "${hyprland}/bin/hyprctl dispatch dpms on";
            }
            {
              timeout = lockTimeout * 60 + 10;
              command = "${pkgs.systemd}/bin/loginctl lock-session";
            }
          ] ++ optional
            (!cfg.nvidiaSupport) # TODO https://github.com/hyprwm/Hyprland/issues/1728
            {
              timeout = 30 * 60;
              command = "${pkgs.systemd}/bin/systemctl suspend-and-hibernate";
            };
        systemdTarget = "hyprland-session.target";
      };
    };

    # adds pam module for swaylock
    security.pam.services.swaylock = { };

    # add user packages for wayland and hyprland in particular
    users.users.moritz.packages = with pkgs; [
      brightnessctl # control brightness
      grimblast # screenshot tool for hyprland
      pamixer # pulse audio cli
      playerctl # control media playback
      slurp # region select for wayland (for screensharing)
      wdisplays # manage monitors
      wl-clipboard # clipboard tool for wayland
    ];


    # additional environment variables
    environment.sessionVariables =
      {
        XDG_CURRENT_DESKTOP = "Hyprland";
        XDG_SESSION_TYPE = "wayland";
        XDG_SESSION_DESKTOP = "Hyprland";
        QT_AUTO_SCREEN_SCALE_FACTOR = "1";
        QT_QPA_PLATFORM = "wayland;xcb";
        QT_WAYLAND_DISABLE_WINDOWDECORATION = "1";
        QT_QPA_PLATFORMTHEME = "qt5ct";
        _JAVA_AWT_WM_NONEREPARENTING = "1";
      } // (optionalAttrs cfg.nvidiaSupport
        {
          LIBVA_DRIVER_NAME = "nvidia";
          GBM_BACKEND = "nvidia-drm";
          __GLX_VENDOR_LIBRARY_NAME = "nvidia";
          WLR_NO_HARDWARE_CURSORS = "1";
          __GL_VRR_ALLOWED = "0";
        });

    services = {
      dbus.enable = true;
      # use pipewire (needed for screensharing)
      pipewire = {
        enable = true;
        alsa.enable = true;
        alsa.support32Bit = true;
        pulse.enable = true;
      };
      displayManager = {
        enable = true;
        autoLogin = {
          enable = true;
          user = "moritz";
        };
        defaultSession = "hyprland";
      };
      xserver.enable = true;
      xserver.displayManager.lightdm.enable = true;
    };
    security.rtkit.enable = true;

    home-manager.users.moritz.systemd.user.services =
      let
        units = [ "waybar" ];
        mkAfter = _: {
          Unit = {
            After = [ "hyprland-session.target" ];
            Wants = [ "hyprland-session.target" ];
          };
        };
      in
      genAttrs units mkAfter;

    systemd.user.services =
      let
        units = [ "pipewire" "xdg-desktop-portal" "xdg-desktop-portal-hyprland" "wireplumber" ];
        mkAfter = _: {
          after = [ "hyprland-session.target" ];
          wants = [ "hyprland-session.target" ];
        };
      in
      genAttrs units mkAfter;
  };
}