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

let
  inherit (lib) types;
  cfg = config.wayland.windowManager.river;

  # Systemd integration
  variables = builtins.concatStringsSep " " cfg.systemd.variables;
  extraCommands = builtins.concatStringsSep " " (map (f: "&& ${f}") cfg.systemd.extraCommands);
  systemdActivation = ''
    ${pkgs.dbus}/bin/dbus-update-activation-environment --systemd ${variables} ${extraCommands}
  '';

  toValue =
    val:
    if lib.isString val || lib.isDerivation val then
      toString val
    else if true == val then
      "enabled"
    else if false == val then
      "disabled"
    else if lib.isInt val then
      toString val
    else if lib.isFloat val then
      lib.strings.floatToString val
    else
      abort "unsupported type ${builtins.typeOf val}";

  # Intermediary function that converts some value (attrs, str, ...) to one or several commands.
  toArgs =
    path: value:

    let
      stringValue = lib.concatStringsSep " " (path ++ [ (toValue value) ]);
      finalValue =
        if lib.isAttrs value then
          toCommand path value
        else if lib.isList value then
          lib.lists.flatten (map (x: toArgs path x) value)
        else if value == null then
          [ ]
        else
          [ stringValue ];
    in
    finalValue;

  # toCommand :: [string] -> attrs -> [string]
  # Recursive function that converts an attrs to a list of commands that can be written to the
  # config file.
  toCommand =
    basePath: attrs:
    lib.concatLists (
      lib.mapAttrsToList (
        key: value:
        let
          path = basePath ++ [ key ];
        in
        toArgs path value
      ) attrs
    );

in
{
  meta.maintainers = [ lib.maintainers.GaetanLepage ];

  options.wayland.windowManager.river = {
    enable = lib.mkEnableOption "the river window manager";

    package = lib.mkPackageOption pkgs "river" {
      nullable = true;
      extraDescription = ''
        Set to `null` to not add any river package to your path.
        This should be done if you want to use the NixOS river module to install river.
      '';
    };

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

    systemd = {
      enable = lib.mkEnableOption null // {
        default = true;
        description = ''
          Whether to enable {file}`river-session.target` on
          river startup. This links to {file}`graphical-session.target`}.
          Some important environment variables will be imported to systemd
          and D-Bus user environment before reaching the target, including
          - `DISPLAY`
          - `WAYLAND_DISPLAY`
          - `XDG_CURRENT_DESKTOP`
          - `NIXOS_OZONE_WL`
          - `XCURSOR_THEME`
          - `XCURSOR_SIZE`
        '';
      };

      variables = lib.mkOption {
        type = types.listOf types.str;
        default = [
          "DISPLAY"
          "WAYLAND_DISPLAY"
          "XDG_CURRENT_DESKTOP"
          "NIXOS_OZONE_WL"
          "XCURSOR_THEME"
          "XCURSOR_SIZE"
        ];
        example = [ "-all" ];
        description = ''
          Environment variables to be imported in the systemd & D-Bus user
          environment.
        '';
      };

      extraCommands = lib.mkOption {
        type = types.listOf types.str;
        default = [
          "systemctl --user stop river-session.target"
          "systemctl --user start river-session.target"
        ];
        description = "Extra commands to be run after D-Bus activation.";
      };
    };

    extraSessionVariables = lib.mkOption {
      type = types.attrs;
      default = { };
      description = "Extra session variables set when running the compositor.";
      example = {
        MOZ_ENABLE_WAYLAND = "1";
      };
    };

    settings = lib.mkOption {
      type =
        let
          valueType =
            with types;
            nullOr (oneOf [
              bool
              int
              float
              str
              path
              (attrsOf valueType)
              (listOf valueType)
            ])
            // {
              description = "River configuration value";
            };
        in
        valueType;
      default = { };
      description = "General settings given to `riverctl`.";
      example = {
        border-width = 2;
        declare-mode = [
          "locked"
          "normal"
          "passthrough"
        ];
        map.normal."Alt Q" = "close";
        input.pointer-foo-bar = {
          accel-profile = "flat";
          events = true;
          pointer-accel = -0.3;
          tap = false;
        };
        rule-add."-app-id" = {
          "'bar'" = "csd";
          "'float*'"."-title"."'foo'" = "float";
        };
        set-cursor-warp = "on-output-change";
        set-repeat = "50 300";
        xcursor-theme = "someGreatTheme 12";
        spawn = [
          "firefox"
          "'foot -a terminal'"
        ];
      };
    };

    extraConfig = lib.mkOption {
      type = types.lines;
      default = "";
      example = ''
        rivertile -view-padding 6 -outer-padding 6 &
      '';
      description = "Extra lines appended to {file}`$XDG_CONFIG_HOME/river/init`.";
    };
  };

  config = lib.mkIf cfg.enable {
    assertions = [
      (lib.hm.assertions.assertPlatform "wayland.windowManager.river" pkgs lib.platforms.linux)
    ];

    home.packages =
      lib.optional (cfg.package != null) cfg.package
      ++ lib.optional cfg.xwayland.enable pkgs.xwayland;

    # Configuration file ~/.config/river/init
    xdg.configFile."river/init".source = pkgs.writeShellScript "init" (
      ''
        ### This file was generated with Nix. Don't modify this file directly.

        ### SHELL VARIABLES ###
        ${config.lib.shell.exportAll cfg.extraSessionVariables}

        ### CONFIGURATION ###
        ${lib.concatStringsSep "\n" (toCommand [ "riverctl" ] cfg.settings)}

        ### EXTRA CONFIGURATION ###
        ${cfg.extraConfig}

      ''
      + (lib.optionalString cfg.systemd.enable ''
        ### SYSTEMD INTEGRATION ###
        ${systemdActivation}
      '')
    );

    # Systemd integration
    systemd.user.targets.river-session = lib.mkIf cfg.systemd.enable {
      Unit = {
        Description = "river compositor session";
        Documentation = [ "man:systemd.special(7)" ];
        BindsTo = [ "graphical-session.target" ];
        Wants = [ "graphical-session-pre.target" ];
        After = [ "graphical-session-pre.target" ];
      };
    };
  };
}
