{ lib, helpers }:
with lib;
let
  mkKindOption = helpers.defaultNullOpts.mkEnum [
    "split"
    "vsplit"
    "split_above"
    "tab"
    "floating"
    "replace"
    "auto"
  ];
in
{
  filewatcher = {
    enabled = helpers.defaultNullOpts.mkBool true ''
      When enabled, will watch the `.git/` directory for changes and refresh the status buffer
      in response to filesystem events.
    '';
  };

  graph_style =
    helpers.defaultNullOpts.mkEnumFirstDefault
      [
        "ascii"
        "unicode"
      ]
      ''
        - "ascii"   is the graph the git CLI generates
        - "unicode" is the graph like https://github.com/rbong/vim-flog
      '';

  disable_hint = helpers.defaultNullOpts.mkBool false ''
    Hides the hints at the top of the status buffer.
  '';

  disable_context_highlighting = helpers.defaultNullOpts.mkBool false ''
    Disables changing the buffer highlights based on where the cursor is.
  '';

  disable_signs = helpers.defaultNullOpts.mkBool false ''
    Disables signs for sections/items/hunks.
  '';

  git_services = helpers.defaultNullOpts.mkAttrsOf types.str {
    "github.com" = "https://github.com/$\{owner}/$\{repository}/compare/$\{branch_name}?expand=1";
    "bitbucket.org" = "https://bitbucket.org/$\{owner}/$\{repository}/pull-requests/new?source=$\{branch_name}&t=1";
    "gitlab.com" = "https://gitlab.com/$\{owner}/$\{repository}/merge_requests/new?merge_request[source_branch]=$\{branch_name}";
  } "Used to generate URL's for branch popup action 'pull request'.";

  fetch_after_checkout = helpers.defaultNullOpts.mkBool false ''
    Perform a fetch if the newly checked out branch has an upstream or pushRemote set.
  '';

  telescope_sorter = helpers.mkNullOrLuaFn ''
    Allows a different telescope sorter.
    Defaults to 'fuzzy_with_index_bias'.
    The example below will use the native fzf sorter instead.
    By default, this function returns `nil`.

    Example:
    ```lua
      require("telescope").extensions.fzf.native_fzf_sorter
    ```
  '';

  disable_insert_on_commit =
    helpers.defaultNullOpts.mkNullable (with types; either bool (enum [ "auto" ])) "auto"
      ''
        Changes what mode the Commit Editor starts in.
        `true` will leave nvim in normal mode, `false` will change nvim to insert mode, and `"auto"`
        will change nvim to insert mode IF the commit message is empty, otherwise leaving it in normal
        mode.
      '';

  use_per_project_settings = helpers.defaultNullOpts.mkBool true ''
    Scope persisted settings on a per-project basis.
  '';

  remember_settings = helpers.defaultNullOpts.mkBool true ''
    Persist the values of switches/options within and across sessions.
  '';

  auto_refresh = helpers.defaultNullOpts.mkBool true ''
    Neogit refreshes its internal state after specific events, which can be expensive depending on
    the repository size.
    Disabling `auto_refresh` will make it so you have to manually refresh the status after you open
    it.
  '';

  sort_branches = helpers.defaultNullOpts.mkStr "-committerdate" ''
    Value used for `--sort` option for `git branch` command.
    By default, branches will be sorted by commit date descending.
    Flag description: https://git-scm.com/docs/git-branch#Documentation/git-branch.txt---sortltkeygt
    Sorting keys: https://git-scm.com/docs/git-for-each-ref#_options
  '';

  kind = mkKindOption "tab" ''
    Change the default way of opening neogit.
  '';

  disable_line_numbers = helpers.defaultNullOpts.mkBool true ''
    Disable line numbers and relative line numbers.
  '';

  console_timeout = helpers.defaultNullOpts.mkUnsignedInt 2000 ''
    The time after which an output console is shown for slow running commands.
  '';

  auto_show_console = helpers.defaultNullOpts.mkBool true ''
    Automatically show console if a command takes more than `consoleTimeout` milliseconds.
  '';

  status = {
    recent_commit_count = helpers.defaultNullOpts.mkUnsignedInt 10 ''
      Recent commit count in the status buffer.
    '';
  };

  commit_editor = {
    kind = mkKindOption "auto" "The type of window that should be opened.";
  };

  commit_select_view = {
    kind = mkKindOption "tab" "The type of window that should be opened.";
  };

  commit_view = {
    kind = mkKindOption "vsplit" "The type of window that should be opened.";

    verify_commit = helpers.mkNullOrStrLuaOr types.bool ''
      Show commit signature information in the buffer.
      Can be set to true or false, otherwise we try to find the binary.

      Default: "os.execute('which gpg') == 0"
    '';
  };

  log_view = {
    kind = mkKindOption "tab" "The type of window that should be opened.";
  };

  rebase_editor = {
    kind = mkKindOption "auto" "The type of window that should be opened.";
  };

  reflog_view = {
    kind = mkKindOption "tab" "The type of window that should be opened.";
  };

  merge_editor = {
    kind = mkKindOption "auto" "The type of window that should be opened.";
  };

  description_editor = {
    kind = mkKindOption "auto" "The type of window that should be opened.";
  };

  tag_editor = {
    kind = mkKindOption "auto" "The type of window that should be opened.";
  };

  preview_buffer = {
    kind = mkKindOption "split" "The type of window that should be opened.";
  };

  popup = {
    kind = mkKindOption "split" "The type of window that should be opened.";
  };

  signs =
    mapAttrs
      (
        n: v:
        helpers.defaultNullOpts.mkListOf types.str
          [
            "${v.closed}"
            "${v.opened}"
          ]
          ''
            The icons to use for open and closed ${n}s.
          ''
      )
      {
        hunk = {
          closed = "";
          opened = "";
        };
        item = {
          closed = ">";
          opened = "v";
        };
        section = {
          closed = ">";
          opened = "v";
        };
      };

  integrations = {
    telescope = helpers.mkNullOrOption types.bool ''
      If enabled, use telescope for menu selection rather than `vim.ui.select`.
      Allows multi-select and some things that `vim.ui.select` doesn't.
    '';

    diffview = helpers.mkNullOrOption types.bool ''
      Neogit only provides inline diffs.
      If you want a more traditional way to look at diffs, you can use `diffview`.
      The diffview integration enables the diff popup.
    '';

    fzf-lua = helpers.mkNullOrOption types.bool ''
      If enabled, uses fzf-lua for menu selection.
      If the telescope integration is also selected then telescope is used instead.
    '';
  };

  sections =
    mapAttrs
      (
        name: default:
        mkOption {
          type =
            with types;
            nullOr (submodule {
              options = {
                folded = mkOption {
                  type = types.bool;
                  description = "Whether or not this section should be open or closed by default.";
                };

                hidden = mkOption {
                  type = types.bool;
                  description = "Whether or not this section should be shown.";
                };
              };
            });
          inherit default;
          description = "Settings for the ${name} section";
        }
      )
      {
        sequencer = {
          folded = false;
          hidden = false;
        };
        untracked = {
          folded = false;
          hidden = false;
        };
        unstaged = {
          folded = false;
          hidden = false;
        };
        staged = {
          folded = false;
          hidden = false;
        };
        stashes = {
          folded = true;
          hidden = false;
        };
        unpulled_upstream = {
          folded = true;
          hidden = false;
        };
        unmerged_upstream = {
          folded = false;
          hidden = false;
        };
        unpulled_pushRemote = {
          folded = true;
          hidden = false;
        };
        unmerged_pushRemote = {
          folded = false;
          hidden = false;
        };
        recent = {
          folded = true;
          hidden = false;
        };
        rebase = {
          folded = true;
          hidden = false;
        };
      };

  ignored_settings =
    helpers.defaultNullOpts.mkListOf types.str
      [
        "NeogitPushPopup--force-with-lease"
        "NeogitPushPopup--force"
        "NeogitPullPopup--rebase"
        "NeogitCommitPopup--allow-empty"
        "NeogitRevertPopup--no-edit"
      ]
      ''
        Table of settings to never persist.
        Uses format "Filetype--cli-value".
      '';

  mappings =
    let
      mkMappingOption = helpers.defaultNullOpts.mkAttrsOf (with types; either str (enum [ false ]));
    in
    {
      commit_editor = mkMappingOption {
        q = "Close";
        "<c-c><c-c>" = "Submit";
        "<c-c><c-k>" = "Abort";
        "<m-p>" = "PrevMessage";
        "<m-n>" = "NextMessage";
        "<m-r>" = "ResetMessage";
      } "Mappings for the commit editor.";

      commit_editor_I = mkMappingOption {
        "<c-c><c-c>" = "Submit";
        "<c-c><c-k>" = "Abort";
      } "Mappings for the commit editor (insert mode)";

      rebase_editor = mkMappingOption {
        p = "Pick";
        r = "Reword";
        e = "Edit";
        s = "Squash";
        f = "Fixup";
        x = "Execute";
        d = "Drop";
        b = "Break";
        q = "Close";
        "<cr>" = "OpenCommit";
        gk = "MoveUp";
        gj = "MoveDown";
        "<c-c><c-c>" = "Submit";
        "<c-c><c-k>" = "Abort";
        "[c" = "OpenOrScrollUp";
        "]c" = "OpenOrScrollDown";
      } "Mappings for the rebase editor.";

      rebase_editor_I = mkMappingOption {
        "<c-c><c-c>" = "Submit";
        "<c-c><c-k>" = "Abort";
      } "Mappings for the rebase editor (insert mode).";

      finder = mkMappingOption {
        "<cr>" = "Select";
        "<c-c>" = "Close";
        "<esc>" = "Close";
        "<c-n>" = "Next";
        "<c-p>" = "Previous";
        "<down>" = "Next";
        "<up>" = "Previous";
        "<tab>" = "MultiselectToggleNext";
        "<s-tab>" = "MultiselectTogglePrevious";
        "<c-j>" = "NOP";
        "<ScrollWheelDown>" = "ScrollWheelDown";
        "<ScrollWheelUp>" = "ScrollWheelUp";
        "<ScrollWheelLeft>" = "NOP";
        "<ScrollWheelRight>" = "NOP";
        "<LeftMouse>" = "MouseClick";
        "<2-LeftMouse>" = "NOP";
      } "Mappings for the finder.";

      popup = mkMappingOption {
        "?" = "HelpPopup";
        A = "CherryPickPopup";
        d = "DiffPopup";
        M = "RemotePopup";
        P = "PushPopup";
        X = "ResetPopup";
        Z = "StashPopup";
        i = "IgnorePopup";
        t = "TagPopup";
        b = "BranchPopup";
        B = "BisectPopup";
        w = "WorktreePopup";
        c = "CommitPopup";
        f = "FetchPopup";
        l = "LogPopup";
        m = "MergePopup";
        p = "PullPopup";
        r = "RebasePopup";
        v = "RevertPopup";
      } "Mappings for popups.";

      status = mkMappingOption {
        q = "Close";
        I = "InitRepo";
        "1" = "Depth1";
        "2" = "Depth2";
        "3" = "Depth3";
        "4" = "Depth4";
        "<tab>" = "Toggle";
        x = "Discard";
        s = "Stage";
        S = "StageUnstaged";
        "<c-s>" = "StageAll";
        u = "Unstage";
        K = "Untrack";
        U = "UnstageStaged";
        y = "ShowRefs";
        "$" = "CommandHistory";
        Y = "YankSelected";
        "<c-r>" = "RefreshBuffer";
        "<cr>" = "GoToFile";
        "<c-v>" = "VSplitOpen";
        "<c-x>" = "SplitOpen";
        "<c-t>" = "TabOpen";
        "{" = "GoToPreviousHunkHeader";
        "}" = "GoToNextHunkHeader";
        "[c" = "OpenOrScrollUp";
        "]c" = "OpenOrScrollDown";
      } "Mappings for status.";
    };

  notification_icon = helpers.defaultNullOpts.mkStr "󰊢" ''
    Icon for notifications.
  '';

  use_default_keymaps = helpers.defaultNullOpts.mkBool true ''
    Set to false if you want to be responsible for creating _ALL_ keymappings.
  '';

  highlight = genAttrs [
    "italic"
    "bold"
    "underline"
  ] (n: helpers.defaultNullOpts.mkBool true "Set the ${n} property of the highlight group.");
}