{ 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" ];
      };
    };

    systemd.user.targets.tray = {
      Unit = {
        Description = "Home Manager System Tray";
        Requires = [ "graphical-session-pre.target" ];
      };
    };
  };
}