import React from 'react';
import hoistNonReactMethods from 'hoist-non-react-methods';
import { st, classes } from './Focusable.st.css.js';
import { getDisplayName, isRefableComponent, isStatelessComponent, } from '../hocUtils';
/**
 * Singleton for managing current input method (keyboard or mouse).
 */
const inputMethod = new (class {
    constructor() {
        // Default is keyboard in case an element is focused programmatically.
        this.method = 'keyboard';
        this.subscribers = new Map();
        this.subscribe = (target, callback) => this.subscribers.set(target, callback);
        this.unsubscribe = (target) => this.subscribers.delete(target);
        /**
         * Is the current input method `keyboard`. if `false` is means it is `mouse`
         */
        this.isKeyboard = () => this.method === 'keyboard';
        if (typeof window !== 'undefined') {
            window.addEventListener('mousedown', () => this.setMethod('mouse'));
            window.addEventListener('keydown', () => this.setMethod('keyboard'));
            // We need to listen on keyUp, in case a TAB is made from the browser's address-bar,
            // so the keyDown is not fired, only the keyUp.
            window.addEventListener('keyup', () => this.setMethod('keyboard'));
        }
    }
    setMethod(method) {
        if (method !== this.method) {
            this.method = method;
            this.subscribers.forEach(f => f());
        }
    }
})();
/*
 * TODO: Consider adding 'disabled' state to this HOC, since:
 * - When component is focused and then it becomes disabled, then the focus needs to be blurred.
 *
 * TODO: Consider using [Recompose](https://github.com/acdlite/recompose/tree/master/src/packages/recompose) to do:
 *  - the static hoisting
 *  - set displayName
 */
export function withFocusable(Component, options = {}) {
    class Focusable extends React.Component {
        constructor() {
            super(...arguments);
            this.wrappedComponentRef = null;
            this.state = {
                focus: false,
                focusVisible: false,
            };
            this.focus = (options) => {
                if (this.wrappedComponentRef?.focus) {
                    this.wrappedComponentRef.focus();
                    // If focusVisible option is explicitly provided, update the state accordingly
                    if (options && options.focusVisible !== undefined) {
                        this.setState({
                            focus: true,
                            focusVisible: options.focusVisible,
                        });
                    }
                }
            };
            this.setWrappedComponentRef = (ref) => {
                this.wrappedComponentRef = ref;
            };
            this.markAsFocused = () => {
                this.setState({
                    focus: true,
                    focusVisible: options.isFocusWithMouse || inputMethod.isKeyboard(),
                });
                inputMethod.subscribe(this, () => {
                    if (options.isFocusWithMouse || inputMethod.isKeyboard()) {
                        this.setState({ focusVisible: true });
                    }
                });
            };
            this.markAsBlurred = () => {
                inputMethod.unsubscribe(this);
                this.setState({ focus: false, focusVisible: false });
            };
            this.onFocus = (event) => {
                const { onFocus } = this.props;
                if (onFocus) {
                    onFocus(event, {
                        blur: this.markAsBlurred,
                        focus: this.markAsFocused,
                    });
                }
                this.markAsFocused();
            };
            this.onBlur = (event) => {
                const { onBlur } = this.props;
                if (onBlur) {
                    onBlur(event, { blur: this.markAsBlurred, focus: this.markAsFocused });
                }
                this.markAsBlurred();
            };
        }
        componentWillUnmount() {
            inputMethod.unsubscribe(this);
        }
        componentDidUpdate(prevProps) {
            /*
              in case when button was focused and then become disabled,
              we need to trigger blur logic and remove all listers, as disabled button
              do not trigger onFocus and onBlur events
            */
            const isFocused = this.state.focus || this.state.focusVisible;
            const isBecomeDisabled = !prevProps.disabled && this.props.disabled;
            if (isFocused && isBecomeDisabled) {
                this.onBlur({});
            }
        }
        render() {
            const reference = isRefableComponent(Component)
                ? this.setWrappedComponentRef
                : undefined;
            return (React.createElement(Component, { ...this.props, ref: reference, focusableOnFocus: this.onFocus, focusableOnBlur: this.onBlur, className: st(classes.root, {
                    focus: this.state.focus,
                    'focus-visible': this.state.focusVisible,
                }, this.props.className) }));
        }
    }
    Focusable.displayName = getDisplayName(Component);
    Focusable.defaultProps = Component.defaultProps;
    return isStatelessComponent(Component)
        ? Focusable
        : hoistNonReactMethods(Focusable, Component, {
            delegateTo: (c) => c.wrappedComponentRef,
            hoistStatics: true,
        });
}
//# sourceMappingURL=Focusable.js.map