import { documentEnd, documentStart, docxStrings } from './resources/docxFiles.js';

import { opt } from '../containers/app.js';

import { assignParagraphs } from '../utils/reflowPars.js';

import { pageMetricsAll } from '../containers/dataContainer.js';
import ocr from '../objects/ocrObjects.js';

/**
 * Convert an array of ocrPage objects to XML for a Word document.
 *
 * @param {Object} params
 * @param {Array<OcrPage>} params.ocrCurrent -
 * @param {?Array<number>} [params.pageArr=null] - Array of 0-based page indices to include. Overrides minpage/maxpage when provided.
 * @param {number} [params.minpage=0] - The first page to include in the document.
 * @param {number} [params.maxpage=-1] - The last page to include in the document.
 * @param {boolean} [params.reflowText=false] - Remove line breaks within what appears to be the same paragraph.
 * @param {?Array<string>} [params.wordIds=null] - An array of word IDs to include in the document.
 *    If omitted, all words are included.
 */
export function writeDocxContent({
  ocrCurrent, pageArr = null, minpage = 0, maxpage = -1, reflowText = false, wordIds = null,
}) {
  let textStr = '';

  if (!pageArr) {
    if (maxpage === -1) maxpage = ocrCurrent.length - 1;
    pageArr = [];
    for (let i = minpage; i <= maxpage; i++) pageArr.push(i);
  }

  let newLine = false;

  for (const g of pageArr) {
    if (!ocrCurrent[g] || ocrCurrent[g].lines.length === 0) continue;

    const pageObj = ocrCurrent[g];

    if (reflowText && (!pageObj.textSource || !['textract', 'abbyy', 'google_vision', 'azure_doc_intel', 'docx'].includes(pageObj.textSource))) {
      const angle = pageMetricsAll[g].angle || 0;
      assignParagraphs(pageObj, angle);
    }

    let parCurrent = pageObj.lines[0].par;

    let fontStylePrev = '';
    let supPrev = false;

    for (let h = 0; h < pageObj.lines.length; h++) {
      const lineObj = pageObj.lines[h];

      if (reflowText) {
        if (g > 0 && h === 0 || lineObj.par !== parCurrent) newLine = true;
        parCurrent = lineObj.par;
      } else {
        newLine = true;
      }

      for (let i = 0; i < lineObj.words.length; i++) {
        const wordObj = lineObj.words[i];
        if (!wordObj) continue;

        if (wordIds && !wordIds.includes(wordObj.id)) continue;

        let fontStyle = '';
        if (wordObj.style.bold) fontStyle += '<w:b/>';
        if (wordObj.style.italic) fontStyle += '<w:i/>';

        if (wordObj.style.smallCaps) {
          fontStyle += '<w:smallCaps/>';
        }

        if (wordObj.style.underline) {
          fontStyle += '<w:u w:val="single"/>';
        }

        if (wordObj.style.sup) {
          fontStyle += '<w:vertAlign w:val="superscript"/>';
        }

        if (wordObj.style.font) {
          fontStyle += `<w:rFonts w:ascii="${ocr.escapeXml(wordObj.style.font)}" w:hAnsi="${ocr.escapeXml(wordObj.style.font)}"/>`;
        }

        if (newLine || fontStyle !== fontStylePrev || (h === 0 && g === 0 && i === 0)) {
          const styleStr = fontStyle === '' ? '' : `<w:rPr>${fontStyle}</w:rPr>`;

          if (h === 0 && g === 0 && i === 0) {
            textStr = `${textStr}<w:p><w:r>${styleStr}<w:t xml:space="preserve">`;
          } else if (newLine) {
            textStr = `${textStr}</w:t></w:r></w:p><w:p><w:r>${styleStr}<w:t xml:space="preserve">`;
          // If the previous word was a superscript, the space is added switching back to normal text.
          } else if (supPrev) {
            textStr = `${textStr}</w:t></w:r><w:r>${styleStr}<w:t xml:space="preserve"> `;
          // If this word is a superscript, no space is added between words.
          } else if (wordObj.style.sup && i > 0) {
            textStr = `${textStr}</w:t></w:r><w:r>${styleStr}<w:t xml:space="preserve">`;
          } else {
            textStr = `${textStr} </w:t></w:r><w:r>${styleStr}<w:t xml:space="preserve">`;
          }
        } else {
          textStr += ' ';
        }

        fontStylePrev = fontStyle;
        supPrev = wordObj.style.sup;

        newLine = false;

        // DOCX is an XML format, so any escaped XML characters need to continue being escaped.
        // TODO: Figure out how to properly export superscripts to Word
        textStr += ocr.escapeXml(wordObj.text);
      }
    }
    opt.progressHandler({ n: g, type: 'export', info: { } });
  }

  // Add final closing tags
  if (textStr) textStr += '</w:t></w:r></w:p>';

  return textStr;
}

/**
 * Create a Word document from an array of ocrPage objects.
 *
 * @param {Object} params
 * @param {Array<OcrPage>} params.hocrCurrent -
 * @param {?Array<number>} [params.pageArr=null] - Array of 0-based page indices to include. Overrides minpage/maxpage when provided.
 * @param {number} [params.minpage=0] - The first page to include in the document.
 * @param {number} [params.maxpage=-1] - The last page to include in the document.
 */
export async function writeDocx({
  hocrCurrent, pageArr = null, minpage = 0, maxpage = -1,
}) {
  const { Uint8ArrayWriter, TextReader, ZipWriter } = await import('../../lib/zip.js/index.js');

  if (!pageArr) {
    if (maxpage === -1) maxpage = hocrCurrent.length - 1;
    pageArr = [];
    for (let i = minpage; i <= maxpage; i++) pageArr.push(i);
  }

  const zipFileWriter = new Uint8ArrayWriter();
  const zipWriter = new ZipWriter(zipFileWriter);

  const textReader = new TextReader(documentStart + writeDocxContent({
    ocrCurrent: hocrCurrent,
    pageArr,
    reflowText: opt.reflow,
  }) + documentEnd);
  await zipWriter.add('word/document.xml', textReader);

  for (let i = 0; i < docxStrings.length; i++) {
    const textReaderI = new TextReader(docxStrings[i].content);
    await zipWriter.add(docxStrings[i].path, textReaderI);
  }

  await zipWriter.close();

  const zipFileData = await zipFileWriter.getData();

  return zipFileData;
}
