# TODO: The keys to reset the workspaces need to depend on actual workspace config
# TODO: Many of the text file generations can be made simpler with pipe and concatLines functions...
{
  config,
  nixosConfig,
  lib,
  mylib,
  pkgs,
  ...
}: let
  cfg = config.modules.hyprland;
in {
  options.modules.hyprland = import ./options.nix {inherit lib mylib;};

  config = lib.mkIf cfg.enable {
    assertions = [
      {
        assertion = nixosConfig.programs.hyprland.enable;
        message = "Can't enable Hyprland module with Hyprland disabled!";
      }
    ];

    gtk = {
      enable = true;
      iconTheme.package = pkgs.papirus-icon-theme;
      iconTheme.name = "Papirus";
    };

    # TODO: catppuccin-cursors
    home.pointerCursor = {
      gtk.enable = true;
      x11.enable = true;
      package = pkgs.bibata-cursors;
      name = "Bibata-Modern-Classic";
      # package = pkgs.catppuccin-cursors.latteMauve;
      # name = "Catppuccin-Latte-Mauve-Cursors";
      size = 16;
    };

    home.packages = with pkgs; [
      hyprpaper # Wallpaper setter
      hyprpicker # Color picker

      wl-clipboard
      clipman # Clipboard manager (wl-paste)

      moc # Audio player
      ncpamixer # ncurses pavucontrol
      slurp # Region selector for screensharing
      grim # Grab images from compositor

      xfce.thunar
      xfce.tumbler # Thunar thumbnails
      libsForQt5.polkit-kde-agent
    ];

    programs = {
      imv = {
        enable = true;
        settings = {
          options.background = "ffffff";
        };
      };
    };

    services = {
      # TODO: Make a module out of this
      # Notification service
      dunst = {
        enable = true;

        iconTheme.package = pkgs.papirus-icon-theme;
        iconTheme.name = "Papirus";

        settings = {
          global = {
            monitor = 1;
            font = "JetBrains Nerd Font Mono 11";
            offset = "20x20";
            frame_color = "#1E66F5";
            frame_width = 2;
            corner_radius = 5;
            separator_color = "frame";
          };

          urgency_low = {
            background = "#EFF1F5";
            foreground = "#4C4F69";
          };

          urgency_normal = {
            background = "#EFF1F5";
            foreground = "#4C4F69";
          };

          urgency_critical = {
            background = "#EFF1F5";
            foreground = "#4C4F69";
            frame_color = "#FE640B";
          };
        };
      };
    };

    home.sessionVariables = {
    };

    #
    # Generate many individual config files from the options
    #

    # Polkit
    home.file.".config/hypr/polkit.conf".text = ''
      exec-once = ${pkgs.libsForQt5.polkit-kde-agent}/libexec/polkit-kde-authentication-agent-1
    '';

    # Monitors for different systems
    home.file.".config/hypr/monitors.conf".text = let
      mkMonitor = name: conf: "monitor = ${name}, ${toString conf.width}x${toString conf.height}@${toString conf.rate}, ${toString conf.x}x${toString conf.y}, ${toString conf.scale}";
    in
      lib.pipe cfg.monitors [
        (builtins.mapAttrs mkMonitor)
        builtins.attrValues
        (builtins.concatStringsSep "\n")
      ];

    # Bind workspaces to monitors
    home.file.".config/hypr/workspaces.conf".text = let
      mkWorkspace = monitor: workspace: "workspace = ${toString workspace}, monitor:${toString monitor}";
      mkWorkspaces = monitor: workspace-list: map (mkWorkspace monitor) workspace-list;
    in
      lib.pipe cfg.workspaces [
        (builtins.mapAttrs mkWorkspaces)
        builtins.attrValues
        builtins.concatLists
        (builtins.concatStringsSep "\n")
      ];

    # Keybindings
    home.file.".config/hypr/keybindings.conf".text = let
      always-bind = {
        # Hyprland control
        "$mainMod, Q" = ["killactive"];
        "$mainMod, V" = ["togglefloating"];
        "$mainMod, F" = ["fullscreen"];
        "$mainMod, C" = ["exec, clipman pick --tool=rofi"];
        "$mainMod, G" = ["togglegroup"];
        "ALT, tab" = ["changegroupactive"];
      };

      mkBind = key: action: "bind = ${key}, ${action}";
      mkBinds = key: actions: builtins.map (mkBind key) actions;
      binds = lib.pipe (lib.mergeAttrs cfg.keybindings.bindings always-bind) [
        (builtins.mapAttrs mkBinds)
        builtins.attrValues
        builtins.concatLists
        (builtins.concatStringsSep "\n")
      ];
    in ''
      $mainMod = ${cfg.keybindings.main-mod}
      ${binds}
    '';

    # Autostart applications
    home.file.".config/hypr/autostart.conf".text = let
      # Stuff that is not negotiable
      always-exec = [
        "dunst" # Notifications
        "hyprpaper"
        "wl-paste -t text --watch clipman store --no-persist"
        "wl-paste -p -t text --watch clipman store -P --histpath=\"~/.local/share/clipman-primary.json\""
        "hyprctl setcursor Bibata-Modern-Classic 16"
      ];

      mkExec = prog: "exec-once = ${prog}";
    in
      lib.pipe (cfg.autostart ++ always-exec) [
        (builtins.map mkExec)
        (builtins.concatStringsSep "\n")
      ];

    # Assign windows to workspaces
    home.file.".config/hypr/workspacerules.conf".text = let
      mkWorkspaceRule = workspace: class: "windowrulev2 = workspace ${workspace}, class:^(${class})$";
      mkWorkspaceRules = workspace: class-list: map (mkWorkspaceRule workspace) class-list;
    in
      lib.pipe cfg.workspacerules [
        (builtins.mapAttrs mkWorkspaceRules)
        builtins.attrValues
        builtins.concatLists
        (builtins.concatStringsSep "\n")
      ];

    # Make windows float
    home.file.".config/hypr/floatingrules.conf".text = let
      mkFloatingRule = attrs:
        "windowrulev2 = float"
        + (lib.optionalString (builtins.hasAttr "class" attrs) ", class:^(${attrs.class})$")
        + (lib.optionalString (builtins.hasAttr "title" attrs) ", title:^(${attrs.title})$");
    in
      lib.pipe cfg.floating [
        (builtins.map mkFloatingRule)
        (builtins.concatStringsSep "\n")
      ];

    # Make windows translucent
    home.file.".config/hypr/translucentrules.conf".text = let
      opacity = 0.8;
      mkTranslucentRule = class: "windowrulev2 = opacity ${toString opacity} ${toString opacity}, class:^(${class})$";
    in
      lib.pipe cfg.transparent [
        (builtins.map mkTranslucentRule)
        (builtins.concatStringsSep "\n")
      ];

    # Set wallpaper for each configured monitor
    home.file.".config/hypr/hyprpaper.conf".text = let
      mkWallpaper = monitor: "wallpaper = ${monitor}, ${config.home.homeDirectory}/NixFlake/wallpapers/${cfg.theme}.png";
      wallpapers = lib.pipe cfg.monitors [
        builtins.attrNames
        (builtins.map mkWallpaper)
        (builtins.concatStringsSep "\n")
      ];
    in ''
      preload = ~/NixFlake/wallpapers/${cfg.theme}.png
      ${wallpapers}
    '';

    # Keyboard layout
    home.file.".config/hypr/input.conf".text = ''
      input {
          kb_layout = ${cfg.kb-layout}
          kb_variant = ${cfg.kb-variant}
          kb_model = pc104
          kb_options =
          kb_rules =

          follow_mouse = 1

          touchpad {
              natural_scroll = no
          }

          sensitivity = 0 # -1.0 - 1.0, 0 means no modification.
      }
    '';

    home.activation = {
      # NOTE: Keep the hyprland config symlinked, to allow easy changes with hotreload
      # TODO: Don't symlink at all, why not just tell Hyprland where the config is? Much easier
      linkHyprlandConfig =
        lib.hm.dag.entryAfter ["writeBoundary"]
        (mylib.modules.mkLink "~/NixFlake/config/hyprland/hyprland.conf" "~/.config/hypr/hyprland.conf");
    };
  };
}
