{ pkgs, ... }:
let
  inherit (pkgs) lib;

  # Count plugins of given type excluding 'build.extraFiles'
  pluginCount =
    pkg: files: type:
    builtins.length (builtins.filter (p: p != files) pkg.packpathDirs.myNeovimPackages.${type});
in
{
  # Test basic functionality
  default =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        nvim-lspconfig
        nvim-treesitter
      ];
      extraConfigLuaPost = ''
        -- Plugins are loadable
        require("lspconfig")
        require("nvim-treesitter")

        -- No separate plugin entries in nvim_list_runtime_paths
        assert(not vim.iter(vim.api.nvim_list_runtime_paths()):any(function(entry)
          return entry:find("treesitter") or entry:find("lspconfig")
        end), "separate plugins are found in runtime")

        -- Help tags are generated
        assert(vim.fn.getcompletion("lspconfig", "help")[1], "no help tags for nvim-lspconfig")
        assert(vim.fn.getcompletion("nvim-treesitter", "help")[1], "no help tags for nvim-treesitter")
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one plugin is defined in packpathDirs, expected one plugin pack.";
        }
      ];
    };

  # Test disabled option
  disabled =
    { config, ... }:
    {
      performance.combinePlugins.enable = false;
      extraPlugins = with pkgs.vimPlugins; [
        nvim-lspconfig
        nvim-treesitter
      ];
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" >= 2;
          message = "Only one plugin is defined in packpathDirs, expected at least two.";
        }
      ];
    };

  # Test that plugin dependencies are handled
  dependencies =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        # Depends on nvim-cmp
        cmp-dictionary
        # We have to manually add cmp-dictionary's dependence: nvim-cmp
        nvim-cmp
        # Depends on telescope-nvim which itself depends on plenary-nvim
        telescope-undo-nvim
      ];
      extraConfigLuaPost = ''
        -- Plugins and its dependencies are loadable
        require("cmp_dictionary")
        require("cmp")
        require("telescope-undo")
        require("telescope")
        require("plenary")
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one plugin is defined in packpathDirs.";
        }
      ];
    };

  # Test that pathsToLink option works
  paths-to-link =
    { config, ... }:
    {
      performance.combinePlugins = {
        enable = true;
        # fzf native library is in build directory
        pathsToLink = [ "/build" ];
      };
      extraPlugins = [ pkgs.vimPlugins.telescope-fzf-native-nvim ];
      extraConfigLuaPost = ''
        -- Native library is in runtimepath
        assert(
          vim.api.nvim_get_runtime_file("build/libfzf.so", false)[1],
          "build/libfzf.so is not found in runtimepath"
        )

        -- Native library is loadable
        require("fzf_lib")
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one plugin is defined in packpathDirs.";
        }
      ];
    };

  # Test that plugin python3 dependencies are handled
  python-dependencies =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        # No python3 dependencies
        plenary-nvim
        # Duplicate python3 dependencies
        (nvim-lspconfig.overrideAttrs { passthru.python3Dependencies = ps: [ ps.pyyaml ]; })
        (nvim-treesitter.overrideAttrs { passthru.python3Dependencies = ps: [ ps.pyyaml ]; })
        # Another python3 dependency
        (nvim-cmp.overrideAttrs { passthru.python3Dependencies = ps: [ ps.requests ]; })
      ];
      extraConfigLuaPost = ''
        -- Python modules are importable
        vim.cmd.py3("import yaml")
        vim.cmd.py3("import requests")
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one plugin is defined in packpathDirs.";
        }
      ];
    };

  # Test that optional plugins are handled
  optional-plugins =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        # Start plugins
        plenary-nvim
        nvim-lspconfig
        # Optional plugin
        {
          plugin = nvim-treesitter;
          optional = true;
        }
        # Optional plugin with dependency on plenary-nvim
        # Dependencies should not be duplicated
        {
          plugin = telescope-nvim;
          optional = true;
        }
      ];
      extraConfigLuaPost = ''
        -- Start plugins are loadable
        require("plenary")
        require("lspconfig")

        -- Opt plugins are not loadable
        local ok = pcall(require, "nvim-treesitter")
        assert(not ok, "nvim-treesitter plugin is loadable")
        ok = pcall(require, "telescope")
        assert(not ok, "telescope-nvim plugin is loadable")

        -- Load plugins
        vim.cmd.packadd("nvim-treesitter")
        vim.cmd.packadd("telescope.nvim")

        -- Now opt plugins are loadable
        require("nvim-treesitter")
        require("telescope")

        -- Only one copy of plenary-nvim should be available
        assert(
          #vim.api.nvim_get_runtime_file("lua/plenary/init.lua", true) == 1,
          "plenary-nvim is duplicated"
        )
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one start plugin is defined in packpathDirs";
        }
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "opt" == 2;
          message = "Less than two opt plugins are defined in packpathDirs";
        }
      ];
    };

  # Test that plugin configs are handled
  configs =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        # A plugin without config
        plenary-nvim
        # Plugins with configs
        {
          plugin = nvim-treesitter;
          config = "let g:treesitter_config = 1";
        }
        {
          plugin = nvim-lspconfig;
          config = "let g:lspconfig_config = 1";
        }
        # Optional plugin with config
        {
          plugin = telescope-nvim;
          optional = true;
          config = "let g:telescope_config = 1";
        }
      ];
      extraConfigLuaPost = ''
        -- Plugins are loadable
        require("plenary")
        require("nvim-treesitter")
        require("lspconfig")

        -- Configs are evaluated
        assert(vim.g.treesitter_config == 1, "nvim-treesitter config isn't evaluated")
        assert(vim.g.lspconfig_config == 1, "nvim-lspconfig config isn't evaluated")
        assert(vim.g.telescope_config == 1, "telescope-nvim config isn't evaluated")
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one start plugin is defined in packpathDirs";
        }
      ];
    };

  # Test that config.build.extraFiles is not combined
  files-plugin =
    { config, ... }:
    {
      performance.combinePlugins.enable = true;
      extraPlugins = with pkgs.vimPlugins; [
        nvim-treesitter
        vim-nix
      ];
      # Ensure that build.extraFiles is added extraPlugins
      wrapRc = true;
      # Extra user files colliding with plugins
      extraFiles = {
        "ftplugin/nix.vim".text = "let b:test = 1";
        "queries/nix/highlights.scm".text = ''
          ;; extends
          (comment) @comment
        '';
      };
      # Another form of user files
      files = {
        "ftdetect/nix.vim" = {
          autoCmd = [
            {
              event = [
                "BufRead"
                "BufNewFile"
              ];
              pattern = "*.nix";
              command = "setf nix";
            }
          ];
        };
      };
      extraConfigLuaPost = ''
        local function get_paths(name)
          local paths = vim.api.nvim_get_runtime_file(name, true);
          return vim.tbl_filter(function(v)
            -- Skip paths from neovim runtime
            return not v:find("/nvim/runtime/")
          end, paths)
        end

        -- Both plugin and user version are available
        assert(#get_paths("ftplugin/nix.vim") == 2, "only one version of ftplugin/nix.vim")
        assert(#get_paths("ftdetect/nix.vim") == 2, "only one version of ftdetect/nix.vim")
        assert(#get_paths("queries/nix/highlights.scm") == 2, "only one version of queries/nix/highlights.scm")

        -- First found file is from build.extraFiles
        assert(
          get_paths("ftplugin/nix.vim")[1]:find("${lib.getName config.build.extraFiles}", 1, true),
          "first found ftplugin/nix.vim isn't in build.extraFiles runtime path"
        )
        assert(
          get_paths("queries/nix/highlights.scm")[1]:find("${lib.getName config.build.extraFiles}", 1, true),
          "first found queries/nix/highlights.scm isn't in build.extraFiles runtime path"
        )
        assert(
          get_paths("queries/nix/highlights.scm")[1]:find("${lib.getName config.build.extraFiles}", 1, true),
          "first found queries/nix/highlights.scm isn't in build.extraFiles runtime path"
        )
      '';
      assertions = [
        {
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 1;
          message = "More than one start plugin is defined in packpathDirs";
        }
      ];
    };

  # Test that standalonePlugins option works
  standalone-plugins =
    { config, ... }:
    {
      performance.combinePlugins = {
        enable = true;
        standalonePlugins = with pkgs.vimPlugins; [
          # By plugin name
          "nvim-treesitter"
          # By package itself
          nvim-lspconfig
          # Its dependency, plenary-nvim, not in this list, so will be combined
          telescope-nvim
          # Dependency of other plugin
          "nvim-cmp"
        ];
      };
      extraPlugins = with pkgs.vimPlugins; [
        nvim-treesitter
        nvim-lspconfig
        telescope-nvim
        # Only its dependency (nvim-cmp) won't be combined, but not the plugin itself
        cmp-dictionary
        # We have to manually add cmp-dictionary's dependence: nvim-cmp
        nvim-cmp
        # More plugins
        gitsigns-nvim
        luasnip
      ];
      extraConfigLuaPost = ''
        -- Plugins are loadable
        require("nvim-treesitter")
        require("lspconfig")
        require("telescope")
        require("plenary")
        require("cmp_dictionary")
        require("cmp")
        require("gitsigns")
        require("luasnip")

        -- Verify if plugin is standalone or combined
        local function is_standalone(name, dirname)
          local paths = vim.api.nvim_get_runtime_file("lua/" .. name, true);
          assert(#paths == 1, "more than one copy of " .. name .. " in runtime")
          return paths[1]:match("^(.+)/lua/"):find(dirname or name, 1, true) ~= nil
        end

        -- Standalone plugins
        assert(is_standalone("nvim-treesitter"), "nvim-treesitter is combined, expected standalone")
        assert(is_standalone("lspconfig"), "nvim-lspconfig is combined, expected standalone")
        assert(is_standalone("telescope"), "telescope-nvim is combined, expected standalone")
        -- Add trailing slash to ensure that it doesn't match cmp_dictionary
        assert(is_standalone("cmp/", "nvim-cmp"), "nvim-cmp is combined, expected standalone")
        -- Combined plugins
        assert(not is_standalone("plenary"), "plenary-nvim is standalone, expected combined")
        assert(not is_standalone("cmp_dictionary", "cmp-dictionary"), "cmp-dictionary is standalone, expected combined")
        assert(not is_standalone("gitsigns"), "gitsigns-nvim is standalone, expected combined")
        assert(not is_standalone("luasnip"), "luasnip is standalone, expected combined")
      '';
      assertions = [
        {
          # plugin-pack, nvim-treesitter, nvim-lspconfig, telescope-nvim, nvim-cmp
          assertion = pluginCount config.build.nvimPackage config.build.extraFiles "start" == 5;
          message = "Wrong number of plugins in packpathDirs";
        }
      ];
    };

  # Test if plenary.filetype is working
  plenary-nvim = {
    performance.combinePlugins.enable = true;
    extraPlugins = [ pkgs.vimPlugins.plenary-nvim ];
    extraConfigLuaPost = ''
      -- Plenary filetype detection is usable
      assert(require("plenary.filetype").detect(".bashrc") == "sh", "plenary.filetype is not working")
    '';
  };
}
