import {AlignmentType, Document, HeadingLevel, Packer, Paragraph, TextRun} from "docx";
import PDFDocument from "pdfkit";
import JSZip from "jszip";
import {mainStyle} from "./EpubStyle.js";
import Chapter, {ChapterContentData, CompleteChapterContent} from "./Chapter.js";
import {CompleteBookData} from "./Book.js";
import System from "../System.js";

export interface ExportResult {
    buffer: Buffer;
    fileName: string;
}

export default class Export {
    static async transformToDOCX(bookData: CompleteBookData): Promise<ExportResult> {
        const bookTitle: string = bookData.title;
        const filename: string = `${bookTitle}.docx`;

        const docParagraphs: Paragraph[] = [];

        docParagraphs.push(
            new Paragraph({
                children: [
                    new TextRun({text: bookTitle, bold: true, size: 48}),
                ],
                alignment: AlignmentType.CENTER,
                spacing: {after: 400},
            })
        );

        if (bookData.subTitle) {
            docParagraphs.push(
                new Paragraph({
                    children: [
                        new TextRun({text: bookData.subTitle, italics: true, size: 32}),
                    ],
                    alignment: AlignmentType.CENTER,
                    spacing: {after: 300},
                })
            );
        }

        if (bookData.summary) {
            docParagraphs.push(
                new Paragraph({
                    children: [
                        new TextRun({text: bookData.summary, size: 24, italics: true}),
                    ],
                    alignment: AlignmentType.JUSTIFIED,
                    spacing: {after: 400},
                })
            );
        }

        const chapters: ChapterContentData[] = Chapter.getChaptersOrSheet(bookData.chapters);

        for (const chapter of chapters) {
            if (!chapter.content) continue;

            docParagraphs.push(
                new Paragraph({
                    text: chapter.title,
                    heading: HeadingLevel.HEADING_1,
                    pageBreakBefore: true,
                    alignment: AlignmentType.CENTER,
                    spacing: {after: 200},
                })
            );

            const paragraphs: string[] = chapter.content.split(/\r?\n/);

            for (const paragraph of paragraphs) {
                if (paragraph.trim() === "") continue;
                docParagraphs.push(
                    new Paragraph({
                        children: [new TextRun({text: paragraph, size: 24})],
                        alignment: AlignmentType.JUSTIFIED,
                        spacing: {after: 200},
                    })
                );
            }
        }

        const doc: Document = new Document({
            sections: [{children: docParagraphs}],
        });

        const buffer: Buffer = await Packer.toBuffer(doc) as Buffer;

        return {buffer, fileName: filename};
    }

    static async transformToPDF(bookData: CompleteBookData): Promise<ExportResult> {
        const bookTitle: string = bookData.title;
        const filename: string = `${bookTitle}.pdf`;
        const chunks: Buffer[] = [];
        const pdfDoc: PDFKit.PDFDocument = new PDFDocument();

        pdfDoc.on('data', (chunk: Buffer): void => {
            chunks.push(chunk);
        });

        pdfDoc.fontSize(20).text(bookTitle, {align: 'center'});
        pdfDoc.moveDown();

        if (bookData.subTitle && bookData.subTitle.trim() !== '') {
            pdfDoc.fontSize(16).text(bookData.subTitle, {align: 'center'});
            pdfDoc.moveDown();
        }

        if (bookData.summary && bookData.summary.trim() !== '') {
            pdfDoc.fontSize(12).text(bookData.summary, {align: 'justify'});
            pdfDoc.moveDown();
        }

        const chapters: ChapterContentData[] = Chapter.getChaptersOrSheet(bookData.chapters);

        for (const chapter of chapters) {
            if (!chapter.content) continue;
            pdfDoc.addPage();
            pdfDoc.fontSize(16).text(chapter.title, {align: 'center'});
            pdfDoc.moveDown();
            pdfDoc.fontSize(12).text(chapter.content, {align: 'justify'});
        }

        pdfDoc.end();

        await new Promise<void>((resolve: () => void, reject: (reason: Error) => void) => {
            pdfDoc.on('end', resolve);
            pdfDoc.on('error', reject);
        });

        const pdfBuffer: Buffer = Buffer.concat(chunks);
        return {buffer: pdfBuffer, fileName: filename};
    }

    static async transformToEpub(bookData: CompleteBookData): Promise<ExportResult> {
        const bookTitle: string = bookData.title;
        const bookId: string = bookData.bookId;
        const epub: JSZip = new JSZip();

        epub.file('mimetype', 'application/epub+zip', {compression: 'STORE'});
        epub.file('META-INF/container.xml', `<?xml version="1.0" encoding="UTF-8"?>
<container version="1.0" xmlns="urn:oasis:names:tc:opendocument:xmlns:container">
  <rootfiles>
    <rootfile full-path="OEBPS/content.opf" media-type="application/oebps-package+xml"/>
  </rootfiles>
</container>`);

        let contentOpf: string = `<?xml version="1.0" encoding="UTF-8"?>
        <package xmlns="http://www.idpf.org/2007/opf" version="2.0" unique-identifier="ERitors-${bookId}">
        <metadata xmlns:dc="http://purl.org/dc/elements/1.1/">
          <dc:title>${bookTitle}${bookData.subTitle ? ' - ' + bookData.subTitle : ''}</dc:title>
          <dc:language>fr</dc:language>
          <dc:identifier id="ERitors-${bookId}">urn:uuid:${bookId}</dc:identifier>
          <dc:creator>${bookData.userInfos.firstName} ${bookData.userInfos.lastName}</dc:creator>
          <dc:publisher>ERitors Scribe</dc:publisher>
          <meta name="cover" content="cover-image-id" />
        </metadata>
        <manifest>`;

        let spine: string = `<spine toc="toc">`;

        const hasRegularChapters: boolean = bookData.chapters.some(
            (chapter: CompleteChapterContent): boolean => chapter.order > 0
        );
        const chaptersToExport: CompleteChapterContent[] = hasRegularChapters
            ? bookData.chapters.filter((chapter: CompleteChapterContent): boolean => chapter.order > 0)
            : bookData.chapters.filter((chapter: CompleteChapterContent): boolean => chapter.order === -1);

        for (const chapter of chaptersToExport) {
            if (!chapter.content) continue;
            const chapterIndex: string = `chapter${chapter.order}`;
            const htmlContent: string = Chapter.tipTapToHtml(JSON.parse(chapter.content) as JSON);

            const xhtmlPage: string = `<?xml version="1.0" encoding="utf-8"?>
            <html xmlns="http://www.w3.org/1999/xhtml">
              <head>
                <title>${chapter.title}</title>
                <link rel="stylesheet" type="text/css" href="styles.css"/>
              </head>
              <body>
                ${htmlContent}
              </body>
            </html>`;

            epub.file(`OEBPS/${chapterIndex}.xhtml`, xhtmlPage);
            contentOpf += `<item id="${chapterIndex}" href="${chapterIndex}.xhtml" media-type="application/xhtml+xml"/>`;
            spine += `<itemref idref="${chapterIndex}" linear="yes"/>`;
        }

        spine += `</spine>`;

        contentOpf += `<item id="toc" href="toc.ncx" media-type="application/x-dtbncx+xml"/>
  <item id="style" href="styles.css" media-type="text/css"/>`;
        contentOpf += spine;
        contentOpf += `</package>`;

        epub.file('OEBPS/content.opf', contentOpf);
        epub.file('OEBPS/styles.css', mainStyle);

        if (bookData.coverImage) {
            const imageBuffer: Buffer = Buffer.from(bookData.coverImage, 'base64');
            epub.file('OEBPS/cover.jpg', imageBuffer);
        }

        const epubBuffer: Buffer = await epub.generateAsync({type: 'nodebuffer'}) as Buffer;

        return {buffer: epubBuffer, fileName: `${bookTitle}.epub`};
    }
}
