import EventEmitter from 'node:events';
import path from 'node:path';
import process from 'node:process';
import { PDFDocument } from 'pdf-lib';
import { chromium } from 'playwright';
import { red } from 'yoctocolors';
import { getOutlineNodes, setOutlineNodes, } from './outline.js';
import { setMetadata } from './postprocesser.js';
export class Printer extends EventEmitter {
    debug;
    headless;
    allowLocal;
    outlineTags;
    allowRemote;
    initScripts;
    additionalScripts;
    additionalStyles;
    allowedPaths;
    allowedDomains;
    ignoreHTTPSErrors;
    browserWSEndpoint;
    browserArgs;
    timeout;
    emulateMedia;
    enableWarnings;
    outlineContainerSelector;
    browser;
    browserContext;
    page;
    url;
    constructor(options = {}) {
        super();
        this.debug = options.debug ?? false;
        this.headless = options.headless ?? true;
        this.allowLocal = options.allowLocal ?? false;
        this.allowRemote = options.allowRemote ?? true;
        this.outlineTags = options.outlineTags ?? [
            'h1',
            'h2',
            'h3',
            'h4',
            'h5',
            'h6',
        ];
        this.initScripts = options.initScripts ?? [];
        this.additionalScripts = options.additionalScripts ?? [];
        this.additionalStyles = options.additionalStyles ?? [];
        this.allowedPaths = options.allowedPaths ?? [];
        this.allowedDomains = options.allowedDomains ?? [];
        this.ignoreHTTPSErrors = options.ignoreHTTPSErrors ?? false;
        this.browserWSEndpoint = options.browserEndpoint;
        this.browserArgs = options.browserArgs ?? [];
        this.timeout = options.timeout ?? 0;
        this.emulateMedia = options.emulateMedia ?? 'print';
        this.enableWarnings = options.enableWarnings ?? false;
        this.outlineContainerSelector = options.outlineContainerSelector ?? '';
        if (this.debug) {
            this.headless = false;
        }
    }
    async setup(launchOptions) {
        if (this.page) {
            return this.page;
        }
        const browserOptions = {
            pipe: true,
            headless: this.headless,
            args: ['--disable-dev-shm-usage', '--export-tagged-pdf'],
            ...launchOptions,
        };
        if (this.allowLocal) {
            browserOptions.args.push('--allow-file-access-from-files');
        }
        if (this.browserArgs.length) {
            browserOptions.args.push(...this.browserArgs);
        }
        if (this.browserWSEndpoint) {
            this.browser = await chromium.connect(this.browserWSEndpoint, browserOptions);
        }
        else {
            this.browser = await chromium.launch(browserOptions);
        }
        this.browserContext = await this.browser.newContext({
            ignoreHTTPSErrors: this.ignoreHTTPSErrors,
        });
        for (const script of this.initScripts) {
            await this.browserContext.addInitScript(script);
        }
        const page = await this.browserContext.newPage();
        this.page = page;
        await page.emulateMedia({
            colorScheme: 'light',
            media: this.emulateMedia,
        });
        if (this.needsAllowedRules()) {
            await page.route('**/*', (route) => {
                const uri = new URL(route.request().url());
                const { host, protocol, pathname } = uri;
                const local = protocol === 'file:';
                if (local && !this.withinAllowedPath(pathname)) {
                    return route.abort();
                }
                if (local && !this.allowLocal) {
                    return route.abort();
                }
                if (host && !this.isAllowedDomain(host)) {
                    return route.abort();
                }
                if (host && !this.allowRemote) {
                    return route.abort();
                }
                return route.continue();
            });
        }
        return page;
    }
    async render(url) {
        const page = this.page || (await this.setup());
        if (url === this.url) {
            return page;
        }
        this.url = url;
        try {
            await page.goto(url);
            for (const style of this.additionalStyles) {
                await page.addStyleTag({ path: style });
            }
            for (const script of this.additionalScripts) {
                await page.addScriptTag({ path: script });
            }
            await page.waitForLoadState('networkidle', { timeout: this.timeout });
            return page;
        }
        catch (error) {
            await this.closeBrowser();
            throw error;
        }
    }
    async pdf(url, options = {}, pdfOutlines) {
        const page = await this.render(url);
        try {
            // Get metatags
            const meta = await page.evaluate(() => {
                const meta = {};
                const title = document.querySelector('title');
                if (title) {
                    meta.title = title.textContent?.trim();
                }
                const lang = document.querySelector('html')?.getAttribute('lang');
                if (lang) {
                    meta.lang = lang;
                }
                const metaTags = document.querySelectorAll('meta');
                metaTags.forEach((tag) => {
                    if (tag.name) {
                        meta[tag.name] = tag.content;
                    }
                });
                return meta;
            });
            const pdfExportOptions = {
                scale: !options.scale ? 1 : options.scale,
                displayHeaderFooter: false,
                headerTemplate: options.headerTemplate,
                footerTemplate: options.footerTemplate,
                preferCSSPageSize: options.preferCSSPageSize,
                printBackground: options.printBackground,
                width: options.width,
                height: options.height,
                landscape: options.landscape,
                format: options.format,
            };
            if (options.margin) {
                pdfExportOptions.margin = options.margin;
            }
            if (options.pageRanges) {
                pdfExportOptions.pageRanges = options.pageRanges;
            }
            if (options.headerTemplate || options.footerTemplate) {
                pdfExportOptions.displayHeaderFooter = true;
            }
            const pdf = await page.pdf(pdfExportOptions);
            this.emit('postprocessing');
            const pdfDoc = await PDFDocument.load(pdf);
            setMetadata(pdfDoc, meta);
            let outlineNodes = [];
            if (pdfOutlines) {
                outlineNodes = await getOutlineNodes(page, this.outlineTags, this.outlineContainerSelector);
                setOutlineNodes(pdfDoc, outlineNodes, this.enableWarnings);
            }
            return {
                data: await pdfDoc.save(),
                outlineNodes,
            };
        }
        catch (error) {
            await this.closeBrowser();
            throw error;
        }
    }
    async closeBrowser() {
        if (this.browser) {
            await this.browser.close();
            this.browser = undefined;
            this.browserContext = undefined;
            this.page = undefined;
        }
        else {
            process.stdout.write(red('Browser instance not found'));
        }
    }
    needsAllowedRules() {
        return this.allowedPaths.length || this.allowedDomains.length;
    }
    withinAllowedPath(pathname) {
        if (!this.allowedPaths.length) {
            return true;
        }
        for (const parent of this.allowedPaths) {
            const relative = path.relative(parent, pathname);
            if (relative &&
                !relative.startsWith('..') &&
                !path.isAbsolute(relative)) {
                return true;
            }
        }
        return false;
    }
    isAllowedDomain(domain) {
        if (!this.allowedDomains.length) {
            return true;
        }
        return this.allowedDomains.includes(domain);
    }
}
