import { assertExists } from '@blockeditor/global/utils';
import { blockElementGetter, getBlockComponentByModel, getEditorContainer, isInsideDocEditor, matchFlavours, } from '../../_common/utils/index.js';
import { getBlocksInFrame } from '../../page-block/edgeless/frame-manager.js';
import { xywhArrayToObject } from '../../page-block/edgeless/utils/convert.js';
import { getBackgroundGrid } from '../../page-block/edgeless/utils/query.js';
import { Bound } from '../../surface-block/utils/bound.js';
import { FileExporter } from './file-exporter.js';
export const DEFAULT_IMAGE_PROXY_ENDPOINT = 'https://workers.toeverything.workers.dev/proxy/image';
const IGNORE_TAGS = [
    'AFFINE-BLOCK-HUB',
    'EDGELESS-TOOLBAR',
    'AFFINE-DRAG-HANDLE-WIDGET',
];
export class ExportManager {
    constructor(blockService, options) {
        this._replaceRichTextWithSvgElement = async (element) => {
            const richList = Array.from(element.querySelectorAll('.inline-editor'));
            await Promise.all(richList.map(async (rich) => {
                const svgEle = await this._elementToSvgElement(rich.cloneNode(true), rich.clientWidth, rich.clientHeight + 1);
                rich.parentElement?.appendChild(svgEle);
                rich.parentElement?.removeChild(rich);
            }));
        };
        this._exportOptions = options;
        this._blockService = blockService;
    }
    get page() {
        return this._blockService.std.page;
    }
    get editorHost() {
        return this._blockService.std.host;
    }
    async _checkReady() {
        const pathname = location.pathname;
        const pageMode = isInsideDocEditor(this.editorHost);
        const promise = new Promise((resolve, reject) => {
            let count = 0;
            const checkReactRender = setInterval(() => {
                try {
                    this._checkCanContinueToCanvas(pathname, pageMode);
                }
                catch (e) {
                    clearInterval(checkReactRender);
                    reject(e);
                }
                const root = this.page.root;
                const pageBlock = root
                    ? getBlockComponentByModel(this.editorHost, root)
                    : null;
                const imageCard = pageBlock?.querySelector('affine-image-block-card');
                const isReady = !imageCard || imageCard.getAttribute('imageState') === '0';
                if (pageBlock && isReady) {
                    clearInterval(checkReactRender);
                    resolve(true);
                }
                count++;
                if (count > 10 * 60) {
                    clearInterval(checkReactRender);
                    resolve(false);
                }
            }, 100);
        });
        return await promise;
    }
    _drawEdgelessBackground(ctx, { size, backgroundColor, gridColor, }) {
        const svgImg = `<svg width='${ctx.canvas.width}px' height='${ctx.canvas.height}px' xmlns='http://www.w3.org/2000/svg' style='background-size:${size}px ${size}px;background-color:${backgroundColor}; background-image: radial-gradient(${gridColor} 1px, ${backgroundColor} 1px)'></svg>`;
        const img = new Image();
        const cleanup = () => {
            img.onload = null;
            img.onerror = null;
        };
        return new Promise((resolve, reject) => {
            img.onload = () => {
                cleanup();
                ctx.drawImage(img, 0, 0);
                resolve();
            };
            img.onerror = e => {
                cleanup();
                reject(e);
            };
            img.src = `data:image/svg+xml,${encodeURIComponent(svgImg)}`;
        });
    }
    async _html2canvas(htmlElement, options = {}) {
        const html2canvas = (await import('html2canvas'))
            .default;
        const html2canvasOption = {
            ignoreElements: function (element) {
                if (element.tagName === 'AFFINE-BLOCK-HUB' ||
                    element.tagName === 'EDGELESS-TOOLBAR' ||
                    element.classList.contains('dg')) {
                    return true;
                }
                else {
                    return false;
                }
            },
            onclone: async (documentClone, element) => {
                // html2canvas can't support transform feature
                element.style.setProperty('transform', 'none');
                const layer = element.classList.contains('.affine-edgeless-layer')
                    ? element
                    : null;
                if (layer instanceof HTMLElement) {
                    layer.style.setProperty('transform', 'none');
                }
                const boxShadowEles = documentClone.querySelectorAll("[style*='box-shadow']");
                boxShadowEles.forEach(function (element) {
                    if (element instanceof HTMLElement) {
                        element.style.setProperty('box-shadow', 'none');
                    }
                });
                await this._replaceRichTextWithSvgElement(element);
            },
            useCORS: this._exportOptions.imageProxyEndpoint ? false : true,
            proxy: this._exportOptions.imageProxyEndpoint,
        };
        return html2canvas(htmlElement, Object.assign(html2canvasOption, options));
    }
    _createCanvas(bound, fillStyle) {
        const canvas = document.createElement('canvas');
        const dpr = window.devicePixelRatio || 1;
        const ctx = canvas.getContext('2d');
        canvas.width = (bound.w + 100) * dpr;
        canvas.height = (bound.h + 100) * dpr;
        ctx.scale(dpr, dpr);
        ctx.fillStyle = fillStyle;
        ctx.fillRect(0, 0, canvas.width, canvas.height);
        return { canvas, ctx };
    }
    async edgelessToCanvas(surfaceRenderer, bound, blockElementGetter = () => null, edgeless, nodes, surfaces, edgelessBackground) {
        const root = this.page.root;
        if (!root)
            return;
        const pathname = location.pathname;
        const pageMode = isInsideDocEditor(this.editorHost);
        const editorContainer = getEditorContainer(this.editorHost);
        const containerComputedStyle = window.getComputedStyle(editorContainer);
        const html2canvas = (element) => this._html2canvas(element, {
            backgroundColor: containerComputedStyle.backgroundColor,
        });
        const container = editorContainer.querySelector('.affine-block-children-container');
        if (!container)
            return;
        const { ctx, canvas } = this._createCanvas(bound, window.getComputedStyle(container).backgroundColor);
        if (edgelessBackground) {
            await this._drawEdgelessBackground(ctx, {
                backgroundColor: containerComputedStyle.getPropertyValue('--affine-background-primary-color'),
                size: getBackgroundGrid(edgelessBackground.zoom, true).gap,
                gridColor: containerComputedStyle.getPropertyValue('--affine-edgeless-grid-color'),
            });
        }
        // TODO: refactor of this part
        const blocks = nodes ?? edgeless?.getSortedElementsByBound(bound) ?? [];
        for (const block of blocks) {
            if (matchFlavours(block, ['affine:image'])) {
                if (!block.sourceId)
                    return;
                const blob = await block.page.blob.get(block.sourceId);
                if (!blob)
                    return;
                const blobToImage = (blob) => new Promise((resolve, reject) => {
                    const img = new Image();
                    img.onload = () => resolve(img);
                    img.onerror = reject;
                    img.src = URL.createObjectURL(blob);
                });
                const blockBound = xywhArrayToObject(block);
                ctx.drawImage(await blobToImage(blob), blockBound.x - bound.x, blockBound.y - bound.y, blockBound.w, blockBound.h);
            }
            let blockElement = blockElementGetter(block)?.parentElement;
            if (matchFlavours(block, ['affine:note'])) {
                blockElement = blockElement?.closest('.edgeless-block-portal-note');
            }
            if (blockElement) {
                const blockBound = xywhArrayToObject(block);
                const canvasData = await this._html2canvas(blockElement);
                ctx.drawImage(canvasData, blockBound.x - bound.x + 50, blockBound.y - bound.y + 50, blockBound.w, blockBound.h);
            }
            if (matchFlavours(block, ['affine:frame'])) {
                const blocksInsideFrame = getBlocksInFrame(this.page, block, false);
                const frameBound = Bound.deserialize(block.xywh);
                for (let i = 0; i < blocksInsideFrame.length; i++) {
                    const element = blocksInsideFrame[i];
                    const htmlElement = blockElementGetter(element);
                    const blockBound = xywhArrayToObject(element);
                    const canvasData = await html2canvas(htmlElement);
                    ctx.drawImage(canvasData, blockBound.x - bound.x + 50, blockBound.y - bound.y + 50, blockBound.w, (blockBound.w / canvasData.width) * canvasData.height);
                }
                const surfaceCanvas = surfaceRenderer.getCanvasByBound(frameBound);
                ctx.drawImage(surfaceCanvas, 50, 50, frameBound.w, frameBound.h);
            }
            this._checkCanContinueToCanvas(pathname, pageMode);
        }
        const surfaceCanvas = surfaceRenderer.getCanvasByBound(bound, surfaces);
        ctx.drawImage(surfaceCanvas, 50, 50, bound.w, bound.h);
        return canvas;
    }
    async _docToCanvas() {
        const html2canvas = (await import('html2canvas')).default;
        if (!(html2canvas instanceof Function))
            return;
        const pathname = location.pathname;
        const pageMode = isInsideDocEditor(this.editorHost);
        const editorContainer = getEditorContainer(this.editorHost);
        const docViewport = editorContainer.querySelector('.affine-doc-viewport');
        if (!docViewport)
            return;
        const pageContainer = docViewport.querySelector('.affine-doc-page-block-container');
        const rect = pageContainer?.getBoundingClientRect();
        const pageWidth = rect?.width;
        const pageLeft = rect?.left;
        const viewportHeight = docViewport?.scrollHeight;
        const replaceRichTextWithSvgElementFunc = this._replaceRichTextWithSvgElement.bind(this);
        const html2canvasOption = {
            ignoreElements: function (element) {
                if (IGNORE_TAGS.includes(element.tagName) ||
                    element.classList.contains('dg')) {
                    return true;
                }
                else if ((element.classList.contains('close') &&
                    element.parentElement?.classList.contains('meta-data-expanded-title')) ||
                    (element.classList.contains('expand') &&
                        element.parentElement?.classList.contains('meta-data'))) {
                    // the close and expand buttons in affine-page-meta-data is not needed to be showed
                    return true;
                }
                else {
                    return false;
                }
            },
            onclone: async function (_documentClone, element) {
                element.style.height = `${viewportHeight}px`;
                await replaceRichTextWithSvgElementFunc(element);
            },
            backgroundColor: window.getComputedStyle(editorContainer).backgroundColor,
            x: pageLeft,
            width: pageWidth,
            height: viewportHeight,
            useCORS: this._exportOptions.imageProxyEndpoint ? false : true,
            proxy: this._exportOptions.imageProxyEndpoint,
        };
        const data = await html2canvas(docViewport, html2canvasOption);
        this._checkCanContinueToCanvas(pathname, pageMode);
        return data;
    }
    async _elementToSvgElement(node, width, height) {
        const xmlns = 'http://www.w3.org/2000/svg';
        const svg = document.createElementNS(xmlns, 'svg');
        const foreignObject = document.createElementNS(xmlns, 'foreignObject');
        svg.setAttribute('width', `${width}`);
        svg.setAttribute('height', `${height}`);
        svg.setAttribute('viewBox', `0 0 ${width} ${height}`);
        foreignObject.setAttribute('width', '100%');
        foreignObject.setAttribute('height', '100%');
        foreignObject.setAttribute('x', '0');
        foreignObject.setAttribute('y', '0');
        foreignObject.setAttribute('externalResourcesRequired', 'true');
        svg.appendChild(foreignObject);
        foreignObject.appendChild(node);
        return svg;
    }
    _checkCanContinueToCanvas(pathName, pageMode) {
        if (location.pathname !== pathName ||
            isInsideDocEditor(this.editorHost) !== pageMode) {
            throw new Error('Unable to export content to canvas');
        }
    }
    async _toCanvas() {
        await this._checkReady();
        if (isInsideDocEditor(this.editorHost)) {
            return await this._docToCanvas();
        }
        else {
            const root = this.page.root;
            if (!root)
                return;
            const edgeless = getBlockComponentByModel(this.editorHost, root);
            const bound = edgeless.getElementsBound();
            assertExists(bound);
            return await this.edgelessToCanvas(edgeless.surface.viewport, bound, (model) => blockElementGetter(model, this.editorHost.view), edgeless);
        }
    }
    async exportPng() {
        const root = this.page.root;
        if (!root)
            return;
        const canvasImage = await this._toCanvas();
        if (!canvasImage) {
            return;
        }
        FileExporter.exportPng(this.page.root.title.toString(), canvasImage.toDataURL('image/png'));
    }
    async exportPdf() {
        const root = this.page.root;
        if (!root)
            return;
        const canvasImage = await this._toCanvas();
        if (!canvasImage) {
            return;
        }
        const PDFLib = await import('pdf-lib');
        const pdfDoc = await PDFLib.PDFDocument.create();
        const page = pdfDoc.addPage([canvasImage.width, canvasImage.height]);
        const imageEmbed = await pdfDoc.embedPng(canvasImage.toDataURL('PNG'));
        const { width, height } = imageEmbed.scale(1);
        page.drawImage(imageEmbed, {
            x: 0,
            y: 0,
            width,
            height,
        });
        const pdfBase64 = await pdfDoc.saveAsBase64({ dataUri: true });
        FileExporter.exportFile(root.title.toString() + '.pdf', pdfBase64);
    }
}
//# sourceMappingURL=export-manager.js.map