/**
 * @typedef {Object} PointObj
 * @prop {number} x
 * @prop {number} y
 */

/**
 * Struct para representar um ponto para o widget de assinatura.
 */
class Point {
    /**
     * @type {number}
     */
    x = 0;
    /**
     * @type {number}
     */
    y = 0;

    /**
     * @param {string|number} x
     * @param {string|number} y
     */
    constructor(x, y) {
        this.x = (typeof x === 'number' ? x : parseFloat(x)) || 0;
        this.y = (typeof y === 'number' ? y : parseFloat(y)) || 0;
        Object.freeze(this);
    }

    toJSON() {
        return { x: this.x, y: this.y };
    }

    toString() {
        return `Point (${this.x}, ${this.y})`;
    }
}

/**
 * Classe para o widget de assinatura.
 */
class GOVBRPDFJSWidgetAssinatura {
    /**
     * PDFPageView
     * @type {any}
     */
    view;
    /**
     * Promise<PDFPageView>
     * @type {Promise<any>}
     */
    promiseView;
    /**
     * PDFViewer
     * @type {any}
     */
    pdfJSViewer;

    /**
     * @type {string}
     */
    frameId;
    /**
     * @type {import("./GOVBRPDFJSUserControl.mjs").GovbrPDFJSUCConfig}
     */
    config;

    /**
     * @type {HTMLElement}
     */
    widget;
    /**
     * @type {HTMLCanvasElement}
     */
    canvas;
    _pos = new Point(10, 10);
    _tam = new Point(10, 10);
    arrastando = false;

    paginaCarimbo = 0;

    /**
     * @type {Record<string, HTMLElement>}
     */
    anotacoes = {};

    /**
     * @param {any} pdfJS PDFJSApplication
     * @param {import("./GOVBRPDFJSUserControl.mjs").GovbrPDFJSUCConfig} config
     * @param {string} frameId
     */
    constructor(pdfJS, config, frameId) {
        this.config = config;
        this.pdfJSViewer = pdfJS.pdfViewer;
        this.frameId = frameId;
        this.montaEventosPostMessages();
    }

    montaEventosPostMessages() {
        // @ts-ignore
        if (!window['__govbr_eventos_postmessage']) {
            const idFrame = location.search.match(/govbrId=([^&]+)/)[1];
            window.addEventListener('message', async (event) => {
                if (Array.isArray(event.data)) {
                    if (/^https:\/\/.*\.govbr(\.cloud|\.com\.br)|^http:\/\/localhost/.test(event?.origin || '')) {
                        const [nomeEvento, id, ...dadosAdicionais] = event.data;
                        if (idFrame === id) {
                            this.trataMensagemComponente(nomeEvento, dadosAdicionais);
                        } else {
                            GOVBR.logs.error('PDFJS - Falha, não foi possível encontrar o IFrame de origem. ID: ' + id);
                        }
                    } else {
                        GOVBR.logs.error('Domínio inválido - ' + event?.origin);
                    }
                }
            });
        }
    }

    /**
     * Trata as mensagens recebidas via postMessage para o componente.
     * @param {string} nomeEvento
     * @param {any} dadosAdicionais
     */
    async trataMensagemComponente(nomeEvento, dadosAdicionais) {
        switch (nomeEvento) {
            case '__govbr_pdfjs_exibe_carimbo':
                {
                    const configCarimbo = dadosAdicionais[0];
                    if (configCarimbo.carimboCanvas?.imagem) {
                        this.canvas.width = configCarimbo.carimboCanvas.width * configCarimbo.carimboCanvas.escala;
                        this.canvas.height = configCarimbo.carimboCanvas.height * configCarimbo.carimboCanvas.escala;
                        this.canvas.style.setProperty('width', configCarimbo.carimboCanvas.width + 'px');
                        this.canvas.style.setProperty('height', configCarimbo.carimboCanvas.height + 'px');
                        const destCtx = this.canvas.getContext('2d');
                        const img = new Image();
                        img.onload = function () {
                            destCtx.drawImage(img, 0, 0);
                        };
                        img.src = URL.createObjectURL(configCarimbo.carimboCanvas.imagem);
                    }
                    this.setPosicaoCarimboPagina(configCarimbo.posicaoCarimbo);
                    this.setPaginaCarimbo(configCarimbo.paginaCarimbo);
                    await this.exibeWidgetComponente();
                    this.setPossuiCanvas(!!configCarimbo.carimboCanvas);
                }
                break;
            case '__govbr_pdfjs_oculta_carimbo':
                {
                    const configCarimbo = dadosAdicionais[0];
                    if (configCarimbo.carimboCanvas?.imagem) {
                        this.canvas.width = configCarimbo.carimboCanvas.width * configCarimbo.carimboCanvas.escala;
                        this.canvas.height = configCarimbo.carimboCanvas.height * configCarimbo.carimboCanvas.escala;
                        this.canvas.style.setProperty('width', configCarimbo.carimboCanvas.width + 'px');
                        this.canvas.style.setProperty('height', configCarimbo.carimboCanvas.height + 'px');
                        const destCtx = this.canvas.getContext('2d');
                        const img = new Image();
                        img.onload = function () {
                            destCtx.drawImage(img, 0, 0);
                        };
                        img.src = URL.createObjectURL(configCarimbo.carimboCanvas.imagem);
                    }
                    this.setPosicaoCarimboPagina({ x: 0, y: 0 });
                    this.setPaginaCarimbo(0);
                    this.ocultaWidgetComponente();
                }
                break;
        }
    }

    async init() {
        // Cria a folha de estilos personalizada dentro do IFRAME.
        const style = document.createElement('link');
        style.rel = 'stylesheet';
        style.href = this.config.urlCDN + '/genexus/pdfjs/' + this.config.versaoArquivo + '/GOVBRPDFJSEstilos.css';
        document.head.append(style);
        this.removeBotoesDesativados(this.config);

        // @ts-ignore
        await window['PDFViewerApplication'].initializedPromise;
        // @ts-ignore
        const pdfJS = PDFViewerApplication;
        if (!this.config.permiteSidebar) {
            pdfJS.pdfSidebar.toggleButton.remove();
            pdfJS.pdfSidebar.sidebarContainer.remove();
            document.body.style.setProperty('--sidebar-width', '0');
        } else if (window.innerWidth > 1200) {
            // Caso tenha espaço, abre a sidebar.
            pdfJS.pdfSidebar.open();
        }
        this.pdfJSViewer.eventBus.on('pagesloaded', () => {
            parent.postMessage(['__govbr_pdfjs_pagesloaded', this.frameId, pdfJS.pagesCount], '*');
        });

        /**
         * @type {(val: any) => void}
         */
        let fnResolveView;
        this.promiseView = new Promise((resolve) => (fnResolveView = resolve));
        this.canvas = document.createElement('canvas');
        this.canvas.width = 1;
        this.canvas.height = 1;
        this.paginaCarimbo = this.pdfJSViewer.currentPageNumber - 1;
        this.pdfJSViewer.eventBus.on('pagesloaded', () => {
            this.#pos = this.#normalizaPosicaoPagina(new Point(this._pos.x, this._pos.y));
            this.view = this.pdfJSViewer.getPageView(this.paginaCarimbo);
            this.view.eventBus.on('scalechanging', () => {
                if (this.widget) {
                    this.#atualizaWidget();
                }
            });
            this.view.eventBus.on(
                'pagerendered',
                /**
                 * @param {{pageNumber: number}} _
                 */
                ({ pageNumber }) => {
                    if (pageNumber == this.paginaCarimbo + 1 && this.widget) {
                        this.view.div.append(this.widget);
                        this.#atualizaWidget();
                        parent.postMessage(
                            [
                                '__govbr_pdfjs_carimbo_change',
                                this.frameId,
                                {
                                    pos: this.#normalizaPosicaoPagina(this._pos),
                                    tam: this._tam,
                                    pag: this.paginaCarimbo,
                                },
                            ],
                            '*'
                        );
                        setTimeout(() => {
                            this.widget.classList.add('govbr-widget-assinatura-pdf-visivel');
                        }, 0);
                    }
                }
            );
            if (fnResolveView) {
                fnResolveView();
                fnResolveView = null;
            }
        });
        /**
         * @type {number[]}
         */
        const paginas = [];
        this.pdfJSViewer.eventBus.on('scalechanging', () => {
            paginas.forEach((pg) => {
                this.atualizaAnotacaoAssinaturaPDF(pg);
            });
        });
        this.pdfJSViewer.eventBus.on(
            'pagerendered',
            /**
             * @param {{pageNumber: number}} _
             */
            ({ pageNumber }) => {
                this.atualizaAnotacaoAssinaturaPDF(pageNumber);
                paginas.push(pageNumber);
            }
        );
    }

    /**
     * Remove os botões que devem ser desativados conforme as configurações.
     * @param {import("./GOVBRPDFJSUserControl.mjs").GovbrPDFJSUCConfig} config
     */
    removeBotoesDesativados(config) {
        let botoesRemover = ['secondaryOpenFile'];
        if (!config.permiteImprimir) {
            botoesRemover = botoesRemover.concat(['print', 'secondaryPrint']);
        }
        if (!config.permiteDownload) {
            botoesRemover = botoesRemover.concat(['download', 'secondaryDownload']);
        }
        if (!config.permiteAnotacoes) {
            botoesRemover = botoesRemover.concat(['editorModeButtons', 'editorModeSeparator']);
        }
        botoesRemover.forEach((botao) => document.querySelector('#' + botao)?.remove());
    }

    /**
     * Atualiza as anotações de assinatura no PDF para a página
     * @param {number} pagina
     */
    async atualizaAnotacaoAssinaturaPDF(pagina) {
        const view = this.pdfJSViewer.getPageView(pagina - 1);
        const assinaturas = await view.pdfPage.getAnnotations();
        assinaturas.forEach(
            /**
             * @param {any} assinatura
             */
            (assinatura) => {
                if (assinatura.subtype === 'Widget' && assinatura.fieldType == 'Sig') {
                    if (!this.anotacoes[assinatura.fieldName]) {
                        this.anotacoes[assinatura.fieldName] = document.createElement('div');
                        this.anotacoes[assinatura.fieldName].classList.add('govbr-assinatura-pdf');
                        this.anotacoes[assinatura.fieldName].addEventListener('click', () => {
                            parent.postMessage(
                                [
                                    '__govbr_pdfjs_click_assinatura',
                                    this.frameId,
                                    {
                                        id: assinatura.id,
                                        fieldName: assinatura.fieldName,
                                    },
                                ],
                                '*'
                            );
                        });
                    }
                    view.div.append(this.anotacoes[assinatura.fieldName]);
                    const obj = this.anotacoes[assinatura.fieldName];
                    const viewport = view.viewport;
                    // Formato: [infEsq.x, infEsq.y, supDir.x, supDir.y]
                    const posViewport = viewport.convertToViewportRectangle(assinatura.rect);
                    obj.style.setProperty('--escala', view.scale);
                    obj.style.setProperty('--posicao-x', posViewport[0] + 'px');
                    obj.style.setProperty('--posicao-y', posViewport[3] + 'px');
                    obj.style.setProperty('--tamanho-x', (posViewport[2] - posViewport[0]) / view.scale + 'px');
                    obj.style.setProperty('--tamanho-y', (posViewport[1] - posViewport[3]) / view.scale + 'px');
                }
            }
        );
    }

    /**
     * Exibe o widget de assinatura sobre o componente.
     */
    async exibeWidgetComponente(scroll = true) {
        await this.promiseView;
        if (this.paginaCarimbo != this.pdfJSViewer.currentPageNumber - 1) {
            this.paginaCarimbo = this.pdfJSViewer.currentPageNumber - 1;
            this.view = this.pdfJSViewer.getPageView(this.paginaCarimbo);
        }
        if (!this.widget) {
            this.#montaWidgetComponente();
        }
        const promise = new Promise((resolve) => {
            setTimeout(() => {
                // Espera um tempo para scrolar o widget para visão para que ele renderize corretamente ou o scrollIntoView irá falhar.
                this.widget.classList.add('govbr-widget-assinatura-pdf-visivel');
                if (scroll) {
                    this.widget.scrollIntoView({
                        behavior: 'smooth',
                        block: 'center',
                    });
                }
                resolve();
            }, 0);
        });

        this.view.div.append(this.widget);
        this.#atualizaWidget();
        parent.postMessage(
            [
                '__govbr_pdfjs_carimbo_change',
                this.frameId,
                {
                    pos: this.#normalizaPosicaoPagina(this._pos),
                    tam: this._tam,
                    pag: this.paginaCarimbo,
                },
            ],
            '*'
        );
        await promise;
    }

    /**
     * Oculta o widget de assinatura sendo exibido.
     */
    async ocultaWidgetComponente() {
        await this.promiseView;
        if (!this.widget) {
            // Caso não tenha o widget, dispara o exibe para montar o widget antes.
            await this.exibeWidgetComponente(false);
        }
        this.widget.classList.remove('govbr-widget-assinatura-pdf-visivel');
        this.widget.remove();
    }

    /**
     * Monta o widget de assinatura.
     */
    #montaWidgetComponente() {
        this.widget = document.createElement('div');
        this.widget.classList.add('govbr-widget-assinatura-pdf');
        this.widget.append(this.canvas);

        let lastX = -1;
        let lastY = -1;
        this.widget.addEventListener('pointerdown', (event) => {
            this.arrastando = true;
            lastX = event.clientX + this.pdfJSViewer.container.scrollLeft;
            lastY = event.clientY + this.pdfJSViewer.container.scrollTop;
        });
        this.pdfJSViewer.container.addEventListener(
            'pointermove',
            /**
             * @param {PointerEvent} event
             */
            (event) => {
                if (this.arrastando) {
                    if (event.pressure === 0) {
                        this.arrastando = false;
                        this.#pos = this.#normalizaPosicaoPagina(this.#pos);
                        this.#atualizaWidget();
                    } else {
                        let pgDeltaX = 0;
                        let pgDeltaY = 0;
                        const tg = /** @type {HTMLElement} */ (event.target);
                        if (tg.parentElement?.classList.contains('page') && tg.parentElement != this.view.div && tg.parentElement.dataset.pageNumber) {
                            /**
                             * @type {HTMLElement}
                             */
                            const novaPagina = tg.parentElement;
                            const pagAtual = this.view.div;
                            const bbNovaPag = novaPagina.getBoundingClientRect();
                            const bbPagAtual = pagAtual.getBoundingClientRect();
                            if (novaPagina.dataset.pageNumber > pagAtual.dataset.pageNumber) {
                                pgDeltaX = bbNovaPag.x + bbNovaPag.width - (bbPagAtual.x + bbPagAtual.width);
                                pgDeltaY = bbNovaPag.y + bbNovaPag.height - (bbPagAtual.y + bbPagAtual.height);
                            } else if (novaPagina.dataset.pageNumber < pagAtual.dataset.pageNumber) {
                                pgDeltaX = bbNovaPag.x - bbPagAtual.x;
                                pgDeltaY = bbNovaPag.y - bbPagAtual.y;
                            }
                            this.paginaCarimbo = (parseInt(novaPagina.dataset.pageNumber) || 1) - 1;
                            this.view = this.pdfJSViewer.getPageView(this.paginaCarimbo);
                            this.view.div.append(this.widget);
                        }
                        const viewport = this.view.viewport;
                        const lastPos = viewport.convertToPdfPoint(lastX, lastY);
                        const newPos = viewport.convertToPdfPoint(
                            event.clientX + this.pdfJSViewer.container.scrollLeft - pgDeltaX,
                            event.clientY + this.pdfJSViewer.container.scrollTop - pgDeltaY
                        );
                        const deltaX = -(lastPos[0] - newPos[0]);
                        const deltaY = -(lastPos[1] - newPos[1]);
                        this.#pos = new Point(this.#pos.x + deltaX, this.#pos.y + deltaY);
                        this.#atualizaWidget();
                        lastX = event.clientX + this.pdfJSViewer.container.scrollLeft;
                        lastY = event.clientY + this.pdfJSViewer.container.scrollTop;
                        window.getSelection().empty();
                    }
                }
            }
        );
        this.pdfJSViewer.container.addEventListener('pointerup', () => {
            if (this.arrastando) {
                this.arrastando = false;
                this.#pos = this.#normalizaPosicaoPagina(this.#pos);
                this.#atualizaWidget();
            }
        });
    }

    /**
     * Atualiza a posição e escala do widget de assinatura.
     */
    #atualizaWidget() {
        if (!this.widget) {
            return;
        }
        this.widget.style.setProperty('--escala', this.view.scale);
        const viewport = this.view.viewport;
        const posNormalizada = this.#normalizaPosicaoPagina(this.#pos);
        const posViewport = viewport.convertToViewportPoint(posNormalizada.x, posNormalizada.y);
        const bBox = this.widget.getBoundingClientRect();
        this.widget.style.setProperty('--posicao-x', posViewport[0] + 'px');
        this.widget.style.setProperty('--posicao-y', posViewport[1] - bBox.height + 'px');
    }

    /**
     * Normaliza a posição para ficar dentro da página.
     * @param {Point} pos
     */
    #normalizaPosicaoPagina(pos) {
        if (!this.widget) {
            return pos;
        }
        const viewport = this.view.viewport;
        const bBox = this.widget.getBoundingClientRect();
        const tamWidget = viewport.convertToPdfPoint(bBox.width, bBox.height);
        tamWidget[1] = viewport.rawDims.pageHeight - tamWidget[1];
        let x = pos.x;
        if (x < 4) {
            x = 4;
        } else if (x + tamWidget[0] > viewport.rawDims.pageWidth - 4) {
            x = viewport.rawDims.pageWidth - tamWidget[0] - 4;
        }
        let y = pos.y;
        if (y < 4) {
            y = 4;
        } else if (y + tamWidget[1] > viewport.rawDims.pageHeight - 4) {
            y = viewport.rawDims.pageHeight - tamWidget[1] - 4;
        }
        this._tam = new Point(tamWidget[0], tamWidget[1]);
        return new Point(x, y);
    }

    /**
     * Retorna o tamaho do carimbo na página.
     * @returns {Point}
     */
    getTamanhoCarimbo() {
        return this._tam;
    }

    /**
     * Retorna a posição do carimbo na página.
     * @returns {Point}
     */
    getPosicaoCarimboPagina() {
        return this.#normalizaPosicaoPagina(this.#pos);
    }

    /**
     * Define a posição do carimbo na página.
     * @param {PointObj} posicao
     */
    setPosicaoCarimboPagina(posicao) {
        this.#pos = this.#normalizaPosicaoPagina(new Point(posicao.x, posicao.y));
        this.#atualizaWidget();
    }

    /**
     * Retorna a página do carimbo, indexada em 0.
     * @returns {number}
     */
    getPaginaCarimbo() {
        return this.paginaCarimbo;
    }

    /**
     * Define a página do carimbo, indexada em 0.
     * @param {number|string} pagina
     */
    setPaginaCarimbo(pagina) {
        this.paginaCarimbo = typeof pagina === 'number' ? pagina : parseInt(pagina);
        this.view = this.pdfJSViewer.getPageView(this.paginaCarimbo);
        this.#atualizaWidget();
    }

    /**
     * Define se o widget possui ou não um canvas para previsualizar o conteúdo.
     * @param {boolean} possui
     */
    setPossuiCanvas(possui) {
        if (!this.widget) {
            return;
        }
        possui ? this.widget.classList.add('govbr-widget-assinatura-possui-canvas') : this.widget.classList.remove('govbr-widget-assinatura-possui-canvas');
        this.#atualizaWidget();
    }

    get #pos() {
        return this._pos;
    }

    set #pos(pos) {
        if (!pos || isNaN(pos.x) || isNaN(pos.y)) {
            return;
        }
        let disparaEvento = false;
        if (this._pos.x != pos.x || this._pos.y != pos.y) {
            disparaEvento = true;
        }
        this._pos = pos;
        if (disparaEvento && this.view) {
            parent.postMessage(
                [
                    '__govbr_pdfjs_carimbo_change',
                    this.frameId,
                    {
                        pos: this.#normalizaPosicaoPagina(this._pos),
                        tam: this._tam,
                        pag: this.paginaCarimbo,
                    },
                ],
                '*'
            );
        }
    }
}

// Caso esteja no browser, disponibiliza o widget no objeto de window.
if (typeof window !== 'undefined') {
    // @ts-ignore
    window['GOVBRPDFJSWidgetAssinatura'] = GOVBRPDFJSWidgetAssinatura;
}
export default GOVBRPDFJSWidgetAssinatura;
