---@mod lz.n
---
---@brief [[
---A dead simple lazy-loading Lua library for Neovim plugins.
---
---It is intended to be used
---
---- by users of plugin managers that don't provide a convenient API for lazy-loading.
---- by plugin managers, to provide a convenient API for lazy-loading.
---@brief ]]

---@toc lz.n.contents

---@mod lz.n.api lz.n Lua API

local M = {}

if vim.fn.has("nvim-0.10.0") ~= 1 then
    error("lz.n requires Neovim >= 0.10.0")
end

--- The function provides two overloads, each suited for different use cases:
---
---@overload fun(plugin: lz.n.Plugin)
--- **Stateless version:**
---   - Intended for: Use by a `lz.n.Handler`
---   - Description: This version should be used when working with `lz.n.Handler`
---     instances to maintain referential transparency.
---     Each handler has full authority over its internal state, ensuring it
---     remains isolated and unaffected by external influences,
---     thereby preventing multiple sources of truth.
---   - Note: If loading multiple plugins simultaneously,
---     handlers should iterate over |vim.deepcopy| of the plugins,
---     verifying they are still pending before each `trigger_load` call.
---     This practice allows for safe invocation of the stateful `trigger_load`
---     in `before` and `after` hooks.
---
---@overload fun(plugins: string | string[], opts?: lz.n.lookup.Opts): string[]
--- **Stateful version:**
---   - Returns: A list of plugin names that were skipped
---     (empty if all plugins were loaded).
---   - Intended for: Scenarios where handler state is unknown or inaccessible,
---     such as in `before` or `after` hooks.
---   - Description: This version allows you to load plugins by name.
---     It searches through the handlers, querying their `lookup` functions
---     to identify an appropriate plugin, and returns the first match.
---     You can fine-tune the search process by providing a [`lz.n.lookup.Opts` table](#lookup).
M.trigger_load = function(plugins, opts)
    return require("lz.n.loader").load(plugins, function(name)
        return M.lookup(name, opts)
    end)
end

---@overload fun(spec: lz.n.Spec)
---Register a list with your plugin specs or a single plugin spec to be lazy-loaded.
---
---@overload fun(import: string)
---Register a Lua module name that contains your plugin spec(s) to be lazy-loaded.
function M.load(spec)
    if type(spec) == "string" then
        spec = { import = spec }
    end
    --- @cast spec lz.n.Spec
    local spec_mod = require("lz.n.spec")
    local plugins = spec_mod.parse(spec)

    -- calls handler add functions
    require("lz.n.handler").init(plugins)

    -- Because this calls the handlers' `del` functions,
    -- this should be ran after the plugins are registered with the handlers.
    -- even if an eager plugin isn't supposed to have been added to any of them
    -- This allows even startup plugins to call
    -- `require('lz.n').trigger_load()` safely
    require("lz.n.loader").load_startup_plugins(plugins)

    require("lz.n.handler").run_post_load()
end

--- Lookup a plugin that is pending to be loaded by name.
---@param name string
---@param opts? lz.n.lookup.Opts
---@return lz.n.Plugin?
function M.lookup(name, opts)
    return require("lz.n.handler").lookup(name, opts)
end

---@class lz.n.lookup.Opts
---
--- The handlers to include in the search (filtered by `spec_field`)
--- In case of multiple filters, the order of the filter list
--- determines the order in which handlers' `lookup` functions are called.
---@field filter string | string[]

---Register a custom handler.
---@param handler lz.n.Handler
---@return boolean success
M.register_handler = function(handler)
    return require("lz.n.handler").register_handler(handler)
end

return M
