_G.LazyVim = require("util.lazy.util")

---@class LazyVimConfig: LazyVimOptions
local M = {}

M.version = "14.15.0" -- x-release-please-version
require("util").lazy.config = M

---@class LazyVimOptions
local defaults = {
  -- colorscheme can be a string like `catppuccin` or a function that will load the colorscheme
  ---@type string|fun()
  colorscheme = function()
    require("tokyonight").load()
  end,
  -- load the default settings
  defaults = {
    autocmds = true, -- lazyvim.config.autocmds
    keymaps = true, -- lazyvim.config.keymaps
    -- lazyvim.config.options can't be configured here since that's loaded before lazyvim setup
    -- if you want to disable loading options, add `package.loaded["lazyvim.config.options"] = true` to the top of your init.lua
  },
  news = {
    -- When enabled, NEWS.md will be shown when changed.
    -- This only contains big new features and breaking changes.
    lazyvim = true,
    -- Same but for Neovim's news.txt
    neovim = false,
  },
  -- icons used by other plugins
  -- stylua: ignore
  icons = {
    misc = {
      dots = "󰇘",
    },
    ft = {
      octo = "",
    },
    dap = {
      Stopped             = { "󰁕 ", "DiagnosticWarn", "DapStoppedLine" },
      Breakpoint          = " ",
      BreakpointCondition = " ",
      BreakpointRejected  = { " ", "DiagnosticError" },
      LogPoint            = ".>",
    },
    diagnostics = {
      Error = " ",
      Warn  = " ",
      Hint  = " ",
      Info  = " ",
    },
    git = {
      added    = " ",
      modified = " ",
      removed  = " ",
    },
    kinds = {
      Array         = " ",
      Boolean       = "󰨙 ",
      Class         = " ",
      Codeium       = "󰘦 ",
      Color         = " ",
      Control       = " ",
      Collapsed     = " ",
      Constant      = "󰏿 ",
      Constructor   = " ",
      Copilot       = " ",
      Enum          = " ",
      EnumMember    = " ",
      Event         = " ",
      Field         = " ",
      File          = " ",
      Folder        = " ",
      Function      = "󰊕 ",
      Interface     = " ",
      Key           = " ",
      Keyword       = " ",
      Method        = "󰊕 ",
      Module        = " ",
      Namespace     = "󰦮 ",
      Null          = " ",
      Number        = "󰎠 ",
      Object        = " ",
      Operator      = " ",
      Package       = " ",
      Property      = " ",
      Reference     = " ",
      Snippet       = "󱄽 ",
      String        = " ",
      Struct        = "󰆼 ",
      Supermaven    = " ",
      TabNine       = "󰏚 ",
      Text          = " ",
      TypeParameter = " ",
      Unit          = " ",
      Value         = " ",
      Variable      = "󰀫 ",
    },
  },
  ---@type table<string, string[]|boolean>?
  kind_filter = {
    default = {
      "Class",
      "Constructor",
      "Enum",
      "Field",
      "Function",
      "Interface",
      "Method",
      "Module",
      "Namespace",
      "Package",
      "Property",
      "Struct",
      "Trait",
    },
    markdown = false,
    help = false,
    -- you can specify a different filter for each filetype
    lua = {
      "Class",
      "Constructor",
      "Enum",
      "Field",
      "Function",
      "Interface",
      "Method",
      "Module",
      "Namespace",
      -- "Package", -- remove package since luals uses it for control flow structures
      "Property",
      "Struct",
      "Trait",
    },
  },
}

M.json = {
  version = 8,
  loaded = false,
  path = vim.g.lazyvim_json or vim.fn.stdpath("config") .. "/lazyvim.json",
  data = {
    version = nil, ---@type number?
    install_version = nil, ---@type number?
    news = {}, ---@type table<string, string>
    extras = {}, ---@type string[]
  },
}

function M.json.load()
  M.json.loaded = true
  local f = io.open(M.json.path, "r")
  if f then
    local data = f:read("*a")
    f:close()
    local ok, json = pcall(vim.json.decode, data, { luanil = { object = true, array = true } })
    if ok then
      M.json.data = vim.tbl_deep_extend("force", M.json.data, json or {})
      if M.json.data.version ~= M.json.version then Util.lazy.json.migrate() end
    end
  else
    M.json.data.install_version = M.json.version
  end
end

---@type LazyVimOptions
local options
local lazy_clipboard

---@param opts? LazyVimOptions
function M.setup(opts)
  options = vim.tbl_deep_extend("force", defaults, opts or {}) or {}

  -- autocmds can be loaded lazily when not opening a file
  local lazy_autocmds = vim.fn.argc(-1) == 0
  if not lazy_autocmds then M.load("autocmds") end

  local group = vim.api.nvim_create_augroup("LazyVim", { clear = true })
  vim.api.nvim_create_autocmd("User", {
    group = group,
    pattern = "VeryLazy",
    callback = function()
      if lazy_autocmds then M.load("autocmds") end
      if lazy_clipboard ~= nil then vim.opt.clipboard = lazy_clipboard end

      Util.lazy.format.setup()
      -- Util.lazy.news.setup()
      Util.lazy.root.setup()

      -- vim.api.nvim_create_user_command("LazyExtras", function()
      --   Util.lazy.extras.show()
      -- end, { desc = "Manage LazyVim extras" })

      vim.api.nvim_create_user_command("LazyHealth", function()
        vim.cmd([[Lazy! load all]])
        vim.cmd([[checkhealth]])
      end, { desc = "Load all plugins and run :checkhealth" })

      -- local health = require("lazy.health")
    end,
  })

  Util.lazy.track("colorscheme")
  Util.lazy.try(function()
    if type(M.colorscheme) == "function" then
      M.colorscheme()
    else
      vim.cmd.colorscheme(M.colorscheme)
    end
  end, {
    msg = "Could not load your colorscheme",
    on_error = function(msg)
      Util.lazy.error(msg)
      vim.cmd.colorscheme("habamax")
    end,
  })
  Util.lazy.track()
end

---@param buf? number
---@return string[]?
function M.get_kind_filter(buf)
  buf = (buf == nil or buf == 0) and vim.api.nvim_get_current_buf() or buf
  local ft = vim.bo[buf].filetype
  if M.kind_filter == false then return end
  if M.kind_filter[ft] == false then return end
  if type(M.kind_filter[ft]) == "table" then return M.kind_filter[ft] end
  ---@diagnostic disable-next-line: return-type-mismatch
  return type(M.kind_filter) == "table" and type(M.kind_filter.default) == "table" and M.kind_filter.default or nil
end

---@param name "autocmds" | "options" | "keymaps"
function M.load(name)
  local function _load(mod)
    if require("lazy.core.cache").find(mod)[1] then
      Util.lazy.try(function()
        require(mod)
      end, { msg = "Failed loading " .. mod })
    end
  end
  local pattern = "LazyVim" .. name:sub(1, 1):upper() .. name:sub(2)
  -- always load lazyvim, then user file
  if M.defaults[name] or name == "options" then
    _load("lazyvim.config." .. name)
    vim.api.nvim_exec_autocmds("User", { pattern = pattern .. "Defaults", modeline = false })
  end
  _load("config." .. name)
  if vim.bo.filetype == "lazy" then
    -- HACK: LazyVim may have overwritten options of the Lazy ui, so reset this here
    vim.cmd([[do VimResized]])
  end
  vim.api.nvim_exec_autocmds("User", { pattern = pattern, modeline = false })
end

M.did_init = false
function M.init()
  if M.did_init then return end
  M.did_init = true

  package.preload["lazyvim.plugins.lsp.format"] = function()
    return Util.lazy.format
  end

  -- delay notifications till vim.notify was replaced or after 500ms
  Util.lazy.lazy_notify()

  -- load options here, before lazy init while sourcing plugin modules
  -- this is needed to make sure options will be correctly applied
  -- after installing missing plugins
  M.load("options")
  -- defer built-in clipboard handling: "xsel" and "pbcopy" can be slow
  lazy_clipboard = vim.opt.clipboard
  vim.opt.clipboard = ""

  if vim.g.deprecation_warnings == false then vim.deprecate = function() end end

  M.json.load()
end

---@alias LazyVimDefault {name: string, extra: string, enabled?: boolean, origin?: "global" | "default" | "extra" }

-- local default_extras ---@type table<string, LazyVimDefault>
-- function M.get_defaults()
--   if default_extras then return default_extras end
--   ---@type table<string, LazyVimDefault[]>
--   local checks = {
--     picker = {
--       { name = "snacks", extra = "editor.snacks_picker" },
--       { name = "fzf", extra = "editor.fzf" },
--       { name = "telescope", extra = "editor.telescope" },
--     },
--     cmp = {
--       { name = "blink.cmp", extra = "coding.blink", enabled = vim.fn.has("nvim-0.10") == 1 },
--       { name = "nvim-cmp", extra = "coding.nvim-cmp" },
--     },
--     explorer = {
--       { name = "snacks", extra = "editor.snacks_explorer" },
--       { name = "neo-tree", extra = "editor.neo-tree" },
--     },
--   }
--
--   -- existing installs keep their defaults
--   if (Util.lazy.config.json.data.install_version or 7) < 8 then
--     table.insert(checks.picker, 1, table.remove(checks.picker, 2))
--     table.insert(checks.explorer, 1, table.remove(checks.explorer, 2))
--   end
--
--   default_extras = {}
--   for name, check in pairs(checks) do
--     local valid = {} ---@type string[]
--     for _, extra in ipairs(check) do
--       if extra.enabled ~= false then valid[#valid + 1] = extra.name end
--     end
--     local origin = "default"
--     local use = vim.g["lazyvim_" .. name]
--     use = vim.tbl_contains(valid, use or "auto") and use or nil
--     origin = use and "global" or origin
--     for _, extra in ipairs(use and {} or check) do
--       if extra.enabled ~= false and Util.lazy.has_extra(extra.extra) then
--         use = extra.name
--         break
--       end
--     end
--     origin = use and "extra" or origin
--     use = use or valid[1]
--     for _, extra in ipairs(check) do
--       local import = "lazyvim.plugins.extras." .. extra.extra
--       extra = vim.deepcopy(extra)
--       extra.enabled = extra.name == use
--       if extra.enabled then extra.origin = origin end
--       default_extras[import] = extra
--     end
--   end
--   return default_extras
-- end

setmetatable(M, {
  __index = function(_, key)
    if options == nil then return vim.deepcopy(defaults)[key] end
    ---@cast options LazyVimConfig
    return options[key]
  end,
})

return M
