export const extractTextItems = async (
  getPage: () => Promise<any>
): Promise<string[]> => {
  const page = await getPage();
  let textContent = await page.getTextContent({
    normalizeWhitespace: true,
  });

  textContent = textContent.items;

  const strBuf = [];

  for (let j = 0, len = textContent.length; j < len; j += 1) {
    // add whitespace in front if start character is Uppercase
    if (
      textContent[j].str.match(/^[A-Z]/) &&
      j > 0 &&
      textContent[j - 1].str !== ' '
    ) {
      strBuf.push(` ${textContent[j].str}`);
    } else {
      strBuf.push(textContent[j].str);
    }
  }

  return strBuf;
};

export const convertMatches = (
  queryString: string,
  matchIndex: number,
  textContentItem: any[]
): Record<string, any> => {
  let i = 0;
  let iIndex = 0;
  const end = textContentItem.length - 1;
  const queryLen = queryString.length;

  // Loop over the divIdxs.
  while (i !== end && matchIndex >= iIndex + textContentItem[i].length) {
    iIndex += textContentItem[i].length;
    i += 1;
  }

  if (i === textContentItem.length) {
    console.error('Could not find a matching mapping');
  }

  const match: Record<string, any> = {
    begin: {
      divIdx: i,
      offset: matchIndex - iIndex,
    },
  };

  // Calculate the end position.
  // eslint-disable-next-line no-param-reassign
  matchIndex += queryLen;

  // Somewhat the same array as above, but use > instead of >= to get
  // the end position right.
  while (i !== end && matchIndex > iIndex + textContentItem[i].length) {
    iIndex += textContentItem[i].length;
    i += 1;
  }

  match.end = {
    divIdx: i,
    offset: matchIndex - iIndex,
  };

  return match;
};

const appendTextToDiv = async (
  domElements: HTMLElement[],
  getPage: () => Promise<any>,
  divIdx: number,
  fromOffset: number,
  toOffset: number | undefined,
  highlight: boolean,
  id: string
): Promise<any> => {
  const textContentItem = await extractTextItems(getPage);

  const domElement = domElements[divIdx];
  const content = textContentItem[divIdx].substring(fromOffset, toOffset);
  const node = document.createTextNode(content);
  const span = document.createElement('span');

  if (highlight) {
    span.setAttribute('class', id);
    span.style.backgroundColor = 'rgba(255, 211, 0, 0.7)';
    span.appendChild(node);
    domElement.appendChild(span);
  } else {
    // eslint-disable-next-line no-param-reassign
    domElement.textContent = '';
    domElement.appendChild(node);
  }
};

const beginText = async (
  domElements: HTMLElement[],
  getPage: () => Promise<any>,
  begin: Record<string, any>
): Promise<any> => {
  const { divIdx } = begin;
  const domElement = domElements[divIdx];

  if (domElement) {
    // eslint-disable-next-line no-param-reassign
    domElement.textContent = '';
    appendTextToDiv(domElements, getPage, divIdx, 0, begin.offset, false, '');
  }
};

export const renderMatches = async (
  domElements: HTMLElement[],
  getPage: () => Promise<any>,
  matchIndex: number,
  queryStr: string,
  id: string
): Promise<any> => {
  const textContentItem = await extractTextItems(getPage);
  const { begin, end } = convertMatches(queryStr, matchIndex, textContentItem);

  beginText(domElements, getPage, begin);

  if (begin.divIdx === end.divIdx) {
    appendTextToDiv(
      domElements,
      getPage,
      begin.divIdx,
      begin.offset,
      end.offset,
      true,
      id
    );
  } else {
    for (let i = begin.divIdx; i <= end.divIdx; i += 1) {
      switch (i) {
        case begin.divIdx:
          appendTextToDiv(
            domElements,
            getPage,
            begin.divIdx,
            begin.offset,
            undefined,
            true,
            id
          );
          break;
        case end.divIdx:
          beginText(domElements, getPage, { divIdx: end.divIdx, offset: 0 });
          appendTextToDiv(
            domElements,
            getPage,
            end.divIdx,
            0,
            end.offset,
            true,
            id
          );
          break;
        default: {
          beginText(domElements, getPage, { divIdx: i, offset: 0 });
          appendTextToDiv(domElements, getPage, i, 0, undefined, true, id);
          break;
        }
      }
    }
  }
};
