{
  lib,
  pkgs,
  ...
}:
let
  inherit (lib.nixvim) defaultNullOpts;
  inherit (lib) types;
in
lib.nixvim.plugins.mkVimPlugin {
  name = "gitgutter";
  packPathName = "vim-gitgutter";
  package = "vim-gitgutter";
  globalPrefix = "gitgutter_";

  maintainers = [ lib.maintainers.GaetanLepage ];

  # TODO introduced 2024-12-16: remove after 25.05
  optionsRenamedToSettings = import ./renamed-options.nix lib;
  imports =
    let
      basePluginPath = [
        "plugins"
        "gitgutter"
      ];
    in
    [
      (lib.mkRemovedOptionModule (
        basePluginPath ++ [ "grep" ]
      ) "Please, use `plugins.gitgutter.grepPackage` and/or `plugins.gitgutter.settings.grep`.")
      (lib.mkRemovedOptionModule (
        basePluginPath
        ++ [
          "signs"
          "modifiedAbove"
        ]
      ) "This option has been removed from upstream")

      # TODO: added 2025-04-06, remove after 25.05
      (lib.nixvim.mkRemovedPackageOptionModule {
        plugin = "gitgutter";
        packageName = "git";
      })
    ];

  dependencies = [ "git" ];

  extraOptions = {
    recommendedSettings = lib.mkOption {
      type = types.bool;
      default = true;
      description = ''
        Set recommended neovim option.
      '';
    };

    grepPackage = lib.mkPackageOption pkgs "gnugrep" {
      nullable = true;
    };
  };

  extraConfig = cfg: {
    opts = lib.optionalAttrs cfg.recommendedSettings {
      updatetime = 100;
      foldtext = "gitgutter#fold#foldtext";
    };

    extraPackages = [
      cfg.grepPackage
    ];
  };

  settingsOptions = {
    preview_win_location = defaultNullOpts.mkEnumFirstDefault [ "bo" "to" "bel" "abo" ] ''
      This option determines where the preview window pops up as a result of the
      `:GitGutterPreviewHunk` command.

      See the end of the `|opening-window|` docs.
    '';

    git_executable = defaultNullOpts.mkStr' {
      pluginDefault = "git";
      example = lib.literalExpression "lib.getExe pkgs.git";
      description = ''
        This option determines what `git` binary to use.
        Set this if git is not on your path.
      '';
    };

    git_args = defaultNullOpts.mkStr' {
      pluginDefault = "";
      example = ''--gitdir=""'';
      description = ''
        Use this option to pass any extra arguments to `git` when running `git-diff`.
      '';
    };

    diff_args = defaultNullOpts.mkStr' {
      pluginDefault = "";
      example = "-w";
      description = ''
        Use this option to pass any extra arguments to `git-diff`.
      '';
    };

    diff_relative_to = defaultNullOpts.mkStr' {
      pluginDefault = "index";
      example = "working_tree";
      description = ''
        By default buffers are diffed against the index.
        Use this option to diff against the working tree.
      '';
    };

    diff_base = defaultNullOpts.mkStr' {
      pluginDefault = "";
      example = "";
      description = ''
        By default buffers are diffed against the index.
        Use this option to diff against a revision instead.

        If you are looking at a previous version of a file with _Fugitive_ (e.g. via `:0Gclog`),
        gitgutter sets the diff base to the parent of the current revision.

        This setting is ignore when the diff is relative to the working tree (`diff_relative_to`).
      '';
    };

    grep = defaultNullOpts.mkStr' {
      pluginDefault = "grep";
      example = "grep --color=never";
      description = ''
        The plugin pipes the output of `git-diff` into `grep` to minimise the amount of data vim has
        to process.
        Set this option if `grep` is not on your path.

        `grep` must produce plain-text output without any ANSI escape codes or colours.
        Use this option to turn off colours if necessary (`grep --color=never` for example).

        If you do not want to use `grep` at all (perhaps to debug why signs are not showing), set
        this option to an empty string.
      '';
    };

    signs = defaultNullOpts.mkBool true ''
      Determines whether or not to show signs.
    '';

    highlight_lines = defaultNullOpts.mkBool false ''
      Determines whether or not to show line highlights.
    '';

    highlight_linenrs = defaultNullOpts.mkBool false ''
      Determines whether or not to show line number highlights.
    '';

    max_signs = defaultNullOpts.mkInt (-1) ''
      Sets the maximum number of signs to show in a buffer.

      To avoid slowing down the GUI the number of signs can be capped.
      When the number of changed lines exceeds this value, the plugin removes all signs and displays
      a warning message.

      When set to `-1` the limit is not applied.
    '';

    sign_priority = defaultNullOpts.mkUnsignedInt 10 ''
      Sets the `|sign-priority|` gitgutter assigns to its signs.
    '';

    sign_allow_clobber = defaultNullOpts.mkBool true ''
      Determines whether gitgutter preserves non-gitgutter signs.
      When `true`, gitgutter will not preserve non-gitgutter signs.
    '';
  }
  // (lib.mapAttrs'
    (n: default: {
      name = "sign_${n}";
      value = defaultNullOpts.mkStr default ''
        Icon for the _${n}_ sign.

        You can use unicode characters but not images.
        Signs must not take up more than 2 columns.
      '';
    })
    {
      added = "+";
      modified = "~";
      removed = "_";
      removed_first_line = "‾";
      removed_above_and_below = "_¯";
      modified_removed = "~_";
    }
  )
  // {
    set_sign_backgrounds = defaultNullOpts.mkBool false ''
      Only applies to existing `GitGutter*` highlight groups.
      See `|gitgutter-highlights|`.

      Controls whether to override the signs' background colours to match the `|hl-SignColumn|`.
    '';

    preview_win_floating = defaultNullOpts.mkBool true ''
      Whether to use floating/popup windows for hunk previews.

      Note that if you use popup windows on Vim you will not be able to stage partial hunks via
      the preview window.
    '';

    floating_window_options =
      defaultNullOpts.mkAttrsOf types.anything
        {
          relative = "cursor";
          row = 1;
          col = 0;
          width = 42;
          height = "&previewheight";
          style = "minimal";
        }
        ''
          This dictionary is passed directly to `|nvim_open_win()|`.
        '';

    close_preview_on_escape = defaultNullOpts.mkBool false ''
      Whether pressing <Esc> in a preview window closes it.
    '';

    terminal_reports_focus = defaultNullOpts.mkBool true ''
      Normally the plugin uses `|FocusGained|` to force-update all buffers when Vim receives
      focus.
      However some terminals do not report focus events and so the `|FocusGained|` autocommand
      never fires.

      If this applies to you, either install something like
      [Terminus](https://github.com/wincent/terminus) to make `|FocusGained|` work or set this
      option to `false`.

      If you use `tmux`, try this in your tmux.conf:
      ```
      set -g focus-events on
      ```

      When this option is `false`, the plugin force-updates the buffer on `|BufEnter|` (instead of
      only updating if the buffer's contents has changed since the last update).

    '';

    enabled = defaultNullOpts.mkBool true ''
      Controls whether or not the plugin is on at startup.
    '';

    map_keys = defaultNullOpts.mkBool true ''
      Controls whether or not the plugin provides mappings.
      See `|gitgutter-mappings|`.
    '';

    async = defaultNullOpts.mkBool true ''
      Controls whether or not diffs are run in the background.
    '';

    log = defaultNullOpts.mkBool false ''
      When switched on, the plugin logs to `gitgutter.log` in the directory where it is installed.
      Additionally it logs channel activity to `channel.log`.
    '';

    use_location_list = defaultNullOpts.mkBool false ''
      When switched on, the `:GitGutterQuickFix` command populates the location list of the
      current window instead of the global quickfix list.
    '';

    show_msg_on_hunk_jumping = defaultNullOpts.mkBool true ''
      When switched on, a message like "Hunk 4 of 11" is shown on hunk jumping.
    '';
  };

  settingsExample = {
    set_sign_backgrounds = true;
    sign_modified_removed = "*";
    sign_priority = 20;
    preview_win_floating = true;
  };
}
