/**
 * @module utils/functions
 */
import _ from 'lodash';
import React from 'react';
import ReactDOM from 'react-dom';
import {hashHistory} from 'react-router';
import {isConfigInitialized, getConfig, translate} from '../../utils';

import {History} from 'history';

/**
 * Returns a hash history
 */
export function getSilentRouterHistory(): History {
    if (!hashHistory['_pushStatePatched']) {
        hashHistory['_pushStatePatched'] = true;
        hashHistory['pushState'] = function pushState(state, url) {
            if (typeof console !== 'undefined') {
                // eslint-disable-next-line
                console.error(`Warning: pushState deprecated method for hashHistory`);
            }
            return this.push(url);
        };
    }
    return hashHistory as any;
}

/**
 * Changes document title
 * @TODO Replace with React Helmet
 * @param title
 */
export function setDocumentTitle(title: string): void {
    let suffix = isConfigInitialized()
        ? getConfig().headerAccountBlock || getConfig().brandDisplayName
        : '';
    document.title = title + ' - ' + suffix;
}

/**
 * @param val
 * @return {boolean}
 */
export function isReactComponent(val) {
    return val instanceof React.Component.constructor;
}

/**
 * @param val
 * @return {*|boolean}
 */
export function isReactElement(val): boolean {
    return React.isValidElement(val);
}

/**
 * @param {React.Element} element
 * @param {constructor} type
 * @return {boolean}
 * @example isElementHasType(tabPanelChild, NavDropdown)
 */
export function isElementHasType(element: React.ReactElement<any>, type: any): boolean {
    return element.type === type || (element.type as any).prototype instanceof type;
}

/**
 * Filters empty [null, undefined] children of React component
 * @param {ReactNode} children
 * @returns {React.ReactChild[]}
 */
export function cleanEmptyChildren(children: React.ReactNode): React.ReactChild[] {
    return (React.Children.toArray(children) as any).filter(Boolean);
}

/**
 * @param Component
 * @param container
 * @param params
 */
export function mountComponent(
    Component: React.ReactNode,
    container: Element,
    params: any
): React.Component<any> | Element | void {
    let element;
    //FIXME React Router needs to be created from scratch otherwise a previous state is picked up
    //FIXME Replace all existing routers with router factory functions
    if (typeof Component === 'function' && Component.name === 'createRouter') {
        Component = Component();
    }
    if (isReactElement(Component)) {
        element = Component;
    } else if (isReactComponent(Component)) {
        element = React.createElement(Component as any, params);
    }

    if (!React.isValidElement(element)) {
        // eslint-disable-next-line
        console.error('Cannot render invalid React element', Component);
        throw new Error('Cannot render invalid React element');
    }

    // eslint-disable-next-line react/no-render-return-value
    return ReactDOM.render(element, container);
}

/**
 * @param value
 * @return {boolean}
 */
export function isSafeStringValue(value): boolean {
    if (value.length > 0) {
        return (
            value.indexOf('"') === -1 &&
            value.indexOf('/') === -1 &&
            value.indexOf('<') === -1 &&
            value.indexOf('>') === -1 &&
            value.indexOf('&') === -1
        );
    }
    return true;
}

//PC userAgent: "Mozilla/5.0 (Windows NT 6.1; WOW64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/45.0.2454.85 Safari/537.36"
//MAC userAgent: ""
export let OS = {
    is: {
        mac: /mac\sos\sx/i.test(navigator.userAgent),
        win: /windows/i.test(navigator.userAgent),
        others: null as any
    },
    name: 'others'
};
OS.is.others = !Object.keys(OS.is).some(
    (o: any): any => {
        if (OS.is[o]) {
            OS.name = o;
            return true;
        }
    }
);

/**
 * @param str
 * @return {*}
 */
export function fillBrandName(str: string): string {
    return translate(str, {brandName: getConfig().shortBrandName});
}

/**
 * @param prevState
 * @param newState
 * @return {*}
 */
export function extendFormState<TObject, TSource>(
    prevState: TObject,
    newState: TSource
): TObject & TSource {
    return _.merge(prevState, newState);
}

/**
 * @param prevState
 * @param removedIds
 * @return {*|{}}
 */
export function cleanFormState(prevState: object, removedIds: object): object {
    return _.omit(prevState, removedIds as any);
}

export function wait(mileSeconds): Promise<void> {
    return new Promise(resolve => {
        setTimeout(resolve, mileSeconds);
    });
}

export function deprecation(Component: React.ReactNode | any, message: string): React.Component {
    const name = Component.displayName || Component.name || 'Component';

    class Deprecated extends React.Component {
        static displayName = `Deprecated(${name})`;

        componentDidMount() {
            // eslint-disable-next-line no-console
            console.warn(`Component ${name} is deprecated.`, message || '');
        }

        render() {
            return <Component {...this.props} />;
        }
    }

    return Deprecated as any;
}

const KEY_CODE_ENTER = 13;
const KEY_CODE_SPACE = 32;
const defaultKeyCodesFilter = [KEY_CODE_ENTER, KEY_CODE_SPACE];

export const filterKeyCodes = (
    callback: Function,
    keyCodes: Array<number> = defaultKeyCodesFilter
) => ({keyCode}) => {
    if (_.includes(keyCodes, keyCode)) {
        callback();
    }
};

export const scrollElementIntoView = element => {
    if (element && typeof element.scrollIntoView === 'function') {
        element.scrollIntoView({behavior: 'smooth', block: 'nearest'});
    }
};

export const stripTags = string => string.replace(/(<([^>]+)>)/gi, '');
