import React from 'react';
import PropTypes from 'prop-types';
import IMask from 'imask/esm/imask';

const MASK_PROPS = {
  // common
  mask: PropTypes.oneOfType([PropTypes.array, PropTypes.func, PropTypes.string, PropTypes.instanceOf(RegExp), PropTypes.oneOf([Date, Number, IMask.Masked]), PropTypes.instanceOf(IMask.Masked)]),
  value: PropTypes.any,
  unmask: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['typed'])]),
  prepare: PropTypes.func,
  prepareChar: PropTypes.func,
  validate: PropTypes.func,
  commit: PropTypes.func,
  overwrite: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['shift'])]),
  eager: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['append', 'remove'])]),
  skipInvalid: PropTypes.bool,
  // events
  onAccept: PropTypes.func,
  onComplete: PropTypes.func,
  // pattern
  placeholderChar: PropTypes.string,
  displayChar: PropTypes.string,
  lazy: PropTypes.bool,
  definitions: PropTypes.object,
  blocks: PropTypes.object,
  // enum
  enum: PropTypes.arrayOf(PropTypes.string),
  // range
  maxLength: PropTypes.number,
  from: PropTypes.number,
  to: PropTypes.number,
  // date
  pattern: PropTypes.string,
  format: PropTypes.func,
  parse: PropTypes.func,
  autofix: PropTypes.oneOfType([PropTypes.bool, PropTypes.oneOf(['pad'])]),
  // number
  radix: PropTypes.string,
  thousandsSeparator: PropTypes.string,
  mapToRadix: PropTypes.arrayOf(PropTypes.string),
  scale: PropTypes.number,
  normalizeZeros: PropTypes.bool,
  padFractionalZeros: PropTypes.bool,
  min: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
  max: PropTypes.oneOfType([PropTypes.number, PropTypes.instanceOf(Date)]),
  // dynamic
  dispatch: PropTypes.func,
  // ref
  inputRef: PropTypes.oneOfType([PropTypes.func, PropTypes.shape({
    current: PropTypes.object
  })])
};
const MASK_PROPS_NAMES = Object.keys(MASK_PROPS).filter(p => p !== 'value');
const NON_MASK_OPTIONS_PROPS_NAMES = ['value', 'unmask', 'onAccept', 'onComplete', 'inputRef'];
const MASK_OPTIONS_PROPS_NAMES = MASK_PROPS_NAMES.filter(pName => NON_MASK_OPTIONS_PROPS_NAMES.indexOf(pName) < 0);
function IMaskMixin(ComposedComponent) {
  var _class;
  const MaskedComponent = (_class = class MaskedComponent extends React.Component {
    constructor(props) {
      super(props);
      this._inputRef = this._inputRef.bind(this);
    }
    componentDidMount() {
      if (!this.props.mask) return;
      this.initMask();
    }
    componentDidUpdate() {
      const props = this.props;
      const maskOptions = this._extractMaskOptionsFromProps(props);
      if (maskOptions.mask) {
        if (this.maskRef) {
          this.maskRef.updateOptions(maskOptions); // TODO fix
          if ('value' in props && props.value !== undefined) this.maskValue = props.value;
        } else {
          this.initMask(maskOptions);
        }
      } else {
        this.destroyMask();
        if ('value' in props && props.value !== undefined) {
          var _this$element;
          if ((_this$element = this.element) != null && _this$element.isContentEditable && this.element.tagName !== 'INPUT' && this.element.tagName !== 'TEXTAREA') this.element.textContent = props.value;else this.element.value = props.value;
        }
      }
    }
    componentWillUnmount() {
      this.destroyMask();
    }
    _inputRef(el) {
      this.element = el;
      if (this.props.inputRef) {
        if (Object.prototype.hasOwnProperty.call(this.props.inputRef, 'current')) this.props.inputRef.current = el;else this.props.inputRef(el);
      }
    }
    initMask(maskOptions) {
      if (maskOptions === void 0) {
        maskOptions = this._extractMaskOptionsFromProps(this.props);
      }
      this.maskRef = IMask(this.element, maskOptions).on('accept', this._onAccept.bind(this)).on('complete', this._onComplete.bind(this));
      if ('value' in this.props && this.props.value !== undefined) this.maskValue = this.props.value;
    }
    destroyMask() {
      if (this.maskRef) {
        this.maskRef.destroy();
        delete this.maskRef;
      }
    }
    _extractMaskOptionsFromProps(props) {
      const {
        ...cloneProps
      } = props;

      // keep only mask options
      Object.keys(cloneProps).filter(prop => MASK_OPTIONS_PROPS_NAMES.indexOf(prop) < 0).forEach(nonMaskProp => {
        delete cloneProps[nonMaskProp];
      });
      return cloneProps;
    }
    _extractNonMaskProps(props) {
      const {
        ...cloneProps
      } = props;
      MASK_PROPS_NAMES.forEach(maskProp => {
        delete cloneProps[maskProp];
      });
      if (!('defaultValue' in cloneProps)) cloneProps.defaultValue = props.mask ? '' : cloneProps.value;
      delete cloneProps.value;
      return cloneProps;
    }
    get maskValue() {
      if (!this.maskRef) return '';
      if (this.props.unmask === 'typed') return this.maskRef.typedValue;
      if (this.props.unmask) return this.maskRef.unmaskedValue;
      return this.maskRef.value;
    }
    set maskValue(value) {
      if (!this.maskRef) return;
      value = value == null && this.props.unmask !== 'typed' ? '' : value;
      if (this.props.unmask === 'typed') this.maskRef.typedValue = value;else if (this.props.unmask) this.maskRef.unmaskedValue = value;else this.maskRef.value = value;
    }
    _onAccept(e) {
      if (this.props.onAccept && this.maskRef) this.props.onAccept(this.maskValue, this.maskRef, e);
    }
    _onComplete(e) {
      if (this.props.onComplete && this.maskRef) this.props.onComplete(this.maskValue, this.maskRef, e);
    }
    render() {
      return React.createElement(ComposedComponent, {
        ...this._extractNonMaskProps(this.props),
        inputRef: this._inputRef
      });
    }
  }, _class.displayName = void 0, _class.propTypes = void 0, _class);
  const nestedComponentName = ComposedComponent.displayName || ComposedComponent.name || 'Component';
  MaskedComponent.displayName = "IMask(" + nestedComponentName + ")";
  MaskedComponent.propTypes = MASK_PROPS;
  return React.forwardRef((props, ref) => React.createElement(MaskedComponent, {
    ...props,
    ref
  }));
}

export { IMaskMixin as default };
