local safeRequire = require("lib").safeRequire
local nvim_lsp = safeRequire("lspconfig")

vim.lsp.set_log_level("ERROR")

-- fallback if lsp exit suddenly
--[[ vim.api.nvim_create_autocmd("LspDetach", {
	callback = function(args)
		-- print("detaching LSP..")
		local bufname = vim.fn.bufname(args.buf)
		-- if bufname:match("%.go$") then
		-- print("bufname " .. bufname)
		-- print("restarting LSP..")
		vim.cmd("LspStart")
		-- end
	end,
}) ]]
local lsp_formatting = function(bufnr)
	vim.lsp.buf.format({
		filter = function(client)
			-- apply whatever logic you want (in this example, we'll only use null-ls)
			return client.name == "null-ls"
		end,
		bufnr = bufnr,
	})
end
-- if you want to set up formatting on save, you can use this as a callback
local augroup = vim.api.nvim_create_augroup("LspFormatting", {})
-- get function name in body of golang function
function _G.get_cur_go_func_name()
	-- get current line number
	local line = vim.api.nvim_win_get_cursor(0)[1]
	-- get current line
	local line_str = vim.api.nvim_buf_get_lines(0, line - 1, line, false)[1]
	-- get function name if not find, try to find in previous line
	-- for loop
	while not string.find(line_str, "func%s+([%w_]+)") do
		line = line - 1
		line_str = vim.api.nvim_buf_get_lines(0, line - 1, line, false)[1]
		if not line_str then
			return nil
		end
	end
	return string.match(line_str, "func%s+([%w_]+)")
end

local on_attach = function(client, bufnr)
	vim.cmd("syntax on")
	if client.name == "gopls" and not client.server_capabilities.semanticTokensProvider then
		local semantic = client.config.capabilities.textDocument.semanticTokens
		client.server_capabilities.semanticTokensProvider = {
			full = true,
			legend = { tokenModifiers = semantic.tokenModifiers, tokenTypes = semantic.tokenTypes },
			range = true,
		}
	end
	local function buf_set_keymap(...)
		vim.api.nvim_buf_set_keymap(bufnr, ...)
	end

	local function buf_set_option(...)
		vim.api.nvim_buf_set_option(bufnr, ...)
	end

	-- add to your shared on_attach callback
	if client.supports_method("textDocument/formatting") then
		vim.api.nvim_clear_autocmds({ group = augroup, buffer = bufnr })
		vim.api.nvim_create_autocmd("BufWritePre", {
			group = augroup,
			buffer = bufnr,
			callback = function()
				lsp_formatting(bufnr)
			end,
		})
	end
	-- end

	buf_set_option("omnifunc", "v:lua.vim.lsp.omnifunc")

	-- cousor hold for 3 seconds, show signature helper
	-- silent
	-- vim.api.nvim_command([[autocmd CursorHold  <buffer> lua vim.lsp.buf.hover() ]])
	-- Mappings.
	local opts = { noremap = true, silent = true }
	buf_set_keymap("n", "<Enter>", "<Nop>", opts)
	buf_set_keymap("n", "gD", "<cmd>lua vim.lsp.buf.type_definition()<CR>", opts)
	buf_set_keymap("n", "gd", "<cmd>lua vim.lsp.buf.definition()<CR>", opts)
	buf_set_keymap("n", "<Leader>gd", "<cmd>vsplit | lua vim.lsp.buf.definition()<CR>", opts)
	-- buf_set_keymap("n", "gv", "<cmd>lua vim.lsp.buf.peek_definition()<CR>", opts) -- Note: Native LSP doesn't have peek_definition
	-- buf_set_keymap("n", "<Leader>ga", "<cmd>lua vim.lsp.buf.code_action()<CR>", opts)
	-- -- coode action for extract function or variable
	-- buf_set_keymap("v", "ga", "<cmd>lua vim.lsp.bug.code_action()<CR>", opts)
	-- buf_set_keymap("v", "ga", "<cmd>lua vim.lsp.buf.range_code_action()<CR>", opts)
	buf_set_keymap("n", "K", "<cmd>lua vim.lsp.buf.hover()<CR>", opts)
	buf_set_keymap("n", "<space>dt", "<cmd>lua require('dap-go').debug_test()<CR>", opts)
	buf_set_keymap("n", "<space>wa", "<cmd>lua vim.lsp.buf.add_workspace_folder()<CR>", opts)
	buf_set_keymap("n", "<space>wr", "<cmd>lua vim.lsp.buf.remove_workspace_folder()<CR>", opts)
	buf_set_keymap("n", "<space>wl", "<cmd>lua print(vim.inspect(vim.lsp.buf.list_workspace_folders()))<CR>", opts)
	buf_set_keymap("n", "<space>rn", "<cmd>lua vim.lsp.buf.rename()<CR>", opts)
	vim.keymap.set("n", "<space>gr", vim.lsp.buf.references, opts)
	vim.keymap.set("n", "gi", vim.lsp.buf.implementation, opts)
	buf_set_keymap("n", "<Leader>f", ":lua vim.lsp.buf.format()<CR>", opts)
	-- if current buff end with _test.go, then set keymap for error
	local buf_name = vim.api.nvim_buf_get_name(bufnr)
	buf_set_keymap("n", "<space>ge", "<cmd>lua vim.diagnostic.goto_next()<CR>", opts)

	-- Set autocommands conditional on server_capabilities
	if client.server_capabilities.document_highlight then
		vim.api.nvim_exec(
			[[
      hi LspReferenceRead cterm=bold ctermbg=DarkMagenta guibg=LightYellow
      hi LspReferenceText cterm=bold ctermbg=DarkMagenta guibg=LightYellow
      hi LspReferenceWrite cterm=bold ctermbg=DarkMagenta guibg=LightYellow
      augroup lsp_document_highlight
        autocmd! * <buffer>
        autocmd CursorHold <buffer> lua vim.lsp.buf.document_highlight()
        autocmd CursorMoved <buffer> lua vim.lsp.buf.clear_references()
      augroup END
    ]],
			false
		)
	end
end

-- auto cmd

function gofumpt(timeoutms)
	-- get current file path
	local file_path = vim.api.nvim_buf_get_name(0)
	local command = string.format("!gofumpt -w %s", file_path)
	-- run cmd in background
	vim.cmd(command)
end

local lsp_configs = safeRequire("lspconfig/configs")

-- set up lspconfig

local common_servers = {
	-- "ocamllsp",
	-- "awk_ls",
	"sqlls",
	"jqls",
	"pyright",
	"bashls",
	"vimls",
	"ts_ls",
	"yamlls",
	"terraformls",
	"rnix",
}

local capabilities = require("blink.cmp").get_lsp_capabilities()
capabilities.textDocument.foldingRange = {
	dynamicRegistration = false,
	lineFoldingOnly = true,
}
for _, server in pairs(common_servers) do
	-- https://www.reddit.com/r/neovim/comments/mm1h0t/lsp_diagnostics_remain_stuck_can_someone_please/
	nvim_lsp[server].setup({
		flags = {
			allow_incremental_sync = false,
			debounce_text_changes = 500,
		},
		on_attach = on_attach,
		capabilities = capabilities,
	})
end

nvim_lsp.lua_ls.setup({
	capabilities = capabilities,
	on_attach = on_attach,
	settings = {
		Lua = {
			runtime = {
				-- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
				version = "LuaJIT",
			},
			diagnostics = {
				-- Get the language server to recognize the `vim` global
				globals = { "vim", "hs" },
			},
			workspace = {
				checkThirdParty = false,
				-- Make the server aware of Neovim runtime files
				library = {
					-- vim.api.nvim_get_runtime_file("", true),
					"/Applications/Hammerspoon.app/Contents/Resources/extensions/hs/",
					vim.fn.expand("~/lualib/share/lua/5.4"),
					vim.fn.expand("~/lualib/lib/luarocks/rocks-5.4"),
					"/opt/homebrew/opt/openresty/lualib",
				},
			},
			-- Do not send telemetry data containing a randomized but unique identifier
			telemetry = {
				enable = false,
			},
		},
	},
})

nvim_lsp.gopls.setup({
	cmd = { "gopls" },
	-- for postfix snippets and analyzers
	flags = {
		allow_incremental_sync = false,
		debounce_text_changes = 500,
	},
	capabilities = capabilities,
	settings = {
		gopls = {
			-- PAINPOINT
			usePlaceholders = false,
			-- semanticTokens = true,
			experimentalPostfixCompletions = true,
			analyses = {
				unusedparams = true,
				shadow = true,
			},
			-- use gopls build by myself https://github.com/xiantang/tools
			-- staticcheck = true,
			expandWorkspaceToModule = true,
		},
	},
	on_attach = on_attach,
})

vim.api.nvim_create_user_command("LspCapabilities", function()
	local curBuf = vim.api.nvim_get_current_buf()
	local clients = vim.lsp.get_active_clients({ bufnr = curBuf })

	for _, client in pairs(clients) do
		if client.name ~= "null-ls" then
			local capAsList = {}
			for key, value in pairs(client.server_capabilities) do
				if value and key:find("Provider") then
					local capability = key:gsub("Provider$", "")
					table.insert(capAsList, "- " .. capability)
				end
			end
			table.sort(capAsList) -- sorts alphabetically
			local msg = "# " .. client.name .. "\n" .. table.concat(capAsList, "\n")
			vim.notify(msg, "trace", {
				on_open = function(win)
					local buf = vim.api.nvim_win_get_buf(win)
					vim.api.nvim_buf_set_option(buf, "filetype", "markdown")
				end,
				timeout = 14000,
			})
			fn.setreg("+", "Capabilities = " .. vim.inspect(client.server_capabilities))
		end
	end
end, {})
