import React from 'react';
import PropTypes from 'prop-types';
import { withFormsy, propTypes as formsyPropTypes } from 'formsy-react';
import { Controlled as CodeMirror } from 'react-codemirror2';
import 'codemirror/addon/display/placeholder';
import { FormGroup } from '../../core/molecules/formGroup/FormGroup';

const propTypes = {
  ...formsyPropTypes,
  children: PropTypes.node,
  disabled: PropTypes.bool,
  /**
   * Object of props passed to the <FormGroup> component
   */
  groupProps: PropTypes.object,
  helper: PropTypes.node,
  isHeightAuto: PropTypes.bool,
  label: PropTypes.node,
  onChange: PropTypes.func,
  placeholder: PropTypes.string,
  /**
   * Object of props passed to the <CodeMirror> element (from `react-codemirror2` package)
   */
  codeMirrorProps: PropTypes.object,
  /**
   * Object of options passed to the <CodeMirror>
   * options property (from `react-codemirror2` package)
   */
  codeMirrorOptions: PropTypes.object,
};

const defaultProps = {
  children: null,
  disabled: false,
  groupProps: {},
  helper: null,
  isHeightAuto: false,
  label: null,
  onChange: () => {},
  placeholder: null,
  codeMirrorProps: {},
  codeMirrorOptions: {},
};

export const FieldCodemirrorUI = ({
  // Formsy props
  getErrorMessage,
  getValue,
  isFormSubmitted,
  isPristine,
  isRequired,
  isValid,
  setValue,

  // Global props
  children,
  disabled,
  groupProps,
  helper,
  label,
  onChange,
  placeholder,

  // Custom props
  codeMirrorProps,
  codeMirrorOptions,
  isHeightAuto,
}) => {
  const [isTouched, setIsTouched] = React.useState(false);
  const [isFocused, setIsFocused] = React.useState(false);
  const [codeMirrorInstance, setCodeMirrorInstance] = React.useState(null);
  const [forceUpdateViewTimeout, setForceUpdateViewTimeout] = React.useState(null);

  const isFeedbackVisible = (isTouched && !isPristine()) || isFormSubmitted();
  const isError = !isValid() && isFeedbackVisible;

  const handleChange = (value) => {
    setValue(value);
    onChange(value);
  };

  const handleFocus = () => {
    setIsFocused(true);
    setIsTouched(true);
  };

  const handleBlur = () => {
    setIsFocused(false);
  };

  const refreshCodeMirror = () => {
    if (!codeMirrorInstance) {
      return;
    }

    codeMirrorInstance.refresh();
  };

  const clearForceUpdateView = () => {
    if (!forceUpdateViewTimeout) {
      return;
    }
    clearTimeout(forceUpdateViewTimeout);
  };

  const forceUpdateView = () => {
    if (!codeMirrorInstance) {
      return;
    }

    clearForceUpdateView();

    const previousHeight = codeMirrorInstance.getScrollInfo().height || 0;

    setForceUpdateViewTimeout(setTimeout(() => {
      const currentHeight = codeMirrorInstance.getScrollInfo().height || 0;

      if (
        // Don't refresh if no height (CodeMirror is not visible)
        currentHeight <= 0
        // Don't refresh if same height (before timeout)
        || previousHeight === currentHeight
      ) {
        return;
      }

      refreshCodeMirror();
    }));
  };

  React.useEffect(() => {
    forceUpdateView();

    return () => {
      clearForceUpdateView();
    };
  });

  return (
    <FormGroup
      label={label}
      helper={helper}
      isOptional={!isRequired()}
      validationState={!isFocused && isError ? 'danger' : null}
      feedbackMessage={isFeedbackVisible && getErrorMessage()}
      {...groupProps}
    >
      <CodeMirror
        className={`${isHeightAuto ? 'CodeMirror--heightAuto' : 'CodeMirror--heightSmall'} ${disabled ? 'CodeMirror--readOnly' : ''}`}
        value={getValue() || ''}
        onBeforeChange={(instance, data, value) => {
          handleChange(value);
        }}
        onFocus={handleFocus}
        onBlur={handleBlur}
        options={{
          mode: 'text',
          lineWrapping: true,
          lineNumbers: true,
          viewportMargin: Infinity,
          autoRefresh: true,
          readOnly: disabled,
          placeholder,
          ...codeMirrorOptions,
        }}
        editorDidMount={(editor) => {
          setCodeMirrorInstance(editor);
        }}
        {...codeMirrorProps}
      />
      {children}
    </FormGroup>
  );
};

FieldCodemirrorUI.propTypes = propTypes;
FieldCodemirrorUI.defaultProps = defaultProps;

export const FieldCodemirror = withFormsy(FieldCodemirrorUI);
