import { parseQueryString, removeNullCharacters } from "./ui_utils.js";

const DEFAULT_LINK_REL = "noopener noreferrer nofollow";

const LinkTarget = {
  NONE: 0, // Default value.
  SELF: 1,
  BLANK: 2,
  PARENT: 3,
  TOP: 4,
};

/**
 * @typedef {Object} ExternalLinkParameters
 * @property {string} url - An absolute URL.
 * @property {LinkTarget} [target] - The link target. The default value is
 *   `LinkTarget.NONE`.
 * @property {string} [rel] - The link relationship. The default value is
 *   `DEFAULT_LINK_REL`.
 * @property {boolean} [enabled] - Whether the link should be enabled. The
 *   default value is true.
 */

/**
 * @typedef {Object} PDFLinkServiceOptions
 * @property {EventBus} eventBus - The application event bus.
 * @property {number} [externalLinkTarget] - Specifies the `target` attribute
 *   for external links. Must use one of the values from {LinkTarget}.
 *   Defaults to using no target.
 * @property {boolean} [ignoreDestinationZoom] - Ignores the zoom argument,
 *   thus preserving the current zoom level in the viewer, when navigating
 *   to internal destinations. The default value is `false`.
 */

/**
 * Performs navigation functions inside PDF, such as opening specified page,
 * or destination.
 * @implements {IPDFLinkService}
 */
class PDFLinkService {
  #pagesRefCache = new Map();

  /**
   * @param {PDFLinkServiceOptions} options
   */
  constructor() {
    this.baseUrl = null;
    this.pdfDocument = null;
    this.pdfViewer = null;
  }

  setDocument(pdfDocument, baseUrl = null) {
    this.baseUrl = baseUrl;
    this.pdfDocument = pdfDocument;
    this.#pagesRefCache.clear();
  }

  setViewer(pdfViewer) {
    this.pdfViewer = pdfViewer;
  }

  /**
   * @type {number}
   */
  get pagesCount() {
    return this.pdfDocument ? this.pdfDocument.numPages : 0;
  }

  /**
   * @type {number}
   */
  get page() {
    return this.pdfViewer.currentPageNumber;
  }

  /**
   * @param {number} value
   */
  set page(value) {
    this.pdfViewer.currentPageNumber = value;
  }

  /**
   * @type {number}
   */
  get rotation() {
    return this.pdfViewer.pagesRotation;
  }

  /**
   * @param {number} value
   */
  set rotation(value) {
    this.pdfViewer.pagesRotation = value;
  }

  /**
   * This method will, when available, also update the browser history.
   *
   * @param {number|string} val - The page number, or page label.
   */
  goToPage(val) {
    if (!this.pdfDocument) {
      return;
    }
    const pageNumber =
      (typeof val === "string" && this.pdfViewer.pageLabelToPageNumber(val)) ||
      val | 0;
    if (
      !(
        Number.isInteger(pageNumber) &&
        pageNumber > 0 &&
        pageNumber <= this.pagesCount
      )
    ) {
      console.error(`PDFLinkService.goToPage: "${val}" is not a valid page.`);
      return;
    }

    this.pdfViewer.scrollPageIntoView({ pageNumber });
  }

  /**
   * @param {number} pageNum - page number.
   * @param {Object} pageRef - reference to the page.
   */
  cachePageRef(pageNum, pageRef) {
    if (!pageRef) {
      return;
    }
    const refStr =
      pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
    this.#pagesRefCache.set(refStr, pageNum);
  }

  /**
   * @ignore
   */
  _cachedPageNumber(pageRef) {
    if (!pageRef) {
      return null;
    }
    const refStr =
      pageRef.gen === 0 ? `${pageRef.num}R` : `${pageRef.num}R${pageRef.gen}`;
    return this.#pagesRefCache.get(refStr) || null;
  }

  /**
   * @param {number} pageNumber
   */
  isPageVisible(pageNumber) {
    return this.pdfViewer.isPageVisible(pageNumber);
  }

  /**
   * @param {number} pageNumber
   */
  isPageCached(pageNumber) {
    return this.pdfViewer.isPageCached(pageNumber);
  }
}

export { LinkTarget, PDFLinkService };
