"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const async = require("../../../base/common/async.js");
const errors = require("../../../base/common/errors.js");
const descriptors = require("./descriptors.js");
const graph = require("./graph.js");
const instantiation = require("./instantiation.js");
const serviceCollection = require("./serviceCollection.js");
class CyclicDependencyError extends Error {
  constructor(graph2) {
    var _a;
    super("cyclic dependency between services");
    this.message = (_a = graph2.findCycleSlow()) !== null && _a !== void 0 ? _a : `UNABLE to detect cycle, dumping graph: 
${graph2.toString()}`;
  }
}
class InstantiationService {
  constructor(services = new serviceCollection.ServiceCollection(), strict = false, parent) {
    this._activeInstantiations = /* @__PURE__ */ new Set();
    this._services = services;
    this._strict = strict;
    this._parent = parent;
    this._services.set(instantiation.IInstantiationService, this);
  }
  createChild(services) {
    return new InstantiationService(services, this._strict, this);
  }
  invokeFunction(fn, ...args) {
    let _trace = Trace.traceInvocation(fn);
    let _done = false;
    try {
      const accessor = {
        get: (id) => {
          if (_done) {
            throw errors.illegalState("service accessor is only valid during the invocation of its target method");
          }
          const result = this._getOrCreateServiceInstance(id, _trace);
          if (!result) {
            throw new Error(`[invokeFunction] unknown service '${id}'`);
          }
          return result;
        }
      };
      return fn(accessor, ...args);
    } finally {
      _done = true;
      _trace.stop();
    }
  }
  createInstance(ctorOrDescriptor, ...rest) {
    let _trace;
    let result;
    if (ctorOrDescriptor instanceof descriptors.SyncDescriptor) {
      _trace = Trace.traceCreation(ctorOrDescriptor.ctor);
      result = this._createInstance(ctorOrDescriptor.ctor, ctorOrDescriptor.staticArguments.concat(rest), _trace);
    } else {
      _trace = Trace.traceCreation(ctorOrDescriptor);
      result = this._createInstance(ctorOrDescriptor, rest, _trace);
    }
    _trace.stop();
    return result;
  }
  _createInstance(ctor, args = [], _trace) {
    let serviceDependencies = instantiation._util.getServiceDependencies(ctor).sort((a, b) => a.index - b.index);
    let serviceArgs = [];
    for (const dependency of serviceDependencies) {
      let service = this._getOrCreateServiceInstance(dependency.id, _trace);
      if (!service) {
        this._throwIfStrict(`[createInstance] ${ctor.name} depends on UNKNOWN service ${dependency.id}.`, false);
      }
      serviceArgs.push(service);
    }
    let firstServiceArgPos = serviceDependencies.length > 0 ? serviceDependencies[0].index : args.length;
    if (args.length !== firstServiceArgPos) {
      console.warn(`[createInstance] First service dependency of ${ctor.name} at position ${firstServiceArgPos + 1} conflicts with ${args.length} static arguments`);
      let delta = firstServiceArgPos - args.length;
      if (delta > 0) {
        args = args.concat(new Array(delta));
      } else {
        args = args.slice(0, firstServiceArgPos);
      }
    }
    return new ctor(...[...args, ...serviceArgs]);
  }
  _setServiceInstance(id, instance) {
    if (this._services.get(id) instanceof descriptors.SyncDescriptor) {
      this._services.set(id, instance);
    } else if (this._parent) {
      this._parent._setServiceInstance(id, instance);
    } else {
      throw new Error("illegalState - setting UNKNOWN service instance");
    }
  }
  _getServiceInstanceOrDescriptor(id) {
    let instanceOrDesc = this._services.get(id);
    if (!instanceOrDesc && this._parent) {
      return this._parent._getServiceInstanceOrDescriptor(id);
    } else {
      return instanceOrDesc;
    }
  }
  _getOrCreateServiceInstance(id, _trace) {
    let thing = this._getServiceInstanceOrDescriptor(id);
    if (thing instanceof descriptors.SyncDescriptor) {
      return this._safeCreateAndCacheServiceInstance(id, thing, _trace.branch(id, true));
    } else {
      _trace.branch(id, false);
      return thing;
    }
  }
  _safeCreateAndCacheServiceInstance(id, desc, _trace) {
    if (this._activeInstantiations.has(id)) {
      throw new Error(`illegal state - RECURSIVELY instantiating service '${id}'`);
    }
    this._activeInstantiations.add(id);
    try {
      return this._createAndCacheServiceInstance(id, desc, _trace);
    } finally {
      this._activeInstantiations.delete(id);
    }
  }
  _createAndCacheServiceInstance(id, desc, _trace) {
    const graph$1 = new graph.Graph((data) => data.id.toString());
    let cycleCount = 0;
    const stack = [{ id, desc, _trace }];
    while (stack.length) {
      const item = stack.pop();
      graph$1.lookupOrInsertNode(item);
      if (cycleCount++ > 1e3) {
        throw new CyclicDependencyError(graph$1);
      }
      for (let dependency of instantiation._util.getServiceDependencies(item.desc.ctor)) {
        let instanceOrDesc = this._getServiceInstanceOrDescriptor(dependency.id);
        if (!instanceOrDesc) {
          this._throwIfStrict(`[createInstance] ${id} depends on ${dependency.id} which is NOT registered.`, true);
        }
        if (instanceOrDesc instanceof descriptors.SyncDescriptor) {
          const d = { id: dependency.id, desc: instanceOrDesc, _trace: item._trace.branch(dependency.id, true) };
          graph$1.insertEdge(item, d);
          stack.push(d);
        }
      }
    }
    while (true) {
      const roots = graph$1.roots();
      if (roots.length === 0) {
        if (!graph$1.isEmpty()) {
          throw new CyclicDependencyError(graph$1);
        }
        break;
      }
      for (const { data } of roots) {
        const instanceOrDesc = this._getServiceInstanceOrDescriptor(data.id);
        if (instanceOrDesc instanceof descriptors.SyncDescriptor) {
          const instance = this._createServiceInstanceWithOwner(data.id, data.desc.ctor, data.desc.staticArguments, data.desc.supportsDelayedInstantiation, data._trace);
          this._setServiceInstance(data.id, instance);
        }
        graph$1.removeNode(data);
      }
    }
    return this._getServiceInstanceOrDescriptor(id);
  }
  _createServiceInstanceWithOwner(id, ctor, args = [], supportsDelayedInstantiation, _trace) {
    if (this._services.get(id) instanceof descriptors.SyncDescriptor) {
      return this._createServiceInstance(ctor, args, supportsDelayedInstantiation, _trace);
    } else if (this._parent) {
      return this._parent._createServiceInstanceWithOwner(id, ctor, args, supportsDelayedInstantiation, _trace);
    } else {
      throw new Error(`illegalState - creating UNKNOWN service instance ${ctor.name}`);
    }
  }
  _createServiceInstance(ctor, args = [], _supportsDelayedInstantiation, _trace) {
    if (!_supportsDelayedInstantiation) {
      return this._createInstance(ctor, args, _trace);
    } else {
      const idle = new async.IdleValue(() => this._createInstance(ctor, args, _trace));
      return new Proxy(/* @__PURE__ */ Object.create(null), {
        get(target, key) {
          if (key in target) {
            return target[key];
          }
          let obj = idle.value;
          let prop = obj[key];
          if (typeof prop !== "function") {
            return prop;
          }
          prop = prop.bind(obj);
          target[key] = prop;
          return prop;
        },
        set(_target, p, value) {
          idle.value[p] = value;
          return true;
        }
      });
    }
  }
  _throwIfStrict(msg, printWarning) {
    if (printWarning) {
      console.warn(printWarning);
    }
    if (this._strict) {
      throw new Error(msg);
    }
  }
}
class Trace {
  constructor(type, name) {
    this.type = type;
    this.name = name;
    this._start = Date.now();
    this._dep = [];
  }
  static traceInvocation(ctor) {
    return Trace._None;
  }
  static traceCreation(ctor) {
    return Trace._None;
  }
  branch(id, first) {
    let child = new Trace(2, id.toString());
    this._dep.push([id, first, child]);
    return child;
  }
  stop() {
    let dur = Date.now() - this._start;
    Trace._totals += dur;
    let causedCreation = false;
    function printChild(n, trace) {
      let res = [];
      let prefix = new Array(n + 1).join("	");
      for (const [id, first, child] of trace._dep) {
        if (first && child) {
          causedCreation = true;
          res.push(`${prefix}CREATES -> ${id}`);
          let nested = printChild(n + 1, child);
          if (nested) {
            res.push(nested);
          }
        } else {
          res.push(`${prefix}uses -> ${id}`);
        }
      }
      return res.join("\n");
    }
    let lines = [
      `${this.type === 0 ? "CREATE" : "CALL"} ${this.name}`,
      `${printChild(1, this)}`,
      `DONE, took ${dur.toFixed(2)}ms (grand total ${Trace._totals.toFixed(2)}ms)`
    ];
    if (dur > 2 || causedCreation) {
      console.log(lines.join("\n"));
    }
  }
}
Trace._None = new class extends Trace {
  constructor() {
    super(-1, null);
  }
  stop() {
  }
  branch() {
    return this;
  }
}();
Trace._totals = 0;
exports.InstantiationService = InstantiationService;
exports.Trace = Trace;
