/**
 * Copyright 2015 CANAL+ Group
 *
 * 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 config from "../config";
import log from "../log";
import isNonEmptyString from "../utils/is_non_empty_string";
import isNullOrUndefined from "../utils/is_null_or_undefined";
import noop from "../utils/noop";
import SharedReference from "../utils/reference";
import isNode from "./is_node";
var BROWSER_PREFIXES = ["", "webkit", "moz", "ms"];
var pixelRatio = isNode ||
    window.devicePixelRatio == null ||
    window.devicePixelRatio === 0 ? 1 :
    window.devicePixelRatio;
/**
 * Find the first supported event from the list given.
 * @param {HTMLElement} element
 * @param {string} eventNameSuffix
 * @returns {Boolean}
 */
function isEventSupported(element, eventNameSuffix) {
    var clone = document.createElement(element.tagName);
    var eventName = "on" + eventNameSuffix;
    if (eventName in clone) {
        return true;
    }
    else {
        clone.setAttribute(eventName, "return;");
        return typeof clone[eventName] === "function";
    }
}
/**
 * Find the first supported event from the list given.
 * @param {HTMLElement} element
 * @param {Array.<string>} eventNames
 * @returns {string|undefined}
 */
function findSupportedEvent(element, eventNames) {
    return eventNames
        .filter(function (name) { return isEventSupported(element, name); })[0];
}
/**
 * @param {Array.<string>} eventNames
 * @param {Array.<string>|undefined} prefixes
 * @returns {Array.<string>}
 */
function eventPrefixed(eventNames, prefixes) {
    return eventNames.reduce(function (parent, name) {
        return parent.concat((prefixes === undefined ? BROWSER_PREFIXES :
            prefixes)
            .map(function (p) { return p + name; }));
    }, []);
}
/**
 * Returns a function allowing to add event listeners for particular event(s)
 * optionally automatically adding browser prefixes if needed.
 * @param {Array.<string>} eventNames - The event(s) to listen to. If multiple
 * events are set, the event listener will be triggered when any of them emits.
 * @param {Array.<string>|undefined} [prefixes] - Optional vendor prefixes with
 * which the event might also be sent. If not defined, default prefixes might be
 * tested.
 * @returns {Function} - Returns function allowing to easily add a callback to
 * be triggered when that event is emitted on a given event target.
 */
function createCompatibleEventListener(eventNames, prefixes) {
    var mem;
    var prefixedEvents = eventPrefixed(eventNames, prefixes);
    return function (element, listener, cancelSignal) {
        if (cancelSignal.isCancelled()) {
            return;
        }
        // if the element is a HTMLElement we can detect
        // the supported event, and memoize it in `mem`
        if (element instanceof HTMLElement) {
            if (typeof mem === "undefined") {
                mem = findSupportedEvent(element, prefixedEvents);
            }
            if (isNonEmptyString(mem)) {
                element.addEventListener(mem, listener);
                cancelSignal.register(function () {
                    if (mem !== undefined) {
                        element.removeEventListener(mem, listener);
                    }
                });
            }
            else {
                if (0 /* __ENVIRONMENT__.CURRENT_ENV */ === 1 /* __ENVIRONMENT__.DEV */) {
                    log.warn("compat: element ".concat(element.tagName) +
                        " does not support any of these events: " +
                        prefixedEvents.join(", "));
                }
                return;
            }
        }
        prefixedEvents.forEach(function (eventName) {
            var hasSetOnFn = false;
            if (typeof element.addEventListener === "function") {
                element.addEventListener(eventName, listener);
            }
            else {
                hasSetOnFn = true;
                /* eslint-disable @typescript-eslint/no-unsafe-member-access */
                /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
                element["on" + eventName] = listener;
                /* eslint-enable @typescript-eslint/no-unsafe-member-access */
            }
            cancelSignal.register(function () {
                if (typeof element.removeEventListener === "function") {
                    element.removeEventListener(eventName, listener);
                }
                if (hasSetOnFn) {
                    /* eslint-disable @typescript-eslint/no-unsafe-member-access */
                    /* eslint-disable-next-line @typescript-eslint/no-explicit-any */
                    delete element["on" + eventName];
                    /* eslint-enable @typescript-eslint/no-unsafe-member-access */
                }
            });
        });
    };
}
/**
 * Returns a reference:
 *   - set to `true` when the document is visible
 *   - set to `false` when the document is hidden
 * @param {Object} stopListening - `CancellationSignal` allowing to free the
 * ressources allocated to update this value.
 * @returns {Object}
 */
function getDocumentVisibilityRef(stopListening) {
    var prefix;
    var doc = document;
    if (doc.hidden != null) {
        prefix = "";
    }
    else if (doc.mozHidden != null) {
        prefix = "moz";
    }
    else if (doc.msHidden != null) {
        prefix = "ms";
    }
    else if (doc.webkitHidden != null) {
        prefix = "webkit";
    }
    var hidden = isNonEmptyString(prefix) ? prefix + "Hidden" :
        "hidden";
    var visibilityChangeEvent = isNonEmptyString(prefix) ? prefix + "visibilitychange" :
        "visibilitychange";
    var isHidden = document[hidden];
    var ref = new SharedReference(!isHidden, stopListening);
    addEventListener(document, visibilityChangeEvent, function () {
        var isVisible = !(document[hidden]);
        ref.setValueIfChanged(isVisible);
    }, stopListening);
    return ref;
}
/**
 * Returns a reference:
 *   - Set to `true` when the current page is considered visible and active.
 *   - Set to `false` otherwise.
 * @param {Object} stopListening - `CancellationSignal` allowing to free the
 * resources allocated to update this value.
 * @returns {Object}
 */
function getPageActivityRef(stopListening) {
    var isDocVisibleRef = getDocumentVisibilityRef(stopListening);
    var currentTimeout;
    var ref = new SharedReference(true, stopListening);
    stopListening.register(function () {
        clearTimeout(currentTimeout);
        currentTimeout = undefined;
    });
    isDocVisibleRef.onUpdate(function onDocVisibilityChange(isVisible) {
        clearTimeout(currentTimeout); // clear potential previous timeout
        currentTimeout = undefined;
        if (!isVisible) {
            var INACTIVITY_DELAY = config.getCurrent().INACTIVITY_DELAY;
            currentTimeout = window.setTimeout(function () {
                ref.setValueIfChanged(false);
            }, INACTIVITY_DELAY);
        }
        ref.setValueIfChanged(true);
    }, { clearSignal: stopListening, emitCurrentValue: true });
    return ref;
}
/**
 * Get video width from Picture-in-Picture window
 * @param {HTMLMediaElement} mediaElement
 * @param {Object} pipWindow
 * @returns {number}
 */
function getVideoWidthFromPIPWindow(mediaElement, pipWindow) {
    var width = pipWindow.width, height = pipWindow.height;
    var videoRatio = mediaElement.clientHeight / mediaElement.clientWidth;
    var calcWidth = height / videoRatio;
    return Math.min(width, calcWidth);
}
/**
 * Emit when video enters and leaves Picture-In-Picture mode.
 * @param {HTMLMediaElement} elt
 * @param {Object} stopListening
 * @returns {Object}
 */
function getPictureOnPictureStateRef(elt, stopListening) {
    var mediaElement = elt;
    if (mediaElement.webkitSupportsPresentationMode === true &&
        typeof mediaElement.webkitSetPresentationMode === "function") {
        var isWebKitPIPEnabled = mediaElement.webkitPresentationMode === "picture-in-picture";
        var ref_1 = new SharedReference({
            isEnabled: isWebKitPIPEnabled,
            pipWindow: null,
        }, stopListening);
        addEventListener(mediaElement, "webkitpresentationmodechanged", function () {
            var isEnabled = mediaElement.webkitPresentationMode === "picture-in-picture";
            ref_1.setValue({ isEnabled: isEnabled, pipWindow: null });
        }, stopListening);
        return ref_1;
    }
    var isPIPEnabled = (document.pictureInPictureElement === mediaElement);
    var ref = new SharedReference({ isEnabled: isPIPEnabled,
        pipWindow: null }, stopListening);
    addEventListener(mediaElement, "enterpictureinpicture", function (evt) {
        var _a;
        ref.setValue({
            isEnabled: true,
            pipWindow: (_a = evt.pictureInPictureWindow) !== null && _a !== void 0 ? _a : null,
        });
    }, stopListening);
    addEventListener(mediaElement, "leavepictureinpicture", function () {
        ref.setValue({ isEnabled: false, pipWindow: null });
    }, stopListening);
    return ref;
}
/**
 * Returns a reference:
 *   - Set to `true` when video is considered as visible (the page is visible
 *     and/or the Picture-In-Picture is activated).
 *   - Set to `false` otherwise.
 * @param {Object} pipStatus
 * @param {Object} stopListening - `CancellationSignal` allowing to free the
 * resources reserved to listen to video visibility change.
 * @returns {Object}
 */
function getVideoVisibilityRef(pipStatus, stopListening) {
    var isDocVisibleRef = getDocumentVisibilityRef(stopListening);
    var currentTimeout;
    var ref = new SharedReference(true, stopListening);
    stopListening.register(function () {
        clearTimeout(currentTimeout);
        currentTimeout = undefined;
    });
    isDocVisibleRef.onUpdate(checkCurrentVisibility, { clearSignal: stopListening });
    pipStatus.onUpdate(checkCurrentVisibility, { clearSignal: stopListening });
    checkCurrentVisibility();
    return ref;
    function checkCurrentVisibility() {
        clearTimeout(currentTimeout);
        currentTimeout = undefined;
        if (pipStatus.getValue().isEnabled || isDocVisibleRef.getValue()) {
            ref.setValueIfChanged(true);
        }
        else {
            var INACTIVITY_DELAY = config.getCurrent().INACTIVITY_DELAY;
            currentTimeout = window.setTimeout(function () {
                ref.setValueIfChanged(false);
            }, INACTIVITY_DELAY);
        }
    }
}
/**
 * Get video width from HTML video element, or video estimated dimensions
 * when Picture-in-Picture is activated.
 * @param {HTMLMediaElement} mediaElement
 * @param {Object} pipStatusRef
 * @param {Object} stopListening
 * @returns {Object}
 */
function getVideoWidthRef(mediaElement, pipStatusRef, stopListening) {
    var ref = new SharedReference(mediaElement.clientWidth * pixelRatio, stopListening);
    var clearPreviousEventListener = noop;
    pipStatusRef.onUpdate(checkVideoWidth, { clearSignal: stopListening });
    addEventListener(window, "resize", checkVideoWidth, stopListening);
    var interval = window.setInterval(checkVideoWidth, 20000);
    checkVideoWidth();
    stopListening.register(function stopUpdatingVideoWidthRef() {
        clearPreviousEventListener();
        clearInterval(interval);
    });
    return ref;
    function checkVideoWidth() {
        clearPreviousEventListener();
        var pipStatus = pipStatusRef.getValue();
        if (!pipStatus.isEnabled) {
            ref.setValueIfChanged(mediaElement.clientWidth * pixelRatio);
        }
        else if (!isNullOrUndefined(pipStatus.pipWindow)) {
            var pipWindow_1 = pipStatus.pipWindow;
            var firstWidth = getVideoWidthFromPIPWindow(mediaElement, pipWindow_1);
            var onPipResize_1 = function () {
                ref.setValueIfChanged(getVideoWidthFromPIPWindow(mediaElement, pipWindow_1) * pixelRatio);
            };
            pipWindow_1.addEventListener("resize", onPipResize_1);
            clearPreviousEventListener = function () {
                pipWindow_1.removeEventListener("resize", onPipResize_1);
                clearPreviousEventListener = noop;
            };
            ref.setValueIfChanged(firstWidth * pixelRatio);
        }
        else {
            ref.setValueIfChanged(Infinity);
        }
    }
}
/**
 * @param {HTMLMediaElement} mediaElement
 */
var onLoadedMetadata = createCompatibleEventListener(["loadedmetadata"]);
/**
 * @param {HTMLMediaElement} mediaElement
 */
var onTimeUpdate = createCompatibleEventListener(["timeupdate"]);
/**
 * @param {HTMLElement} element
 */
var onFullscreenChange = createCompatibleEventListener(["fullscreenchange", "FullscreenChange"], 
// On IE11, fullscreen change events is called MSFullscreenChange
BROWSER_PREFIXES.concat("MS"));
/**
 * @param {TextTrackList} textTrackList
 */
var onTextTrackAdded = createCompatibleEventListener(["addtrack"]);
/**
 * @param {TextTrackList} textTrackList
 */
var onTextTrackRemoved = createCompatibleEventListener(["removetrack"]);
/**
 * @param {MediaSource} mediaSource
 * @param {Function} listener
 * @param {Object} cancelSignal
 */
var onSourceOpen = createCompatibleEventListener(["sourceopen", "webkitsourceopen"]);
/**
 * @param {MediaSource} mediaSource
 * @param {Function} listener
 * @param {Object} cancelSignal
 */
var onSourceClose = createCompatibleEventListener(["sourceclose", "webkitsourceclose"]);
/**
 * @param {MediaSource} mediaSource
 * @param {Function} listener
 * @param {Object} cancelSignal
 */
var onSourceEnded = createCompatibleEventListener(["sourceended", "webkitsourceended"]);
/**
 * @param {MediaSource} mediaSource
 * @param {Function} listener
 * @param {Object} cancelSignal
 */
var onSourceBufferUpdate = createCompatibleEventListener(["update"]);
/**
 * @param {SourceBufferList} sourceBuffers
 * @param {Function} listener
 * @param {Object} cancelSignal
 */
var onRemoveSourceBuffers = createCompatibleEventListener(["removesourcebuffer"]);
/**
 * @param {MediaKeySession} mediaKeySession
 */
var onKeyMessage = createCompatibleEventListener(["keymessage", "message"]);
/**
 * @param {MediaKeySession} mediaKeySession
 */
var onKeyAdded = createCompatibleEventListener(["keyadded", "ready"]);
/**
 * @param {MediaKeySession} mediaKeySession
 */
var onKeyError = createCompatibleEventListener(["keyerror", "error"]);
/**
 * @param {MediaKeySession} mediaKeySession
 */
var onKeyStatusesChange = createCompatibleEventListener(["keystatuseschange"]);
/**
 * @param {HTMLMediaElement} mediaElement
 */
var onSeeking = createCompatibleEventListener(["seeking"]);
/**
 * @param {HTMLMediaElement} mediaElement
 */
var onSeeked = createCompatibleEventListener(["seeked"]);
/**
 * @param {HTMLMediaElement} mediaElement
 */
var onEnded = createCompatibleEventListener(["ended"]);
/**
 * Utilitary function allowing to add an event listener and remove it
 * automatically once the given `CancellationSignal` emits.
 * @param {EventTarget} elt - The element on which should be attached the event
 * listener.
 * @param {string} evt - The event you wish to listen to
 * @param {Function} listener - The listener function
 * @param {Object} stopListening - Removes the event listener once this signal
 * emits
 */
function addEventListener(elt, evt, listener, stopListening) {
    elt.addEventListener(evt, listener);
    stopListening.register(function () {
        elt.removeEventListener(evt, listener);
    });
}
export { addEventListener, createCompatibleEventListener, getPageActivityRef, getPictureOnPictureStateRef, getVideoVisibilityRef, getVideoWidthRef, onEnded, onFullscreenChange, onKeyAdded, onKeyError, onKeyMessage, onKeyStatusesChange, onLoadedMetadata, onRemoveSourceBuffers, onSeeked, onSeeking, onSourceClose, onSourceEnded, onSourceOpen, onTimeUpdate, onSourceBufferUpdate, onTextTrackAdded, onTextTrackRemoved, };
