{ lib, pkgs, ... }:
let
  pluginStubs = pkgs.callPackage ../../../utils/plugin-stubs.nix { };

  isByteCompiledFun = # lua
    ''
      -- LuaJIT bytecode header is: ESC L J version
      -- https://github.com/LuaJIT/LuaJIT/blob/v2.1/src/lj_bcdump.h
      -- We are comparing the first three bytes of the file (until version)
      local expected_header = string.char(0x1b, 0x4c, 0x4a)
      local function is_byte_compiled(filename)
        local f = assert(io.open(filename, "rb"))
        local data = assert(f:read(3))
        f:close()
        return data == expected_header
      end

      local function test_rtp_file(name, is_compiled)
        local file = assert(vim.api.nvim_get_runtime_file(name, false)[1], "file " .. name .. " not found in runtime")
        if is_compiled then
          assert(is_byte_compiled(file), name .. " is expected to be byte compiled, but it's not")
        else
          assert(not is_byte_compiled(file), name .. " is not expected to be byte compiled, but it is")
        end
      end

      local function test_lualib_file(func, is_compiled)
        -- Get the source of the func
        local info = debug.getinfo(func, "S")
        -- The source returned by debug.getinfo is prefixed with '@'
        local file = info.source:sub(2)
        assert(vim.uv.fs_stat(file))
        if is_compiled then
          assert(is_byte_compiled(file), file .. " is expected to be byte compiled, but it's not")
        else
          assert(not is_byte_compiled(file), file .. " is not expected to be byte compiled, but it is")
        end
      end

      local function assert_g_var(varname, filename)
        assert(
          vim.g[varname] == true or vim.g[varname] == 1,
          string.format(
            "expected vim.g.%s to be truthy, got %s. File %s isn't executed?",
            varname,
            vim.g[varname],
            filename
          )
        )
      end
    '';

  # In order to know where lua library come from
  # split plugins and libs to avoid lua libraries intersection.
  shortPluginList = with pluginStubs; [
    plugin1
    pluginWithDep4
  ];
  shortPluginNames = [
    "plugin1"
    "plugin_with_dep4"
    "plugin4"
    "plugin3"
  ];
  shortPluginLuaNames = [
    "lib1"
    "lib2"
    # "lib3" # is in both
  ];
  shortLuaList = with pluginStubs; [
    lib4
    libWithDep5
  ];
  shortLuaNames = [
    "lib4"
    "lib_with_dep5"
    "lib5"
    # "lib3" # is in both
  ];
  shortLuaAllNames = shortPluginLuaNames ++ shortLuaNames ++ [ "lib3" ];
  shortChecks =
    pluginStubs.pluginChecksFor shortPluginNames
    + pluginStubs.libChecksFor shortLuaAllNames
    + pluginStubs.pythonChecksFor [
      "yaml"
      "numpy"
    ];
in
{
  default =
    {
      config,
      lib,
      pkgs,
      ...
    }:
    let
      writeLua = lib.nixvim.builders.writeLuaWith pkgs;
    in
    {
      performance.byteCompileLua.enable = true;

      extraFiles = {
        # By text
        "plugin/extra_file_text.lua".text = "vim.g.extra_file_text = true";
        # By simple source derivation using buildCommand
        "plugin/extra_file_source.lua".source =
          writeLua "extra_file_source.lua" "vim.g.extra_file_source = true";
        # By standard derivation, it needs to execute fixupPhase
        "plugin/extra_file_drv.lua".source = pkgs.stdenvNoCC.mkDerivation {
          name = "extra_file_drv.lua";
          src = pkgs.emptyDirectory;
          buildPhase = ''
            echo "vim.g.extra_file_drv = true" > $out
          '';
        };
        # By path
        "plugin/extra_file_path.lua".source = ./files/file.lua;
        # By string
        "plugin/extra_file_string.lua".source =
          builtins.toFile "extra_file_path.lua" "vim.g.extra_file_string = true";
        # By derivation converted to string
        "plugin/extra_file_drv_string.lua".source = toString (
          writeLua "extra_file_drv_string.lua" "vim.g.extra_file_drv_string = true"
        );
        # Non-lua file
        "plugin/extra_file_non_lua.vim".text = "let g:extra_file_non_lua = 1";
        # Lua file with txt extension won't be byte compiled
        "text.txt".source = writeLua "text.txt" "vim.g.text = true";
      };

      files = {
        "plugin/file.lua" = {
          globals.file_lua = true;
        };
        "plugin/file.vim" = {
          globals.file_vimscript = true;
        };
      };

      extraPlugins = shortPluginList;

      extraLuaPackages = _: shortLuaList;

      extraConfigLua = ''
        -- The test will search for the next string in nixvim-print-init's output: VALIDATING_STRING.
        -- Since this is the comment, it won't appear in byte compiled file.

        vim.g.init_lua = true
      '';

      # Using plugin for the test code to avoid infinite recursion
      extraFiles."plugin/test.lua".text =
        # lua
        ''
          ${isByteCompiledFun}

          ${shortChecks}

          -- vimrc is byte compiled
          local init = vim.env.MYVIMRC or vim.fn.getscriptinfo({name = "init.lua"})[1].name
          assert(is_byte_compiled(init), "MYVIMRC is expected to be byte compiled, but it's not")
          assert_g_var("init_lua", "MYVIMRC")

          -- nixvim-print-init prints text
          local init_content = vim.fn.system("${lib.getExe config.build.printInitPackage}")
          assert(init_content:find("VALIDATING_STRING"), "nixvim-print-init's output is byte compiled")

          local runtime_lua_files = {
            -- lua 'extraFiles' are byte compiled
            { "plugin/extra_file_text.lua", true, "extra_file_text" },
            { "plugin/extra_file_source.lua", true, "extra_file_source" },
            { "plugin/extra_file_drv.lua", true, "extra_file_drv" },
            { "plugin/extra_file_path.lua", true, "extra_file_path" },
            { "plugin/extra_file_string.lua", true, "extra_file_string" },
            { "plugin/extra_file_drv_string.lua", true, "extra_file_drv_string" },
            -- other 'extraFiles'
            { "plugin/extra_file_non_lua.vim", false, "extra_file_non_lua" },
            -- lua 'files' are byte compiled
            { "plugin/file.lua", true, "file_lua" },
            -- other 'files'
            { "plugin/file.vim", false, "file_vimscript" },
          }
          for _, test in ipairs(runtime_lua_files) do
            local file, expected_byte_compiled, varname = unpack(test)
            test_rtp_file(file, expected_byte_compiled)
            -- Runtime plugin scripts are loaded last, so activate each manually before a test
            vim.cmd.runtime(file)
            assert_g_var(varname, file)
          end

          -- Nvim runtime isn't byte compiled by default
          test_rtp_file("lua/vim/lsp.lua", false)

          -- Plugins aren't byte-compiled by default
          ${lib.concatMapStrings (name: ''
            test_rtp_file("lua/${name}/init.lua", false)
            test_rtp_file("plugin/${name}.lua", false)
          '') shortPluginNames}

          -- Lua library isn't byte-compiled by default
          ${lib.concatMapStrings (name: ''
            test_lualib_file(require("${name}").name, false)
          '') shortLuaAllNames}
        '';

    };

  disabled = {
    performance.byteCompileLua.enable = false;

    extraFiles."plugin/test1.lua".text = "vim.opt.tabstop = 2";

    files."plugin/test2.lua".opts.tabstop = 2;

    extraPlugins = shortPluginList;

    extraLuaPackages = _: shortLuaList;

    extraConfigLua = ''
      ${isByteCompiledFun}

      -- Nothing is byte compiled
      -- vimrc
      local init = vim.env.MYVIMRC or vim.fn.getscriptinfo({name = "init.lua"})[1].name
      assert(not is_byte_compiled(init), "MYVIMRC is not expected to be byte compiled, but it is")
      -- extraFiles
      test_rtp_file("plugin/test1.lua", false)
      -- files
      test_rtp_file("plugin/test2.lua", false)
      -- Neovim runtime
      test_rtp_file("lua/vim/lsp.lua", false)
      -- plugins
      test_rtp_file("lua/${builtins.elemAt shortPluginNames 0}/init.lua", false)
      -- lua library
      test_lualib_file(require("${builtins.elemAt shortLuaNames 0}").name, false)
      test_lualib_file(require("${builtins.elemAt shortPluginLuaNames 0}").name, false)
    '';
  };

  init-lua-disabled = {
    performance.byteCompileLua = {
      enable = true;
      initLua = false;
    };

    extraConfigLuaPost = ''
      ${isByteCompiledFun}

      -- vimrc is not byte compiled
      local init = vim.env.MYVIMRC or vim.fn.getscriptinfo({name = "init.lua"})[1].name
      assert(not is_byte_compiled(init), "MYVIMRC is not expected to be byte compiled, but it is")
    '';
  };

  configs-disabled = {
    performance.byteCompileLua = {
      enable = true;
      configs = false;
    };

    extraFiles."plugin/test1.lua".text = "vim.opt.tabstop = 2";

    files."plugin/test2.lua".opts.tabstop = 2;

    extraConfigLuaPost = ''
      ${isByteCompiledFun}

      -- extraFiles
      test_rtp_file("plugin/test1.lua", false)
      -- files
      test_rtp_file("plugin/test2.lua", false)
    '';
  };

  nvim-runtime = {
    performance.byteCompileLua = {
      enable = true;
      nvimRuntime = true;
    };

    extraPlugins = shortPluginList;
    extraLuaPackages = _: shortLuaList;

    extraConfigLuaPost = ''
      ${isByteCompiledFun}

      ${shortChecks}

      -- vim namespace is working
      vim.opt.tabstop = 2
      vim.api.nvim_get_runtime_file("init.lua", false)
      vim.lsp.get_clients()
      vim.treesitter.language.get_filetypes("nix")
      vim.iter({})

      test_rtp_file("lua/vim/lsp.lua", true)
      test_rtp_file("lua/vim/iter.lua", true)
      test_rtp_file("lua/vim/treesitter/query.lua", true)
      test_rtp_file("lua/vim/lsp/buf.lua", true)
      test_rtp_file("plugin/editorconfig.lua", true)
    '';
  };

  # performance.byteCompileLua.luaLib for extraLuaPackages
  lua-lib-extra-lua-packages = {
    performance.byteCompileLua = {
      enable = true;
      luaLib = true;
    };

    extraLuaPackages = _: pluginStubs.libPack;

    extraConfigLuaPost = ''
      ${pluginStubs.libChecks}

      ${isByteCompiledFun}

      -- Lua modules are byte-compiled
      ${lib.concatMapStringsSep "\n" (
        name: "test_lualib_file(require('${name}').name, true)"
      ) pluginStubs.libNames}
    '';
  };

  # performance.byteCompileLua.luaLib for propagatedBuildInputs
  lua-lib-propagated-build-inputs =
    { config, ... }:
    {
      performance.byteCompileLua = {
        enable = true;
        luaLib = true;
      };

      extraPlugins = pluginStubs.pluginPack;

      extraConfigLuaPost = ''
        ${pluginStubs.pluginChecks}

        ${isByteCompiledFun}

        -- Lua modules are byte-compiled
        ${lib.concatMapStringsSep "\n" (
          name: "test_lualib_file(require('${name}').name, true)"
        ) pluginStubs.libNames}

        -- Plugins themselves are not byte-compiled
        ${lib.concatMapStringsSep "\n" (
          name: "test_rtp_file('lua/${name}/init.lua', false)"
        ) pluginStubs.pluginNames}
      '';

      assertions =
        let
          # Get plugins with all dependencies
          getDeps = drv: [ drv ] ++ builtins.concatMap getDeps drv.dependencies or [ ];
          plugins = lib.pipe config.build.plugins [
            (builtins.catAttrs "plugin")
            (builtins.concatMap getDeps)
            lib.unique
          ];
          # Collect both propagatedBuildInputs and requiredLuaModules to one list
          getAllRequiredLuaModules = lib.flip lib.pipe [
            (
              drvs: builtins.catAttrs "propagatedBuildInputs" drvs ++ builtins.catAttrs "requiredLuaModules" drvs
            )
            (builtins.concatMap (deps: deps ++ getAllRequiredLuaModules deps))
            lib.unique
          ];
          allRequiredLuaModules = getAllRequiredLuaModules plugins;
        in
        [
          # Ensure that lua dependencies are byte-compiled recursively
          # by checking that every library is present only once.
          # If there are two different derivations with the same name,
          # then one of them probably isn't byte-compiled.
          {
            assertion = lib.allUnique (map lib.getName allRequiredLuaModules);
            message = ''
              Expected propagatedBuildInputs and requiredLuaModules of all plugins to have unique names.
              Got the following derivations:''\n${lib.concatLines allRequiredLuaModules}
              Possible reasons:
              - not all dependencies are overridden the same way
              - requiredLuaModules are not updated along with propagatedBuildInputs
            '';
          }
        ];
    };
}
//
  # Two equal tests, one with combinePlugins.enable = true
  lib.genAttrs
    [
      "plugins"
      "plugins-combined"
    ]
    (name: {
      performance = {
        byteCompileLua = {
          enable = true;
          plugins = true;
        };

        combinePlugins.enable = lib.hasSuffix "combined" name;
      };

      extraPlugins = pluginStubs.pluginPack ++ [
        # A plugin with invalid lua file
        (pluginStubs.mkPlugin "invalid" {
          postInstall = ''
            mkdir $out/ftplugin
            echo "if true then" >$out/ftplugin/invalid.lua
          '';
        })
      ];

      extraConfigLuaPost = ''
        ${pluginStubs.pluginChecks}

        ${isByteCompiledFun}

        -- Plugins are byte-compiled
        ${lib.concatMapStrings (name: ''
          test_rtp_file("lua/${name}/init.lua", true)
          test_rtp_file("plugin/${name}.lua", true)
        '') pluginStubs.pluginNames}

        -- Lua modules aren't byte-compiled
        ${lib.concatMapStrings (name: ''
          test_lualib_file(require("${name}").name, false)
        '') pluginStubs.libNames}

        -- A plugin with invalid file
        ${pluginStubs.pluginChecksFor [ "invalid" ]}
        -- invalid file exists, but isn't byte compiled
        test_rtp_file("ftplugin/invalid.lua", false)
      '';
    })
