local null_ls = require('null-ls')

local register_completion = function(name, fn, filetypes)
  null_ls.register({
    name = name,
    method = null_ls.methods.COMPLETION,
    filetypes = filetypes or {},
    generator = { async = true, fn = fn },
  })
end

local Job = require('plenary.job')
register_completion('rg-dictionary', function(params, done)
  local dictionaries = vim.opt.dictionary:get()
  if vim.tbl_isempty(dictionaries) then
    return
  end

  local args = {
    '--ignore-case',
    '--no-heading',
    '--no-line-number',
    '--color=never',
    '^' .. params.word_to_complete,
  }
  vim.list_extend(args, dictionaries)

  local on_exit = function(j, exit_status)
    if exit_status ~= 0 then
      return
    end

    local items = {}
    for _, v in ipairs(j:result()) do
      if v ~= '' then
        local dict_path, word = unpack(vim.split(v, ':'))
        local item = {
          label = word,
          kind = vim.lsp.protocol.CompletionItemKind.Text,
          detail = '[D]',
          documentation = dict_path,
          sortText = '~~~' .. word,
        }
        table.insert(items, item)
      end
    end

    done({ { items = items, isIncomplete = #items > 0 } })
  end

  Job:new({
    command = 'rg',
    args = args,
    on_exit = on_exit,
  }):start()
end)

register_completion('ex-commands', function(params, done)
  local cmds = vim.fn.getcompletion(params.word_to_complete, 'command', 1)

  local items = {}
  for _, cmd in ipairs(cmds) do
    if cmd ~= '' then
      local item = {
        label = cmd,
        detail = '[C]',
        kind = vim.lsp.protocol.CompletionItemKind.Operator,
      }
      table.insert(items, item)
    end
  end

  done({ { items = items, isIncomplete = #items > 0 } })
end, { 'vim', 'lua' })

register_completion('au-events', function(params, done)
  local events = vim.fn.getcompletion(params.word_to_complete, 'event', 1)

  local items = {}
  for _, event in ipairs(events) do
    if event ~= '' then
      local item = {
        label = event,
        detail = '[E]',
        kind = vim.lsp.protocol.CompletionItemKind.Event,
      }
      table.insert(items, item)
    end
  end

  done({ { items = items, isIncomplete = #items > 0 } })
end, { 'vim', 'lua' })

register_completion('file', function(params, done)
  local line = string.sub(params.content[params.row], 1, params.col)
  local files = {}
  local s, t = vim.regex('\\f\\+$'):match_str(line)
  if s and t then
    local word_to_complete = vim.fs.normalize(string.sub(line, s + 1, t))
    files = vim.fn.getcompletion(word_to_complete, 'file', 1)
  end
  if vim.tbl_isempty(files) then
    return
  end

  local items = {}
  for _, file in ipairs(files) do
    local path = vim.fs.normalize(file)
    if path ~= '' then
      local is_dir = vim.fn.isdirectory(path) == 1
      local item = {
        label = vim.fs.basename(string.gsub(path, '/$', '')),
        detail = '[F]',
        kind = is_dir and vim.lsp.protocol.CompletionItemKind.Folder
          or vim.lsp.protocol.CompletionItemKind.File,
        documentation = path,
      }
      table.insert(items, item)
    end
  end

  done({ { items = items, isIncomplete = #items > 0 } })
end)

register_completion('around', function(params, done)
  -- local term = params.word_to_complete

  local range = 100
  local word_pattern = '[%w](_?[%w])+'

  -- no need to limit in 1..#params.content because vim.list_slice do that
  local lnum_from = params.row - range
  local lnum_to = params.row + range

  local items = {}
  local exists = {}
  for _, line in ipairs(vim.list_slice(params.content, lnum_from, lnum_to)) do
    for w in string.gmatch(line, word_pattern) do
      -- if not exists[w] and string.match(w, '^' .. term .. '.') then
      if not exists[w] then
        -- avoid duplication
        exists[w] = true

        local item = {
          label = w,
          detail = '[A]',
          kind = vim.lsp.protocol.CompletionItemKind.Keyword,
          sortText = '~~~~' .. w,
        }
        table.insert(items, item)
      end
    end
  end

  done({ { items = items, isIncomplete = #items > 0 } })
end)