const CLASS_PATTERN = /^class[ {]/;
function isClass(fn) {
  return CLASS_PATTERN.test(fn.toString());
}
function isArray(obj) {
  return Array.isArray(obj);
}
function hasOwnProp(obj, prop) {
  return Object.prototype.hasOwnProperty.call(obj, prop);
}
function annotate(...args) {
  if (args.length === 1 && isArray(args[0])) {
    args = args[0];
  }
  args = [...args];
  const fn = args.pop();
  fn.$inject = args;
  return fn;
}
const CONSTRUCTOR_ARGS = /constructor\s*[^(]*\(\s*([^)]*)\)/m;
const FN_ARGS = /^(?:async\s+)?(?:function\s*[^(]*)?(?:\(\s*([^)]*)\)|(\w+))/m;
const FN_ARG = /\/\*([^*]*)\*\//m;
function parseAnnotations(fn) {
  if (typeof fn !== "function") {
    throw new Error(`Cannot annotate "${fn}". Expected a function!`);
  }
  const match = fn.toString().match(isClass(fn) ? CONSTRUCTOR_ARGS : FN_ARGS);
  if (!match) {
    return [];
  }
  const args = match[1] || match[2];
  return args && args.split(",").map((arg) => {
    const argMatch = arg.match(FN_ARG);
    return (argMatch && argMatch[1] || arg).trim();
  }) || [];
}
function Injector(modules, parent) {
  parent = parent || {
    get: function(name, strict) {
      currentlyResolving.push(name);
      if (strict === false) {
        return null;
      } else {
        throw error(`No provider for "${name}"!`);
      }
    }
  };
  const currentlyResolving = [];
  const providers = this._providers = Object.create(parent._providers || null);
  const instances = this._instances = /* @__PURE__ */ Object.create(null);
  const self = instances.injector = this;
  const error = function(msg) {
    const stack = currentlyResolving.join(" -> ");
    currentlyResolving.length = 0;
    return new Error(stack ? `${msg} (Resolving: ${stack})` : msg);
  };
  function get(name, strict) {
    if (!providers[name] && name.indexOf(".") !== -1) {
      const parts = name.split(".");
      let pivot = get(parts.shift());
      while (parts.length) {
        pivot = pivot[parts.shift()];
      }
      return pivot;
    }
    if (hasOwnProp(instances, name)) {
      return instances[name];
    }
    if (hasOwnProp(providers, name)) {
      if (currentlyResolving.indexOf(name) !== -1) {
        currentlyResolving.push(name);
        throw error("Cannot resolve circular dependency!");
      }
      currentlyResolving.push(name);
      instances[name] = providers[name][0](providers[name][1]);
      currentlyResolving.pop();
      return instances[name];
    }
    return parent.get(name, strict);
  }
  function fnDef(fn, locals) {
    if (typeof locals === "undefined") {
      locals = {};
    }
    if (typeof fn !== "function") {
      if (isArray(fn)) {
        fn = annotate(fn.slice());
      } else {
        throw error(`Cannot invoke "${fn}". Expected a function!`);
      }
    }
    const inject = fn.$inject || parseAnnotations(fn);
    const dependencies = inject.map((dep) => {
      if (hasOwnProp(locals, dep)) {
        return locals[dep];
      } else {
        return get(dep);
      }
    });
    return {
      fn,
      dependencies
    };
  }
  function instantiate(Type) {
    const {
      fn,
      dependencies
    } = fnDef(Type);
    const Constructor = Function.prototype.bind.apply(fn, [null].concat(dependencies));
    return new Constructor();
  }
  function invoke(func, context, locals) {
    const {
      fn,
      dependencies
    } = fnDef(func, locals);
    return fn.apply(context, dependencies);
  }
  function createPrivateInjectorFactory(childInjector) {
    return annotate((key) => childInjector.get(key));
  }
  function createChild(modules2, forceNewInstances) {
    if (forceNewInstances && forceNewInstances.length) {
      const fromParentModule = /* @__PURE__ */ Object.create(null);
      const matchedScopes = /* @__PURE__ */ Object.create(null);
      const privateInjectorsCache = [];
      const privateChildInjectors = [];
      const privateChildFactories = [];
      let provider;
      let cacheIdx;
      let privateChildInjector;
      let privateChildInjectorFactory;
      for (let name in providers) {
        provider = providers[name];
        if (forceNewInstances.indexOf(name) !== -1) {
          if (provider[2] === "private") {
            cacheIdx = privateInjectorsCache.indexOf(provider[3]);
            if (cacheIdx === -1) {
              privateChildInjector = provider[3].createChild([], forceNewInstances);
              privateChildInjectorFactory = createPrivateInjectorFactory(privateChildInjector);
              privateInjectorsCache.push(provider[3]);
              privateChildInjectors.push(privateChildInjector);
              privateChildFactories.push(privateChildInjectorFactory);
              fromParentModule[name] = [privateChildInjectorFactory, name, "private", privateChildInjector];
            } else {
              fromParentModule[name] = [privateChildFactories[cacheIdx], name, "private", privateChildInjectors[cacheIdx]];
            }
          } else {
            fromParentModule[name] = [provider[2], provider[1]];
          }
          matchedScopes[name] = true;
        }
        if ((provider[2] === "factory" || provider[2] === "type") && provider[1].$scope) {
          forceNewInstances.forEach((scope) => {
            if (provider[1].$scope.indexOf(scope) !== -1) {
              fromParentModule[name] = [provider[2], provider[1]];
              matchedScopes[scope] = true;
            }
          });
        }
      }
      forceNewInstances.forEach((scope) => {
        if (!matchedScopes[scope]) {
          throw new Error('No provider for "' + scope + '". Cannot use provider from the parent!');
        }
      });
      modules2.unshift(fromParentModule);
    }
    return new Injector(modules2, self);
  }
  const factoryMap = {
    factory: invoke,
    type: instantiate,
    value: function(value) {
      return value;
    }
  };
  function createInitializer(moduleDefinition, injector) {
    const initializers = moduleDefinition.__init__ || [];
    return function() {
      initializers.forEach((initializer) => {
        if (typeof initializer === "string") {
          injector.get(initializer);
        } else {
          injector.invoke(initializer);
        }
      });
    };
  }
  function loadModule(moduleDefinition) {
    const moduleExports = moduleDefinition.__exports__;
    if (moduleExports) {
      const nestedModules = moduleDefinition.__modules__;
      const clonedModule = Object.keys(moduleDefinition).reduce((clonedModule2, key) => {
        if (key !== "__exports__" && key !== "__modules__" && key !== "__init__" && key !== "__depends__") {
          clonedModule2[key] = moduleDefinition[key];
        }
        return clonedModule2;
      }, /* @__PURE__ */ Object.create(null));
      const childModules = (nestedModules || []).concat(clonedModule);
      const privateInjector = createChild(childModules);
      const getFromPrivateInjector = annotate(function(key) {
        return privateInjector.get(key);
      });
      moduleExports.forEach(function(key) {
        providers[key] = [getFromPrivateInjector, key, "private", privateInjector];
      });
      const initializers = (moduleDefinition.__init__ || []).slice();
      initializers.unshift(function() {
        privateInjector.init();
      });
      moduleDefinition = Object.assign({}, moduleDefinition, {
        __init__: initializers
      });
      return createInitializer(moduleDefinition, privateInjector);
    }
    Object.keys(moduleDefinition).forEach(function(key) {
      if (key === "__init__" || key === "__depends__") {
        return;
      }
      if (moduleDefinition[key][2] === "private") {
        providers[key] = moduleDefinition[key];
        return;
      }
      const type = moduleDefinition[key][0];
      const value = moduleDefinition[key][1];
      providers[key] = [factoryMap[type], arrayUnwrap(type, value), type];
    });
    return createInitializer(moduleDefinition, self);
  }
  function resolveDependencies(moduleDefinitions, moduleDefinition) {
    if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
      return moduleDefinitions;
    }
    moduleDefinitions = (moduleDefinition.__depends__ || []).reduce(resolveDependencies, moduleDefinitions);
    if (moduleDefinitions.indexOf(moduleDefinition) !== -1) {
      return moduleDefinitions;
    }
    return moduleDefinitions.concat(moduleDefinition);
  }
  function bootstrap(moduleDefinitions) {
    const initializers = moduleDefinitions.reduce(resolveDependencies, []).map(loadModule);
    let initialized = false;
    return function() {
      if (initialized) {
        return;
      }
      initialized = true;
      initializers.forEach((initializer) => initializer());
    };
  }
  this.get = get;
  this.invoke = invoke;
  this.instantiate = instantiate;
  this.createChild = createChild;
  this.init = bootstrap(modules);
}
function arrayUnwrap(type, value) {
  if (type !== "value" && isArray(value)) {
    value = annotate(value.slice());
  }
  return value;
}
export {
  Injector,
  annotate,
  parseAnnotations
};
