import RenderInBody from '../RenderInBody';
import React from 'react';
import Hint from '../Hint';

class HintRoot extends React.Component {
    constructor(props) {
        super(props);
        this.show = this.show.bind(this);
        this.close = this.close.bind(this);
        this.registerObserver = this.registerObserver.bind(this);

        this.state = {
            show: false,
            text: '',
        };

        this.nodesEvents = {
            mouseenter: this.show,
            mouseleave: this.close,
        };

        this.attributeName = 'react-hint';
    }

    componentDidMount() {
        this.observer = new MutationObserver((mutations) => {
            mutations.forEach((mutation) => {
                const node = mutation.target;

                if (node.hasAttribute(`pa-${this.attributeName}`) && !node.hasAttribute('data-handled')) {
                    this.addEventListeners(node);
                    this.registerObserver(node);
                    node.dataset.handled = true;
                }
            });
        });

        this.observer.observe(document, {
            attributes: true,
            childList: true,
            subtree: true,
        });

        document.addEventListener('click', this.targetOnClickHandler);

        document.querySelectorAll(`[pa-${this.attributeName}]:not([data-handled])`).forEach((node) => {
            this.addEventListeners(node);
            this.registerObserver(node);
            node.dataset.handled = true;
        });
    }

    componentWillUnmount() {
        this.observer.disconnect();
        document.removeEventListener('click', this.targetOnClickHandler);

        document.querySelectorAll(`[pa-${this.attributeName}]`).forEach((node) => {
            this.removeEventListeners(node);
        });
    }

    getPosition() {
        if (!this.state.show) return {};
        const targetBounds = this.state.node.getBoundingClientRect();
        const hintNode = this.element;
        const hintBounds = hintNode && hintNode.getBoundingClientRect();
        const position = {
            top: targetBounds.bottom - (hintBounds.height / 2),
            left: targetBounds.left - (hintBounds.width / 2),
            bottom: 'initial',
            right: 'initial',
        };

        position.top += window.pageYOffset;
        position.left += window.pageXOffset;

        return position;
    }

    registerObserver(node) {
        const nodeObserver = new MutationObserver((nodeMutations) => {
            nodeMutations.forEach((nodeMutation) => {
                if (nodeMutation.type === 'attributes' && nodeMutation.attributeName === 'tooltip-text') {
                    this.updateText(null, nodeMutation.target);
                }
            });
        });

        nodeObserver.observe(node, {
            attributes: true,
            attributeOldValue: true,
        });
    }

    addEventListeners(node) {
        const extendNodeEvents = (function() {
            const events = JSON.parse(node.getAttribute('tooltip-events'));
            Object.entries(events).forEach(([key, value]) => {
                this.nodesEvents[key] = this[value];
            });
        }).bind(this);

        if (node.hasAttribute('tooltip-events')) {
            extendNodeEvents();
        }
        Object.entries(this.nodesEvents).forEach(([type, handler]) => {
            node.addEventListener(type, handler);
        });
    }

    removeEventListeners(node) {
        Object.entries(this.nodesEvents).forEach(([type, handler]) => {
            node.removeEventListener(type, handler);
        });
    }

    targetOnClickHandler() {
        if (this.element) {
            this.close();
        }
    }

    show(event) {
        const node = event.currentTarget;

        this.setState({
            node,
            show: true,
            position: node.getAttribute(`${this.attributeName}-position`) || 'right',
            text: node.getAttribute(`${this.attributeName}-text`),
        });

        this.updatePosition();
    }

    updatePosition() {
        Object.entries(this.getPosition()).forEach(([key, value]) => {
            this.element.style[key] = `${Math.round(value)}px`;
        });
    }

    close() {
        this.setState({
            show: false,
        });
    }

    refHandler(el) {
        this.element = el;
        if (this.element) {
            this.element.style.animationDuration = `${this.animationDuration}ms`;
        }
    }

    render() {
        return (
            <RenderInBody>
                <div
                    ref={this.refHandler.bind(this)}
                    className={`Tooltip Tooltip__${this.state.position}`}
                >
                    <Hint message={this.state.text} withIcon={false} isVisible={this.state.show} />
                </div>
            </RenderInBody>
        );
    }
}

export default HintRoot;
