import {
  AlreadyConstructedMarker,
  CustomElementDefinition,
} from './CustomElementDefinition';

const randomString = () => Math.random().toString(32).substring(2);

export const constructedFlag = 'constructed_' + randomString();
export const elementStateFlag = 'elementState_' + randomString();

/**
 * @enum {number}
 */
export const CustomElementState = {
  custom: 1,
  failed: 2,
};

export class CustomElementInternals {
  constructor() {
    /** @type {!Map<string, !CustomElementDefinition>} */
    this._localNameToDefinition = new Map();

    /** @type {!Map<!Function, !CustomElementDefinition>} */
    this._constructorToDefinition = new Map();
  }

  /**
   * @param {string} localName
   * @param {!CustomElementDefinition} definition
   */
  setDefinition(localName, definition) {
    this._localNameToDefinition.set(localName, definition);
    this._constructorToDefinition.set(definition.constructor, definition);
  }

  /**
   * @param {string} localName
   * @return {!CustomElementDefinition|undefined}
   */
  localNameToDefinition(localName) {
    return this._localNameToDefinition.get(localName);
  }

  /**
   * @param {!Function} constructor
   * @return {!CustomElementDefinition|undefined}
   */
  constructorToDefinition(constructor) {
    return this._constructorToDefinition.get(constructor);
  }

  /**
   * @param {!Node} root
   */
  upgradeTree(root) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
    do {
      const currentNode = /** @type {!Element} */ (walker.currentNode);
      this.upgradeElement(currentNode);
    } while (walker.nextNode());
  }

  /**
   * @param {!Element} element
   */
  upgradeElement(element) {
    if (
      element[elementStateFlag] === CustomElementState.custom ||
      element[elementStateFlag] === CustomElementState.failed
    ) return;

    const definition = this.localNameToDefinition(element.localName);
    if (!definition) return;

    // Enqueue attributes.

    // Enqueue connected.

    definition.constructionStack.push(element);

    const constructor = definition.constructor;
    try {
      try {
        let result = new (constructor)();
        if (result !== element) {
          throw new Error('The custom element constructor did not produce the element being upgraded.');
        }
      } finally {
        definition.constructionStack.pop();
      }
    } catch (e) {
      element[elementStateFlag] = CustomElementState.failed;
      throw e;
    }

    element[elementStateFlag] = CustomElementState.custom;
  }
}

CustomElementInternals.constructedFlag = constructedFlag;
CustomElementInternals.elementStateFlag = elementStateFlag;

import {
  AlreadyConstructedMarker,
  CustomElementDefinition,
} from './CustomElementDefinition';

const randomString = () => Math.random().toString(32).substring(2);

export const elementStateFlag = 'elementState_' + randomString();

/**
 * @enum {number}
 */
export const CustomElementState = {
  custom: 1,
  failed: 2,
};

export class CustomElementInternals {
  constructor() {
    /** @type {!Map<string, !CustomElementDefinition>} */
    this._localNameToDefinition = new Map();

    /** @type {!Map<!Function, !CustomElementDefinition>} */
    this._constructorToDefinition = new Map();
  }

  /**
   * @param {string} localName
   * @param {!CustomElementDefinition} definition
   */
  setDefinition(localName, definition) {
    this._localNameToDefinition.set(localName, definition);
    this._constructorToDefinition.set(definition.constructor, definition);
  }

  /**
   * @param {string} localName
   * @return {!CustomElementDefinition|undefined}
   */
  localNameToDefinition(localName) {
    return this._localNameToDefinition.get(localName);
  }

  /**
   * @param {!Function} constructor
   * @return {!CustomElementDefinition|undefined}
   */
  constructorToDefinition(constructor) {
    return this._constructorToDefinition.get(constructor);
  }

  /**
   * @param {!Node} root
   */
  upgradeTree(root) {
    const walker = document.createTreeWalker(root, NodeFilter.SHOW_ELEMENT);
    do {
      const currentNode = /** @type {!Element} */ (walker.currentNode);
      this.upgradeElement(currentNode);
    } while (walker.nextNode());
  }

  /**
   * @param {!Element} element
   */
  upgradeElement(element) {
    if (
      element[elementStateFlag] === CustomElementState.custom ||
      element[elementStateFlag] === CustomElementState.failed
    ) return;

    const definition = this.localNameToDefinition(element.localName);
    if (!definition) return;

    // Enqueue attributes.

    // Enqueue connected.

    definition.constructionStack.push(element);

    const constructor = definition.constructor;
    try {
      try {
        let result = new (constructor)();
        if (result !== element) {
          throw new Error('The custom element constructor did not produce the element being upgraded.');
        }
      } finally {
        definition.constructionStack.pop();
      }
    } catch (e) {
      element[elementStateFlag] = CustomElementState.failed;
      throw e;
    }

    element[elementStateFlag] = CustomElementState.custom;
  }
}

CustomElementInternals.elementStateFlag = elementStateFlag;

Remove unused `constructedFlag`.