/* eslint-disable no-param-reassign */
import { v4 as uuidv4 } from 'uuid';
import dayjs from 'dayjs';
import queryString from 'query-string';

import { ANNOTATION_TYPE, FORM_TYPE } from '../constants';
import { getPdfPage, renderTextLayer } from './pdf';
import { getPosition, parsePositionForBackend } from './position';
import { normalizeRound, floatToHex } from './utility';
import { xmlParser, getElementsByTagName } from './dom';

type GetFontAttributeFunc = (
  type: string,
  element: HTMLElement,
) => Record<string, unknown>;

const getContent = (type: string, element: HTMLElement): string => {
  if (type !== 'Text' && type !== 'FreeText') return '';

  let content = '';
  const nodes = Array.prototype.slice.call(element.childNodes);

  nodes.forEach((ele: HTMLElement) => {
    if (ele.tagName === 'contents') {
      content = ele.innerHTML || ele.textContent || '';
    }
  });
  return content;
};

const getFontAttribute: GetFontAttributeFunc = (type, element) => {
  if (type !== 'FreeText') return {};
  const appearanceString =
    (element.childNodes[1] as HTMLElement).innerHTML ||
    element.childNodes[1].textContent ||
    '';
  const arr = appearanceString.split(' ');

  return {
    fontsize: parseInt(arr[5], 10),
    fontname: arr[4].substr(1),
    textcolor: floatToHex(
      parseFloat(arr[0]),
      parseFloat(arr[1]),
      parseFloat(arr[2]),
    ),
  };
};

export const parseAnnotationFromXml = (xmlString: string): AnnotationType[] => {
  if (!xmlString) return [];

  const xmlDoc = xmlParser(xmlString);
  const elements = xmlDoc.firstElementChild || xmlDoc.firstChild;
  const element = getElementsByTagName(elements as ChildNode, 'annots') || [];
  const annotations: HTMLElement[] = Array.prototype.slice.call(element);
  const filterAnnots = annotations.reduce(
    (acc: AnnotationType[], cur: HTMLElement) => {
      const type = ANNOTATION_TYPE[cur.tagName];

      const attributes = cur.attributes as ElementAttributeType;

      if (type) {
        const page = parseInt(attributes.page.value, 10);
        acc.push({
          id: uuidv4(),
          obj_type: type,
          obj_attr: {
            title: attributes.title ? attributes.title.value : undefined,
            date: attributes.date ? attributes.date.value : undefined,
            page,
            position: getPosition(type, cur),
            bdcolor: attributes.color ? attributes.color.value : undefined,
            bdwidth: attributes.width
              ? parseInt(attributes.width.value, 10)
              : 0,
            transparency: attributes.opacity
              ? parseFloat(attributes.opacity.value)
              : 1,
            content: getContent(type, cur) || undefined,
            fcolor: attributes['interior-color']
              ? attributes['interior-color'].value
              : undefined,
            ftransparency: attributes['interior-opacity']
              ? parseFloat(attributes['interior-opacity'].value)
              : undefined,
            is_arrow: !!attributes.tail,
            ...getFontAttribute(type, cur),
          },
        });
      }

      return acc;
    },
    [],
  );

  return filterAnnots;
};

export const parseFormElementFromXml = (
  xmlString: string,
): AnnotationType[] => {
  if (!xmlString) return [];

  const xmlDoc = xmlParser(xmlString);
  const elements = xmlDoc.firstElementChild || xmlDoc.firstChild;
  const element = getElementsByTagName(elements as ChildNode, 'widgets') || [];
  const annotations: HTMLElement[] = Array.prototype.slice.call(element);
  const filterForm = annotations.reduce(
    (acc: AnnotationType[], cur: HTMLElement) => {
      const type = FORM_TYPE[cur.tagName];

      const attributes = cur.attributes as ElementAttributeType;

      if (type) {
        const page = parseInt(attributes.page.value, 10);
        acc.push({
          id: uuidv4(),
          obj_type: type,
          obj_attr: {
            date: attributes.date ? attributes.date.value : undefined,
            page,
            position: getPosition(type, cur),
            bdcolor: attributes.color ? attributes.color.value : undefined,
            style: attributes.style ? attributes.style.value : undefined,
            bdwidth: attributes.width
              ? parseInt(attributes.width.value, 10)
              : 0,
            transparency: attributes.opacity
              ? parseFloat(attributes.opacity.value)
              : 1,
          },
        });
      }

      return acc;
    },
    [],
  );

  return filterForm;
};

// eslint-disable-next-line consistent-return
const getEleText = (
  coord: PositionType,
  elements: HTMLElement[],
  viewport: ViewportType,
  scale: number,
): string => {
  const top = normalizeRound(viewport.height - coord.top * scale);
  const left = normalizeRound(coord.left * scale);
  const bottom = normalizeRound(viewport.height - coord.bottom * scale);
  const right = normalizeRound(coord.right * scale);

  for (let i = 0, len = elements.length; i <= len; i += 1) {
    const element = elements[i];
    if (element) {
      const eleTop = normalizeRound(element.offsetTop);
      const eleLeft = normalizeRound(element.offsetLeft);
      const eleRight = normalizeRound(element.offsetLeft + element.offsetWidth);

      if (eleTop >= top && eleTop <= bottom) {
        const textLength = element.innerText.length;
        const width = element.offsetWidth;

        if (eleLeft < left && eleRight > right) {
          const distanceL = left - eleLeft;
          const rateL = distanceL / width;
          const start = Math.floor(textLength * rateL);
          const distanceR = eleRight - right;
          const rateR = distanceR / width;
          const end = Math.floor(textLength - textLength * rateR);
          return ` ${element.innerText.slice(start, end)}`;
        }
        if (eleLeft < left && eleRight > left) {
          const distance = left - eleLeft;
          const rate = distance / width;
          const start = Math.floor(textLength * rate);
          return ` ${element.innerText.slice(start)}`;
        }
        if (eleRight > right && eleLeft < right) {
          const distance = eleRight - right;
          const rate = distance / width;
          const end = Math.floor(textLength - textLength * rate);
          return ` ${element.innerText.slice(0, end)}`;
        }
        if (eleLeft >= left && eleRight <= right) {
          return ` ${element.innerText}`;
        }
      }
    }
  }
  return '';
};

export const getAnnotationText = async ({
  viewport,
  scale,
  page,
  coords,
  pdf,
}: {
  viewport: ViewportType;
  scale: number;
  page: number;
  coords: PositionType[];
  pdf: PdfType;
}): Promise<string> => {
  const pageContainer = document.getElementById(`page_${page}`) as HTMLElement;
  const textLayer = pageContainer.querySelector(
    '[data-id="text-layer"]',
  ) as HTMLElement;
  const pdfPage = await getPdfPage(pdf, page);

  if (!textLayer.childNodes.length) {
    await renderTextLayer({
      textLayer,
      pdfPage,
      viewport,
    });
  }

  const textElements = Array.prototype.slice.call(textLayer.childNodes);

  let text = '';

  for (let i = 0, len = coords.length; i < len; i += 1) {
    const coord = coords[i];

    text += getEleText(coord, textElements, viewport, scale);
  }

  return text;
};

export const parseAnnotationObject = (
  {
    id,
    obj_type,
    obj_attr: {
      page,
      bdcolor,
      transparency,
      fcolor,
      ftransparency,
      position = { left: 0, top: 0, right: 0, bottom: 0 },
      content,
      style,
      bdwidth,
      fontname,
      fontsize,
      textcolor,
      is_arrow,
      src,
    },
  }: AnnotationType,
  pageHeight: number,
  scale: number,
): AnnotationType => ({
  id: id || uuidv4(),
  obj_type,
  obj_attr: {
    page: page - 1,
    bdcolor,
    position: parsePositionForBackend(obj_type, position, pageHeight, scale),
    transparency: transparency ? transparency * 0.01 : 0,
    content: content || undefined,
    style,
    fcolor,
    ftransparency: ftransparency ? ftransparency * 0.01 : 0,
    bdwidth,
    fontname,
    fontsize,
    textcolor,
    is_arrow,
    src,
  },
});

export const appendUserIdAndDate = (
  annotateObj: AnnotationType,
): AnnotationType => {
  const parsed = queryString.parse(window.location.search);

  if (parsed.watermark) {
    annotateObj.obj_attr.title = parsed.watermark;
  }

  const datetime = dayjs().format('YYYY-MM-DD_HH:mm:ss');
  annotateObj.obj_attr.date = datetime;

  return annotateObj;
};
