{ lib }:
let
  inherit (lib) optionalAttrs isAttrs types;
  inherit (lib.nixvim) defaultNullOpts mkNullOrOption mkNullOrStr;
in
rec {
  # These are the configuration options that change the behavior of each mapping.
  mapConfigOptions = {
    silent = defaultNullOpts.mkBool false "Whether this mapping should be silent. Equivalent to adding `<silent>` to a map.";

    nowait = defaultNullOpts.mkBool false "Whether to wait for extra input on ambiguous mappings. Equivalent to adding `<nowait>` to a map.";

    script = defaultNullOpts.mkBool false "Equivalent to adding `<script>` to a map.";

    expr = defaultNullOpts.mkBool false "Means that the action is actually an expression. Equivalent to adding `<expr>` to a map.";

    unique = defaultNullOpts.mkBool false "Whether to fail if the map is already defined. Equivalent to adding `<unique>` to a map.";

    noremap = defaultNullOpts.mkBool true "Whether to use the `noremap` variant of the command, ignoring any custom mappings on the defined action. It is highly advised to keep this on, which is the default.";

    remap = defaultNullOpts.mkBool false "Make the mapping recursive. Inverses `noremap`.";

    desc = mkNullOrStr "A textual description of this keybind, to be shown in which-key, if you have it.";

    buffer = defaultNullOpts.mkBool false "Make the mapping buffer-local. Equivalent to adding `<buffer>` to a map.";

    replace_keycodes = mkNullOrOption types.bool ''
      When `expr` is `true`, replace keycodes in the resulting string.

      Returning `nil` from the Lua `callback` is equivalent to returning an empty string.
    '';
  };

  modes = [
    "" # normal, visual, select, and operator-pending (same as plain ':map')
    "n" # normal
    "!" # insert and command-line
    "i" # insert
    "c" # command
    "v" # visual and select
    "x" # visual only
    "s" # select
    "o" # operator-pending
    "t" # terminal
    "l" # insert, command-line and lang-arg
    "!a" # abbreviation in insert and command-line
    "ia" # abbreviation in insert
    "ca" # abbreviation in command
  ];

  modeEnum = lib.types.enum modes;

  modeType =
    with lib.types;
    either modeEnum (nonEmptyListOf modeEnum)
    // {
      description =
        "one of or non-empty list of" + lib.strings.removePrefix "one of" modeEnum.description;
      descriptionClass = "conjunction";
    };

  mapOptionSubmodule = mkMapOptionSubmodule { };

  # NOTE: options that have the deprecated `lua` sub-option must use `removeDeprecatedMapAttrs`
  # to ensure `lua` isn't evaluated when (e.g.) generating lua code.
  # Failure to do so will result in "option used but not defined" errors!
  deprecatedMapOptionSubmodule = mkMapOptionSubmodule { lua = true; };
  removeDeprecatedMapAttrs = lib.flip removeAttrs [
    "lua"
    "assertions"
    "warnings"
  ];

  mkModeOption =
    default:
    lib.mkOption {
      type = modeType;
      description = ''
        One or several modes.
        Use the short-names (`"n"`, `"v"`, ...).
        See [`:h map-modes`] to learn more.

        [`:h map-modes`]: https://neovim.io/doc/user/map.html#%3Amap-modes
      '';
      inherit default;
      example = [
        "n"
        "v"
      ];
    };

  mkMapOptionSubmodule =
    {
      # Allow overriding defaults for key, action, mode, etc
      defaults ? { },

      # key and action can be true/false to enable/disable adding the option,
      # or an attrset to enable the option and add/override mkOption args.
      key ? true,
      action ? true,
      lua ? false, # WARNING: for historic use only - do not use in new options!

      # Allow passing additional options or modules to the submodule
      # Useful for plugin-specific features
      extraOptions ? { },
      extraModules ? [ ],
    }:
    types.submodule {
      imports =
        extraModules
        ++ lib.optional (isAttrs lua || lua) (
          { options, ... }:
          {
            imports = [
              ../modules/misc/assertions.nix
              # NOTE: we embed the full ${options.lua}, as mkRemovedOptionModule does not.
              (lib.mkRemovedOptionModule [ "lua" ] ''
                Full option: `${options.lua}`
                Use a "raw lua" `action` instead;
                e.g. `action.__raw = "<lua code>"` or `action = lib.nixvim.mkRaw "<lua code>"`.
              '')
            ];
          }
        );

      options =
        (lib.optionalAttrs (isAttrs key || key) {
          key = lib.mkOption (
            {
              type = types.str;
              description = "The key to map.";
              example = "<C-m>";
            }
            // (optionalAttrs (isAttrs key) key)
            // (optionalAttrs (defaults ? key) { default = defaults.key; })
          );
        })
        // (optionalAttrs (isAttrs action || action) {
          action = lib.mkOption (
            {
              type = types.maybeRaw types.str;
              description = "The action to execute.";
            }
            // (optionalAttrs (isAttrs action) action)
            // (optionalAttrs (defaults ? action) { default = defaults.action; })
          );
        })
        // {
          mode = mkModeOption defaults.mode or "";
          options = mapConfigOptions;
        }
        // extraOptions;
    };

  # Correctly merge two attrs (partially) representing a mapping.
  mergeKeymap =
    defaults: keymap:
    let
      # First, merge the `options` attrs of both options.
      mergedOpts = (defaults.options or { }) // (keymap.options or { });
    in
    # Then, merge the root attrs together and add the previously merged `options` attrs.
    (defaults // keymap) // { options = mergedOpts; };

  mkKeymaps = defaults: map (mergeKeymap defaults);
}
