local completion_engine = {
	{
		"hrsh7th/nvim-cmp",
		branch = "main",
		version = false, -- new releases (>2022) are sadly not versioned
		dependencies = {
			-- TODO: Move to lsp
			"hrsh7th/cmp-nvim-lsp",
			"hrsh7th/cmp-nvim-lsp-signature-help",
			"hrsh7th/cmp-path",
			"hrsh7th/cmp-buffer",
			"hrsh7th/cmp-calc",
			"hrsh7th/cmp-cmdline",
			-- TODO: Move me into a separate load?
			"cbarrete/completion-vcard",
			"f3fora/cmp-spell",
			"jc-doyle/cmp-pandoc-references",
			-- TODO: Decide: get rid or just enable in very specific circumstances
			"lukas-reineke/cmp-rg",
			-- TODO: Move to treesitter
			{ "ray-x/cmp-treesitter", dependencies = { "nvim-treesitter/nvim-treesitter" } },
		},
		opts = function()
			local cmp = require("cmp")
			-- style 'ghosttext' which appears behind cursor, showing current completion
			vim.api.nvim_set_hl(0, "CmpGhostText", { link = "Comment", default = true })

			local has_words_before = function()
				---@diagnostic disable-next-line:deprecated
				local line, col = unpack(vim.api.nvim_win_get_cursor(0))
				return col ~= 0
					and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
			end

			local kind_icons = {
				Text = "",
				Method = "",
				Function = "󰊕",
				Constructor = "",
				Field = "",
				Variable = "",
				Class = "",
				Interface = "",
				Module = "",
				Property = "",
				Unit = "",
				Value = "V",
				Enum = "",
				Keyword = "",
				Snippet = "",
				Color = "",
				File = "",
				Reference = "",
				Folder = "",
				EnumMember = "",
				Constant = "",
				Struct = "",
				Event = "",
				Operator = "",
				TypeParameter = "",
			}

			-- `/` cmdline setup.
			cmp.setup.cmdline("/", {
				completion = { completeopt = "menu,menuone,noinsert,noselect" },
				preselect = cmp.PreselectMode.None,
				mapping = cmp.mapping.preset.cmdline(),
				sources = { { name = "buffer" } },
			})
			-- `:` cmdline setup.
			cmp.setup.cmdline(":", {
				completion = { completeopt = "menu,menuone,noinsert,noselect" },
				preselect = cmp.PreselectMode.None,
				mapping = cmp.mapping.preset.cmdline(),
				sources = cmp.config.sources({ { name = "path" } }, {
					{ name = "cmdline", option = { ignore_cmds = { "Man", "!" } } },
				}),
			})
			return {
				window = { documentation = cmp.config.window.bordered() },
				-- add noselect to not automatically select first item
				completion = { completeopt = "menu,menuone,noinsert" },
				preselect = cmp.PreselectMode.Item, -- not sure what this changes diff than above?
				experimental = {
					ghost_text = {
						hl_group = "CmpGhostText",
					},
				},
				sources = {
					{ name = "nvim_lsp" },
					{ name = "nvim_lsp_signature_help" },
					{ name = "pandoc_references" },
					{ name = "calc" },
					{ name = "path" },
					{ name = "buffer", keyword_length = 3 },
					{ name = "spell", keyword_length = 3 },
					-- { name = 'rg',           keyword_length = 5 },
					{ name = "vCard" },
				},
				mapping = cmp.mapping.preset.insert({
					["<C-b>"] = cmp.mapping.scroll_docs(-4),
					["<C-f>"] = cmp.mapping.scroll_docs(4),
					["<CR>"] = cmp.mapping({
						i = function(fallback)
							if cmp.visible() and cmp.get_active_entry() then
								cmp.confirm({
									behavior = cmp.ConfirmBehavior.Replace,
									select = false,
								})
							else
								fallback()
							end
						end,
						s = cmp.mapping.confirm({ select = true }),
						c = cmp.mapping.confirm({
							behavior = cmp.ConfirmBehavior.Replace,
							select = false,
						}), -- disable selection in cmd mode
					}),
					["<Tab>"] = cmp.mapping(function(fallback)
						-- expand_or_jumpable() will always jump
						-- expand_or_locally_jumpable() only jumps when still inside snippet region
						if cmp.visible() then
							cmp.select_next_item()
						elseif has_words_before() then
							cmp.complete()
						else
							fallback()
						end
					end, { "i", "s" }),
					["<S-Tab>"] = cmp.mapping(function(fallback)
						if cmp.visible() then
							cmp.select_prev_item()
						else
							fallback()
						end
					end, { "i", "s" }),
				}),
				formatting = {
					expandable_indicator = true,
					fields = { "kind", "abbr", "menu" },
					format = function(entry, vim_item)
						-- Kind icons, removing kind text leaving only icon
						-- vim_item.kind = string.format('%s %s', kind_icons[vim_item.kind], vim_item.kind)
						vim_item.kind = string.format("%s", kind_icons[vim_item.kind])

						-- Source
						vim_item.menu = ({
							buffer = "",
							calc = "󰃬",
							digraphs = "∬",
							latex_symbols = "𝓧",
							luasnip = "",
							nvim_lsp = "ℒ",
							nvim_lua = "󰢱",
							pandoc_references = "",
							spell = "󰓆",
							vCard = "󰛋",
						})[entry.source.name]
						return vim_item
					end,
				},
			}
		end,
		event = { "InsertEnter", "CmdlineEnter" },
	},
}
--
-- TODO: Enable more lazy loaded startup? And integrate into
-- cmp as insert source instead of in its setup config below.
local snippet_engine = {
	"nvim-cmp",
	dependencies = {
		"L3MON4D3/LuaSnip",
		"rafamadriz/friendly-snippets",
		"saadparwaiz1/cmp_luasnip",
		{
			"benfowler/telescope-luasnip.nvim",
			dependencies = { { "nvim-telescope/telescope.nvim", optional = true } },
			config = function()
				if require("core.util").is_available("telescope") then
					require("telescope").load_extension("luasnip")
				end
			end,
		},
	},
	event = { "InsertEnter" },
	build = "make install_jsregexp",
	opts = function(_, opts)
		local cmp = require("cmp")
		local luasnip = require("luasnip")

		require("luasnip.loaders.from_vscode").lazy_load({ exclude = { "markdown", "quarto" } })
		require("luasnip.loaders.from_snipmate").lazy_load()

		opts.snippet = {
			expand = function(item)
				require("luasnip").lsp_expand(item.body)
			end,
		}
		local has_words_before = function()
			---@diagnostic disable-next-line:deprecated
			local line, col = unpack(vim.api.nvim_win_get_cursor(0))
			return col ~= 0 and vim.api.nvim_buf_get_lines(0, line - 1, line, true)[1]:sub(col, col):match("%s") == nil
		end

		table.insert(opts.sources, { name = "luasnip", keyword_length = 1 })

		opts.mapping["<Tab>"] = cmp.mapping(function(fallback) -- expand_or_jumpable() will always jump
			-- expand_or_locally_jumpable() only jumps when still inside snippet region
			if luasnip.expand_or_locally_jumpable() then
				luasnip.expand_or_jump()
			elseif cmp.visible() then
				cmp.select_next_item()
			elseif has_words_before() then
				cmp.complete()
			else
				fallback()
			end
		end, { "i", "s" })
		opts.mapping["<S-Tab>"] = cmp.mapping(function(fallback)
			if luasnip.locally_jumpable(-1) then
				luasnip.jump(-1)
			elseif cmp.visible() then
				cmp.select_prev_item()
			else
				fallback()
			end
		end, { "i", "s" })
	end,
}

local beancount_cmp = {
	"nvim-cmp",
	dependencies = {
		"crispgm/cmp-beancount",
	},
	ft = "beancount",
	opts = function(_, opts)
		vim.g.python3_host_prog = "/home/marty/.local/pipx/venvs/beancount/bin/python"
		table.insert(opts.sources, {
			name = "beancount",
			-- option = {
			--     -- TODO: implement dynamically
			--     -- I believe if we don't supply this it automatically takes
			--     -- from the open file which would be good enough
			-- 	account = "/home/marty/documents/records/budget/main.beancount",
			-- },
		})
	end,
}

local latex_cmp = {
	"nvim-cmp",
	dependencies = {
		-- TODO: Needs better lazy loading
		"kdheepak/cmp-latex-symbols",
	},
	event = "CursorHold",
	opts = function(_, opts)
		table.insert(opts.sources, { name = "latex_symbols" })
	end,
}

return {
	completion_engine,
	snippet_engine,
	beancount_cmp,
	latex_cmp,
}
