#
# Configure Niri, our wayland compositor.
#
# vim: et:ts=2:sw=2:
{
  lib,
  pkgs,
  niri,
  ...
}:
let
  niri-flake = niri;
in
{
  # Add in some basic control utilities to our system config.
  environment.systemPackages = [
    pkgs.wlrctl
  ];

  #
  # Niri configuration.
  #
  home-manager.users.deprekated =
    {
      pkgs,
      config,
      niri,
      ...
    }:
    with lib;
    let
      binds =
        {
          suffixes,
          prefixes,
          substitutions ? { },
        }:

        #
        # This quite-useful monstrosity stolen from @sodiboo.
        #
        let
          replacer = replaceStrings (attrNames substitutions) (attrValues substitutions);
          format =
            prefix: suffix:
            let
              actual-suffix =
                if isList suffix.action then
                  {
                    action = head suffix.action;
                    args = tail suffix.action;
                  }
                else
                  {
                    inherit (suffix) action;
                    args = [ ];
                  };

              action = replacer "${prefix.action}-${actual-suffix.action}";
            in
            {
              name = "${prefix.key}+${suffix.key}";
              value.action.${action} = actual-suffix.args;
            };
          pairs =
            attrs: fn:
            concatMap (
              key:
              fn {
                inherit key;
                action = attrs.${key};
              }
            ) (attrNames attrs);
        in
        listToAttrs (pairs prefixes (prefix: pairs suffixes (suffix: [ (format prefix suffix) ])));

      xwayland-satellite-loop = pkgs.writeScriptBin "xwayland-satellite-loop" ''
        #!${pkgs.bash}/bin/bash

        while true; do
          ${niri-flake.packages."${pkgs.system}".xwayland-satellite-unstable}/bin/xwayland-satellite
        done
      '';
    in
    {

      #
      # Core Niri settings.
      #
      programs.niri.settings = {

        # Use Wayland programs when possible.
        environment = {
          NIXOS_OZONE_WL = "1";
          SHELL_TYPE = "prompt_toolkit";
          XDG_DATA_DIRS = "/var/lib/flatpak/exports/share:/home/deprekated/.nix-profile/share:/nix/profile/share:/home/deprekated/.local/state/nix/profile/share:/etc/profiles/per-user/deprekated/share:/nix/var/nix/profiles/default/share:/run/current-system/sw/share";

          # Provided by xwayland-satellite below.
          DISPLAY = ":0";
        };

        # Avoid client-side decorations, when possible.
        prefer-no-csd = true;

        # Don't show our hotkeys on startup.
        hotkey-overlay.skip-at-startup = true;

        #
        # Adjust our layouts.
        #
        layout = {

          # No wasted space between windows.
          gaps = 0;

          # Default to half the screen.
          default-column-width = {
            proportion = 1.0 / 2.0;
          };

          # Set up the column widths for Meta+R.
          preset-column-widths = [
            { proportion = 1.0 / 2.0; }
            { proportion = 2.0 / 3.0; }
            { proportion = 1.0 / 3.0; }
          ];

          # Improve the color of focused windows.
          border = {
            active = {
              color = "#268BD2";
            };
          };
        };

        #
        # Keyboard, mouse, etc. settings.
        #
        input = {

          # Moving focus with the keyboard should move the mouse.
          warp-mouse-to-focus = true;

          touchpad = {

            # No tap to click, but use the number of fingers to press
            # multiple buttons.
            tap = false;
            tap-button-map = "left-right-middle";
            click-method = "clickfinger";

            # Disable while typing.
            dwt = true;

            # Use natural scrolling.
            natural-scroll = true;
          };

          trackpoint = {
            accel-speed = 0.2;
            accel-profile = "flat";
          };

          keyboard = {
            # Make caps lock a second escape, and ralt a compose.
            xkb.options = "caps:escape,compose:ralt";
          };
        };

        #
        # Per-application settings.
        #
        window-rules = [

          # Wezterm workaround: wezterm gets sad when it
          # doesn't get to set its own width, so let it.
          {
            matches = [ { app-id = "^org.wezfurlong.wezterm$"; } ];
            default-column-width = { };
          }

          # Bitwig needs to set its own width.
          {
            matches = [ { app-id = "^com.bitwig.BitwigStudio$"; } ];
            default-column-width = { };
          }

          # Let gamescope be fullscreen, by default.
          {
            matches = [ { app-id = "^gamescope$"; } ];
            default-column-width = {
              proportion = 1.0;
            };
          }
        ];

        #
        # Key bindings.
        #
        binds =
          with config.lib.niri.actions;
          let
            sh = spawn "sh" "-c";
          in
          lib.attrsets.mergeAttrsList [
            {
              # Kate keys.
              "Mod+O".action = sh "haxtype '⚪O>'";
              "Mod+T".action = sh "haxtype '🔵T>'";
              "Mod+K".action = sh "haxtype '🟣K>'";
              "Mod+W".action = sh "haxtype '🟢W>'";
              "Mod+S".action = sh "haxtype '🔴S>'";
              "Mod+E".action = sh "haxtype '⚫E>'";

              # Kate keys.
              "Mod+F1".action = sh "haxtype ⑴";
              "Mod+F2".action = sh "haxtype ⑵";
              "Mod+F3".action = sh "haxtype ⑶";

              "Print".action = screenshot;

              "Mod+Return".action = spawn "wezterm";
              "Mod+D".action = spawn "fuzzel";
              "Mod+Space".action = spawn "fuzzel";

              "XF86AudioRaiseVolume".action = sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 0.1+";
              "XF86AudioLowerVolume".action = sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 0.1-";
              "XF86Launch6".action = sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 0.1-";
              "XF86Tools".action = sh "wpctl set-volume -l 1 @DEFAULT_AUDIO_SINK@ 0.1+";
              "XF86AudioMute".action = sh "wpctl set-mute @DEFAULT_AUDIO_SINK@ toggle";
              "XF86AudioMicMute".action = sh "wpctl set-mute @DEFAULT_AUDIO_SOURCE@ toggle";

              "XF86MonBrightnessDown".action = sh "brightnessctl set 10%-";
              "XF86MonBrightnessUp".action = sh "brightnessctl set 10%+";

              "XF86Messenger".action = sh "rfkill toggle bluetooth";
              "XF86Go".action = sh "rfkill toggle wwan";
              "Cancel".action = sh "rfkill block nfc";
              "XF86Favorites".action = sh "mpc toggle";

              # Lenovo buttons.
              "0x47".action = sh "brightnessctl set 10%-";
              "0x48".action = sh "brightnessctl set 10%+";

              "Mod+Q".action = close-window;

              "XF86AudioPrev".action = sh "mpc prev";
              "XF86AudioNext".action = sh "mpc next";
              "XF86AudioPlay".action = sh "mpc toggle";
              "XF86Launch5".action = sh "mpc toggle";
            }
            (binds {
              suffixes."Left" = "column-left";
              suffixes."Down" = "window-down";
              suffixes."Up" = "window-up";
              suffixes."Right" = "column-right";
              prefixes."Mod" = "focus";
              prefixes."Mod+Ctrl" = "move";
              prefixes."Mod+Shift" = "focus-monitor";
              prefixes."Mod+Shift+Ctrl" = "move-window-to-monitor";
              substitutions."monitor-column" = "monitor";
              substitutions."monitor-window" = "monitor";
            })
            (binds {
              suffixes."Home" = "first";
              suffixes."End" = "last";
              prefixes."Mod" = "focus-column";
              prefixes."Mod+Ctrl" = "move-column-to";
            })
            (binds {
              suffixes."U" = "workspace-down";
              suffixes."I" = "workspace-up";
              prefixes."Mod" = "focus";
              prefixes."Mod+Ctrl" = "move-window-to";
              prefixes."Mod+Shift" = "move";
            })
            (binds {
              suffixes = builtins.listToAttrs (
                map (n: {
                  name = toString n;
                  value = [
                    "workspace"
                    n
                  ];
                }) (range 1 9)
              );
              prefixes."Mod" = "focus";
              prefixes."Mod+Ctrl" = "move-window-to";
            })
            {
              "Mod+Comma".action = consume-window-into-column;
              "Mod+Period".action = expel-window-from-column;

              "Mod+R".action = switch-preset-column-width;
              "Mod+F".action = maximize-column;
              "Mod+Shift+F".action = fullscreen-window;
              "Mod+C".action = center-column;

              "Mod+Minus".action = set-column-width "-10%";
              "Mod+Plus".action = set-column-width "+10%";
              "Mod+Shift+Minus".action = set-window-height "-10%";
              "Mod+Shift+Plus".action = set-window-height "+10%";

              "Mod+Print".action = screenshot-window;

              "Mod+Shift+E".action = quit;
              "Mod+Shift+P".action = power-off-monitors;

              "Mod+Shift+Ctrl+T".action = toggle-debug-tint;
            }
          ];

        # Startup our wallpaper and our credential manager.
        spawn-at-startup =
          let
            niri-init = pkgs.writeScriptBin "niri-init" ''
              #!${pkgs.bash}/bin/bash
              systemctl --user restart swaybg
              niri msg action spawn -- gnome-keyring

              sleep 2

              systemctl --user stop waybar
              niri msg action spawn -- waybar

              sleep 2

              niri msg action spawn -- ${xwayland-satellite-loop}/bin/xwayland-satellite-loop
              niri msg action spawn -- home-assistant-desktop

              sleep 10

              niri msg action spawn -- kdeconnect-cli -l
            '';
          in
          [ { command = [ "${niri-init}/bin/niri-init" ]; } ];
      };
    };
}
