import React from 'react';
import PropTypes from 'prop-types';
import hoistNonReactStatic from 'hoist-non-react-statics';
import isEqual from 'lodash.isequal';

import { MENUS_TO_SCHEMA } from '../constants/menus';

const postMessageActions = {
    SETTINGS: 'settings',
    REORDER: 'reorder',
    MENUS: 'menus'
};

const dragEventType = {
    DRAG_START: 'dragStart',
    DRAG_OVER: 'dragOver',
    DRAG_END: 'dragEnd'
};

const parseSettings = (settings = {}, overrideMap = {}) => {
    const mappedSettings = { ...settings };
    Object.entries(mappedSettings).forEach(([settingProperty, settingContent]) => {
        if (overrideMap[settingProperty]) {
            mappedSettings[settingProperty] = overrideMap[settingProperty];
        } else if (Object.prototype.toString.call(settingContent) === '[object Object]') {
            const { value = null } = settingContent;
            if (value !== null) {
                mappedSettings[settingProperty] = value;
            }
        }
    });
    return Object.keys(mappedSettings).length !== 0 ? mappedSettings : undefined;
};

const withListener = WrappedComponent => {
    class Listener extends React.Component {
        constructor(props) {
            super(props);
            const { id, data, settings, orderableComponents, selectedComponentId, replaceIndex, menus } = props;
            const parsedSettings = parseSettings(settings);
            const WrappedComponentDefaultData = WrappedComponent.defaultProps.data || {};

            this.state = {
                id,
                data: {
                    ...WrappedComponentDefaultData,
                    ...data
                },
                settings: parsedSettings,
                menus,
                orderableComponents,
                selectedComponentId,
                replaceIndex
            };
        }

        componentDidMount() {
            window.addEventListener('message', this.onChangeMessage, false);
        }

        componentDidUpdate(prevProps) {
            const { data, settings } = this.props;
            const newState = {};

            // For components that use adapter to connect with graphQL,
            // we need update the data state when the adapter parsed and mapped
            // the new data to a component
            if (prevProps.data !== data) {
                newState.data = data;
            }

            // Storybook 6.3+ knobs calls update instead of re-mounting component
            if (typeof window !== 'undefined'
                        && window.STORYBOOK_ENV
                        && window.STORYBOOK_ENV === 'react'
                        && !isEqual(prevProps.settings, settings)) {
                newState.settings = settings;
            }

            if (newState.data || newState.settings) {
                this.setState(newState); // eslint-disable-line react/no-did-update-set-state
            }
        }

        componentWillUnmount() {
            window.removeEventListener('message', this.onChangeMessage, false);
        }

        // On adding or deleting of an orderable component update orderableComponents state
        // by making sure to not loose data and saved settings for existing orderable component
        updateOrderableComponents = orderableComponentsParam => {
            const { orderableComponents } = this.state;
            const updatedOrderableComponents = orderableComponentsParam.map(element => {
                for (let i = 0; i < orderableComponents.length; i += 1) {
                    if (element.id === orderableComponents[i].id) {
                        return orderableComponents[i];
                    }
                }
                const { id, name } = element;

                // get data for newly added component in order to properly see the
                // component in preview
                const data = window.SFB_UI_RENDER.get_ad_data(
                    window.dataStore,
                    null,
                    window.__ADComponents[name] // eslint-disable-line no-underscore-dangle
                );
                return {
                    id,
                    name,
                    data
                };
            });
            this.setState({
                orderableComponents: updatedOrderableComponents
            });
        };

        updateOrderableComponentSettings = (componentId, parsedSettings, isPreviewMode) => {
            if (componentId.includes('orderable')) {
                const { orderableComponents } = this.state;
                const updatedOrderableComponents = orderableComponents.map(element => {
                    if (componentId === element.id) {
                        return {
                            id: element.id,
                            name: element.name,
                            settings: parsedSettings,
                            data: element.data
                        };
                    }
                    return element;
                });
                this.setState({
                    orderableComponents: updatedOrderableComponents
                });
            } else {
                const { settings: currentSettings } = this.state;

                this.setState({
                    settings: {
                        ...currentSettings,
                        ...parsedSettings
                    },
                    isPreviewMode
                });
            }
        };

        handleMessageSettings = ({
            componentId,
            settings,
            overrideMap,
            orderableComponents,
            isPreviewMode = false
        }) => {
            const { id } = this.state;
            const parsedSettings = parseSettings(settings, overrideMap);
            const { settings: currentSettings } = this.state;
            if (componentId === id) {
                this.setState({
                    settings: {
                        ...currentSettings,
                        ...parsedSettings
                    },
                    isPreviewMode
                });
            } else if (!orderableComponents && componentId) {
                this.updateOrderableComponentSettings(componentId, parsedSettings, isPreviewMode);
            } else if (orderableComponents) {
                this.updateOrderableComponents(orderableComponents);
            }
        };

        handleMessageMenus = ({
            menus,
            isPreviewMode = false
        }) => {
            const { menus: currentMenus } = this.state;
            const updatedMenusState = {};
            // menus = { footer: {...}, bottomBar: {...}, ...}
            Object.entries(menus).forEach(([menuKey, menuValue]) => {
                const { value: { data }, type, isLocalized } = menuValue;
                // footer: FOOTER_MENU, bottomBar: BOTTOM_BAR_MENU, ...
                const menuSchema = MENUS_TO_SCHEMA[menuKey];

                if (menuSchema) {
                    updatedMenusState[menuSchema] = {
                        menu: data,
                        menuType: type,
                        isLocalized
                    };
                }
            });

            this.setState({
                menus: {
                    ...currentMenus,
                    ...updatedMenusState
                },
                isPreviewMode
            });
        }

        handleMessageReorder = ({ selectedComponentId, dragEvent, replaceIndex }) => {
            const { replaceIndex: replaceIndexState } = this.state;

            switch (dragEvent) {
                case dragEventType.DRAG_START:
                    this.setState({
                        selectedComponentId
                    });
                    break;
                case dragEventType.DRAG_OVER:
                    if (replaceIndex !== replaceIndexState) {
                        this.setState({
                            replaceIndex
                        });
                    }
                    break;
                case dragEventType.DRAG_END:
                    this.setState({
                        replaceIndex: null,
                        selectedComponentId
                    });
                    break;
                default:
                    break;
            }
        };

        onChangeMessage = ({ data = {} }) => {
            const { postMessageAction } = data;
            switch (postMessageAction) {
                case postMessageActions.SETTINGS:
                    this.handleMessageSettings(data);
                    break;
                case postMessageActions.REORDER:
                    this.handleMessageReorder(data);
                    break;
                case postMessageActions.MENUS:
                    this.handleMessageMenus(data);
                    break;

                default:
                    break;
            }
        };

        render() {
            return <WrappedComponent {...this.state} />;
        }
    }

    hoistNonReactStatic(Listener, WrappedComponent);

    Listener.propTypes = {
        id: PropTypes.string,
        data: PropTypes.shape({}),
        settings: PropTypes.shape({}),
        menus: PropTypes.shape({}),
        orderableComponents: PropTypes.arrayOf(PropTypes.shape({})),
        selectedComponentId: PropTypes.string,
        replaceIndex: PropTypes.number
    };

    Listener.defaultProps = {
        id: '',
        data: {},
        settings: {},
        menus: {},
        orderableComponents: [],
        selectedComponentId: '',
        replaceIndex: null,
        ...WrappedComponent.defaultProps
    };

    Listener.schema = WrappedComponent.schema;

    return Listener;
};
export { parseSettings };
export default withListener;
