import fs from "fs";
import path from "path";
import pug from "pug";
import HTMLtoDOCX from "html-to-docx";
import puppeteer from "puppeteer";
import { spawnSync } from "node:child_process";
import yaml from "js-yaml";
import { marked } from "marked";
import groupBy from "lodash/groupBy.js";
import { markedSmartypants } from "marked-smartypants";
import { fileURLToPath } from "url";
import { createRequire } from "module";
const __filename = fileURLToPath(import.meta.url);
const __dirname = path.dirname(__filename);
const require = createRequire(import.meta.url);
// it is surprising that https://github.com/jstransformers/jstransformer-marked picks up this object (undocumented API)
// source of this call: https://github.com/markedjs/marked-custom-heading-id/blob/main/src/index.js (MIT License, Copyright (c) 2021 @markedjs)
marked.use({
    renderer: {
        heading(text, level, _) {
            // WEC patch: add \:
            const headingIdRegex = /(?: +|^)\{#([a-z][\:\w-]*)\}(?: +|$)/i;
            const hasId = text.match(headingIdRegex);
            if (!hasId) {
                // fallback to original heading renderer
                return false;
            }
            return `<h${level} id="${hasId[1]}">${text.replace(headingIdRegex, "")}</h${level}>\n`;
        },
    },
});
marked.use(markedSmartypants());
export class Reporter {
    constructor(options, logger) {
        this.options = options;
        this.logger = logger;
    }
    /*
     * Takes the output of the inspector and generates a HTML report.
     */
    generateHtmlReport(inspectionData, outputFilename = "inspection.html", customTemplate) {
        const templatePath = this.options.htmlTemplate ||
            customTemplate ||
            path.join(__dirname, "../assets/template.pug");
        const htmlReport = this.renderHtmlReport(inspectionData, templatePath);
        this.saveFile(outputFilename, htmlReport);
        if (this.options.printHtmlToConsole) {
            this.logger.info(htmlReport);
        }
        return htmlReport;
    }
    renderHtmlReport(inspectionData, templatePath) {
        const pugInputData = Object.assign({}, inspectionData, {
            pretty: true,
            basedir: path.resolve(path.join(__dirname, "../assets")),
            groupBy: groupBy,
            marked: marked, // we need to pass the markdown engine to template for access at render-time (as opposed to comile time), see https://github.com/pugjs/pug/issues/1171
            fs: fs,
            yaml: yaml,
            path: path,
            inlineCSS: fs.readFileSync(require.resolve("github-markdown-css/github-markdown.css"), "utf8"),
            inspection: inspectionData,
            extra: this.options.extraFiles,
            filterOptions: { marked: {} },
        });
        return pug.renderFile(templatePath, pugInputData);
    }
    async convertHtmlToPdfInMemory(htmlContent) {
        const browser = await puppeteer.launch({});
        const pages = await browser.pages();
        await pages[0].setContent(htmlContent);
        const pdfBuffer = await pages[0].pdf({
            format: "A4",
            printBackground: true,
            displayHeaderFooter: true,
            headerTemplate: `
            <div class="page-footer" style="width: 100%; font-size: 11px; padding: 5px 5px 0; position: relative;">
                <div style="bottom: 5px; text-align: center;"><span class="title"></span></div>
            </div>
          `,
            footerTemplate: `
            <div class="page-header" style="width: 100%; font-size: 11px; padding: 5px 5px 0; position: relative;">
                <div style="top: 5px; text-align: center;"><span class="pageNumber"></span>/<span class="totalPages"></span></div>
            </div>
          `,
            // this is needed to prevent content from being placed over the footer
            margin: { top: "1.5cm", bottom: "1cm" },
        });
        await browser.close();
        return pdfBuffer;
    }
    /*
     * Generates either a .docx or .odt.
     * For .docx a conversion with pandoc or with js is available
     * For .odt only pandoc is available.
     */
    async saveAsOfficeDoc(inspectionData, outputFilePath = "inspection.docx", customTemplate) {
        const officeTemplatePath = this.options.officeTemplate ||
            customTemplate ||
            path.join(__dirname, "../assets/template-office.pug");
        const htmlReport = this.renderHtmlReport(inspectionData, officeTemplatePath);
        if (this.options.usePandoc) {
            await this.htmlToOfficeWithPandoc(htmlReport, outputFilePath);
            return;
        }
        if (outputFilePath.endsWith(".odt")) {
            this.logger.error("To generate .odt, you must have pandoc installed and specify --use-pandoc.");
            process.exit(1);
        }
        await this.htmlToDocxJavascript(inspectionData, htmlReport, outputFilePath);
    }
    async htmlToOfficeWithPandoc(html_dump, outputFile) {
        const ret = spawnSync("pandoc", ["-f", "html", "--number-sections", "--toc", "--output", outputFile], {
            input: html_dump,
            encoding: "utf8",
        });
        if (ret[2]) {
            this.logger.info(ret[2]);
        }
    }
    async htmlToDocxJavascript(inspectionData, htmlReport, outputFileName) {
        const documentOptions = {
            orientation: "portrait",
            pageSize: { width: "21.0cm", height: "29.7cm" },
            pageNumber: true,
            title: inspectionData.title,
            lang: "en-UK",
            creator: `EDPS Website Evidence Collector v${inspectionData.script.version.npm} using NPM html-to-docx`,
        };
        try {
            let docx = await HTMLtoDOCX(htmlReport, null, documentOptions, null);
            this.saveFile(outputFileName, docx);
        }
        catch (e) {
            this.logger.error(e);
        }
    }
    saveJsonToFile(data, filename) {
        const json_dump = JSON.stringify(data, null, 2);
        this.saveFile(filename, json_dump);
        if (this.options.printJsonToConsole) {
            this.logger.info(json_dump);
        }
    }
    saveYamlToFile(data, filename) {
        const yaml_dump = yaml.dump(data, {
            noRefs: true,
            replacer: function replacer(_, value) {
                return value instanceof URL ? value.toString() : value;
            },
        });
        this.saveFile(filename, yaml_dump);
        if (this.options.printYamlToConsole) {
            this.logger.info(yaml_dump);
        }
    }
    loadYamlFile(filename) {
        return yaml.load(fs.readFileSync(path.join(this.options.outputPath, filename), "utf8"));
    }
    saveFile(filename, data) {
        if (this.options.outputPath) {
            let effectivePath = path.join(this.options.outputPath, filename);
            this.logger.debug(`Saving file to ${effectivePath}`);
            fs.writeFileSync(effectivePath, data);
        }
    }
}
