require('nvim-autopairs').setup {}

-- nvim_lsp object
local nvim_lsp = require 'lspconfig'

-- function to attach completion when setting up lsp
local on_attach = function(client)
end

-- Set my preferred diagnostic options
local diagnostic_options = {
  virtual_text = false,
  signs = true,
  update_in_insert = false,
}

local rust_capabilities = vim.lsp.protocol.make_client_capabilities()
rust_capabilities.textDocument.completion.completionItem.snippetSupport = true
rust_capabilities.textDocument.completion.completionItem.resolveSupport = {
  properties = {
    'documentation',
    'detail',
    'additionalTextEdits',
  }
}

local default_capabilities = vim.lsp.protocol.make_client_capabilities()
default_capabilities.textDocument.completion.completionItem.snippetSupport = true
default_capabilities.textDocument.completion.completionItem.resolveSupport = {
  properties = {
    'documentation',
    'detail',
    'additionalTextEdits',
  }
}

-- Enable rust_analyzer
nvim_lsp.rust_analyzer.setup({
  on_attach = function(client, bufnr)
    client.server_capabilities.semanticTokensProvider = nil
  end,
  capabilities = rust_capabilities,
  settings = {
    ['rust-analyzer'] = {
      checkOnSave = {
        allFeatures = true,
        overrideCommand = {
          'cargo', 'clippy', '--workspace', '--message-format=json',
          '--all-targets', '--all-features'
        }
      },
      runnables = {
        extraEnv = {
          TORCH_CUDA_VERSION = "cu121",
        }
      }
    }
  }
})
nvim_lsp.gopls.setup({
  on_attach = function(client, bufnr)
    client.server_capabilities.semanticTokensProvider = {
      full = true,
      legend = {
        --tokenTypes = { 'namespace', 'type', 'class', 'enum', 'interface', 'struct', 'typeParameter', 'parameter', 'variable', 'property', 'enumMember', 'event', 'function', 'method', 'macro', 'keyword', 'modifier', 'comment', 'string', 'number', 'regexp', 'operator', 'decorator', },
        tokenTypes = { 'namespace' },
        tokenModifiers = { 'declaration', 'definition', 'readonly', 'static', 'deprecated', 'abstract', 'async',
          'modification', 'documentation', 'defaultLibrary',
        },
      }
    }
  end,
  settings = {
    gopls = {
      semanticTokens = true,
    },
  },
})

local function filter(arr, func)
  -- Filter in place
  -- https://stackoverflow.com/questions/49709998/how-to-filter-a-lua-array-inplace
  local new_index = 1
  local size_orig = #arr
  for old_index, v in ipairs(arr) do
    if func(v, old_index) then
      arr[new_index] = v
      new_index = new_index + 1
    end
  end
  for i = new_index, size_orig do arr[i] = nil end
end

local function pyright_accessed_filter(diagnostic)
  -- Allow kwargs to be unused, sometimes you want many functions to take the
  -- same arguments but you don't use all the arguments in all the functions,
  -- so kwargs is used to suck up all the extras
  if diagnostic.message == '"kwargs" is not accessed' then
    return false
  end
  -- Allow variables starting with an underscore
  if string.match(diagnostic.message, '"_.+" is not accessed') then
    return false
  end

  return true
end

local function pyright_custom_diagnostic(a, params, client_id, c, config)
  filter(params.diagnostics, pyright_accessed_filter)
  vim.lsp.diagnostic.on_publish_diagnostics(a, params, client_id, c, config)
end

nvim_lsp.pyright.setup({
  on_attach = function(client, bufnr)
    vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(pyright_custom_diagnostic, diagnostic_options)
  end
})
--nvim_lsp.pyright.setup({ on_attach = on_attach })
--nvim_lsp.pylsp.setup({
--on_attach = on_attach,
--settings = {
--pylsp = {
--plugins = {
---- formatter options
----black = { enabled = true },
--autopep8 = { enabled = false },
--yapf = { enabled = false },
---- linter options
--pylint = { enabled = false, executable = "pylint" },
--pyflakes = { enabled = false },
--pycodestyle = { enabled = false },
---- type checker
--pylsp_mypy = { enabled = true },
---- auto-completion options
--jedi_completion = { enabled = false },
---- import sorting
----pyls_isort = { enabled = true },
----flake8 = {
----maxLineLength = 100,
----},
--pycodestyle = {
--maxLineLength = 100,
--},
----rope_autoimport = {
----enabled = true
----},
----rope_completion = {
----enabled = true
----},
--}
--}
--}
--})

nvim_lsp.clangd.setup({ on_attach = on_attach })
nvim_lsp.tsserver.setup {
  cmd = { "/home/barak/.yarn/bin/typescript-language-server", "--stdio" }
}
nvim_lsp.vuels.setup {
  cmd = { "/home/barak/.yarn/bin/vls" }
}
nvim_lsp.hls.setup {
  cmd = { "haskell-language-server-wrapper", "--lsp" }
}

nvim_lsp.svelte.setup {
  cmd = { "/home/barak/.bun/bin/bunx", "svelteserver", "--stdio" }
}

local runtime_path = vim.split(package.path, ';')
table.insert(runtime_path, "lua/?.lua")
table.insert(runtime_path, "lua/?/init.lua")

require 'lspconfig'.lua_ls.setup {
  settings = {
    Lua = {
      runtime = {
        -- Tell the language server which version of Lua you're using (most likely LuaJIT in the case of Neovim)
        version = 'LuaJIT',
        -- Setup your lua path
        path = runtime_path,
      },
      diagnostics = {
        -- Get the language server to recognize the `vim` global
        globals = { 'vim' },
      },
      workspace = {
        -- Make the server aware of Neovim runtime files
        library = vim.api.nvim_get_runtime_file("", true),
      },
      -- Do not send telemetry data containing a randomized but unique identifier
      telemetry = {
        enable = false,
      },
    },
  },
}


require('go').setup({
  iferr_vertical_shift = 2
})

require('telescope').load_extension('goimpl')

function org_imports(wait_ms)
  local params = vim.lsp.util.make_range_params()

  params.context = { only = { "source.organizeImports" } }

  local result = vim.lsp.buf_request_sync(0, "textDocument/codeAction", params, wait_ms)

  for _, res in pairs(result or {}) do
    for _, r in pairs(res.result or {}) do
      if r.edit then
        vim.lsp.util.apply_workspace_edit(r.edit, "utf-8")
      else
        vim.lsp.buf.execute_command(r.command)
      end
    end
  end
end

-- register linters
local luaformat = require("efmls-configs.formatters.lua_format")
local rustfmt = require('efmls-configs.formatters.rustfmt')
local black = require('efmls-configs.formatters.black')
local mypy = require('efmls-configs.linters.mypy')
local eslint = require('efmls-configs.linters.eslint')
local isort = require('efmls-configs.formatters.isort')
local languages = {
  typescript = { eslint },
  lua = { luaformat },
  python = { black, mypy, isort },
  rust = { rustfmt },
}

local efmls_config = {
  filetypes = vim.tbl_keys(languages),
  settings = {
    rootMarkers = { '.git/' },
    languages = languages,
  },
  init_options = {
    documentFormatting = true,
    documentRangeFormatting = true,
  },
}

require('lspconfig').efm.setup(vim.tbl_extend('force', efmls_config, {
  -- Pass your custom lsp config below like on_attach and capabilities
  on_attach = on_attach,
  capabilities = default_capabilities,
}))

-- Enable diagnostics
vim.lsp.handlers["textDocument/publishDiagnostics"] = vim.lsp.with(
  vim.lsp.diagnostic.on_publish_diagnostics,
  diagnostic_options
)

-- Compe setup
require 'compe'.setup {
  enabled = true,
  autocomplete = true,
  debug = false,
  min_length = 1,
  preselect = 'disable',
  throttle_time = 80,
  source_timeout = 200,
  incomplete_delay = 400,
  max_abbr_width = 100,
  max_kind_width = 100,
  max_menu_width = 100,
  documentation = true,

  source = {
    path = true,
    nvim_lsp = true,
  },
}

--signature_cfg = {
--hint_enable = true,
--hint_prefix = "% ",
--handler_opts = {
--border = "none"
--},
--}

--require'lsp_signature'.on_attach(signature_cfg)

local saga_cfg = {
  code_action_prompt = {
    enable = false,
    sign = false,
    virtual_text = false,
  },
}
-- require'lspsaga'.init_lsp_saga(saga_cfg)

local t = function(str)
  return vim.api.nvim_replace_termcodes(str, true, true, true)
end

local check_back_space = function()
  local col = vim.fn.col('.') - 1
  if col == 0 or vim.fn.getline('.'):sub(col, col):match('%s') then
    return true
  else
    return false
  end
end

-- Use (s-)tab to:
--- move to prev/next item in completion menuone
--- jump to prev/next snippet's placeholder
_G.tab_complete = function()
  if vim.fn.pumvisible() == 1 then
    return t "<C-n>"
  elseif check_back_space() then
    return t "<Tab>"
  else
    return vim.fn['compe#complete']()
  end
end

_G.s_tab_complete = function()
  if vim.fn.pumvisible() == 1 then
    return t "<C-p>"
  else
    return t "<S-Tab>"
  end
end

vim.api.nvim_set_keymap("i", "<Tab>", "v:lua.tab_complete()", { expr = true })
vim.api.nvim_set_keymap("s", "<Tab>", "v:lua.tab_complete()", { expr = true })
vim.api.nvim_set_keymap("i", "<S-Tab>", "v:lua.s_tab_complete()", { expr = true })
vim.api.nvim_set_keymap("s", "<S-Tab>", "v:lua.s_tab_complete()", { expr = true })

require('lspkind').init({
  -- disables text annotations
  --with_text = false,
  --with_text = true,

  -- defines how annotations are shown
  -- default: symbol
  -- options: 'text', 'text_symbol', 'symbol_text', 'symbol'
  mode = 'symbol',

  -- default symbol map
  -- can be either 'default' or 'codicons'
  preset = 'default',

  -- override preset symbols
  -- default: {}
  --symbol_map = {
  --Method = 'm',
  --Function = 'f',
  --Text = 'txt',
  --Constructor = 'new',
  --Variable = 'var',
  --Class = 'cls',
  --Interface = 'iface',
  --Module = 'mod',
  --Property = 'prop',
  --Unit = 'unit',
  --Value = 'val',
  --Enum = 'enum',
  --Keyword = 'kw',
  --Snippet = 'sn',
  --Color = 'color',
  --File = 'file',
  --Folder = 'fold',
  --EnumMember = 'enum',
  --Constant = 'const',
  --Struct = 'struct',
  --},
})

--require('rust-tools').setup({
--})

require('trouble').setup {
  mode = "document_diagnostics",
  auto_close = true,
  action_keys = { -- key mappings for actions in the trouble list
    -- map to {} to remove a mapping, for example:
    -- close = {},
    --close = "q", -- close the list
    --cancel = "<esc>", -- cancel the preview and get back to your last window / buffer / cursor
    --refresh = "r", -- manually refresh
    jump = { "o", "<tab>" }, -- jump to the diagnostic or open / close folds
    --open_split = { "<c-x>" }, -- open buffer in new split
    --open_vsplit = { "<c-v>" }, -- open buffer in new vsplit
    open_tab = {},                -- open buffer in new tab
    jump_close = { "<cr>" },      -- jump to the diagnostic and close the list
    toggle_mode = "m",            -- toggle between "workspace" and "document" diagnostics mode
    toggle_preview = "P",         -- toggle auto_preview
    hover = "K",                  -- opens a small popup with the full multiline message
    preview = "p",                -- preview the diagnostic location
    close_folds = { "zM", "zm" }, -- close all folds
    open_folds = { "zR", "zr" },  -- open all folds
    toggle_fold = { "zA", "za" }, -- toggle fold of current file
    previous = "k",               -- previous item
    next = "j"                    -- next item
  },
}

local trouble_telescope = require("trouble.providers.telescope")
local builtin = require 'telescope.builtin'

local sendtoqf = function(loc)
  local actions = require('telescope.actions')
  actions.send_to_qflist(loc)
  builtin.quickfix({ initial_mode = "normal" })
end

require('telescope').setup {
  defaults = {
    mappings = {
      n = {
        ["q"] = require('telescope.actions').close,
        ["ff"] = sendtoqf,
        ["<c-t>"] = trouble_telescope.open_with_trouble,
      },
      i = {
        ["<c-t>"] = trouble_telescope.open_with_trouble,
        ["ff"] = require('telescope.actions').send_to_qflist,
      },
    },
    path_display = { "smart", "shorten" },
    dynamic_preview_title = true,
  },
  pickers = {
    goimpl = {
      theme = "ivy",
      layout_config = {
        height = 10,
      },
    },
    lsp_code_actions = {
      theme = "ivy",
      layout_config = {
        height = 10,
      },
    },
    lsp_range_code_actions = {
      theme = "ivy",
      layout_config = {
        height = 10,
      },
    },
    lsp_references = {
      theme = "ivy",
      layout_config = {
        height = 15,
      },
    },
    quickfix = {
      theme = "ivy",
      layout_config = {
        height = 15,
      },
      initial_mode = "normal",
    },
    git_files = {
      theme = "ivy",
    },
    grep_string = {
      theme = "ivy",
      layout_config = {
        height = 15,
      },
      initial_mode = "normal",
    },
    live_grep = {
      theme = "ivy",
      layout_config = {
        height = 15,
      },
    },
    buffers = {
      theme = "ivy",
      layout_config = {
        height = 15,
      },
      ignore_current_buffer = true,
      initial_mode = "normal",
    },
  },
}


--
-- Treesitter
--

require 'nvim-treesitter.configs'.setup {
  -- One of "all", "maintained" (parsers with maintainers), or a list of languages
  ensure_installed = {
    "c",
    "cpp",
    "css",
    "dockerfile",
    "fish",
    "go",
    "haskell",
    "html",
    "javascript",
    "json",
    "lua",
    "make",
    "markdown",
    "proto",
    "python",
    "query",
    "rust",
    "terraform",
    "toml",
    "tsx",
    "typescript",
    "vim",
    "yaml",
  },

  -- Install languages synchronously (only applied to `ensure_installed`)
  sync_install = false,

  -- List of parsers to ignore installing
  -- ignore_install = { "javascript" },

  highlight = {
    -- `false` will disable the whole extension
    enable = true,

    -- list of language that will be disabled
    -- disable = { "c", "rust" },

    -- Setting this to true will run `:h syntax` and tree-sitter at the same time.
    -- Set this to `true` if you depend on 'syntax' being enabled (like for indentation).
    -- Using this option may slow down your editor, and you may see some duplicate highlights.
    -- Instead of true it can also be a list of languages
    additional_vim_regex_highlighting = false,
    --custom_captures = {
    --["generic_type_param"] = "TSCGenericTypeParam",
    --["type_identifier"] = "TSCGenericTypeParam",
    --},
  },

  indent = {
    enable = true
  },

  playground = {
    enable = true,
    disable = {},
    updatetime = 25,         -- Debounced time for highlighting nodes in the playground from source code
    persist_queries = false, -- Whether the query persists across vim sessions
    keybindings = {
      toggle_query_editor = 'o',
      toggle_hl_groups = 'i',
      toggle_injected_languages = 't',
      toggle_anonymous_nodes = 'a',
      toggle_language_display = 'I',
      focus_language = 'f',
      unfocus_language = 'F',
      update = 'R',
      goto_node = '<cr>',
      show_help = '?',
    },
  }
}

require "neogen".setup {}

require('leap').set_default_keymaps()
require("flit").setup {}

require('snippy').setup({
  mappings = {
    is = {
      [',,'] = 'expand_or_advance',
      [',.'] = 'previous',
    },
    nx = {
      ['<leader>x'] = 'cut_text',
    },
  },
})


---
--- My own "where" clause generator for Rust
---
function string.insert(str1, str2, pos)
  return str1:sub(1, pos) .. str2 .. str1:sub(pos + 1)
end

function string.split(s, delimiter)
  local result = {};
  for match in (s .. delimiter):gmatch("(.-)" .. delimiter) do
    table.insert(result, match);
  end
  return result;
end

local function rust_whereify_line(line, var_name)
  if string.find(line, "fn") == nil then
    return nil
  end

  local paren = string.find(line, "%(")
  if paren == nil then return nil end

  local open_generic = string.find(line, "<")
  if open_generic ~= nil then
    if open_generic < paren then
      -- TODO(barakmich): it's already generic, we need to do something for that
      -- probably a different function
      return nil
    end
    -- just continue otherwise
  end

  local whitespace = string.match(line, "^(%s*)")

  local generic = "<" .. var_name .. ">"
  local out = string.insert(line, generic, paren - 1)

  local brace = string.find(out, "%{")
  if brace == nil then brace = string.len(out) end

  out = string.insert(out, "\n" .. whitespace .. "where\n" .. whitespace .. " " .. var_name .. ": ,\n" .. whitespace,
    brace - 1)

  return string.split(out, "\n")
end

function rust_where_at_line()
  local var_name = vim.fn.input("Variable: ")
  local lineNum, col = unpack(vim.api.nvim_win_get_cursor(0))
  local replacements = rust_whereify_line(vim.api.nvim_get_current_line(), var_name)
  vim.api.nvim_buf_set_lines(0, lineNum - 1, lineNum, false, replacements)
  vim.api.nvim_win_set_cursor(0, { lineNum + 2, 4 })
end

function neogen_dwim()
  local table = {
    ["function_declaration"] = "func",
    ["function_definition"] = "func",
    ["function_item"] = "func",
    ["function_signature_item"] = "func",
    ["struct_item"] = "class",
    ["trait_item"] = "class",
    ["package_clause"] = "type",
    ["const_declaration"] = "type",
    ["var_declaration"] = "type",
    ["class_definition"] = "class",
  }

  local ts_utils = require 'nvim-treesitter.ts_utils'
  local current_node = ts_utils.get_node_at_cursor()
  while (current_node) do
    local v = table[current_node:type()]
    if v then
      require('neogen').generate({ type = v })
      break
    else
      current_node = current_node:parent()
    end
  end
end

require("murdock")
require("lir_setup")
require("lualine_setup")
