/**
 * @type {GOVBRSuggest[]}
 */
const suggestsCriados = [];

/**
 * Classe para exibição de suggest para os componentes da plataforma.
 */
class GOVBRSuggest {
    /**
     * @type {HTMLElement}
     */
    container;
    /**
     * @type {HTMLElement}
     */
    suggestAberto;
    /**
     * @type {boolean}
     */
    ignoraCloseSuggest;
    /**
     * @type {number}
     */
    timeoutReposicionaSuggest;

    fixarSuggest = false;

    forcePosicaoX = '';
    forcePosicaoY = '';

    /**
     * Cria um suggest para ser exibido dentro do alvo. Note que o alvo deve ter position: relative ou o posicionamento estará incorreto.
     * @param {HTMLElement} containerSuggest
     */
    constructor(containerSuggest) {
        this.container = containerSuggest || document.body;
        GOVBRPlataformaJS.importarStyleSheet('', 'suggest', '', 'GOVBRSuggest', containerSuggest);

        document.addEventListener('click', (event) => {
            if (this.ignoraCloseSuggest || this.suggestAberto?.contains?.(/** @type {HTMLElement} */ (event.target))) {
                return;
            }
            this.fechaSuggest();
        });

        suggestsCriados.push(this);
    }

    /**
     * Indica se o Suggest está aberto para o alvo.
     * @param {Node} alvo
     * @returns {boolean}
     */
    isAberto(alvo = null) {
        if (this.suggestAberto) {
            // @ts-ignore
            if (this.suggestAberto.__govbr_alvo === alvo || !alvo) {
                return true;
            }
        }
        return false;
    }

    /**
     * Abre o suggest para o alvo. Retorna a div contendo o conteúdo do suggest
     * @param {Node} alvo
     * @param {Function} fnClose
     */
    abreSuggest(alvo, fnClose = null) {
        if (this.suggestAberto) {
            this.fechaSuggest();
        }
        const divSuggest = document.createElement('div');
        divSuggest.classList.add('govbr-plataforma-suggest');
        this.container.append(divSuggest);
        this.suggestAberto = divSuggest;
        // @ts-ignore
        this.suggestAberto.__govbr_alvo = alvo;
        // @ts-ignore
        this.suggestAberto.__govbr_close = fnClose;
        this.suggestAberto.style.display = 'none';
        // Dá um tempo para montar o conteúdo antes de tentar posicionar.
        this.ignoraCloseSuggest = true;
        setTimeout(() => {
            this.ignoraCloseSuggest = false;
            if (this.suggestAberto) {
                this.suggestAberto.style.display = '';
                this.reposicionaSuggest();
            }
        }, 0);
        return divSuggest;
    }

    /**
     * Atualiza dinâmicamente a posição do suggest. Sempre é disparado automáticamente no scroll do window/container e no resize do window.
     */
    reposicionaSuggest() {
        if (this.timeoutReposicionaSuggest) {
            return;
        }
        if (!this.suggestAberto) {
            return;
        }
        this.timeoutReposicionaSuggest = setTimeout(() => {
            if (!this.suggestAberto) {
                this.timeoutReposicionaSuggest = null;
                return;
            }
            /**
             * @type {{top: number, left: number, bottom: number, right: number}}
             */
            const box = {
                top: null,
                left: null,
                bottom: null,
                right: null,
            };
            this.suggestAberto.style['top'] = '0';
            this.suggestAberto.style['left'] = '0';
            this.suggestAberto.style['bottom'] = 'auto';
            this.suggestAberto.style['right'] = 'auto';
            // @ts-ignore
            const bBoxTarget = this.suggestAberto.__govbr_alvo.getBoundingClientRect();
            const bBoxSuggest = this.suggestAberto.getBoundingClientRect();
            const bBoxSelf = this.container.getBoundingClientRect();

            let posicaoX = 'inicio-apos';
            if (this.forcePosicaoX) {
                posicaoX = this.forcePosicaoX;
            } else if (bBoxTarget.left > window.innerWidth * 0.5 && bBoxTarget.right > window.innerWidth * 0.7) {
                posicaoX = 'fim-antes';
            }
            let posicaoY = 'fim-apos';
            if (this.forcePosicaoY) {
                posicaoY = this.forcePosicaoY;
            } else if (bBoxTarget.top > window.innerHeight * 0.5 && bBoxTarget.bottom > window.innerHeight * 0.7) {
                posicaoY = 'inicio-antes';
            }

            [
                [posicaoX, 'left', 'right', 'x', 'width'],
                [posicaoY, 'top', 'bottom', 'y', 'height'],
            ].forEach(
                /**
                 * @param {[string, 'left'|'top', 'right'|'bottom', 'x'|'y', 'width'|'height']} _
                 */
                ([valor, prop1, prop2, boxPropPosicao, boxPropTamanho]) => {
                    // Calcula o limite de tamanho (innerWidth pra H, innerHeight pra V).
                    // @ts-ignore
                    const limite = window['inner' + boxPropTamanho[0].toUpperCase() + boxPropTamanho.slice(1)];
                    let targetPos = 0;
                    const valores = valor.split('-');
                    const posicao = valores[0];
                    let direcao = valores[1];
                    direcao = direcao || 'apos';
                    targetPos = posicao === 'inicio' ? bBoxTarget[boxPropPosicao] : bBoxTarget[boxPropPosicao] + bBoxTarget[boxPropTamanho];
                    if (direcao == 'apos') {
                        // Define a propriedade inicial (left/top).
                        box[prop1] = targetPos - bBoxSelf[prop1];
                        // Se o tamanho do elemento sairia fora do limite da tela.
                        if (targetPos + bBoxSuggest[boxPropTamanho] > limite) {
                            // Define a propriedade final (right/bottom) para limitar o tamanho.
                            box[prop2] = 0;
                        }
                    } else if (direcao == 'antes') {
                        // Define a propriedade inicial (right/bottom).
                        box[prop2] = limite - targetPos + (bBoxSelf[prop2] - limite);
                        // Se o tamanho do elemento saiu fora do limite da tela.
                        if (targetPos - bBoxSuggest[boxPropTamanho] < 0) {
                            // Define a propriedade final (left,top) para limitar o tamanho.
                            box[prop1] = 0;
                        }
                    }
                }
            );

            Object.entries(box).forEach(([prop, val]) => {
                // @ts-ignore
                this.suggestAberto.style[prop] = typeof val === 'number' ? val + 'px' : '';
            });
            this.timeoutReposicionaSuggest = null;
        }, 0);
    }

    /**
     * Fecha o suggest atualmente aberto.
     */
    fechaSuggest() {
        this.fixarSuggest = false;
        if (this.suggestAberto) {
            // @ts-ignore
            if (this.suggestAberto.__govbr_close) {
                // @ts-ignore
                this.suggestAberto.__govbr_close();
            }
            const suggest = this.suggestAberto;
            this.suggestAberto = null;
            suggest.classList.add('govbr-plataforma-suggest-desaparecer');
            setTimeout(() => {
                // Aguarda a animação.
                suggest.remove();
            }, 500);
        }
    }

    /**
     * Verifica se o alvo é parte interna do suggest.
     * @param {HTMLElement} alvo
     * @returns {boolean}
     */
    isAlvoInterno(alvo) {
        return this.suggestAberto?.contains?.(alvo);
    }

    /**
     * Aplica um suggest de hint ao elemento, este é exibido quando o cursor está sobre o item.
     * @param {Node} alvo
     * @param {string} hint
     */
    aplicaHint(alvo, hint) {
        const fnAbre = () => {
            if (!this.fixarSuggest) {
                const divSuggest = this.abreSuggest(alvo);
                divSuggest.classList.add('govbr-plataforma-suggest-hint');
                divSuggest.innerText = hint;
            }
        };
        alvo.addEventListener('click', () => {
            fnAbre();
            this.fixarSuggest = true;
        });
        alvo.addEventListener('mouseenter', fnAbre);
        alvo.addEventListener('mouseleave', () => {
            if (!this.fixarSuggest) {
                this.fechaSuggest();
            }
        });
    }
}

window.addEventListener(
    'scroll',
    (ev) => {
        suggestsCriados.forEach((e) => {
            if (!e.suggestAberto?.contains?.(/** @type {HTMLElement} */ (ev.target))) {
                e.reposicionaSuggest();
            }
        });
    },
    {
        capture: true,
        passive: true,
    }
);
window.addEventListener('resize', () => {
    suggestsCriados.forEach((e) => e.reposicionaSuggest());
});

/**
 * @type {GOVBRSuggest}
 */
let SuggestGlobal = null;
if (typeof window !== 'undefined') {
    // @ts-ignore
    window['GOVBRSuggest'] = GOVBRSuggest;
    // @ts-ignore
    if (window['__govbr_suggest']) {
        // @ts-ignore
        SuggestGlobal = window['__govbr_suggest'];
    } else {
        SuggestGlobal = new GOVBRSuggest(document.body);
        // @ts-ignore
        window['__govbr_suggest'] = SuggestGlobal;
    }
}
export { SuggestGlobal, GOVBRSuggest };
