import React, { useState } from 'react';
import PropTypes from 'prop-types';
import withListener from '../withListener';
import { createNamespace } from '../../tools/namingTools';
import { useContainerWidth } from '../../tools/widths/useContainerWidth';
import { TOGGLE, DROPDOWN, COLORPICKER } from '../../constants/schemaComponentTypes';
import { bMedium } from '../../constants/breakpoints';
import { generateUniqueID } from '../../tools/helpers';
import { replacePlaceholders } from '../../tools/stringTools';
import { i18nMock } from '../../../mocks/comparableProducts/i18n';
import { comparableProductsDataShape } from '../../constants/components/prop-types/comparable-products';
import './styles/ComparableProducts.scss';
import Button from '../../atoms/button/Button';
import Icon from '../../atoms/icon/Icon';

import { CompareProductHeader } from './atoms/compare-product-header/CompareProductHeader';
import { CompareProductSectionHeader } from './atoms/compare-product-section-header/CompareProductSectionHeader';
import { CompareProductDescription } from './atoms/compare-product-description/CompareProductDescription';
import { CompareProductFeatures } from './atoms/compare-product-features/CompareProductFeatures';
import { CompareProductScreenshots } from './atoms/compare-product-screenshots/CompareProductScreenshots';
import { CompareProductCategories } from './atoms/compare-product-categories/CompareProductCategories';
import { CompareProductDeveloper } from './atoms/CompareProductDeveloper';
import { CompareProductEditions } from './atoms/compare-product-editions/CompareProductEditions';
import { CompareProductSpecifications } from './atoms/CompareProductSpecifications';
import { CompareProductReviews } from './atoms/compare-product-reviews/CompareProductReviews';

export const LEFT = 'left';
export const INLINE = 'inline';

const n = createNamespace('ComparableProducts');

const minItems = 2;
const maxItems = 6;
const headingNbGridRows = 2;
const productNbGridRows = 3;
const DESCRIPTION = 'description';
const FEATURES = 'features';
const SCREENSHOTS = 'screenshots';
const CATEGORIES = 'categories';
const DEVELOPER = 'developer';
const EDITIONS = 'editions';
const SPECIFICATIONS = 'specifications';
const TOP_REVIEWS = 'topReviews';
const SESSIONSTORAGE_KEY = 'selectedCompareApps';

const sectionConfigs = {
    [DESCRIPTION]: {
        HeadingComponent: CompareProductHeader,
        DetailsComponent: CompareProductDescription
    },
    [FEATURES]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductFeatures
    },
    [SCREENSHOTS]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductScreenshots
    },
    [CATEGORIES]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductCategories
    },
    [SPECIFICATIONS]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductSpecifications
    },
    [EDITIONS]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductEditions
    },
    [DEVELOPER]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductDeveloper
    },
    [TOP_REVIEWS]: {
        HeadingComponent: CompareProductSectionHeader,
        DetailsComponent: CompareProductReviews
    }
};

export const ComparableProducts = ({
    data: { items, i18n: i18nRaw },
    settings: schemaSettings
}) => {
    const [productItems, setProductItems] = useState(items);

    // make sure that all i18n are filled with defaults, as React's default props are shallow merged
    const i18n = {
        ...ComparableProducts.defaultProps.data.i18n,
        ...i18nRaw
    };

    const settings = {
        ...ComparableProducts.defaultProps.settings,
        ...schemaSettings
    };

    const [containerRef, containerWidth] = useContainerWidth();
    const isMobile = containerWidth && containerWidth < bMedium;

    const nItems = (productItems && productItems.length) || 0;
    const hasAddProductFeature = nItems ? nItems < maxItems : true;
    const hasRemoveProductFeature = nItems && nItems > minItems;
    const isInlineLayout = isMobile || settings.headingsPositions === INLINE;

    const getGridRowWidth = nRow => `calc(100% / 12 * ${nRow})`;

    const getProductRowWidth = () => getGridRowWidth(productNbGridRows);

    const getGridInitialValue = () => {
        const headingRowWidth = isInlineLayout ? '' : `${getGridRowWidth(headingNbGridRows)} `;
        const addProductRowWidth = hasAddProductFeature ? `${getProductRowWidth()} ` : '';

        return `${headingRowWidth}${addProductRowWidth}`;
    };

    const getGridTemplateColumns = () => {
        const initialValue = getGridInitialValue();

        return nItems ? productItems.reduce(accumulator => `${accumulator} ${getProductRowWidth()}`, initialValue) : initialValue;
    };

    const componentVariables = { 'compare-product-grid-template-columns': getGridTemplateColumns(),
        'compare-product-border-width': settings.displayDividers ? '1px' : '0',
        ...settings.headerBackgroundColor && {
            'compare-product-header-bgcolor': settings.headerBackgroundColor
        },
        ...settings.dividersColor && {
            'compare-product-border-color': settings.dividersColor
        } };

    const containerModifiers = [
        ...(hasAddProductFeature ? ['with-add-button'] : []),
        ...(hasRemoveProductFeature ? ['with-remove-button'] : []),
        ...(isInlineLayout ? ['inline-header-layout'] : ['left-header-layout'])
    ];

    // sections will render in the order of this array
    let propsToRender = [DESCRIPTION];
    if (settings.isFeaturesVisible) {
        propsToRender.push(FEATURES);
    }
    if (settings.isScreenshotsVisible) {
        propsToRender.push(SCREENSHOTS);
    }
    if (settings.isCategoriesVisible) {
        propsToRender.push(CATEGORIES);
    }
    if (settings.isDeveloperVisible) {
        propsToRender.push(DEVELOPER);
    }
    if (settings.isEditionsVisible) {
        propsToRender.push(EDITIONS);
    }
    if (settings.isSpecificationsVisible) {
        propsToRender.push(SPECIFICATIONS);
    }
    if (settings.isTopReviewsVisible) {
        propsToRender.push(TOP_REVIEWS);
    }

    // make sure that each prop is renderable, that is, every product
    // in the compare list has something to display for each prop
    propsToRender = propsToRender.filter(propName => {
        const { DetailsComponent } = sectionConfigs[propName];

        // if component has no method to check, it is renderable by default
        if (!DetailsComponent.isRenderable) {
            return true;
        }

        // otherwise, query the renderer to make sure each item has something to render
        return nItems
            ? productItems.reduce((carry, item) => carry || DetailsComponent.isRenderable(item), false)
            : false;
    });

    const updateSessionStorage = () => {
        const updatedItemIds = productItems.map(item => item.id);
        sessionStorage.setItem(SESSIONSTORAGE_KEY, updatedItemIds);
    };

    const removeGridItem = index => {
        // remove index of item to be omitted
        const updatedItems = [...productItems];
        updatedItems.splice(index, 1);
        // functional component needs new instance to re-render
        setProductItems(updatedItems);
        updateSessionStorage();
    };

    return (
        <section {...n('wrapper').withVariables(componentVariables).props} ref={containerRef}>
            <div {...n('scrollContainer').props}>
                <div {...n('container', containerModifiers).withTestId().withE2EId().props}>
                    <div {...n('grid').withE2EId('grid').props}>
                        {propsToRender.map((propName, propIndex) => {
                            const { HeadingComponent } = sectionConfigs[propName];

                            // render label either as a cell left to all the items, or as a row between each
                            // default left: labels 1st column each prop on its row
                            const headingGridProps = { gridColumn: 1, gridRow: propIndex + 1 };
                            if (isInlineLayout) {
                                // inline: one item per column, alterning rows between label / item props
                                // label spans as many columns as there are items
                                headingGridProps.gridColumn = `1 / span ${hasAddProductFeature ? nItems + 1 : nItems}`;
                                headingGridProps.gridRow = (propIndex * 2) + 1;
                            }

                            // use the right label for this section, labels coming from props.
                            // could be simplified if every i18n had the same key as the prop
                            let sectionLabel;
                            switch (propName) {
                                case DESCRIPTION:
                                    sectionLabel = i18n.compareApplications;
                                    break;
                                case SPECIFICATIONS:
                                    sectionLabel = i18n.specifications;
                                    break;
                                case EDITIONS:
                                    sectionLabel = i18n.editions;
                                    break;
                                case CATEGORIES:
                                    sectionLabel = i18n.category;
                                    break;
                                case FEATURES:
                                    sectionLabel = i18n.features;
                                    break;
                                case SCREENSHOTS:
                                    sectionLabel = i18n.screenshots;
                                    break;
                                case DEVELOPER:
                                    sectionLabel = i18n.developer;
                                    break;
                                case TOP_REVIEWS:
                                    sectionLabel = i18n.rating;
                                    break;
                                default:
                                    break;
                            }

                            return (
                                <div {...n('gridItem', 'heading').withTestId('heading').withE2EId('heading').props} style={headingGridProps} key={propName}>
                                    <div {...n('heading', propName).props}>
                                        <HeadingComponent label={sectionLabel} i18n={i18n} nItems={nItems} />
                                    </div>
                                </div>
                            );
                        })}
                        {nItems ? productItems.map((item, itemIndex) => propsToRender.map((propName, propIndex) => {
                            // takes the components/config for this section in the definition object
                            const { DetailsComponent } = sectionConfigs[propName];

                            // render label either as a cell left to all the items, or as a row between each
                            // default left: labels 1st column each prop on its row
                            const headingGridProps = { gridColumn: 1, gridRow: propIndex + 1 };
                            if (isInlineLayout) {
                                // inline: one item per column, alterning rows between label / item props
                                // label spans as many columns as there are items
                                headingGridProps.gridColumn = `1 / span ${hasAddProductFeature ? nItems + 1 : nItems}`;
                                headingGridProps.gridRow = (propIndex * 2) + 1;
                            }

                            const gridItemModifiers = [
                                ...(itemIndex === 0 ? ['first'] : []),
                                ...(itemIndex === nItems - 1 ? ['last'] : []),
                                ...[propName]
                            ];

                            const itemGridProps = { gridColumn: itemIndex + 2, gridRow: propIndex + 1 };

                            if (isInlineLayout) {
                                itemGridProps.gridColumn = itemIndex + 1;
                                itemGridProps.gridRow = (propIndex * 2) + 2;
                            }

                            const detailComponentBlock = (
                                <DetailsComponent {...item} i18n={i18n} settings={settings} />
                            );

                            return (
                                <div {...n('gridItem', gridItemModifiers).props} style={itemGridProps} key={generateUniqueID([propName, item.id])}>
                                    {propName === 'description' ? (
                                        <a href={item.url} {...n('overlayLink').props}>
                                            <span {...n('overlayLinkText').props}>{replacePlaceholders(i18n.goToProductPage, { productName: item.name })}</span>
                                        </a>
                                    ) : ''}
                                    <div {...n('content', propName).props}>
                                        {hasRemoveProductFeature && propName === 'description' && (
                                            <div {...n('removeButton').props}>
                                                <Button
                                                    onClick={() => removeGridItem(itemIndex)}
                                                    aria-label={replacePlaceholders(
                                                        i18n.removeProduct,
                                                        { productName: item.name }
                                                    )}
                                                >
                                                    {i18n.remove}
                                                    <span {...n('removeIcon').props}>
                                                        <Icon type="close" width="12px" fill="#fff" />
                                                    </span>
                                                </Button>
                                            </div>
                                        )}
                                        {detailComponentBlock}
                                    </div>
                                </div>
                            );
                        })) : null}
                        {hasAddProductFeature && (() => {
                            const addProductGridRowStart = isInlineLayout ? '2' : '1';
                            const addProductGridColumnStart = isInlineLayout ? nItems + 1 : nItems + 2;
                            const addProductGridArea = `${addProductGridRowStart} / ${addProductGridColumnStart} / auto / auto`;

                            return (
                                <div {...n('gridItem', 'add').props} style={{ gridArea: addProductGridArea }}>
                                    <div {...n('content', 'add').props}>
                                        <CompareProductDescription url="/listing" i18n={i18n} />
                                    </div>
                                </div>
                            );
                        })()}
                    </div>
                </div>
            </div>
        </section>
    );
};

const HEADINGS_POSITIONS_OPTIONS = [
    { value: LEFT, label: 'uieditor.sfbComponent.dropdown.option.left' },
    { value: INLINE, label: 'uieditor.sfbComponent.dropdown.option.inline' }
];

ComparableProducts.schema = () => ({
    name: 'ComparableProducts',
    title: 'uieditor.sfbComponent.comparableProduct.title',
    form: {
        headerBackgroundColor: {
            title: 'uieditor.sfbComponent.comparableProduct.headerBackgroundColor.title',
            type: COLORPICKER
        },
        displayDividers: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.displayDividers.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.displayDividers.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        dividersColor: {
            title: 'uieditor.sfbComponent.comparableProduct.dividersColor.title',
            type: COLORPICKER
        },
        headingsPositions: {
            title: 'uieditor.sfbComponent.comparableProduct.headingsPositions.title',
            type: DROPDOWN,
            required: true,
            defaultValue: HEADINGS_POSITIONS_OPTIONS[0].value,
            options: HEADINGS_POSITIONS_OPTIONS
        },
        isStartingPriceVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isStartingPriceVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isStartingPriceVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isFeaturesVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isFeaturesVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isFeaturesVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isScreenshotsVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isScreenshotsVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isScreenshotsVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isCategoriesVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isCategoriesVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isCategoriesVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isDeveloperVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isDeveloperVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isDeveloperVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isEditionsVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isEditionsVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isEditionsVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isSpecificationsVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isSpecificationsVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isSpecificationsVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        },
        isTopReviewsVisible: {
            labelOn: 'uieditor.sfbComponent.comparableProduct.isTopReviewsVisible.labelOn',
            labelOff: 'uieditor.sfbComponent.comparableProduct.isTopReviewsVisible.labelOff',
            defaultValue: true,
            type: TOGGLE
        }
    },
    marketplaceData: {
        items: 'comparableProducts.items',
        i18n: 'comparableProducts.i18n'
    }
});

ComparableProducts.propTypes = {
    data: PropTypes.shape(comparableProductsDataShape),
    settings: PropTypes.shape({
        headerBackgroundColor: PropTypes.string,
        displayDividers: PropTypes.bool,
        dividersColor: PropTypes.string,
        headingsPositions: PropTypes.oneOf(HEADINGS_POSITIONS_OPTIONS.map(({ value }) => value)),
        showRatings: PropTypes.bool,
        productNameSize: PropTypes.oneOf(['medium']),
        descTextSize: PropTypes.oneOf(['medium']),
        isStartingPriceVisible: PropTypes.bool,
        isFeaturesVisible: PropTypes.bool,
        isScreenshotsVisible: PropTypes.bool,
        isCategoriesVisible: PropTypes.bool,
        isDeveloperVisible: PropTypes.bool,
        isEditionsVisible: PropTypes.bool,
        isSpecificationsVisible: PropTypes.bool,
        isTopReviewsVisible: PropTypes.bool
    })
};

const SETTINGS = ComparableProducts.schema().form;

ComparableProducts.defaultProps = {
    data: {
        items: null,
        i18n: i18nMock
    },
    settings: {
        headerBackgroundColor: null,
        displayDividers: true,
        dividersColor: null,
        showRatings: false,
        headingsPosition: LEFT,
        productNameSize: 'medium',
        descTextSize: 'medium',
        isStartingPriceVisible: SETTINGS.isStartingPriceVisible.defaultValue,
        isFeaturesVisible: SETTINGS.isFeaturesVisible.defaultValue,
        isScreenshotsVisible: SETTINGS.isScreenshotsVisible.defaultValue,
        isCategoriesVisible: SETTINGS.isCategoriesVisible.defaultValue,
        isDeveloperVisible: SETTINGS.isDeveloperVisible.defaultValue,
        isEditionsVisible: SETTINGS.isEditionsVisible.defaultValue,
        isSpecificationsVisible: SETTINGS.isSpecificationsVisible.defaultValue,
        isTopReviewsVisible: SETTINGS.isTopReviewsVisible.defaultValue
    }
};

export default withListener(ComparableProducts);
