import Rectangle from "../../core/geom/2d/Rectangle.js";
import { VisualTip } from "./VisualTip.js";
import Signal from "../../core/events/signal/Signal.js";
import ObservedBoolean from "../../core/model/ObservedBoolean.js";
import { DomSizeObserver } from "../util/DomSizeObserver.js";
import { SignalBinding } from "../../core/events/signal/SignalBinding.js";
import { MouseEvents } from "../../engine/input/devices/events/MouseEvents.js";

export class DomTooltipObserver {
    /**
     *
     * @param {View} view
     * @param {function} factory
     * @param {*} [factoryContext]
     */
    constructor(view, factory, factoryContext) {
        /**
         *
         * @type {View}
         */
        this.view = view;

        /**
         *
         * @type {Function}
         */
        this.factory = factory;

        const tipTargetRectangle = new Rectangle();

        /**
         *
         * @type {Rectangle}
         */
        this.tipTargetRectangle = tipTargetRectangle;

        /**
         *
         * @type {VisualTip}
         */
        this.tip = new VisualTip(tipTargetRectangle, factory, factoryContext);

        this.on = {
            entered: new Signal(),
            exited: new Signal()
        };

        const isEntered = this.isEntered = new ObservedBoolean(false);

        const sizeObserver = new DomSizeObserver();
        this.sizeObserver = sizeObserver;


        this.bindings = [
            new SignalBinding(view.position.onChanged, this.__copyDimensions, this),
            new SignalBinding(view.size.onChanged, this.__copyDimensions, this),
            new SignalBinding(sizeObserver.dimensions.position.onChanged, this.__copyDimensionsFromBoundingRect, this),
            new SignalBinding(sizeObserver.dimensions.size.onChanged, this.__copyDimensionsFromBoundingRect, this)
        ];


        this.handleMouseEnter = () => {
            isEntered.set(true);

            this.on.entered.send0();

            sizeObserver.attach(view.el);
            sizeObserver.start();
        }

        this.handleMouseLeave = () => {
            isEntered.set(false);

            sizeObserver.stop();

            this.on.exited.send0();

        }

    }

    __copyDimensionsFromBoundingRect() {
        const d = this.sizeObserver.dimensions;

        this.tipTargetRectangle.copy(d);
    };

    __copyDimensions() {
        const view = this.view;

        const position = view.position;
        const scale = view.scale;
        const size = view.size;

        const r = this.tipTargetRectangle;

        r.position.set(position.x, position.y);
        r.size.set(size.x * scale.x, size.y * scale.y);
    }

    __handleViewLinked() {
        const view = this.view;

        const el = view.el;

        //ensure that the element can capture pointer events
        el.style.pointerEvents = "auto";

        el.addEventListener(MouseEvents.Enter, this.handleMouseEnter);
        el.addEventListener(MouseEvents.Leave, this.handleMouseLeave);

        const bindings = this.bindings;
        const binding_count = bindings.length;
        for (let i = 0; i < binding_count; i++) {
            const b = bindings[i];
            b.link();
        }

        this.__copyDimensions();
    }


    __handleViewUnlinked() {
        const view = this.view;

        const el = view.el;

        el.removeEventListener(MouseEvents.Enter, this.handleMouseEnter);
        el.removeEventListener(MouseEvents.Leave, this.handleMouseLeave);

        const bindings = this.bindings;
        const binding_count = bindings.length;
        for (let i = 0; i < binding_count; i++) {
            const b = bindings[i];
            b.unlink();
        }

        if (this.isEntered.getValue()) {
            //remove tip
            this.handleMouseLeave();
        }
    };

    link() {
        const view = this.view;

        if (view.isLinked) {
            this.__handleViewLinked();
        }

        view.on.linked.add(this.__handleViewLinked, this);
        view.on.unlinked.add(this.__handleViewUnlinked, this);

    }

    unlink() {
        const view = this.view;

        if (view.isLinked) {
            this.__handleViewUnlinked();
        }

        view.on.linked.remove(this.__handleViewLinked, this);
        view.on.unlinked.remove(this.__handleViewUnlinked, this);
    }
}
