/*
 * Copyright 2016 Palantir Technologies, Inc. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

import * as Classes from "../common/classes";

/* eslint-disable @typescript-eslint/no-extraneous-class */

/**
 * Efficiently detect when an HTMLElement is resized.
 *
 * Attaches an invisible "resize-sensor" div to the element. Then it checks
 * the element's offsetWidth and offsetHeight whenever a scroll event is
 * triggered on the "resize-sensor" children. These events are further
 * debounced using requestAnimationFrame.
 *
 * Inspired by: https://github.com/marcj/css-element-queries/blob/master/src/ResizeSensor.js
 */
export class ResizeSensor {
    public static attach(element: HTMLElement, callback: () => void) {
        const lifecycle = ResizeSensor.debounce(callback);

        const resizeSensor = document.createElement("div") as HTMLElement;
        resizeSensor.className = Classes.TABLE_RESIZE_SENSOR;
        resizeSensor.style.cssText = ResizeSensor.RESIZE_SENSOR_STYLE;
        resizeSensor.innerHTML = ResizeSensor.RESIZE_SENSOR_HTML;

        element.appendChild(resizeSensor);

        if (getComputedStyle(element, null).getPropertyValue("position") === "static") {
            element.style.position = "relative";
        }

        const expand = resizeSensor.childNodes[0] as HTMLElement;
        const expandChild = expand.childNodes[0] as HTMLElement;
        const shrink = resizeSensor.childNodes[1] as HTMLElement;

        const reset = () => {
            expandChild.style.width = "100000px";
            expandChild.style.height = "100000px";
            expand.scrollLeft = 100000;
            expand.scrollTop = 100000;
            shrink.scrollLeft = 100000;
            shrink.scrollTop = 100000;
        };
        reset();

        let lastWidth: number;
        let lastHeight: number;
        const onScroll = () => {
            if (element == null) {
                return;
            }
            const currentWidth = element.offsetWidth;
            const currentHeight = element.offsetHeight;
            if (currentWidth !== lastWidth || currentHeight !== lastHeight) {
                lastWidth = currentWidth;
                lastHeight = currentHeight;
                lifecycle.trigger();
            }
            reset();
        };
        expand.addEventListener("scroll", onScroll);
        shrink.addEventListener("scroll", onScroll);

        return () => {
            element.removeChild(resizeSensor);
            lifecycle.cancelled = true;
        };
    }

    private static RESIZE_SENSOR_STYLE =
        "position: absolute; left: 0; top: 0; right: 0; " +
        "bottom: 0; overflow: hidden; z-index: -1; visibility: hidden;";

    private static RESIZE_SENSOR_HTML = `<div class="${Classes.TABLE_RESIZE_SENSOR_EXPAND}"
        style="${ResizeSensor.RESIZE_SENSOR_STYLE}"><div style="position: absolute; left: 0; top: 0; transition: 0s;"
        ></div></div><div class="${Classes.TABLE_RESIZE_SENSOR_SHRINK}" style="${ResizeSensor.RESIZE_SENSOR_STYLE}"
        ><div style="position: absolute; left: 0; top: 0; transition: 0s; width: 200%; height: 200%;"></div></div>`;

    private static debounce(callback: () => void) {
        const scope = {
            cancelled: false,
            trigger: () => {
                if (scope.triggered || scope.cancelled) {
                    return;
                }
                scope.triggered = true;
                requestAnimationFrame(() => {
                    scope.triggered = false;
                    if (!scope.cancelled) {
                        callback();
                    }
                });
            },
            triggered: false,
        };
        return scope;
    }
}
