local M = {}

local function sort_default(left, right)
  return left.fname:lower() < right.fname:lower()
end

local function sort_directories_first(left, right)
  if left.ftype ~= right.ftype then
    return left.ftype < right.ftype
  else
    return left.fname:lower() < right.fname:lower()
  end
end

local CONFIG_SPEC = {
  hash_padding = {
    default = 2,
    check = function(val)
      if type(val) ~= "number" or math.floor(val) ~= val or val < 1 then
        return "must be integer larger than 1"
      end
    end,
  },
  show_hidden = {
    default = true,
    check = function(val)
      if type(val) ~= "boolean" then
        return "must be boolean, received " .. type(val)
      end
    end,
  },
  sort_order = {
    default = sort_default,
    check = function(val)
      if val == "default" then
        return nil, sort_default
      elseif val == "directories_first" then
        return nil, sort_directories_first
      elseif type(val) == "function" then
        return nil, val
      else
        return 'must be one of "default", "directories_first", or function'
      end
    end,
  },
  write_cmd = {
    default = "DirbufSync",
    check = function(val)
      if type(val) ~= "string" then
        return "must be string, received " .. type(val)
      end
    end,
  },
}

local user_config = {}

function M.update(opts)
  local errors = {}

  for option_name, spec in pairs(CONFIG_SPEC) do
    local val = opts[option_name]
    if val == nil then
      -- Don't check unset options
      user_config[option_name] = nil
    else
      local err, converted = spec.check(val)
      if err ~= nil then
        table.insert(errors, string.format("`%s` %s", option_name, err))
      elseif converted == nil then
        user_config[option_name] = val
      else
        user_config[option_name] = converted
      end
    end
  end

  local unknown_options = {}
  for key, _ in pairs(opts) do
    if CONFIG_SPEC[key] == nil then
      table.insert(unknown_options, "`" .. key .. "`")
    end
  end
  if #unknown_options > 0 then
    table.insert(errors, table.concat(unknown_options, ", ") .. " not recognized")
  end

  return errors
end

function M.get(opt)
  -- Ensure we don't typo options
  if CONFIG_SPEC[opt] == nil then
    error("Unrecognized option: " .. opt)
  end
  if user_config[opt] == nil then
    return CONFIG_SPEC[opt].default
  else
    return user_config[opt]
  end
end

return M
