{
  pkgs,
  config,
  lib,
  ...
}:
let
  cfg = config.jade.input.remapping;
  evremap = pkgs.rustPlatform.buildRustPackage {
    pname = "evremap";
    version = "0.1.0";
    src = pkgs.fetchFromGitHub {
      owner = "wez";
      repo = "evremap";
      rev = "4480c4eda223b98899b0fbd926bc34f7bd0e1a18";
      sha256 = "sha256-BxSrphgW1n465FX6bKVkq6O0XE2JqanfSYlsGwWUWkQ=";
    };
    useFetchCargoVendor = true;
    cargoHash = "sha256-O1rJ48m8Q7kZABw/UNpmUT7FFBWQedo6CuV+NX9kDt8=";
    nativeBuildInputs = [ pkgs.pkg-config ];
    buildInputs = [ pkgs.libevdev ];
  };
  toml = pkgs.formats.toml { };
in
with lib;
{
  options.jade.input.remapping = {
    enable = mkEnableOption "Enable evremap";
    devices = mkOption {
      type = types.attrsOf (
        types.submodule (
          { name, ... }:
          {
            options = {
              device_name = mkOption {
                type = types.str;
                description = "The device name";
                default = name;
              };
              remap = mkOption {
                type = types.listOf (
                  types.submodule (
                    { ... }:
                    {
                      options.input = mkOption { type = types.listOf types.str; };
                      options.output = mkOption { type = types.listOf types.str; };
                    }
                  )
                );
                default = [ ];
              };
              swapKeys = mkOption {
                description = "Lists with two keys to be swapped on the keyboard layout.";
                type = types.listOf (
                  # verify that each key swapping list contains two elements
                  types.addCheck (types.listOf types.str) (v: builtins.length v == 2)
                );
                default = [ ];
              };
              dual_role = mkOption {
                type = types.listOf (
                  types.submodule (
                    { ... }:
                    {
                      options.input = mkOption { type = types.str; };
                      options.hold = mkOption { type = types.listOf types.str; };
                      options.tap = mkOption { type = types.listOf types.str; };
                    }
                  )
                );
                default = [ ];
              };
            };
          }
        )
      );
    };
  };
  config = mkIf cfg.enable (
    with builtins;
    let
      devs = map (
        {
          device_name,
          remap,
          swapKeys,
          dual_role,
        }:
        {
          inherit device_name dual_role;

          # expand swapKeys to normal remaps
          remap = concatLists [
            remap
            (lib.lists.flatten (
              map (keys: [
                {
                  input = [ (head keys) ];
                  output = [ (lib.lists.last keys) ];
                }
                {
                  input = [ (lib.lists.last keys) ];
                  output = [ (head keys) ];
                }
              ]) swapKeys
            ))
          ];
        }
      ) (attrValues cfg.devices);
    in
    {
      # generate numbered systemd services for each device to be remapped
      # https://github.com/wez/evremap/issues/17
      systemd.services = listToAttrs (
        genList (i: {
          name = "evremap${toString i}";
          value =
            let
              cfgFile = toml.generate "remaps-${toString i}.toml" (elemAt devs i);
            in
            {
              wantedBy = [ "multi-user.target" ];
              serviceConfig.ExecStart = "-${evremap}/bin/evremap remap ${cfgFile}";
              unitConfig = {
                Restart = "on-failure";
              };
            };
        }) (length devs)
      );
      environment.systemPackages = [ evremap ];
    }
  );
}
