import { Thread } from '../../Thread.mjs';
import { CHECK_MESSAGE, RESPONSE_MESSAGE } from './shared.mjs';

/**
 * Creates a thread from within a window created by a parent document (for example,
 * an `iframe` or popup window).
 *
 * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/opener
 *
 * @example
 * import {ThreadNestedWindow} from '@quilted/threads';
 *
 * // Inside a document opened as a popup window
 * const thread = new ThreadNestedWindow(window.opener);
 * await thread.imports.sendMessage('Hello world!');
 */
class ThreadNestedWindow extends Thread {
  /**
   * Creates a thread from within a window created by a parent document (for example,
   * an `iframe` or popup window).
   *
   * @see https://developer.mozilla.org/en-US/docs/Web/API/Window/opener
   *
   * @example
   * import {ThreadNestedWindow} from '@quilted/threads';
   *
   * // Inside a document opened as a popup window
   * const thread = ThreadNestedWindow.from(window.opener);
   * await thread.imports.sendMessage('Hello world!');
   */
  static from(window, options) {
    return new ThreadNestedWindow(window, options);
  }

  /**
   * Starts a thread wrapped around a nested `window` object, and returns the imports
   * of the thread.
   *
   * @example
   * ```ts
   * import {ThreadNestedWindow} from '@quilted/threads';
   *
   * const {getMessage} = ThreadNestedWindow.import(window.opener);
   * const message = await getMessage(); // 'Hello, world!'
   *
   * // In the parent window:
   *
   * import {ThreadWindow} from '@quilted/threads';
   *
   * ThreadWindow.export(window, {
   *   async getMessage() {
   *     return 'Hello, world!';
   *   },
   * });
   * ```
   */
  static import(window, options) {
    return new ThreadNestedWindow(window, options).imports;
  }

  /**
   * Starts a thread wrapped around a nested `window` object, providing the second
   * argument as the exports of the thread.
   *
   * @example
   * ```ts
   * import {ThreadNestedWindow} from '@quilted/threads';
   *
   * ThreadNestedWindow.export(window.opener, {
   *   async getMessage() {
   *     return 'Hello, world!';
   *   },
   * });
   *
   * // In the parent window:
   *
   * import {ThreadWindow} from '@quilted/threads';
   *
   * const {getMessage} = ThreadWindow.import(window);
   * const message = await getMessage(); // 'Hello, world!'
   * ```
   */
  static export(window, exports, options) {
    new ThreadNestedWindow(window, {
      ...options,
      exports
    });
  }
  constructor(parent, {
    targetOrigin = '*',
    ...options
  } = {}) {
    super(nestedWindowToThreadTarget(parent, {
      targetOrigin
    }), options);
    this.parent = parent;
  }
}
function nestedWindowToThreadTarget(parent, {
  targetOrigin = '*'
} = {}) {
  const ready = () => {
    const respond = () => parent.postMessage(RESPONSE_MESSAGE, targetOrigin);
    self.addEventListener('message', ({
      data,
      source
    }) => {
      if (source !== parent) return;
      if (data === CHECK_MESSAGE) respond();
    });
    respond();
  };
  if (document.readyState === 'complete') {
    ready();
  } else {
    document.addEventListener('readystatechange', () => {
      if (document.readyState === 'complete') {
        ready();
      }
    });
  }
  return {
    send(message, transfer) {
      return parent.postMessage(message, targetOrigin, transfer);
    },
    listen(listen, {
      signal
    }) {
      self.addEventListener('message', event => {
        if (event.data === CHECK_MESSAGE) return;
        listen(event.data);
      }, {
        signal
      });
    }
  };
}

export { ThreadNestedWindow, nestedWindowToThreadTarget };
