import { loadTranslations, setLocale, Translate, Localize, I18n } from 'react-redux-i18n';
import React, { Component } from 'react';
import ReactDOMServer from 'react-dom/server';
import { connect } from 'react-redux';
import get from 'lodash/get';
import isObject from 'lodash/isObject';
import isString from 'lodash/isString';
import merge from 'lodash/merge';
import Hyphen from 'react-hyphen';
import { getConf } from '../App/configuration';

/**
 * The Redux Store object
 * @type Store
 */
let store;

/**
 * The setup method
 *  - sets the store object used for redux
 *  - sets the locale
 *  - adds the pre-init translations map
 * @param {Store} reduxStore
 */
export const setStore = (reduxStore, initialCode) => {
    if (!reduxStore.getState || !reduxStore.dispatch) {
        throw new Error('A valid redux store must be given!');
    }

    store = reduxStore;

    if (store && initialCode) {
        // set the locale from the configuration
        changeLocale(initialCode);
    }

    // Adds the pre-init translations
    mergeTranslations(translations);
};

/**
 * Global translations object (pre-setup)
 * @type {{}}
 */
export const translations = {};

/**
 * Function to handle the missing translations.
 * It simply replaces the '[DOT]' with '.' in the key and returns that
 * It is needed because the i18n module uses the dot symbol as a property accessor
 * @param {string} key
 * @param replacements
 */
// eslint-disable-next-line no-unused-vars
const HandleMissingTranslation = (key, replacements) => key.replace(/\[DOT\]/g, '.');
I18n.setHandleMissingTranslation(HandleMissingTranslation);

/**
 * Function to merge the local (module related) translations into the global dictionary
 *  - call it with a translation object eg. { en: { greeting: 'Hi!' }, de: { greeting: 'Hallo!' } }
 *  - useful to call it from within a Translation.js file, doing it this way one need only to import it
 * @param {Object} translationsObject
 */
export const mergeTranslations = translationsObject => {
    if (!store) {
        throw new Error('Store must be set up first!');
    }
    const translations = merge(store.getState().i18n.translations || {}, translationsObject);
    store.dispatch(loadTranslations(translations));
};

/**
 * Function to change the locale
 * @param {string} locale
 */
export const changeLocale = locale => {
    if (!store) {
        throw new Error('Store must be set up first!');
    }
    store.dispatch(setLocale(locale.replace('_', '-')));
};

/**
 * Function to return the current locale
 * @return {string} The current locale
 */
export const getLocale = () => {
    if (!store) {
        throw new Error('Store must be set up first!');
    }
    return store.getState().i18n.locale;
};

/**
 * Translates the given text
 * @param {string} value - The text to translate
 * @param {Object} props - The props passed to the Translate Component. Can contain (escapeDot: true)
 * to search with '[DOT]' instead of '.' (which is used as a property accessor)
 * @return {*}
 * @constructor
 */
export const T = (value, props = {}) => {
    // eslint-disable-next-line react/prop-types
    const { escapeDot, ...otherProps } = props;
    if (value === undefined) {
        return value;
    } else if (escapeDot && isString(value)) {
        value = value.replace(/\./g, '[DOT]');
    }
    if (isObject(otherProps)) {
        for (let p in otherProps) {
            // if the replacement is a jsx element, just render it in place
            if (React.isValidElement(otherProps[p])) {
                otherProps[p] = ReactDOMServer.renderToStaticMarkup(otherProps[p]);
            }
        }
    }
    return <Translate value={'' + value} dangerousHTML {...otherProps} />;
};

/**
 * Localize the given value
 * @param {*} value - The value to localize
 * @param {Object} props - The props passed to the Localize Component. Can contain (escapeDot: true)
 * to search with '[DOT]' instead of '.' (which is used as a property accessor)
 * @return {*}
 * @constructor
 */
export const L = (value, props = {}) => {
    // eslint-disable-next-line react/prop-types
    const { escapeDot, ...otherProps } = props;
    if (value === undefined) {
        return value;
    } else if (escapeDot && isString(value)) {
        value = value.replace(/\./g, '[DOT]');
    }
    return <Localize value={'' + value} {...otherProps} />;
};

export const Hyphenated = connect(({ i18n: { locale } }) => ({ locale }))(({ children, locale }) => {
    const hyphenations = getConf('hyphenations');
    if (hyphenations[locale]) {
        return locale === 'en-GB' ? (
            <Hyphen>{children}</Hyphen>
        ) : (
            <Hyphen language={hyphenations[locale]}>{children}</Hyphen>
        );
    }
    return children;
});

/**
 * Function to merge the local (module related) translations into the global dictionary
 *  - call it with a translation object eg. { en: { greeting: 'Hi!' }, de: { greeting: 'Hallo!' } }
 *  - useful to call it from within a Translation.js file, doing it this way one need only to import it
 * @param {Object} translationsObject
 */
export function use(translationsObject) {
    if (store && store.dispatch) {
        // if store exists just call the merge function
        mergeTranslations(translationsObject);
    } else {
        // merge with pre-init translations
        merge(translations, translationsObject);
    }
}

/**
 * Checks if translation exists for the given key
 * @param {string} key - The key of the translation
 * @return {boolean}
 * @constructor
 */
export const translationExists = key => {
    // Copied from react-i18nify, I18n._translate
    const locale = I18n._translations[I18n._locale] ? I18n._locale : I18n._locale.split('-')[0];

    const translations = I18n._translations[locale];

    if (!translations) {
        return false;
    }

    const translation = get(translations, key, null);

    return translation !== null;
};

export default {
    use,
    changeLocale,
    getLocale,
    T,
    L,
};

export function translateComponent(ComponentToTranslate, mapStateToProps, mapDispatchToProps) {
    class TranslatedComponent extends Component {
        constructor(props) {
            super(props);
            // decorate the component, so it can use directly the t() function
            ComponentToTranslate.prototype.t = this.t;
        }

        t() {
            if (store) {
                return I18n.t.apply(I18n, arguments);
            } else {
                throw new Error('Store must be set up first!');
            }
        }

        setRef = ref => {
            this.wrappedInstance = ref;
        };

        render() {
            // if not child of Component return without ref
            if (!ComponentToTranslate.prototype.render) {
                return <ComponentToTranslate {...{ ...this.props, t: this.t }} />;
            }
            return <ComponentToTranslate ref={this.setRef} {...{ ...this.props, t: this.t }} />;
        }
    }

    // mapping state.locale to the locale prop, so the component will update when locale changes
    function mapMergedProps(state) {
        return {
            ...(mapStateToProps instanceof Function && mapStateToProps(state)),
            locale: state.i18n.locale,
        };
    }

    return connect(mapMergedProps, mapDispatchToProps)(TranslatedComponent);
}

export const t = I18n.t.bind(I18n);

export function useTranslationWithoutStore() {
    I18n.setTranslationsGetter(function() {
        return translations;
    });
    I18n.setLocaleGetter(function() {
        return 'en-GB';
    });
}
// eslint-disable-next-line react-hooks/rules-of-hooks
useTranslationWithoutStore();

window.changeLocale = changeLocale;
