“use strict”; const DOMException = require(“../../web-idl/DOMException.js”); const documentBaseURLSerialized = require(“../helpers/document-base-url.js”).documentBaseURLSerialized; const parseURLToResultingURLRecord = require(“../helpers/document-base-url.js”).parseURLToResultingURLRecord; const traverseHistory = require(“./navigation.js”).traverseHistory;

exports.implementation = class HistoryImpl {

constructor(args, privateData) {
  this._window = privateData.window;
  this._document = privateData.document;
  this._actAsIfLocationReloadCalled = privateData.actAsIfLocationReloadCalled;
  this._state = null;
  this._latestEntry = null;

  this._historyTraversalQueue = new Set();
}

_guardAgainstInactiveDocuments() {
  if (!this._window) {
    throw new DOMException(DOMException.SECURITY_ERR,
      "History object is associated with a document that is not fully active.");
  }
}

get length() {
  this._guardAgainstInactiveDocuments();

  return this._window._sessionHistory.length;
}

get state() {
  this._guardAgainstInactiveDocuments();

  return this._state;
}

go(delta) {
  this._guardAgainstInactiveDocuments();

  if (delta === 0) {
    this._actAsIfLocationReloadCalled();
  } else {
    this._queueHistoryTraversalTask(() => {
      const newIndex = this._window._currentSessionHistoryEntryIndex + delta;
      if (newIndex < 0 || newIndex >= this._window._sessionHistory.length) {
        return;
      }

      const specifiedEntry = this._window._sessionHistory[newIndex];

      // Not implemented: unload a document guard

      // Not clear that this should be queued. html/browsers/history/the-history-interface/004.html can be fixed
      // by removing the queue, but doing so breaks some tests in history.js that also pass in browsers.
      this._queueHistoryTraversalTask(() => traverseHistory(this._window, specifiedEntry));
    });
  }
}

back() {
  this.go(-1);
}

forward() {
  this.go(+1);
}

pushState(data, title, url) {
  this._sharedPushAndReplaceState(data, title, url, "pushState");
}
replaceState(data, title, url) {
  this._sharedPushAndReplaceState(data, title, url, "replaceState");
}

_sharedPushAndReplaceState(data, title, url, methodName) {
  this._guardAgainstInactiveDocuments();

  // TODO structured clone data

  let newURL;
  if (url !== null) {
    // Not implemented: use of entry settings object's API base URL. Instead we just use the document base URL. The
    // difference matters in the case of cross-frame calls.
    newURL = parseURLToResultingURLRecord(url, this._document);

    if (newURL === "failure") {
      throw new DOMException(DOMException.SECURITY_ERR, `Could not parse url argument "${url}" to ${methodName} ` +
        `against base URL "${documentBaseURLSerialized(this._document)}".`);
    }

    if (newURL.scheme !== this._document._URL.scheme ||
        newURL.username !== this._document._URL.username ||
        newURL.password !== this._document._URL.password ||
        newURL.host !== this._document._URL.host ||
        newURL.port !== this._document._URL.port ||
        newURL.cannotBeABaseURL !== this._document._URL.cannotBeABaseURL) {
      throw new DOMException(DOMException.SECURITY_ERR, `${methodName} cannot update history to a URL which ` +
        `differs in components other than in path, query, or fragment.`);
    }

    // Not implemented: origin check (seems to only apply to documents with weird origins, e.g. sandboxed ones)
  } else {
    newURL = this._window._sessionHistory[this._window._currentSessionHistoryEntryIndex].url;
  }

  if (methodName === "pushState") {
    this._window._sessionHistory.splice(this._window._currentSessionHistoryEntryIndex + 1, Infinity);

    this._clearHistoryTraversalTasks();

    this._window._sessionHistory.push({
      document: this._document,
      stateObject: data,
      title,
      url: newURL
    });
    this._window._currentSessionHistoryEntryIndex = this._window._sessionHistory.length - 1;
  } else {
    const currentEntry = this._window._sessionHistory[this._window._currentSessionHistoryEntryIndex];
    currentEntry.stateObject = data;
    currentEntry.title = title;
    currentEntry.url = newURL;
  }

  this._document._URL = newURL;
  this._state = data; // TODO clone again!! O_o
  this._latestEntry = this._window._sessionHistory[this._window._currentSessionHistoryEntryIndex];
}

_queueHistoryTraversalTask(fn) {
  const timeoutId = this._window.setTimeout(() => {
    this._historyTraversalQueue.delete(timeoutId);
    fn();
  }, 0);

  this._historyTraversalQueue.add(timeoutId);
}

_clearHistoryTraversalTasks() {
  for (const timeoutId of this._historyTraversalQueue) {
    this._window.clearTimeout(timeoutId);
  }
  this._historyTraversalQueue.clear();
}

};