"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const errors = require("./errors.js");
const lifecycle = require("./lifecycle.js");
const linkedList = require("./linkedList.js");
const stopwatch = require("./stopwatch.js");
exports.Event = void 0;
(function(Event) {
  Event.None = () => lifecycle.Disposable.None;
  function once(event) {
    return (listener, thisArgs = null, disposables) => {
      let didFire = false;
      let result;
      result = event((e) => {
        if (didFire) {
          return;
        } else if (result) {
          result.dispose();
        } else {
          didFire = true;
        }
        return listener.call(thisArgs, e);
      }, null, disposables);
      if (didFire) {
        result.dispose();
      }
      return result;
    };
  }
  Event.once = once;
  function map(event, map2, disposable) {
    return snapshot((listener, thisArgs = null, disposables) => event((i) => listener.call(thisArgs, map2(i)), null, disposables), disposable);
  }
  Event.map = map;
  function forEach(event, each, disposable) {
    return snapshot((listener, thisArgs = null, disposables) => event((i) => {
      each(i);
      listener.call(thisArgs, i);
    }, null, disposables), disposable);
  }
  Event.forEach = forEach;
  function filter(event, filter2, disposable) {
    return snapshot((listener, thisArgs = null, disposables) => event((e) => filter2(e) && listener.call(thisArgs, e), null, disposables), disposable);
  }
  Event.filter = filter;
  function signal(event) {
    return event;
  }
  Event.signal = signal;
  function any(...events) {
    return (listener, thisArgs = null, disposables) => lifecycle.combinedDisposable(...events.map((event) => event((e) => listener.call(thisArgs, e), null, disposables)));
  }
  Event.any = any;
  function reduce(event, merge, initial, disposable) {
    let output = initial;
    return map(event, (e) => {
      output = merge(output, e);
      return output;
    }, disposable);
  }
  Event.reduce = reduce;
  function snapshot(event, disposable) {
    let listener;
    const options = {
      onFirstListenerAdd() {
        listener = event(emitter.fire, emitter);
      },
      onLastListenerRemove() {
        listener.dispose();
      }
    };
    const emitter = new Emitter(options);
    if (disposable) {
      disposable.add(emitter);
    }
    return emitter.event;
  }
  function debounce(event, merge, delay = 100, leading = false, leakWarningThreshold, disposable) {
    let subscription;
    let output = void 0;
    let handle = void 0;
    let numDebouncedCalls = 0;
    const options = {
      leakWarningThreshold,
      onFirstListenerAdd() {
        subscription = event((cur) => {
          numDebouncedCalls++;
          output = merge(output, cur);
          if (leading && !handle) {
            emitter.fire(output);
            output = void 0;
          }
          clearTimeout(handle);
          handle = setTimeout(() => {
            const _output = output;
            output = void 0;
            handle = void 0;
            if (!leading || numDebouncedCalls > 1) {
              emitter.fire(_output);
            }
            numDebouncedCalls = 0;
          }, delay);
        });
      },
      onLastListenerRemove() {
        subscription.dispose();
      }
    };
    const emitter = new Emitter(options);
    if (disposable) {
      disposable.add(emitter);
    }
    return emitter.event;
  }
  Event.debounce = debounce;
  function latch(event, equals = (a, b) => a === b, disposable) {
    let firstCall = true;
    let cache;
    return filter(event, (value) => {
      const shouldEmit = firstCall || !equals(value, cache);
      firstCall = false;
      cache = value;
      return shouldEmit;
    }, disposable);
  }
  Event.latch = latch;
  function split(event, isT, disposable) {
    return [
      Event.filter(event, isT, disposable),
      Event.filter(event, (e) => !isT(e), disposable)
    ];
  }
  Event.split = split;
  function buffer(event, flushAfterTimeout = false, _buffer = []) {
    let buffer2 = _buffer.slice();
    let listener = event((e) => {
      if (buffer2) {
        buffer2.push(e);
      } else {
        emitter.fire(e);
      }
    });
    const flush = () => {
      if (buffer2) {
        buffer2.forEach((e) => emitter.fire(e));
      }
      buffer2 = null;
    };
    const emitter = new Emitter({
      onFirstListenerAdd() {
        if (!listener) {
          listener = event((e) => emitter.fire(e));
        }
      },
      onFirstListenerDidAdd() {
        if (buffer2) {
          if (flushAfterTimeout) {
            setTimeout(flush);
          } else {
            flush();
          }
        }
      },
      onLastListenerRemove() {
        if (listener) {
          listener.dispose();
        }
        listener = null;
      }
    });
    return emitter.event;
  }
  Event.buffer = buffer;
  class ChainableEvent {
    constructor(event) {
      this.event = event;
    }
    map(fn) {
      return new ChainableEvent(map(this.event, fn));
    }
    forEach(fn) {
      return new ChainableEvent(forEach(this.event, fn));
    }
    filter(fn) {
      return new ChainableEvent(filter(this.event, fn));
    }
    reduce(merge, initial) {
      return new ChainableEvent(reduce(this.event, merge, initial));
    }
    latch() {
      return new ChainableEvent(latch(this.event));
    }
    debounce(merge, delay = 100, leading = false, leakWarningThreshold) {
      return new ChainableEvent(debounce(this.event, merge, delay, leading, leakWarningThreshold));
    }
    on(listener, thisArgs, disposables) {
      return this.event(listener, thisArgs, disposables);
    }
    once(listener, thisArgs, disposables) {
      return once(this.event)(listener, thisArgs, disposables);
    }
  }
  function chain(event) {
    return new ChainableEvent(event);
  }
  Event.chain = chain;
  function fromNodeEventEmitter(emitter, eventName, map2 = (id) => id) {
    const fn = (...args) => result.fire(map2(...args));
    const onFirstListenerAdd = () => emitter.on(eventName, fn);
    const onLastListenerRemove = () => emitter.removeListener(eventName, fn);
    const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove });
    return result.event;
  }
  Event.fromNodeEventEmitter = fromNodeEventEmitter;
  function fromDOMEventEmitter(emitter, eventName, map2 = (id) => id) {
    const fn = (...args) => result.fire(map2(...args));
    const onFirstListenerAdd = () => emitter.addEventListener(eventName, fn);
    const onLastListenerRemove = () => emitter.removeEventListener(eventName, fn);
    const result = new Emitter({ onFirstListenerAdd, onLastListenerRemove });
    return result.event;
  }
  Event.fromDOMEventEmitter = fromDOMEventEmitter;
  function toPromise(event) {
    return new Promise((resolve) => once(event)(resolve));
  }
  Event.toPromise = toPromise;
  function runAndSubscribe(event, handler) {
    handler(void 0);
    return event((e) => handler(e));
  }
  Event.runAndSubscribe = runAndSubscribe;
  function runAndSubscribeWithStore(event, handler) {
    let store = null;
    function run(e) {
      store === null || store === void 0 ? void 0 : store.dispose();
      store = new lifecycle.DisposableStore();
      handler(e, store);
    }
    run(void 0);
    const disposable = event((e) => run(e));
    return lifecycle.toDisposable(() => {
      disposable.dispose();
      store === null || store === void 0 ? void 0 : store.dispose();
    });
  }
  Event.runAndSubscribeWithStore = runAndSubscribeWithStore;
})(exports.Event || (exports.Event = {}));
class EventProfiling {
  constructor(name) {
    this._listenerCount = 0;
    this._invocationCount = 0;
    this._elapsedOverall = 0;
    this._name = `${name}_${EventProfiling._idPool++}`;
  }
  start(listenerCount) {
    this._stopWatch = new stopwatch.StopWatch(true);
    this._listenerCount = listenerCount;
  }
  stop() {
    if (this._stopWatch) {
      const elapsed = this._stopWatch.elapsed();
      this._elapsedOverall += elapsed;
      this._invocationCount += 1;
      console.info(`did FIRE ${this._name}: elapsed_ms: ${elapsed.toFixed(5)}, listener: ${this._listenerCount} (elapsed_overall: ${this._elapsedOverall.toFixed(2)}, invocations: ${this._invocationCount})`);
      this._stopWatch = void 0;
    }
  }
}
EventProfiling._idPool = 0;
class Stacktrace {
  constructor(value) {
    this.value = value;
  }
  static create() {
    var _a;
    return new Stacktrace((_a = new Error().stack) !== null && _a !== void 0 ? _a : "");
  }
  print() {
    console.warn(this.value.split("\n").slice(2).join("\n"));
  }
}
class Listener {
  constructor(callback, callbackThis, stack) {
    this.callback = callback;
    this.callbackThis = callbackThis;
    this.stack = stack;
    this.subscription = new lifecycle.SafeDisposable();
  }
  invoke(e) {
    this.callback.call(this.callbackThis, e);
  }
}
class Emitter {
  constructor(options) {
    var _a;
    this._disposed = false;
    this._options = options;
    this._leakageMon = void 0;
    this._perfMon = ((_a = this._options) === null || _a === void 0 ? void 0 : _a._profName) ? new EventProfiling(this._options._profName) : void 0;
  }
  dispose() {
    var _a, _b, _c, _d;
    if (!this._disposed) {
      this._disposed = true;
      if (this._listeners) {
        this._listeners.clear();
      }
      (_a = this._deliveryQueue) === null || _a === void 0 ? void 0 : _a.clear();
      (_c = (_b = this._options) === null || _b === void 0 ? void 0 : _b.onLastListenerRemove) === null || _c === void 0 ? void 0 : _c.call(_b);
      (_d = this._leakageMon) === null || _d === void 0 ? void 0 : _d.dispose();
    }
  }
  /**
   * For the public to allow to subscribe
   * to events from this Emitter
   */
  get event() {
    if (!this._event) {
      this._event = (callback, thisArgs, disposables) => {
        var _a, _b, _c;
        if (!this._listeners) {
          this._listeners = new linkedList.LinkedList();
        }
        const firstListener = this._listeners.isEmpty();
        if (firstListener && ((_a = this._options) === null || _a === void 0 ? void 0 : _a.onFirstListenerAdd)) {
          this._options.onFirstListenerAdd(this);
        }
        let removeMonitor;
        let stack;
        if (this._leakageMon && this._listeners.size >= 30) {
          stack = Stacktrace.create();
          removeMonitor = this._leakageMon.check(stack, this._listeners.size + 1);
        }
        const listener = new Listener(callback, thisArgs, stack);
        const removeListener = this._listeners.push(listener);
        if (firstListener && ((_b = this._options) === null || _b === void 0 ? void 0 : _b.onFirstListenerDidAdd)) {
          this._options.onFirstListenerDidAdd(this);
        }
        if ((_c = this._options) === null || _c === void 0 ? void 0 : _c.onListenerDidAdd) {
          this._options.onListenerDidAdd(this, callback, thisArgs);
        }
        const result = listener.subscription.set(() => {
          if (removeMonitor) {
            removeMonitor();
          }
          if (!this._disposed) {
            removeListener();
            if (this._options && this._options.onLastListenerRemove) {
              const hasListeners = this._listeners && !this._listeners.isEmpty();
              if (!hasListeners) {
                this._options.onLastListenerRemove(this);
              }
            }
          }
        });
        if (disposables instanceof lifecycle.DisposableStore) {
          disposables.add(result);
        } else if (Array.isArray(disposables)) {
          disposables.push(result);
        }
        return result;
      };
    }
    return this._event;
  }
  /**
   * To be kept private to fire an event to
   * subscribers
   */
  fire(event) {
    var _a, _b;
    if (this._listeners) {
      if (!this._deliveryQueue) {
        this._deliveryQueue = new linkedList.LinkedList();
      }
      for (let listener of this._listeners) {
        this._deliveryQueue.push([listener, event]);
      }
      (_a = this._perfMon) === null || _a === void 0 ? void 0 : _a.start(this._deliveryQueue.size);
      while (this._deliveryQueue.size > 0) {
        const [listener, event2] = this._deliveryQueue.shift();
        try {
          listener.invoke(event2);
        } catch (e) {
          errors.onUnexpectedError(e);
        }
      }
      (_b = this._perfMon) === null || _b === void 0 ? void 0 : _b.stop();
    }
  }
}
class PauseableEmitter extends Emitter {
  constructor(options) {
    super(options);
    this._isPaused = 0;
    this._eventQueue = new linkedList.LinkedList();
    this._mergeFn = options === null || options === void 0 ? void 0 : options.merge;
  }
  pause() {
    this._isPaused++;
  }
  resume() {
    if (this._isPaused !== 0 && --this._isPaused === 0) {
      if (this._mergeFn) {
        const events = Array.from(this._eventQueue);
        this._eventQueue.clear();
        super.fire(this._mergeFn(events));
      } else {
        while (!this._isPaused && this._eventQueue.size !== 0) {
          super.fire(this._eventQueue.shift());
        }
      }
    }
  }
  fire(event) {
    if (this._listeners) {
      if (this._isPaused !== 0) {
        this._eventQueue.push(event);
      } else {
        super.fire(event);
      }
    }
  }
}
class DebounceEmitter extends PauseableEmitter {
  constructor(options) {
    var _a;
    super(options);
    this._delay = (_a = options.delay) !== null && _a !== void 0 ? _a : 100;
  }
  fire(event) {
    if (!this._handle) {
      this.pause();
      this._handle = setTimeout(() => {
        this._handle = void 0;
        this.resume();
      }, this._delay);
    }
    super.fire(event);
  }
}
class EventBufferer {
  constructor() {
    this.buffers = [];
  }
  wrapEvent(event) {
    return (listener, thisArgs, disposables) => {
      return event((i) => {
        const buffer = this.buffers[this.buffers.length - 1];
        if (buffer) {
          buffer.push(() => listener.call(thisArgs, i));
        } else {
          listener.call(thisArgs, i);
        }
      }, void 0, disposables);
    };
  }
  bufferEvents(fn) {
    const buffer = [];
    this.buffers.push(buffer);
    const r = fn();
    this.buffers.pop();
    buffer.forEach((flush) => flush());
    return r;
  }
}
class Relay {
  constructor() {
    this.listening = false;
    this.inputEvent = exports.Event.None;
    this.inputEventListener = lifecycle.Disposable.None;
    this.emitter = new Emitter({
      onFirstListenerDidAdd: () => {
        this.listening = true;
        this.inputEventListener = this.inputEvent(this.emitter.fire, this.emitter);
      },
      onLastListenerRemove: () => {
        this.listening = false;
        this.inputEventListener.dispose();
      }
    });
    this.event = this.emitter.event;
  }
  set input(event) {
    this.inputEvent = event;
    if (this.listening) {
      this.inputEventListener.dispose();
      this.inputEventListener = event(this.emitter.fire, this.emitter);
    }
  }
  dispose() {
    this.inputEventListener.dispose();
    this.emitter.dispose();
  }
}
exports.DebounceEmitter = DebounceEmitter;
exports.Emitter = Emitter;
exports.EventBufferer = EventBufferer;
exports.PauseableEmitter = PauseableEmitter;
exports.Relay = Relay;
