local build = {}

build.dir = nil

local function echo(message, highlight)
  vim.api.nvim_echo({ { message, highlight }, }, true, {})
end

function build.set_dir(dirname)
  if not dirname then
    build.select_dir()
    return
  end
  local current_dir = vim.fn.getcwd()
  local build_dir = current_dir .. '/' .. dirname
  -- print(build_dir)

  if vim.fn.isdirectory(build_dir) == 0 then
    echo('directory does not exist: ' .. build_dir, 'Error')
    return
  end

  build.dir = build_dir

  echo('Processing compile_commands.json with compdb ...', 'DiagnosticInfo')
  local compile_commands = current_dir .. '/compile_commands.json'
  -- Post-process compile_commands.json with compdb as an async job to avoid
  -- blocking the user interface
  vim.fn.jobstart(
    'compdb -p ' .. build.dir .. ' list > ' .. compile_commands, {

      -- Restart clangd language server
      -- TODO: Configure cmake language server?
      on_exit = function()
        vim.cmd.LspRestart('clangd')
        echo('Build directory selected: ' .. dirname, 'DiagnosticInfo')
      end,

      -- Display any error messages to the user
      on_stderr = function(_, output, _)
        -- Remove any lines containing a compdb warning
        local error = {}
        local warning = 'WARNING:compdb'
        for _, line in ipairs(output) do
          if string.sub(line, 1, #warning) ~= warning then
            table.insert(error, line)
          end
        end

        -- Display the error message if there was one
        if table.maxn(error) > 0 then
          echo(vim.fn.join(error, '\n'), 'Error')
          return
        end
      end,

      stderr_buffered = true,
    }
  )
end

function build.select_dir(callback)
  local dirs = build.list_dirs()
  callback = callback or nil

  if table.maxn(dirs) == 1 then
    build.set_dir(dirs[1])
    return
  end

  if vim.fn.exists(':Telescope') > 0 then
    -- Create a Telescope picker with custom action.
    require('telescope.pickers').new({}, {
      prompt_title = 'Select build directory',
      finder = require('telescope.finders').new_table({ results = dirs }),
      sorter = require("telescope.config").values.generic_sorter({}),
      attach_mappings = function(bufnr, map)
        -- Override the default confirm action
        map('i', '<CR>', function()
          -- Get the select build directory
          local state = require('telescope.actions.state')
          build.set_dir(state.get_selected_entry().value)
          -- Close the Telescope floating window
          require('telescope.actions').close(bufnr)
          -- Invoke the callback if provided
          if callback then callback() end
        end)
        return true
      end,
      layout_config = {
        height = function(_, _, max_lines)
          -- Limit the height based on the number of dirs
          return math.min(max_lines, table.maxn(dirs) + 5)
        end
      }
    }):find()
  else
    -- Prompt user to choose dir with to inputlist
    local choices = {}
    for index, choice in ipairs(dirs) do
      table.insert(choices, tostring(index) .. ': ' .. choice)
    end
    local index = vim.fn.inputlist(choices)
    build.dir = dirs[index]
    if callback then
      callback()
    end
  end
end

function build.run(args)
  -- Check if build dir set, if not prompt user to select one
  if not build.dir then
    build.select_dir(function() build.run(args) end)
    return
  end
  local command = nil
  if vim.fn.filereadable(build.dir .. '/build.ninja') then
    command = 'ninja'
  elseif vim.fn.filereadable(build.dir .. '/Makefile') then
    command = 'make'
  else
    echo('could not find build.ninja or Makefile in: ' .. build.dir)
    return
  end
  command = command .. ' -C ' .. build.dir .. ' ' .. vim.fn.join(args, ' ')
  -- TODO: Figure out how to make the terminal window close on success
  -- TODO: Running the terminal in a togglable floating window might be nice
  vim.fn.execute('split | terminal ' .. command)
end

function build.list_dirs()
  local dirs = vim.fn.globpath('.', 'build*')
  dirs = vim.fn.substitute(dirs, '\\.\\/', '', 'g')
  return vim.fn.split(dirs)
end

function build.list_targets()
  local targets = {}
  if not build.dir then
    return targets
  end
  if vim.fn.filereadable(build.dir .. '/build.ninja') then
    -- Query ninja for the list of targets
    targets = vim.fn.systemlist('ninja -C ' .. build.dir .. ' -t targets')
    for index, target in ipairs(targets) do
      targets[index] = string.sub(target, 1, string.find(target, ':') - 1)
    end
  end
  -- TODO: Support make, it's much less straight forwards than ninja.
  return targets
end

-- Create :BuildDir command
vim.api.nvim_create_user_command('BuildDir', function(opts)
  build.set_dir(opts.fargs[1])
end, {
  bang = true, nargs = '?', complete = build.list_dirs,
})

-- Create :Build command
vim.api.nvim_create_user_command('Build', function(opts)
  build.run(opts.fargs)
end, {
  bang = true, nargs = '*', complete = build.list_targets,
})

return build