'use strict';

import React from "react";
import axios from "axios";
import { createRoot } from "react-dom/client";
import PropTypes from "prop-types";
import { DragDropContext, Droppable, Draggable } from "react-beautiful-dnd";
import { v4 as uuidv4 } from 'uuid';
import ProcessOptions from "./shop_components.jsx";
import { OverlayTrigger } from "react-bootstrap";


const data = window.shop_data;
const itemsUnfoldedList = Array.from(data.columns.backlog.categories.map(groupId => groupId.itemIds).flat());

const productStyle = (style, snapshot, removeAnim, hovered, selected, cart=false) => {
  const custom = {
    opacity: snapshot.isDragging ? .7 : 1,
    backgroundColor: (hovered || selected) ? '#eae7f7' : 'initial',
  };

  if (!cart && snapshot.draggingOver == null && // hack for backlog
      ((!snapshot.isDragging) // prevent next elements from animation
      || (snapshot.isDragging && snapshot.isDropAnimating))) { // prevent dragged element from weird animation
    style.transform = "none";
  }

  if (!snapshot.isDropAnimating) {
    return { ...style, ...custom};
  }

  if (removeAnim) {
    // cannot be 0, but make it super tiny
    custom.transitionDuration = '0.001s';
  }

  return {
    ...style,
    ...custom,
  };
}

const cartStyle = (style, snapshot) => {
  const isDraggingOver = snapshot.isDraggingOver;
  return {
    ...style,
    ...{
      backgroundColor: isDraggingOver ? '#f2f2f2' : '#f9f9f9',
      border: isDraggingOver ? '1px dashed #ccc' : '0',
    },
  };
}

const nbrConnectorsStyle = (data) => {
  if (!data || !data.nbrCurrentSlot) {
    return {};
  }

  let p = data.nbrCurrentSlot * 100 / data.nbrSlotMax;
  if (p > 100) {
    p = 100;
  }
  return {
    width: `${p}%`,
  }
};

const nbrClocksStyle = (data) => {
  if (!data || !data.nbrCurrentClock) {
    return {};
  }

  let p = data.nbrCurrentClock * 100 / data.nbrClockMax;
  if (p > 100) {
    p = 100;
  }
  return {
    width: `${p}%`,
  }
};

const copy = (
  model,
  source,
  destination,
  draggableSource,
  droppableDestination
) => {
  const destClone = Array.from(destination.items);

  destClone.splice(droppableDestination.index, 0, ...draggableSource.map((dragged_item, _) => {
    return {
      ...model[dragged_item],
      id: uuidv4(),
    }
  }));

  return destClone;
};

const reorder = (list, startIndex, endIndex) => {
  const result = Array.from(list);
  const [removed] = result.splice(startIndex, 1);
  result.splice(endIndex, 0, removed);

  return result;
};

const remove = (list, startIndex) => {
  const result = Array.from(list);
  result.splice(startIndex, 1);
  return result;
};

const nbrOccupiedSlotsInCrate = (items) => {
  return items.reduce((prev, next) => {
    return prev + (next.hp === 8 ? 2 : 1);
  }, 0);
};

function formatMoney(amount, decimalCount = 2, decimal = ".", thousands = ",") {
  // https://stackoverflow.com/questions/149055/how-can-i-format-numbers-as-currency-string-in-javascript
  // changes: return amount if error in order to avoid empty value
  try {
    decimalCount = Math.abs(decimalCount);
    decimalCount = isNaN(decimalCount) ? 2 : decimalCount;

    const negativeSign = amount < 0 ? "-" : "";

    let i = parseInt(amount = Math.abs(Number(amount) || 0).toFixed(decimalCount)).toString();
    let j = (i.length > 3) ? i.length % 3 : 0;

    return negativeSign + (j ? i.substr(0, j) + thousands : '') + i.substr(j).replace(/(\d{3})(?=\d)/g, "$1" + thousands) + (decimalCount ? decimal + Math.abs(amount - i).toFixed(decimalCount).slice(2) : "");
  } catch (e) {
    return amount;
  }
};


/**
 * Component that provides a base layout (aside/main) for the page.
 */
class Layout extends React.PureComponent {

  static get propTypes() {
    return {
      aside: PropTypes.any,
      main: PropTypes.any,
      mobileSideMenuShouldOpen: PropTypes.bool,
      isMobile: PropTypes.bool,
      newCardJustAdded: PropTypes.bool,
      onClickToggleMobileSideMenu: PropTypes.func,
      onClickCloseRFQFeedback: PropTypes.func,
      RFQBodyType: PropTypes.string,
      RFQBodyOrder: PropTypes.string,
      onClickLoadCustomConf: PropTypes.func,
      items: PropTypes.object,
    };
  }

  static get defaultProps() {
    return {
      mobileSideMenuShouldOpen: false,
    };
  }

  constructor(props) {
    super(props);

    this.state = {
      customconf: '',
      error: null,
    };

    this.handleCustomConfig = this.handleCustomConfig.bind(this);
    this.handleClickLoad = this.handleClickLoad.bind(this);
    this.checkValidation = this.checkValidation.bind(this);

    // retrieve list of available pn
    const items_keys = Object.keys(props.items);
    this.list_pn = items_keys.map(function (key) {
      return props.items[key].name_number;
    });
  }

  handleCustomConfig(e) {
    const value = e.target.value;

    this.checkValidation(value);
  }

  checkValidation(conf) {
    let conf_obj;

    try {
      conf_obj = JSON.parse(conf);
    } catch (e) {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: 'invalid format',
      });
    }

    if (!conf_obj) {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: 'invalid format',
      });
    }

    if ((!conf_obj.items || !conf_obj.type) &&
        (Object.prototype.toString.call(conf_obj.items) !== '[object Array]' ||
        Object.prototype.toString.call(conf_obj.type) !== '[object String]')) {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: 'invalid format',
      });
    }

    if (conf_obj.type !== "desktop" && conf_obj.type !== "rack") {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: 'invalid format',
      });
    }

    conf_obj.items.map(function (item) {
      try {
        return JSON.parse(item);
      } catch (e) {
        return null;
      }
    });

    conf_obj.items = conf_obj.items.filter(function (item) {
      return item;
    });

    if (conf_obj.items.filter(function (item) {
      return Object.prototype.toString.call(item) !== '[object Object]' || !item.pn || Object.prototype.toString.call(item.pn) !== '[object String]';
    }).length > 0) {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: 'invalid format',
      });
    }

    conf_obj.items = conf_obj.items.map(function (item) {
      return {
        pn: item.pn,
        options: item.options ? item.options : null,
      };
    });

    const self = this;
    const unknow_pn = conf_obj.items.filter(function (item_pn) {
      return self.list_pn.includes(item_pn.pn) === false;
    }).map(function (item_pn) {
      return item_pn.pn;
    });

    if (unknow_pn.length > 0) {
      return this.setState({
        ...this.state,
        customconf: conf,
        customconf_ready: null,
        error: `${unknow_pn.join(', ')} unknown${unknow_pn.length > 1 ? 's':''} pn number`,
      });
    }

    this.setState({
      ...this.state,
      customconf: conf,
      error: null,
      customconf_ready: conf_obj,
    });
  }

  handleClickLoad() {
    this.checkValidation(this.state.customconf);

    if (this.props.onClickLoadCustomConf) {
      this.props.onClickLoadCustomConf(this.state.customconf_ready);
    }
  }

  render() {
    const {
      aside,
      main,
      mobileSideMenuShouldOpen,
      isMobile,
      newCardJustAdded,
      onClickToggleMobileSideMenu,
      onClickCloseRFQFeedback,
      showRFQFeedback,
      RFQBodyType,
      RFQBodyOrder,
    } = this.props;

    return (
      <div className="layout">

        <aside className={'aside ' + (mobileSideMenuShouldOpen ? 'menu-opened' : '')}>{aside}</aside>

        {mobileSideMenuShouldOpen ? (
          <section className="main" onClick={onClickToggleMobileSideMenu}>{main}</section>
        ) : (
          <section className="main">{main}</section>
        )}

        {isMobile && newCardJustAdded ? (
          <div className="feedback-add-success">
           ✓ added
          </div>
        ) : null}


        <div className={`modal fade ${ showRFQFeedback ? 'show': ''}`} style={{'display': showRFQFeedback ? 'block':'none'}} id="exampleModal" tabIndex="-1" role="dialog" aria-labelledby="exampleModalLabel" aria-hidden="true">
          <div className="modal-dialog" role="document">
            <div className="modal-content">
              <div className="modal-body rfqFeedback">

                <div className="d-flex w-100">

                  {RFQBodyType === 'email' ? (
                    <div className="d-flex">

                      <div>
                        <img width="30px" src="/images/shop/icon-done.svg" alt="close" />
                      </div>

                      <div style={{'padding': '0 .5em'}}>
                        We've received your request and will be in contact soon.
                      </div>

                    </div>
                  ) : null }

                  {RFQBodyType === 'show' ? (
                    <p>
                      {RFQBodyOrder}
                    </p>
                  ) : null}

                  {RFQBodyType === 'import' ? (
                    <div className="w-100">

                      <form className="w-100">
                        <div className="mb-3">
                          <p className="small">
                            Input the JSON description below. Should be something like:
                            <br />
                            {JSON.stringify({"items":[{"pn":"1124"},{"pn":"2118"},{"pn":"2118"},{"pn":"2128"}],"type":"desktop"})}
                          </p>
                        </div>

                        <div className="mb-3 w-100">
                          <textarea
                            onChange={this.handleCustomConfig}
                            value={this.state.customconf}
                            className="form-control w-100"
                            rows="5"
                            placeholder="Input JSON description here." />
                        </div>
                        {this.state.error ? (
                          <div className="mb-3">
                            <p className="text-danger">{this.state.error}</p>
                          </div>
                        ) : null}
                      </form>

                      <div className="d-flex flex-column flex-sm-row justify-content-end">
                        <a type="button" onClick={onClickCloseRFQFeedback} className="btn btn-sm btn-outline-primary m-0 mb-2 mb-sm-0 me-sm-2">Close</a>
                        <a type="button" onClick={this.handleClickLoad} className={`btn btn-sm btn-primary m-0 ms-sm-2 ${this.state.error ? 'disabled':''}`}>Load configuration</a>
                      </div>
                    </div>
                  ) : null}

                  <div>
                    <button onClick={onClickCloseRFQFeedback}>
                      <img src="/images/shop/icon-close.svg" alt="close" />
                    </button>
                  </div>

                </div>

              </div>

            </div>
          </div>
        </div>

        <div onClick={onClickCloseRFQFeedback} className={`modal-backdrop fade ${ showRFQFeedback ? 'show': ''}`} style={{'display': showRFQFeedback ? 'initial':'none'}}></div>

      </div>
    );
  }
}


/**
 * Component that renders a product.
 * Used in the aside (e.g backlog of product)
 */
class ProductItem extends React.PureComponent {

  static get propTypes() {
    return {
      id: PropTypes.string.isRequired,
      index: PropTypes.number.isRequired,
      name: PropTypes.string.isRequired,
      name_codename: PropTypes.string,
      price: PropTypes.number.isRequired,
      currency: PropTypes.string.isRequired,
      image: PropTypes.string.isRequired,
      specs: PropTypes.array,
      datasheet_file: PropTypes.string,
      datasheet_name: PropTypes.string,
      onClickAddItem: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnClickAddItem = this.handleOnClickAddItem.bind(this);
  }

  handleOnClickAddItem(id, tap, e) {
    if (this.props.onClickAddItem) {
      this.props.onClickAddItem(id, tap);
    }
    e.preventDefault();
  }

  render() {
    const {
      id,
      index,
      name,
      name_codename,
      price,
      currency,
      image,
      specs,
      datasheet_file,
      datasheet_name,
    } = this.props;

    const render_specs = (specs && specs.length > 0 && (
      <ul>
        {specs.map((spec, index) =>
          <li key={index}>{spec}</li>
        )}
      </ul>
    ));

    const render_datasheet_link = (datasheet_file && datasheet_name && (
      <div className="ds">
        <span className='doc-icon'></span>
        <a href={datasheet_file} target="_blank" rel="noopener noreferrer">
          {datasheet_name}
        </a>
      </div>
    ));

    return (
      <section className="productItem">

        <div className="content">
          <h3 style={{ 'marginBottom': name_codename ? '5px' : '20px'}}>{name}</h3>
          {name_codename ? (
            <p>{name_codename}</p>
          ) : null }

          <div className="price">{`${currency} ${formatMoney(price)}`}</div>

          {render_specs}

          {render_datasheet_link}
        </div>

        <div className="content">

          <button onClick={this.handleOnClickAddItem.bind(this, index, true)}>
            <img src="/images/shop/icon-add.svg" alt="add" />
          </button>

          <Draggable draggableId={id} index={index}>
            {(provided, snapshot) => (
              <React.Fragment>
                <img
                    ref={provided.innerRef}
                    {...provided.draggableProps}
                    {...provided.dragHandleProps}
                    style={productStyle(
                      provided.draggableProps.style,
                      snapshot,
                      true,  // hack: remove weird animation after a drop
                    )}
                    src={image} />

                {/* Allows to simulate a clone */}
                {snapshot.isDragging && (
                  <img className="simclone" src={image} />
                )}
              </React.Fragment>
            )}
          </Draggable>

        </div>

      </section>
    );
  }
}


/**
 * Component that renders a product.
 * Used in the crate
 */
class ProductCartItem extends React.PureComponent {

  static get propTypes() {
    return {
      isMobile: PropTypes.bool,
      isTouch: PropTypes.bool,
      hovered: PropTypes.bool,
      index: PropTypes.number.isRequired,
      model: PropTypes.object.isRequired,
      data: PropTypes.object,
      onToggleOverlayRemove: PropTypes.func,
      onClickRemoveItem: PropTypes.func,
      onClickItem: PropTypes.func,
    };
  }

  static get defaultProps() {
    return {
      hovered: false,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnMouseEnterRemoveItem = this.handleOnMouseEnterRemoveItem.bind(this);
    this.handleOnMouseLeaveRemoveItem = this.handleOnMouseLeaveRemoveItem.bind(this);
    this.handleOnClickRemoveItem = this.handleOnClickRemoveItem.bind(this);
    this.handleOnClickItem = this.handleOnClickItem.bind(this);
  }

  handleOnMouseEnterRemoveItem(index, e) {
    if (this.props.onToggleOverlayRemove && !this.props.isMobile) {
      this.props.onToggleOverlayRemove(index, true);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveRemoveItem(index, e) {
    if (this.props.onToggleOverlayRemove && !this.props.isMobile) {
      this.props.onToggleOverlayRemove(index, false);
    }
    e.preventDefault();
  }

  handleOnClickItem(index, e) {
    if (this.props.onClickItem && this.props.isTouch) {
      this.props.onClickItem(index);
    }
    e.preventDefault();
  }

  handleOnClickRemoveItem(index, e) {
    if (this.props.onClickRemoveItem) {
      this.props.onClickRemoveItem(index);
    }
  }

  render() {
    const {
      hovered,
      model,
      data,
      index,
    } = this.props;

    let warning, options, options_data;
    if (data && data.warnings) {
      const warningsKeys = Object.keys(data.warnings);
      if (warningsKeys && warningsKeys.length > 0) {
        // we display only the first warning
        warning = data.warnings[warningsKeys[0]];
      }
    }



    if (data && data.options) {
      options = data.options;
      if (!data.options_data) data.options_data = {};
      options_data = data.options_data;
    }

    let render_progress;
    if (data) {
      switch(model.type) {
        case 'kasli':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>
              <p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>
            </div>
          );
          break;

        case 'vhdcicarrier':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} EEM connectors used`}</p>
            </div>
          );
          break;

        case 'zotino':
        case 'hd68':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentSlot}/${model.nbrSlotMax} connectors used`}</p>
            </div>
          );
          break;

        case 'clocker':
          render_progress = (
            <div className="k-popup-connectors">
              <p>{`${data.nbrCurrentClock}/${model.nbrClockMax} Clock connectors used`}</p>
            </div>
          );
          break;

        default:
          break;
      }
    }

    return (
      <Draggable draggableId={model.id} index={index}>

        {(provided, snapshot) => (
          <div
              ref={provided.innerRef}
              {...provided.draggableProps}
              {...provided.dragHandleProps}
              style={{ ...productStyle(
                provided.draggableProps.style,
                snapshot,
                true,
                hovered ? true : false,
                model.selected ? true : false,
                    true
              )}}
              onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
              onMouseLeave={this.handleOnMouseLeaveRemoveItem.bind(this, index)}
              >

            {/* warning container */}
            <OverlayTrigger
                placement="bottom"
                trigger={warning ? ['click', 'hover', 'focus'] : []}
                overlay={
                  warning ? (<div className="k-popup-warning">
                    <p className="rule warning">
                      <i>{warning.message}</i>
                    </p>
                  </div>) : null
                }
                rootClose
            >
              <div className="progress-container warning">
                {warning && (<img className="alert-warning" src={warning ? `/images${warning.icon}` : null}/>)}
              </div>
            </OverlayTrigger>

            <div
                className="progress-container warning"
                onMouseEnter={this.handleOnMouseEnterWarningItem.bind(this, index, warning)}
                onMouseLeave={this.handleOnMouseLeaveWarningItem.bind(this, index, warning)}>

              {warning ? (
                <img className="alert-warning" src={`/images${warning.icon}`} />
              ) : (options ? <img className="alert-info" src="/images/shop/icon-customize.svg" /> : null) }

              {options ? (
                  <div
                      style={{'display': model.showOverlayRemove ? 'flex' : 'none'}}
                      className="overlayVariant">

                    <ProcessOptions
                      options={options}
                      data={options_data}
                      key={"processed_options" + index}
                      id={"processed_options" + index}
                      target={{
                        construct: ((outvar, value) => {
                          //console.log("construct", outvar, value, options_data);
                          options_data[outvar] = value;
                          this.setState(options_data);
                        }),
                        update: ((outvar, value) => {
                          //console.log("update", outvar, value, options_data);
                          if (outvar in options_data) options_data[outvar] = value;
                          this.setState(options_data);
                        }),
                        unmount: ((outvar) => {
                          //console.log("delete", outvar);
                          delete options_data[outvar];
                        })
                      }}
                    />
                  </div>
              ) : null }

              {warning && model.showWarning &&  (
                <div className={`k-popup-warning ${shouldTooltipWarningClassInverted ? 'inverted': ''}`}>
                  <p className="rule warning">
                    <i>{warning.message}</i>
                  </p>
                </div>
              )}
            </div>

            <h6>{model.name_number}</h6>

            <div
              onMouseEnter={this.handleOnMouseEnterRemoveItem.bind(this, index)}
              onClick={this.handleOnClickItem.bind(this, index)}
              >

              <img
                  className='item-cart'
                  src={`/images${model.image}`} />
            </div>

            {/* remove container */}
            <div
                style={{'display': model.showOverlayRemove ? 'flex':'none'}}
                className="overlayRemove"
                onClick={this.handleOnClickRemoveItem.bind(this, index)}>

              <img src="/images/shop/icon-remove.svg" alt="rm"/>

              <p>Remove</p>
            </div>

            {/* progression container */}
            {render_progress && (
              <OverlayTrigger
                placement="top"
                trigger={['click', 'hover', 'focus']}
                overlay={render_progress}
                rootClose
              >
                <div className="progress-container">

                  {model.nbrSlotMax > 0 && (
                      <div className="nbr-connectors">
                        <div style={{ ...nbrConnectorsStyle(data)}}></div>
                      </div>
                  )}

                  {model.nbrClockMax > 0 && (
                      <div className="nbr-clocks">
                        <div style={{ ...nbrClocksStyle(data)}}></div>
                      </div>
                  )}
                </div>
            </OverlayTrigger>
            )}


          </div>
        )}

      </Draggable>
    );
  }
}


/**
 * Component that displays a placeholder inside crate.
 * Allows to display how it remains space for the current crate.
 */
class FakePlaceholder extends React.PureComponent {

  static get propTypes() {
    return {
      isDraggingOver: PropTypes.bool,
      nbrSlots: PropTypes.number.isRequired,
      items: PropTypes.array.isRequired,
    };
  }

  render() {
    const {
      isDraggingOver,
      nbrSlots,
      items,
    } = this.props;

    const fakePlaceholder = [];
    const nbrOccupied = nbrOccupiedSlotsInCrate(items);

    for (var i = (nbrSlots - nbrOccupied); i > 0; i--) {
      fakePlaceholder.push(
        <div key={i} style={{
          display: isDraggingOver ? 'none' : 'block',
          border: '1px dashed #ccc',
          width: '45px',
          marginBottom: '5px',
        }}></div>
      );
    }

    return (
      <React.Fragment>
        {fakePlaceholder}
      </React.Fragment>
    );

  }
}


/**
 * Component that displays a list of <ProductCartItem>
 */
class Cart extends React.PureComponent {

  static get propTypes() {
    return {
      isMobile: PropTypes.bool,
      isTouch: PropTypes.bool,
      nbrSlots: PropTypes.number,
      itemHovered: PropTypes.string,
      data: PropTypes.object.isRequired,
      onToggleOverlayRemove: PropTypes.func,
      onClickRemoveItem: PropTypes.func,
      onClickItem: PropTypes.func,
    };
  }

  render() {
    const {
      isMobile,
      isTouch,
      nbrSlots,
      itemHovered,
      data,
      onToggleOverlayRemove,
      onClickRemoveItem,
      onClickItem,
    } = this.props;

    const products = data.items.map((item, index) => {
      let itemData;
      if (data.itemsData && index in data.itemsData) {
        itemData = data.itemsData[index];
      }

      return (
        <ProductCartItem
            isMobile={isMobile}
            isTouch={isTouch}
            hovered={item.id === itemHovered}
            key={item.id}
            id={item.id}
            index={index}
            data={itemData}
            onToggleOverlayRemove={onToggleOverlayRemove}
            onClickRemoveItem={onClickRemoveItem}
            onClickItem={onClickItem}
            model={item}>
        </ProductCartItem>
      );
    });

    return (
      <Droppable droppableId={data.id} direction="horizontal">

        {(provided, snapshot) => (
          <div
              ref={provided.innerRef}
              {...provided.droppableProps}
              style={cartStyle(
                provided.droppableProps.style,
                snapshot,
              )}
              className="items-cart-list">

            {products}

            {provided.placeholder && (
              <div style={{ display: 'none' }}>
                {provided.placeholder}
              </div>
            )}

            <FakePlaceholder
                nbrSlots={nbrSlots}
                items={data.items}
                isDraggingOver={snapshot.isDraggingOver} />
          </div>
        )}

      </Droppable>
    );
  }
}


/**
 * Component that displays crate modes
 */
class CrateMode extends React.PureComponent {

  static get propTypes() {
    return {
      items: PropTypes.array.isRequired,
      mode: PropTypes.string.isRequired,
      onClickMode: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnClickMode = this.handleOnClickMode.bind(this);
  }

  handleOnClickMode(mode, e) {
    if (this.props.onClickMode) {
      this.props.onClickMode(mode);
    }
    e.preventDefault();
  }

  render() {
    const {
      mode,
      items,
    } = this.props;

    return (
      <div className="crate-mode">
        {items.map(item => (
          <a
            key={item.id}
            className={mode == item.id ? 'active' : ''}
            onClick={this.handleOnClickMode.bind(this, item.id)}
            href="#"
            role="button">{item.name}</a>
        ))}
      </div>
    );
  }
}


/**
 * Component that displays the main crate with reminder rules.
 * It includes <Cart> and rules
 */
class Crate extends React.PureComponent {

  static get propTypes() {
    return {
      rules: PropTypes.array,
      cart: PropTypes.element,
    };
  }

  render() {
    const {
      rules,
      cart,
    } = this.props;

    return (
      <div className="crate">

        <div className="crate-products">

          {cart}

          {rules && rules.length > 0 && (
            <div className="crate-info">
              {rules.map((rule, index) => (
                <p key={index} className="rule" style={{'color': rule.color ? rule.color : 'inherit'}}>
                  <img src={`/images${rule.icon}`} /> <i><strong>{rule.name}:</strong> {rule.message}</i>
                </p>
              ))}
            </div>
          )}
        </div>

      </div>
    );
  }
}


/**
 * Component that renders all things for order.
 * It acts like-a layout, this component do nothing more.
 */
class OrderPanel extends React.PureComponent {

  static get propTypes() {
    return {
      title: PropTypes.string,
      description: PropTypes.string,
      crateMode: PropTypes.element,
      crate: PropTypes.element,
      summaryPrice: PropTypes.element,
      form: PropTypes.element,
      isMobile: PropTypes.bool,
      onClickToggleMobileSideMenu: PropTypes.func,
      onClickOpenImport: PropTypes.func,
    };
  }

  render() {
    const {
      title,
      description,
      crateMode,
      crate,
      summaryPrice,
      form,
      isMobile,
      onClickToggleMobileSideMenu,
      onClickOpenImport,
    } = this.props;

    return (
      <section className="panel">

        <h2>{title}</h2>

        <div className="control">
          <p className="description">{description}</p>

          {crateMode}
        </div>

        <div>
          <button
              className="btn btn-sm btn-outline-primary m-0 mb-2"
              style={{'cursor': 'pointer'}}
              onClick={onClickOpenImport}>Import JSON</button>
        </div>

        {isMobile ? (
          <div className="mobileBtnDisplaySideMenu">
            <button onClick={onClickToggleMobileSideMenu}>
              <img src="/images/shop/icon-add.svg" alt="add" />
            </button>
          </div>
        ) : null}

        {crate}

        <section className="summary">
          {summaryPrice}

          {form}
        </section>

      </section>
    );
  }
}


/**
 * Components that renders the form to request quote.
 */
class OrderForm extends React.PureComponent {

  static get propTypes() {
    return {
      isProcessing: PropTypes.bool,
      isProcessingComplete: PropTypes.bool,
      onClickSubmit: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.state = {
      note: '',
      email: '',
      error: {
        note: null,
        email: null,
      },
      empty: {
        note: null,
        email: null,
      },
    };

    this.handleEmail = this.handleEmail.bind(this);
    this.handleNote = this.handleNote.bind(this);
    this.handleSubmit = this.handleSubmit.bind(this);
    this.resetEmptyError = this.resetEmptyError.bind(this);
    this.checkValidation = this.checkValidation.bind(this);
  }

  checkValidation() {
    let isValid = true;
    let validationFields = {...this.state};

    const {
      isEmpty: isEmailEmpty,
      isError: isEmailError
    } = this.validateEmail(this.state.email);

    validationFields = {
      ...validationFields,
      error: {
        ...this.state.error,
        email: isEmailError,
      },
      empty: {
        ...this.state.empty,
        email: isEmailEmpty,
      }
    }

    this.setState(validationFields);

    isValid =
      !isEmailEmpty &&
      !isEmailError

    return isValid;
  }

  validateEmail(value) {
    let isEmpty = null;
    let isError = null;

    const { t } = this.props;

    if (!value || value.trim() === '') {
      isEmpty = true;
    } else if (value && !value.match(/^\w+([\+\.-]?\w+)*@\w+([\.-]?\w+)*(\.\w{2,3})+$/)) {
      isError = {
        message: 'Your email is incomplete',
      };
    }

    return { isEmpty, isError };
  }

  validateNote(value) {
    let isEmpty = null;

    if (!value || value.trim() === '') {
      isEmpty = true;
    }

    return { isEmpty };
  }

  resetEmptyError(key) {
    this.setState({
      ...this.state,
      error: {
        ...this.state.error,
        [key]: null,
      },
      empty: {
        ...this.state.empty,
        [key]: null,
      },
    });
  }

  handleEmail(e) {
    const value = e.target.value;
    const { isEmpty, isError } = this.validateEmail(value);

    this.setState({
      ...this.state,
      email: value,
      error: {
        ...this.state.error,
        email: isError,
      },
      empty: {
        ...this.state.empty,
        email: isEmpty,
      }
    });
  }

  handleNote(e) {
    const value = e.target.value;

    this.setState({
      ...this.state,
      note: value,
    });
  }

  handleSubmit(event) {
    event.preventDefault();

    if (this.props.onClickSubmit) {
      // check validation input fields
      const isValidated = this.checkValidation();
      if (!isValidated) {
        return false;
      }

      this.props.onClickSubmit(this.state.note, this.state.email);
    }
  }

  render() {
    const {
      handleEmail,
      handleNote,
      resetEmptyError,
      handleSubmit,
    } = this;

    const {
      onClickShow,
    } = this.props;

    const {
      email,
      note,
      error,
      empty
    } = this.state;

    const { isProcessing, isProcessingComplete } = this.props;

    return (
      <div className="summary-form">

        <form onSubmit={handleSubmit} noValidate>

          <input
            className={`${error && error.email ? 'errorField':''}`}
            type="email"
            placeholder="Email"
            onFocus={() => resetEmptyError('email')}
            onChange={handleEmail}
            onBlur={handleEmail}
            value={email} />

          { empty && empty.email ? (
            <div className="error">
              <small>Required</small>
            </div>
          ) : null}

          { error && error.email ? (
            <div className="error">
              <small>Your email is incomplete</small>
            </div>
          ) : null}

          <textarea
              onChange={handleNote}
              defaultValue={note}
              rows="5"
              placeholder="Additional notes" />

          <div className="d-flex flex-column flex-sm-row justify-content-between">
            <input
              className="btn btn-outline-primary w-100 m-0 mb-2 mb-sm-0 me-sm-2"
              style={{'cursor': 'pointer', 'fontWeight': '700'}}
              defaultValue="Show JSON"
              onClick={onClickShow} />

            <input className="btn btn-primary w-100 m-0 ms-sm-2" type="submit" value={`${isProcessing ? 'Processing ...' : 'Request quote'}`} />
          </div>
          {/*This will open an email window. Send the email to make your request.*/}
        </form>

      </div>
    );
  }
}


/**
 * Components that displays the list of card that are used in the crate.
 * It is a summary of purchase
 */
class OrderSumary extends React.PureComponent {

  static get propTypes() {
    return {
      currency: PropTypes.string,
      modes: PropTypes.array,
      currentMode: PropTypes.string,
      summary: PropTypes.array,
      itemsData: PropTypes.array,
      onDeleteItem: PropTypes.func,
      onDeleteAllItems: PropTypes.func,
      onMouseEnterItem: PropTypes.func,
      onMouseLeaveItem: PropTypes.func,
      onClickSelectItem: PropTypes.func,
    };
  }

  constructor(props) {
    super(props);
    this.handleOnDeleteItem = this.handleOnDeleteItem.bind(this);
    this.handleOnDeleteAllItems = this.handleOnDeleteAllItems.bind(this);
    this.handleOnMouseEnterItem = this.handleOnMouseEnterItem.bind(this);
    this.handleOnMouseLeaveItem = this.handleOnMouseLeaveItem.bind(this);
    this.handleOnClickSelectItem = this.handleOnClickSelectItem.bind(this);
  }

  handleOnDeleteItem(index, e) {
    if (this.props.onDeleteItem) {
      this.props.onDeleteItem(index);
    }
    e.preventDefault();
  }

  handleOnDeleteAllItems(e) {
    if (this.props.onDeleteAllItems) {
      this.props.onDeleteAllItems();
    }
    e.preventDefault();
  }

  handleOnMouseEnterItem(id, e) {
    if (this.props.onMouseEnterItem) {
      this.props.onMouseEnterItem(id);
    }
    e.preventDefault();
  }

  handleOnMouseLeaveItem(e) {
    if (this.props.onMouseLeaveItem) {
      this.props.onMouseLeaveItem();
    }
    e.preventDefault();
  }

  handleOnClickSelectItem(index, e) {
    if (e.target.tagName !== 'IMG') {
      if (this.props.onClickSelectItem) {
        this.props.onClickSelectItem(index);
      }
    }
    return e.preventDefault();
  }

  render() {
    const {
      currency,
      modes,
      currentMode,
      summary,
      itemsData,
    } = this.props;

    const mode = modes.find(elem => elem.id === currentMode);

    return (
      <div className="summary-price">

        <table>

          <thead>
            <tr>
              <td colSpan="2" className="summary-remove-all">
                <span className="item-card-name">Remove all cards</span>

                <button onClick={this.handleOnDeleteAllItems}>
                  <img src="/images/shop/icon-remove.svg" />
                </button>
              </td>
            </tr>

            {mode && (
              <tr>
                <td className="item-card-name">{mode.name}</td>
                <td className="price">
                  <div>
                    {`${currency} ${formatMoney(mode.price)}`}

                    <button style={{'opacity': '0', 'cursor': 'initial'}}>
                      <img src="/images/shop/icon-remove.svg" />
                    </button>
                  </div>

                  <span style={{
                    'display': 'inline-block',
                    'width': '30px',
                  }}>&nbsp;</span>
                </td>
              </tr>
            )}
          </thead>

          <tbody>
            {summary.map((item, index) => {
              let alert;
              let warning;

              if (itemsData[index] && itemsData[index].warnings) {
                alert = itemsData[index];
                const warningsKeys = Object.keys(alert.warnings);
                if (warningsKeys && warningsKeys.length > 0) {
                  warning = alert.warnings[warningsKeys[0]];
                }
              }

              return (
                <tr key={item.id}
                    className={`hoverable ${item.selected ? 'selected' : ''}`}
                    onClick={this.handleOnClickSelectItem.bind(this, index)}
                    onMouseEnter={this.handleOnMouseEnterItem.bind(this, item.id)}
                    onMouseLeave={this.handleOnMouseLeaveItem}>

                  <td className="item-card-name">
                    <div>{`${item.name_number} ${item.name} ${item.name_codename}`}</div>
                  </td>

                  <td className="price">
                    <div>
                      {`${currency} ${formatMoney(item.price)}`}

                      <button onClick={this.handleOnDeleteItem.bind(this, index)}>
                        <img src="/images/shop/icon-remove.svg" />
                      </button>
                    </div>

                    {warning ? (
                      <img
                          style={{'marginLeft': '10px'}}
                          className="alert-warning"
                          src={`/images/${warning.icon}`}
                      />
                    ) : (
                      <img
                          style={{'marginLeft': '10px'}}
                          className="alert-info"
                          src="/images/shop/icon-customize.svg"
                      />
                    )}

                    {!warning && (
                      <span style={{
                        'display': 'inline-block',
                        'width': '30px',
                      }}>&nbsp;</span>
                    )}
                  </td>
                </tr>
              );
            })}
          </tbody>

          <tfoot>
            <tr>
              <td className="item-card-name">Price estimate</td>
              <td className="price">
                <div>
                  {summary.length ? (
                    `${currency} ${formatMoney(summary.reduce(
                      (prev, next) => {
                        return prev + next.price;
                      }, 0
                    ) + mode.price)}`
                  ) : (
                    `${currency} ${formatMoney(mode.price)}`
                  )}

                  <button style={{'opacity': '0', 'cursor': 'initial'}}>
                    <img src="/images/shop/icon-remove.svg" alt="icon remove"/>
                  </button>
                </div>

                <span style={{
                  'display': 'inline-block',
                  'width': '30px',
                }}>&nbsp;</span>
              </td>
            </tr>
          </tfoot>

        </table>

      </div>
    );
  }
}


/**
 * Component that renders the backlog in the aside
 */
class Backlog extends React.PureComponent {

  static get propTypes() {
    return {
      currency: PropTypes.string,
      data: PropTypes.object.isRequired,
      items: PropTypes.object,
      isMobile: PropTypes.bool,
      onClickAddItem: PropTypes.func,
      onClickToggleMobileSideMenu: PropTypes.func,
    };
  }

  static get defaultProps() {
    return {
      items: {},
    };
  }

  render() {
    const {
      currency,
      data,
      items,
      onClickAddItem,
      onClickToggleMobileSideMenu,
      isMobile,
    } = this.props;


    const ordered_groups = data.categories.map(groupItem => ({ name: groupItem.name,
        items: groupItem.itemIds.map(itemId => items[itemId])
    }));
    let item_index = -1;
    const groups = ordered_groups.map((group, g_index) => {
          return (
              <div className="accordion-item" key={`${group.name}`}>
                <h2 className="accordion-header">
                  <button className="accordion-button collapsed" type="button" data-bs-toggle="collapse"
                          data-bs-target={`#collapse${g_index}`} aria-expanded="true" aria-controls={`collapse${g_index}`}>
                    {group.name}
                  </button>
                </h2>
                <div id={`collapse${g_index}`} className="accordion-collapse collapse" aria-labelledby="headingOne"
                     data-bs-parent="#accordion_categories">
                  <div className="accordion-body">
                    {group.items.map(item => {
                      item_index++;
                      return (
                      <ProductItem
                          key={item.id}
                          id={uuidv4()}
                          index={item_index}
                          name={`${item.name_number} ${item.name}`}
                          name_codename={item.name_codename}
                          price={item.price}
                          currency={currency}
                          image={`/images/${item.image}`}
                          specs={item.specs}
                          datasheet_file={item.datasheet_file}
                          datasheet_name={item.datasheet_name}
                          onClickAddItem={onClickAddItem}
                      ></ProductItem>
                    )})}
                  </div>
                </div>
              </div>
          );
        }
    );

    return (
      <Droppable
          droppableId={data.id}
          isDropDisabled={true}>

        {(provided) => (
          <div
              className="backlog-container"
              ref={provided.innerRef}
              {...provided.droppableProps}>

            {isMobile ? (
              <div className="mobileCloseMenu">
                <button onClick={onClickToggleMobileSideMenu}>
                  <img src="/images/shop/icon-close-white.svg" alt="add" />
                </button>
              </div>
            ) : null}

            <div className="accordion accordion-flush" id="accordion_categories">
              {groups}
            </div>

            {provided.placeholder && (
              <div style={{ display: 'none' }}>
                {provided.placeholder}
              </div>
            )}
          </div>
        )}

      </Droppable>
    );
  }
}


/**
 * Component that render the entire shop
 */
class Shop extends React.PureComponent {

  static get propTypes() {
    return {
      data: PropTypes.object.isRequired,
    };
  }

  constructor(props) {
    super(props);
    this.state = this.props.data;
    this.handleCrateModeChange = this.handleCrateModeChange.bind(this);
    this.handleOnDragEnd = this.handleOnDragEnd.bind(this);
    this.handleDeleteItem = this.handleDeleteItem.bind(this);
    this.handleDeleteAllItems = this.handleDeleteAllItems.bind(this);
    this.handleMouseEnterItem = this.handleMouseEnterItem.bind(this);
    this.handleMouseLeaveItem = this.handleMouseLeaveItem.bind(this);
    this.handleClickAddItem = this.handleClickAddItem.bind(this);
    this.checkAlerts = this.checkAlerts.bind(this);
    this.handleClickSelectItem = this.handleClickSelectItem.bind(this);
    this.handleClickSubmit = this.handleClickSubmit.bind(this);
    this.handleToggleOverlayRemove = this.handleToggleOverlayRemove.bind(this);
    this.handleShowOverlayRemove = this.handleShowOverlayRemove.bind(this);
    this.handleClickToggleMobileSideMenu = this.handleClickToggleMobileSideMenu.bind(this);
    this.handleClickCloseRFQFeedback = this.handleClickCloseRFQFeedback.bind(this);
    this.handleClickShowOrder = this.handleClickShowOrder.bind(this);
    this.handleClickOpenImport = this.handleClickOpenImport.bind(this);
    this.handleLoadCustomConf = this.handleLoadCustomConf.bind(this);

    this.timer = null;
    this.timer_remove = null;
  }

  componentDidMount() {
    const source = {
      droppableId: 'backlog',
      indexes: [
          itemsUnfoldedList.findIndex(element => element === "eem_pwr_mod"),
          itemsUnfoldedList.findIndex(element => element === "kasli")
      ],
    };
    const destination = {
      droppableId: 'cart',
      index: 0,
    };

    this.handleOnDragEnd({
      source,
      destination
    });
  }

  componentDidUpdate(prevProps, prevState) {
    /**
     * We check alerts (reminder + warning) only when items inside crate or
     * crate mode change.
     *
     * In the function checkAlerts, we DO NOT want to change items as we will
     * trigger again this function (componentDidUpdate) and thus,
     * making an infinite loop.
     */
    if (
      (prevState.columns.cart.items !== this.state.columns.cart.items) ||
      (prevState.currentMode !== this.state.currentMode)
    ) {
      this.checkAlerts(
        prevState.columns.cart.items,
        this.state.columns.cart.items);
    }

    if (this.state.newCardJustAdded) {
      this.timer = setTimeout(() => {
        this.setState({
          newCardJustAdded: false,
        });
      }, 2000);
    }
  }

  componentWillUnmount() {
    clearTimeout(this.timer);
  }

  handleCrateModeChange(mode) {
    this.setState({
      currentMode: mode,
    });
  }

  handleDeleteItem(index) {
    const cloned = Array.from(this.state.columns.cart.items);
    cloned.splice(index, 1);
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: cloned,
        },
      },
    });
  }

  handleDeleteAllItems() {
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: [],
        },
      },
    });
  }

  handleMouseEnterItem(id) {
    this.setState({
      ...this.state,
      currentItemHovered: id,
    });
  }

  handleMouseLeaveItem() {
    this.setState({
      ...this.state,
      currentItemHovered: null,
    });
  }

  handleClickAddItem(index, tap) {
    const source = {
      droppableId: 'backlog',
      index: index,
    };
    const destination = {
      droppableId: 'cart',
      index: this.state.columns.cart.items.length,
    };

    this.handleOnDragEnd({
      source,
      destination
    }, tap);
  }

  handleClickSelectItem(index) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, id) => {
            return {...item, selected: id === index ? true : false};
          }),
        }
      },
    });
  }

  handleToggleOverlayRemove(index, show) {
    const itemsCloned = Array.from(this.state.columns.cart.items);

    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: itemsCloned.map((item, id) => {
            return {
              ...item,
              showOverlayRemove: id === index ? show : false
            };
          }),
        }
      },
    });
  }

  handleShowOverlayRemove(index) {
    if (this.timer_remove)
      clearTimeout(this.timer_remove);

    this.handleToggleOverlayRemove(index, true);

    this.timer_remove = setTimeout(() => {
      this.handleToggleOverlayRemove(index, false);
    }, 2000);
  }

  handleClickShowOrder() {
    const crate = {
      items: [],
      type: this.state.currentMode,
    };
    const clonedCart = Array.from(this.state.columns.cart.items);
    const clonedCartData = Array.from(this.state.columns.cart.itemsData);
    console.log(clonedCartData);
    for (const i in clonedCart) {
      const item = clonedCart[i];
      const item_data = clonedCartData[i];
      crate.items.push({
        'pn': item.name_number,
        'options': item_data.options_data ? item_data.options_data : null,
      });
    }

    this.setState({
      isProcessing: false,
      shouldShowRFQFeedback: true,
      RFQBodyType: 'show',
      RFQBodyOrder: JSON.stringify(crate),
    });
  }

  handleClickOpenImport() {
    this.setState({
      isProcessing: false,
      shouldShowRFQFeedback: true,
      RFQBodyType: 'import',
    });
  }

  handleLoadCustomConf(customconf) {
    if (!customconf) {return; }

    const items = this.props.data.items;
    console.trace(customconf);
    const self = this;

    let new_items = [];
    let new_items_data = [];


    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          items: [],
        },
      },
    }, function () {

      customconf.items.map(function (item) {
        Object.keys(items).map(key => {
          if (item.pn && item.pn === items[key].name_number) {
            new_items.push(Object.assign({
              ...items[key],
            }, {
              id: uuidv4(),
              options_data: item.options ? item.options : null,
            }));
            new_items_data.push({options_data: item.options? item.options : null});
          }
        });

        return item;
      });

      this.setState({
        ...this.state,
        columns: {
          ...this.state.columns,
          cart: {
            ...this.state.columns.cart,
            items: new_items,
            itemsData: new_items_data,
          },
        },
        currentMode: customconf.type,
      });
    });
  }

  handleClickSubmit(note, email) {
    const crate = {
      items: [],
      type: this.state.currentMode,
    };
    const clonedCart = Array.from(this.state.columns.cart.items);
    const clonedCartData = Array.from(this.state.columns.cart.itemsData);
    for (const i in clonedCart) {
      const item = clonedCart[i];
      const item_data = clonedCartData[i];
      crate.items.push({
        'pn': item.name_number,
        'options': item_data.options_data ? item_data.options_data : null,
      });
    }

    const {data} = this.props;

    this.setState({isProcessing: true});

    axios.post(data.API_RFQ, {
      email,
      note,
      configuration: JSON.stringify(crate)
    }).then(response => {
      this.setState({
        isProcessing: false,
        shouldShowRFQFeedback: true,
        RFQBodyType: 'email',
        isProcessingComplete: true,
      });
    }).catch(err => {
      this.setState({isProcessing: false}, () => {
        alert("We cannot receive your request. Try using the export by coping the configuration and send it to us at sales[at]m-labs.hk");
      });
    })
  }

  handleOnDragEnd(result, newAdded) {
    const {
      source,
      destination,
    } = result;
    let dragged_items = [];
    if (source.indexes) {
      source.indexes.forEach((card_index, _) => {
        dragged_items.push(itemsUnfoldedList[card_index]);
      })
    } else if (source.index >= 0) {
      dragged_items.push(itemsUnfoldedList[source.index]);
    }


    if (!destination) {
      if (source.droppableId === 'cart') {
        this.setState({
          ...this.state,
          newCardJustAdded: false,
          columns: {
            ...this.state.columns,
            [source.droppableId]: {
              ...this.state.columns[source.droppableId],
              items: remove(
                this.state.columns[source.droppableId].items,
                source.index,
              ),
            },
          },
        });
      }

      return;
    }

    switch(source.droppableId) {

      case 'backlog':
        if (source.droppableId !== destination.droppableId) {
          this.setState({
            ...this.state,
            newCardJustAdded: newAdded ? true : false,
            columns: {
              ...this.state.columns,
              [destination.droppableId]: {
                ...this.state.columns[destination.droppableId],
                items: copy(
                    this.state.items,
                    this.state.columns[source.droppableId],
                    this.state.columns[destination.droppableId],
                    dragged_items,
                    destination,
                ),
              },
            },
          });
        }
        break;

      case destination.droppableId:
        this.setState({
          ...this.state,
          newCardJustAdded: false,
          columns: {
            ...this.state.columns,
            [destination.droppableId]: {
              ...this.state.columns[destination.droppableId],
              items: reorder(
                this.state.columns[destination.droppableId].items,
                source.index,
                destination.index,
              ),
            },
          },
        });
        break;

      default:
        break;
    }
  }

  handleClickToggleMobileSideMenu() {
    this.setState({
      ...this.state,
      mobileSideMenuShouldOpen: !this.state.mobileSideMenuShouldOpen,
    });
  }

  handleClickCloseRFQFeedback() {
    this.setState({
      shouldShowRFQFeedback: false,
    });
  }

  checkAlerts(prevItems, newItems) {
    console.log('--- START CHECKING CRATE WARNING ---');

    const {
      currentMode,
      crateModeSlots,
      crateRules,
    } = this.state;

    const itemsCloned = Array.from(newItems);
    const itemsData = [];
    const rules = {};


    // check number of slot in crate
    const nbrOccupied = nbrOccupiedSlotsInCrate(newItems);
    if (nbrOccupied > crateModeSlots[currentMode]) {
      rules[crateRules.maxSlot.type] = {...crateRules.maxSlot};
    } else if (crateModeSlots[currentMode] === 21 && nbrOccupied <= 10) {
      rules[crateRules.compactSlot.type] = {...crateRules.compactSlot};
    }


    // check the number of EEM connectors available for all Kasli
    const idxK = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'kasli' || next.type === 'vhdcicarrier') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxK.length - 1; i++) {
      let slots;
      let nbUsedSlot = 0;
      let nbrCurrentClock = 0;
      let idx = idxK[i];

      if (i !== idxK.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxK[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      if (i == 0) {
        const slots_need_resource = itemsCloned.slice(0, idx);
        const idx_need = slots_need_resource.findIndex(e => (e.rules && e.rules.resources));

        if (idx_need != -1) {
          if (idx_need in itemsData) {
            if ('warnings' in itemsData[idx_need]) {
              itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
            } else {
              itemsData[idx_need].warnings = {};
              itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
            }
          } else {
            itemsData[idx_need] = {...itemsCloned[idx_need]};
            itemsData[idx_need].warnings = {};
            itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
          }
        }
      }

      nbUsedSlot = slots
        .filter(item => item.type !== 'idc-bnc')
        .reduce((prev, next) => {
          return prev + next.slotOccupied;
        }, 0);
      nbrCurrentClock = slots
        .reduce((prev, next) => {
          return next.type === 'clocker' ? prev + next.clockOccupied : prev;
        }, 0);

      if (idx in itemsData) {
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        if (!('warnings' in itemsData[idx])) {
          itemsData[idx].warnings = {};
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        itemsData[idx].warnings = {};
      }

      if (nbUsedSlot > itemsCloned[idx].nbrSlotMax) {
        if (itemsCloned[idx].rules.maxSlot.message) {
          rules[itemsCloned[idx].rules.maxSlot.type] = {...itemsCloned[idx].rules.maxSlot};
        }
        itemsData[idx].warnings.maxSlotWarning = {...itemsCloned[idx].rules.maxSlotWarning};
      }

      if (nbrCurrentClock > itemsCloned[idx].nbrClockMax) {
        rules[itemsCloned[idx].rules.maxClock.type] = {...itemsCloned[idx].rules.maxClock};
        itemsData[idx].warnings.maxClockWarning = {...itemsCloned[idx].rules.maxClockWarning};
      }

      if (itemsCloned.length > (idx + 1)) {
        const ddkali = itemsCloned[idx + 1];
        if (ddkali.type === 'kasli' || ddkali.type === 'vhdcicarrier') {
          rules[ddkali.rules.follow.type] = {...ddkali.rules.follow};
        }
      }
    }

    if (idxK.length === 0) {
      const slots_need_resource = itemsCloned.slice(0);
      const idx_need = slots_need_resource.findIndex(e => (e.rules && e.rules.resources));

      if (idx_need != -1) {
        if (idx_need in itemsData) {
          if ('warnings' in itemsData[idx_need]) {
            itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
          } else {
            itemsData[idx_need].warnings = {};
            itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
          }
        } else {
          itemsData[idx_need] = {...itemsCloned[idx_need]};
          itemsData[idx_need].warnings = {};
          itemsData[idx_need].warnings.resources = {...itemsCloned[idx_need].rules.resources};
        }
      }
    }


    // check number of clock connector available
    const idxC = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'kasli' || next.type === 'clocker') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxC.length - 1; i++) {
      let slots;
      let nbrCurrentClock = 0;
      let idx = idxC[i];

      if (i !== idxC.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxC[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      nbrCurrentClock = slots.reduce((prev, next) => {
        return prev + next.clockOccupied;
      }, 0);

      if (idx in itemsData) {
        if (itemsData[idx].nbrCurrentClock) {
          itemsData[idx].nbrCurrentClock += nbrCurrentClock;
        } else {
          itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentClock = nbrCurrentClock;
        itemsData[idx].warnings = {};
      }

      if (nbrCurrentClock > itemsCloned[idx].nbrClockMax) {
        rules[itemsCloned[idx].rules.maxClock.type] = {...itemsCloned[idx].rules.maxClock};
        itemsData[idx].warnings.maxClockWarning = {...itemsCloned[idx].rules.maxClockWarning};
      }
    }


    // check for number of recommended EEM connectors
    ['novo', 'urukul', 'koster'].map(_type => {
      if (itemsCloned.find(elem => elem.type === _type)) {
        rules[this.state.items[_type].rules.connectors.type] = {...this.state.items[_type].rules.connectors};
      }
      return _type;
    });


    if (itemsCloned.find(elem => elem.type === 'urukul')) {
      if (this.state.items['urukul'].rules.info) {
        rules[this.state.items['urukul'].rules.info.type] = {...this.state.items['urukul'].rules.info};
      }
    }


    // check if IDC-BNC is correctly positionned (after Zotino or HD68)
    const idxIDCBNC = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'idc-bnc') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (var i = idxIDCBNC.length - 1; i >= 0; i--) {
      const ce = idxIDCBNC[i];
      let shouldWarning = false;

      if (ce == 0) {
        shouldWarning = true;
      } else if (ce >= 1) {
        const pe = idxIDCBNC[i] - 1;
        if (itemsCloned[pe].type !== 'zotino' &&
            itemsCloned[pe].type !== 'hd68' &&
            itemsCloned[pe].type !== 'idc-bnc') {
          shouldWarning = true;
        }
      }

      if (shouldWarning) {
        itemsData[ce] = {...itemsCloned[ce]};
        itemsData[ce].warnings = {};
        itemsData[ce].warnings.wrong = {...itemsCloned[ce].rules.wrong};
      }
    }


    // check number of IDC-BNC adapters for a Zotino and HD68-IDC
    const idxZH = itemsCloned.reduce((prev, next, i) => {
      if (next.type === 'zotino' || next.type === 'hd68') {
        prev.push(i);
      }
      return prev;
    }, []);
    for (let i = 0; i <= idxZH.length - 1; i++) {
      let slots;
      let nbUsedSlot = 0;
      let idx = idxZH[i];

      if (i !== idxZH.length - 1) {
        slots = itemsCloned.slice(idx + 1, idxZH[i + 1]);
      } else {
        slots = itemsCloned.slice(idx + 1);
      }

      let stopCount = false;
      nbUsedSlot = slots.reduce((prev, next, ci, ca) => {
        if (ci === 0 && next.type === 'idc-bnc') {
          return prev + 1;
        } else if (ca[0].type === 'idc-bnc' && ci > 0 && ca[ci - 1].type === 'idc-bnc') {
          if (next.type !== 'idc-bnc') { stopCount = true; }
          return prev + (next.type === 'idc-bnc' && !stopCount ? 1 : 0);
        }
        return prev;
      }, 0);

      if (idx in itemsData) {
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        if (!('warnings' in itemsData[idx])) {
          itemsData[idx].warnings = {};
        }
      } else {
        itemsData[idx] = {...itemsCloned[idx]};
        itemsData[idx].nbrCurrentSlot = nbUsedSlot;
        itemsData[idx].warnings = {};
      }

      if (nbUsedSlot > 0) {
        if (itemsCloned[idx].rules.maxSlot.message) {
          rules[itemsCloned[idx].rules.maxSlot.type] = {...itemsCloned[idx].rules.maxSlot};
        }
      }
      if (nbUsedSlot > itemsCloned[idx].nbrSlotMax) {
        itemsData[idx].warnings.maxSlotWarning = {...itemsCloned[idx].rules.maxSlotWarning};
      }

      // check if HD68-IDC has at least 1 IDC-BNC adapter
      if (itemsCloned[idx].type === 'hd68') {
        let shouldWarning = false;

        if (idx < itemsCloned.length - 1) {
          if (itemsCloned[idx + 1].type !== 'idc-bnc') {
            shouldWarning = true;
          }
        } else if (idx === itemsCloned.length - 1) {
          shouldWarning = true;
        }

        if (shouldWarning) {
          if (idx in itemsData) {
            itemsData[idx].warnings.minAdapter = {...itemsCloned[idx].rules.minAdapter};
          } else {
            itemsData[idx] = {...itemsCloned[idx]};
            itemsData[idx].warnings = {};
            itemsData[idx].warnings.minAdapter = {...itemsCloned[idx].rules.minAdapter};
          }
        }
      }
    }

    itemsCloned.forEach((_elem, idx) => {
      if (!(idx in itemsData)) itemsData[idx] = {};
      if (idx in this.state.columns.cart.itemsData && this.state.columns.cart.itemsData[idx].options_data) {
        itemsData[idx].options_data = this.state.columns.cart.itemsData[idx].options_data;
      }
    });


    // update state with rules
    this.setState({
      ...this.state,
      columns: {
        ...this.state.columns,
        cart: {
          ...this.state.columns.cart,
          itemsData: itemsData,
        }
      },
      rules: {
        ...rules,
      },
    });
  }

  render() {

    const {
      currency,
      currentItemHovered,
      currentMode,
      crateModeSlots,
      crateModeItems,
      items,
      columns,
      rules,
      mobileSideMenuShouldOpen,
      newCardJustAdded,
      isProcessing,
      shouldShowRFQFeedback,
      RFQBodyType,
      RFQBodyOrder,
      isProcessingComplete,
    } = this.state;

    const isMobile = window.deviceIsMobile();
    const isTouch = window.isTouchEnabled();

    return (
      <DragDropContext onDragEnd={this.handleOnDragEnd}>

        <Layout
            showRFQFeedback={shouldShowRFQFeedback}
            RFQBodyType={RFQBodyType}
            RFQBodyOrder={RFQBodyOrder}
            className="shop"
            mobileSideMenuShouldOpen={mobileSideMenuShouldOpen}
            isMobile={isMobile}
            newCardJustAdded={newCardJustAdded}
            onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
            onClickCloseRFQFeedback={this.handleClickCloseRFQFeedback}
            onClickLoadCustomConf={this.handleLoadCustomConf}
            items={items}
            aside={
              <Backlog
                  currency={currency}
                  items={items}
                  data={columns['backlog']}
                  onClickAddItem={this.handleClickAddItem}
                  onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
                  isMobile={isMobile}>
              </Backlog>
            }
            main={(
              <OrderPanel
                  onClickToggleMobileSideMenu={this.handleClickToggleMobileSideMenu}
                  onClickOpenImport={this.handleClickOpenImport}
                  isMobile={isMobile}
                  title="Order hardware"
                  description="
                    Drag and drop the cards you want into the crate below to see how the combination would look like. If you have any issues with this ordering system, or if you need other configurations, email us directly anytime at sales@m-****.hk. The price is estimated and must be confirmed by a quote."
                  crateMode={
                    <CrateMode
                        items={crateModeItems}
                        mode={currentMode}
                        onClickMode={this.handleCrateModeChange}>
                    </CrateMode>}
                  crate={
                    <Crate
                        cart={
                          <Cart
                              nbrSlots={crateModeSlots[currentMode]}
                              data={columns['cart']}
                              isMobile={isMobile}
                              isTouch={isTouch}
                              itemHovered={currentItemHovered}
                              onToggleOverlayRemove={this.handleToggleOverlayRemove}
                              onClickRemoveItem={this.handleDeleteItem}
                              onClickItem={this.handleShowOverlayRemove}>
                          </Cart>
                        }
                        rules={Object.values(rules).filter(rule => rule)}>
                    </Crate>
                  }
                  summaryPrice={
                    <OrderSumary
                        currency={currency}
                        currentMode={currentMode}
                        modes={crateModeItems}
                        summary={columns['cart'].items}
                        itemsData={columns.cart.itemsData}
                        onMouseEnterItem={this.handleMouseEnterItem}
                        onMouseLeaveItem={this.handleMouseLeaveItem}
                        onDeleteItem={this.handleDeleteItem}
                        onDeleteAllItems={this.handleDeleteAllItems}
                        onClickSelectItem={this.handleClickSelectItem}>
                    </OrderSumary>
                  }
                  form={
                    <OrderForm
                        isProcessingComplete={isProcessingComplete}
                        processingComplete={this.handleProcessingComplete}
                        isProcessing={isProcessing}
                        onClickSubmit={this.handleClickSubmit}
                        onClickShow={this.handleClickShowOrder}>
                    </OrderForm>
                  }>
              </OrderPanel>
            )}>
        </Layout>

      </DragDropContext>
    );
  }
}

createRoot(document.querySelector('#root-shop')).render(<Shop data={data} />);
