'use strict';
module.exports = function base(app, options) {
if (!isObject(app) && typeof app !== 'function') {
throw new TypeError('expected an object or function');
}
var opts = isObject(options) ? options : {};
var prop = typeof opts.prop === 'string' ? opts.prop : 'fns';
if (!Array.isArray(app[prop])) {
define(app, prop, []);
}
define(app, 'use', use);
define(app, 'run', function(val) {
if (!isObject(val)) return;
if (!val.use || !val.run) {
define(val, prop, val[prop] || []);
define(val, 'use', use);
}
if (!val[prop] || val[prop].indexOf(base) === -1) {
val.use(base);
}
var self = this || app;
var fns = self[prop];
var len = fns.length;
var idx = -1;
while (++idx < len) {
val.use(fns[idx]);
}
return val;
});
function use(type, fn, options) {
var offset = 1;
if (typeof type === 'string' || Array.isArray(type)) {
fn = wrap(type, fn);
offset++;
} else {
options = fn;
fn = type;
}
if (typeof fn !== 'function') {
throw new TypeError('expected a function');
}
var self = this || app;
var fns = self[prop];
var args = [].slice.call(arguments, offset);
args.unshift(self);
if (typeof opts.hook === 'function') {
opts.hook.apply(self, args);
}
var val = fn.apply(self, args);
if (typeof val === 'function' && fns.indexOf(val) === -1) {
fns.push(val);
}
return self;
}
function wrap(type, fn) {
return function plugin() {
return this.type === type ? fn.apply(this, arguments) : plugin;
};
}
return app;
};
function isObject(val) {
return val && typeof val === 'object' && !Array.isArray(val);
}
function define(obj, key, val) {
Object.defineProperty(obj, key, {
configurable: true,
writable: true,
value: val
});
}