import { LISTENERS, OWNER_DOCUMENT, HOOKS, PATH } from './constants.mjs';
import { fireEvent, CAPTURE_MARKER, EventPhase } from './Event.mjs';

const ONCE_LISTENERS = Symbol('onceListeners');
class EventTarget {
  [LISTENERS] = (() => undefined)();
  [ONCE_LISTENERS] = (() => undefined)();

  /**
   * Property set by entities that extend this class that are part of the DOM tree.
   * @internal
   */
  [OWNER_DOCUMENT] = (() => undefined)();
  addEventListener(type, listener, options) {
    if (listener == null) return;
    const capture = options === true || options && options.capture === true;
    const once = typeof options === 'object' && options.once === true;
    const signal = typeof options === 'object' ? options.signal : undefined;
    const key = `${type}${capture ? CAPTURE_MARKER : ''}`;
    let normalizedListener = listener;
    if (once) {
      normalizedListener = function normalizedListener(...args) {
        this.removeEventListener(type, listener, options);
        return typeof listener === 'object' ? listener.handleEvent(...args) : listener.call(this, ...args);
      };
      let onceListeners = this[ONCE_LISTENERS];
      if (!onceListeners) {
        onceListeners = new WeakMap();
        this[ONCE_LISTENERS] = onceListeners;
      }
      onceListeners.set(listener, normalizedListener);
    }
    let listeners = this[LISTENERS];
    if (!listeners) {
      listeners = new Map();
      this[LISTENERS] = listeners;
    }
    let list = listeners.get(key);
    if (!list) {
      list = new Set();
      listeners.set(key, list);
    }
    if (list.has(normalizedListener)) return;
    signal?.addEventListener('abort', () => {
      removeEventListener.call(this, type, listener, options);
    }, {
      once: true
    });
    list.add(normalizedListener);
    this[OWNER_DOCUMENT]?.defaultView[HOOKS].addEventListener?.(this, type, listener, options);
  }
  removeEventListener(type, listener, options) {
    return removeEventListener.call(this, type, listener, options);
  }

  // function isChildNode(node: EventTarget): node is ChildNode {
  //   return PARENT in node;
  // }

  dispatchEvent(event) {
    const path = [];
    // instanceof here is just to keep TypeScript happy
    let target = this;
    while (target != null) {
      path.push(target);
      target = target.parentNode;
    }
    // while (target instanceof Node && (target = target.parentNode)) {
    //   path.push(target);
    // }
    event.target = this;
    event.srcElement = this;
    event[PATH] = path;
    for (let i = path.length; i--;) {
      fireEvent(event, path[i], EventPhase.CAPTURING_PHASE);
      if (event.cancelBubble) return event.defaultPrevented;
    }
    const bubblePath = event.bubbles ? path : path.slice(0, 1);
    for (let i = 0; i < bubblePath.length; i++) {
      fireEvent(event, bubblePath[i], EventPhase.BUBBLING_PHASE);
      if (event.cancelBubble) return event.defaultPrevented;
    }
    return event.defaultPrevented;
  }
}
function removeEventListener(type, listener, options) {
  if (listener == null) return;
  const onceListeners = this[ONCE_LISTENERS];
  const normalizedListener = onceListeners?.get(listener) ?? listener;
  onceListeners?.delete(listener);
  const capture = options === true || options && options.capture === true;
  const key = `${type}${capture ? CAPTURE_MARKER : ''}`;
  const list = this[LISTENERS]?.get(key);
  if (list) {
    const deleted = list.delete(normalizedListener);
    if (deleted) {
      this[OWNER_DOCUMENT]?.defaultView[HOOKS].removeEventListener?.(this, type, listener, options);
    }
  }
}

export { EventTarget };
