import classNames from 'classnames';
import React from 'react';
import PropTypes from 'prop-types';
import flatten from 'lodash/flatten';
import extend from 'extend';

import ClearAll from '../ClearAll';
import optionsFactory, { buildCustomOption, buildOptionLabel } from './options';
import KdSlider from '../KdSlider';
import wrap from '../ItemContainerWrapper';
import { numberHasErrors } from '../../utils/validations.js';
import { numberHandler } from '../../utils/number.js';

const defaultProps = {
  isDisabled: false,
  min: 0,
  max: 1000,
  mainSelector: {
    visible: true,
    notSelectedOption: { label: 'Select...' },
    optionGroups: [
      {
        options: [
          { operator: 'Eq', label: 'Equal To' },
          { operator: 'NotEq', label: 'Not Equal To' },
          { operator: 'Gt', label: 'Greater Than' },
          { operator: 'Gte', label: 'Greater Than Or Equal To' },
          { operator: 'Lt', label: 'Less Than' },
          { operator: 'Lte', label: 'Less Than Or Equal To' },
          { operator: 'Between', label: 'Between' },
        ],
      },
    ],
  },
  events: {},
  slider: {
    label: { visible: true },
  },
  addOnKeyPress: false,
};

const propTypes = {
  min: PropTypes.number,
  max: PropTypes.number,
  type: PropTypes.string,
  addOnKeyPress: PropTypes.bool,
  decimals: PropTypes.number,
  isDisabled: PropTypes.bool,
  selectedValuesInitial: PropTypes.arrayOf(
    PropTypes.shape({
      operator: PropTypes.string,
      values: PropTypes.array,
      label: PropTypes.string,
    }),
  ),
  kdFilterModel: PropTypes.object.isRequired,
  mainSelector: PropTypes.shape({
    visible: PropTypes.bool,
    notSelectedOption: PropTypes.shape({
      label: PropTypes.string,
      selected: PropTypes.bool,
    }),
    optionGroups: PropTypes.arrayOf(
      PropTypes.shape({
        label: PropTypes.string,
        options: PropTypes.arrayOf(
          PropTypes.shape({
            values: PropTypes.arrayOf(PropTypes.oneOfType([PropTypes.number])),
            operator: PropTypes.string,
            selected: PropTypes.bool,
          }),
        ),
      }),
    ),
  }),

  events: PropTypes.shape({
    onSelect: PropTypes.func,
    onChange: PropTypes.func,
  }),

  slider: PropTypes.shape({
    label: PropTypes.shape({
      visible: PropTypes.bool,
      handler: PropTypes.func,
    }),
  }),

  // private props
  theme: PropTypes.oneOfType([PropTypes.object, PropTypes.func]).isRequired,
  onOptionSelected: PropTypes.func,
  onItemAdded: PropTypes.func,
  onClearChicklets: PropTypes.func,
  onError: PropTypes.func,
  emptyItems: PropTypes.bool,
  isChickletNone: PropTypes.bool,
};

class KdSelectNumber extends React.Component {
  constructor(props) {
    super(props);

    const { type, decimals, kdFilterModel, mainSelector } = props;

    this.mainSelector = extend(true, {}, { visible: true }, mainSelector);
    this.numberUtils = numberHandler({ type, decimals });
    this.kdFilterOptions = {
      operator: 'Eq',
      model: kdFilterModel,
      itemHandler: this.numberUtils.handler,
    };

    this.options = this.buildOptions();

    this.state = {
      selectedOption: this.options[0],
      selectedValue: '',
    };
  }

  getDefaultSelected = () => {
    const selected = this.options.filter(o => o.isSelected)[0];
    return selected || this.options[0];
  };

  componentDidMount() {
    const { selectedValuesInitial } = this.props;
    if (selectedValuesInitial.length) {
      this.setSelectedValues(this.props.selectedValuesInitial);
    } else {
      this.onSelectChange({
        target: { value: this.getDefaultSelected().id },
        silent: true,
      });
    }
  }

  componentWillReceiveProps(nextProps) {
    const { kdFilterModel } = this.props;

    if (kdFilterModel !== nextProps.kdFilterModel) {
      this.kdFilterOptions = {
        operator: 'Eq',
        model: nextProps.kdFilterModel,
        itemHandler: this.numberUtils.handler,
      };
      // this.clearValues();
      // this.setSelectedValues(nextProps.selectedValuesInitial);
    }
  }

  setSelectedValues = selectedValuesInitial => {
    const { onError } = this.props;
    if (this.state.selectedOption.operator !== 'any') {
      onError({ errors: numberHasErrors('') });
    }

    selectedValuesInitial.forEach(({ operator, values }) => {
      const relatedOption = this.options.find(
        option => option.operator === operator,
      );
      if (relatedOption) {
        if (Array.isArray(values)) {
          relatedOption.values = values;
        }
        this.addByOperator({
          selectedItem: this.buildItemToAdd(relatedOption),
          silent: true,
        });
        this.setState(
          {
            ...this.state,
            selectedOption: relatedOption,
          },
          () =>
            !this.props.isChickletNone && this.input && (this.input.value = ''),
        );
      }
    });
  };

  buildOptions = () => {
    const { optionGroups, visible, notSelectedOption } = this.mainSelector;

    if (!visible) {
      return [optionsFactory.create('betweenOption')];
    }

    let options = optionGroups[0].options.map(
      ({ values, operator, label, selected }) => {
        if (values && values.length) {
          return buildCustomOption({
            values,
            label,
            operator,
            selected,
          });
        }
        return optionsFactory.create(operator, label, selected);
      },
    );

    if (notSelectedOption) {
      options = [
        optionsFactory.create(
          'Any',
          notSelectedOption.label,
          notSelectedOption.selected,
        ),
        ...options,
      ];
    }

    return options;
  };

  addByOperator({ selectedItem, silent }) {
    const { operator, values, prefix } = selectedItem;
    const { min, max } = this.props;
    switch (operator) {
      case 'Eq':
      case 'NotEq':
        values.forEach(value =>
          this.addItem({
            item: {
              ...selectedItem,
              value,
              chickletLabel: `${prefix}${value}`,
              id: `${selectedItem.option}-${value}`,
              option: `${selectedItem.option}-${value}`,
              key: `${selectedItem.option}-${value}`,
            },
            silent,
          }),
        );
        break;
      case 'Between':
        if (values.length && !numberHasErrors(values, min, max)) {
          this.addItem({ item: { ...selectedItem, value: values }, silent });
        }
        break;
      case 'Gt':
      case 'Gte':
      case 'Lt':
      case 'Lte':
        this.addItem({
          item: {
            ...selectedItem,
            value: values[0],
          },
          silent,
        });
        break;
      default:
        console.error('Error: karmadata-kd-ui - Invalid Operator');
        break;
    }
  }

  addItem = ({ item, silent }) => {
    const {
      onItemAdded,
      events: { onChange },
    } = this.props;
    const callback = () =>
      onChange &&
      onChange({
        data: {
          model: item.kdFilterOptions.model,
          ...item.kdFilterOptions.model.getFilterItemValue(),
        },
      });
    onItemAdded(item, !silent && callback);
  };

  onSelectChange = ({ target, silent }) => {
    const selectedOption = this.options.find(
      option => option.id === target.value,
    );
    if (!selectedOption) {
      return;
    }

    const {
      isChickletNone,
      onOptionSelected,
      onError,
      onClearChicklets,
      events: { onSelect },
    } = this.props;

    this.clearValues();

    if (isChickletNone || selectedOption.operator === 'any') {
      this.props.clearErrors();
    }

    if (selectedOption.values) {
      this.addByOperator({
        selectedItem: this.buildItemToAdd(selectedOption),
        silent,
      });
    } else if (selectedOption.operator !== 'any') {
      onError({ errors: numberHasErrors('') });
    }

    this.setState(
      {
        selectedOption,
        selectedValue: selectedOption.value,
      },
      () => {
        onOptionSelected(this.state.selectedOption.id);
        this.input && this.input.focus();

        const value = selectedOption.selected
          ? selectedOption.selected.value
          : null;
        const label = selectedOption.selected
          ? selectedOption.selected.chickletLabel
          : null;
        const data = { label, value };

        if (onSelect) {
          onSelect({ data });
        }
      },
    );
  };

  onSelectedValueChange = ({ key, target }) => {
    const { value } = target;
    const { operator, id, prefix } = this.state.selectedOption;
    const selectedItem = { operator, id, prefix, values: flatten([value]) };
    this.setState(
      {
        selectedOption: selectedItem,
      },
      () => {
        if (key === 'Enter' || this.props.addOnKeyPress) {
          this.addByOperator({
            selectedItem: this.buildItemToAdd(selectedItem),
          });
          this.input && !this.props.addOnKeyPress && (this.input.value = '');
        }
      },
    );
  };

  buildItemToAdd = selectedItem => {
    const { operator, values, id, prefix, custom } = selectedItem;
    const {
      slider: {
        label: { handler },
      },
      min,
      max,
    } = this.props;
    const { model, itemHandler } = this.kdFilterOptions;
    const chickletLabel = buildOptionLabel({
      values: selectedItem.values,
      unit: this.numberUtils.unit,
      operator,
      handler,
    });

    const remove = items => {
      if (!items.length) {
        this.clearValues();
      }
    };

    return {
      option: id,
      errors: numberHasErrors(values, min, max),
      values,
      chickletLabel,
      remove,
      operator,
      prefix,
      custom,
      kdFilterOptions: { model, operator, itemHandler },
    };
  };

  clearValues = () => {
    this.props.onClearChicklets();
    this.setState(
      {
        selectedOption: this.options[0],
        selectedValue: '',
      },
      () => this.props.onOptionSelected(this.state.selectedOption.id),
    );
    this.options.filter(option => option.custom !== true).forEach(option => {
      option.values = null;
    });
    this.input && (this.input.value = '');
  };

  renderUnit = () => {
    const { unit } = this.numberUtils;
    const { theme } = this.props;
    if (unit) {
      return (
        <span
          className={classNames(
            'c-kd-select-number__unit',
            theme['c-kd-select-number__unit'],
          )}
        >
          {unit}
        </span>
      );
    }
  };

  optionHelper = () => {
    const { theme } = this.props;
    const { operator, custom, values } = this.state.selectedOption;
    if (custom) {
      return null;
    }

    switch (operator) {
      case 'Between': {
        const {
          min,
          max,
          slider,
          onError,
          isChickletNone,
          events: { onChange },
        } = this.props;
        return (
          <KdSlider
            theme={theme}
            slider={slider}
            min={min}
            max={max}
            values={values || []}
            onError={onError}
            unit={this.numberUtils.unit}
            isChickletNone={isChickletNone}
            onChange={onChange}
            onSelectedValue={this.onSelectedValueChange}
          />
        );
      }
      case 'Eq':
      case 'NotEq':
      case 'Gt':
      case 'Gte':
      case 'Lt':
      case 'Lte':
        return (
          <div
            className={classNames(
              'c-kd-select-number',
              theme['c-kd-select-number'],
            )}
          >
            <input
              className={classNames(
                'c-kd-select-number__input',
                'u-1/1',
                'c-kd__input',
                theme['c-kd-select-number__input'],
                theme['c-kd__input'],
                theme['u-1/1'],
              )}
              ref={ref => (this.input = ref)}
              onKeyUp={this.onSelectedValueChange}
              defaultValue={(values && values[0]) || ''}
              type="text"
            />
            {this.renderUnit()}
          </div>
        );
      default:
        return null;
    }
  };

  render() {
    const { theme } = this.props;
    return (
      <div
        className={classNames(
          'c-kd-select-number',
          theme['c-kd-select-number'],
          { 'u-disabled': this.props.isDisabled },
          { [theme['u-disabled']]: this.props.isDisabled },
        )}
      >
        {this.mainSelector.visible && (
          <div className={classNames('c-base-select', theme['c-base-select'])}>
            <select
              onChange={this.onSelectChange}
              value={this.state.selectedOption.id}
            >
              {this.options.map(option => (
                <option value={option.id} key={option.id}>
                  {option.displayName}
                </option>
              ))}
            </select>
          </div>
        )}
        {this.optionHelper()}
        {!this.props.emptyItems &&
          !this.props.isChickletNone &&
          this.props.chicklets.clearAll.visible && (
            <ClearAll theme={theme} onClick={this.clearValues} />
          )}
      </div>
    );
  }
}

KdSelectNumber.propTypes = propTypes;
KdSelectNumber.defaultProps = defaultProps;

export default wrap(KdSelectNumber);
