import PropTypes from 'prop-types';
import * as React from 'react';
import { useController, useFormContext } from 'react-hook-form';
import { IconButton } from '@mui/material';
import FormHelperText from '@mui/material/FormHelperText';
import ArrowDropDownIcon from '@mui/icons-material/ArrowDropDown';
import ClearIcon from '@mui/icons-material/Clear';
import { ActionButtonsWrapper } from './style';
import ClickAwayListener from '@mui/base/ClickAwayListener';
import InputLabel from '@mui/material/InputLabel';
import { createEditor } from 'slate';
import { Editable, ReactEditor } from 'slate-react';
import Slate from 'components/Slate';
import Element from 'components/Slate/Element';
import {
  serialize,
  deserialize,
  insertVariable,
  customizeEditor,
  resetEditor,
  overrideEditorValue,
  focusEditor,
} from 'components/Slate/utils';
import {
  FakeInput,
  InputLabelWrapper,
  ChildrenWrapper,
} from 'components/PowerInput/style';
import CustomOptions from './CustomOptions';
import { processStepWithExecutions } from 'components/PowerInput/data';
import { StepExecutionsContext } from 'contexts/StepExecutions';

function ControlledCustomAutocomplete(props) {
  const {
    defaultValue = '',
    name,
    label,
    required,
    options = [],
    dependsOn = [],
    description,
    loading,
    disabled,
    shouldUnregister,
  } = props;
  const { control, watch } = useFormContext();
  const { field, fieldState } = useController({
    control,
    name,
    defaultValue,
    rules: { required },
    shouldUnregister,
  });
  const {
    value,
    onChange: controllerOnChange,
    onBlur: controllerOnBlur,
  } = field;
  const [, forceUpdate] = React.useReducer((x) => x + 1, 0);
  const [isInitialValueSet, setInitialValue] = React.useState(false);
  const [isSingleChoice, setSingleChoice] = React.useState(undefined);
  const priorStepsWithExecutions = React.useContext(StepExecutionsContext);
  const editorRef = React.useRef(null);

  const renderElement = React.useCallback(
    (props) => <Element {...props} disabled={disabled} />,
    [disabled],
  );

  const [editor] = React.useState(() => customizeEditor(createEditor()));

  const [showVariableSuggestions, setShowVariableSuggestions] =
    React.useState(false);
  let dependsOnValues = [];
  if (dependsOn?.length) {
    dependsOnValues = watch(dependsOn);
  }

  React.useEffect(() => {
    const ref = ReactEditor.toDOMNode(editor, editor);
    resizeObserver.observe(ref);
    return () => resizeObserver.unobserve(ref);
  }, []);

  const promoteValue = () => {
    const serializedValue = serialize(editor.children);
    controllerOnChange(serializedValue);
  };

  const resizeObserver = React.useMemo(function syncCustomOptionsPosition() {
    return new ResizeObserver(() => {
      forceUpdate();
    });
  }, []);

  React.useEffect(() => {
    const hasDependencies = dependsOnValues.length;
    if (hasDependencies) {
      // Reset the field when a dependent has been updated
      resetEditor(editor);
    }
  }, dependsOnValues);

  React.useEffect(
    function updateInitialValue() {
      const hasOptions = options.length;
      const isOptionsLoaded = loading === false;
      if (!isInitialValueSet && hasOptions && isOptionsLoaded) {
        setInitialValue(true);
        const option = options.find((option) => option.value === value);
        if (option) {
          overrideEditorValue(editor, { option, focus: false });
          setSingleChoice(true);
        } else if (value) {
          setSingleChoice(false);
        }
      }
    },
    [isInitialValueSet, options, loading],
  );

  React.useEffect(() => {
    if (!showVariableSuggestions && value !== serialize(editor.children)) {
      promoteValue();
    }
  }, [showVariableSuggestions]);

  const hideSuggestionsOnShift = (event) => {
    if (event.code === 'Tab') {
      setShowVariableSuggestions(false);
    }
  };

  const handleKeyDown = (event) => {
    hideSuggestionsOnShift(event);
    if (event.code === 'Tab') {
      promoteValue();
    }
    if (isSingleChoice && event.code !== 'Tab') {
      event.preventDefault();
    }
  };

  const stepsWithVariables = React.useMemo(() => {
    return processStepWithExecutions(priorStepsWithExecutions);
  }, [priorStepsWithExecutions]);

  const handleVariableSuggestionClick = React.useCallback(
    (variable) => {
      insertVariable(editor, variable, stepsWithVariables);
    },
    [stepsWithVariables],
  );

  const handleOptionClick = React.useCallback(
    (event, option) => {
      event.stopPropagation();
      overrideEditorValue(editor, { option, focus: false });
      setShowVariableSuggestions(false);
      setSingleChoice(true);
    },
    [stepsWithVariables],
  );

  const handleClearButtonClick = (event) => {
    event.stopPropagation();
    resetEditor(editor);
    promoteValue();
    setSingleChoice(undefined);
  };

  const reset = (tabIndex) => {
    const isOptions = tabIndex === 0;
    setSingleChoice(isOptions);
    resetEditor(editor, { focus: true });
  };

  return (
    <Slate
      editor={editor}
      value={deserialize(value, options, stepsWithVariables)}
    >
      <ClickAwayListener
        mouseEvent="onMouseDown"
        onClickAway={() => setShowVariableSuggestions(false)}
      >
        {/* ref-able single child for ClickAwayListener */}
        <ChildrenWrapper style={{ width: '100%' }} data-test="power-input">
          <FakeInput
            disabled={disabled}
            tabIndex={-1}
            onClick={() => {
              focusEditor(editor);
            }}
          >
            <InputLabelWrapper>
              <InputLabel
                shrink={true}
                disabled={disabled}
                variant="outlined"
                sx={{ bgcolor: 'white', display: 'inline-block', px: 0.75 }}
              >
                {`${label}${required ? ' *' : ''}`}
              </InputLabel>
            </InputLabelWrapper>

            <Editable
              readOnly={disabled}
              style={{ width: '100%' }}
              renderElement={renderElement}
              onKeyDown={handleKeyDown}
              onFocus={() => {
                setShowVariableSuggestions(true);
              }}
              onBlur={() => {
                controllerOnBlur();
              }}
            />

            <ActionButtonsWrapper direction="row" mr={1.5}>
              {isSingleChoice && serialize(editor.children) !== '' && (
                <IconButton
                  disabled={disabled}
                  edge="end"
                  size="small"
                  tabIndex={-1}
                  onClick={handleClearButtonClick}
                >
                  <ClearIcon />
                </IconButton>
              )}
              <IconButton
                disabled={disabled}
                edge="end"
                size="small"
                tabIndex={-1}
              >
                <ArrowDropDownIcon />
              </IconButton>
            </ActionButtonsWrapper>
          </FakeInput>
          {/* ghost placer for the variables popover */}
          <div
            ref={editorRef}
            style={{
              position: 'absolute',
              right: 16,
              left: 16,
            }}
          />

          <CustomOptions
            label={label}
            open={showVariableSuggestions}
            initialTabIndex={
              isSingleChoice === undefined ? undefined : isSingleChoice ? 0 : 1
            }
            anchorEl={editorRef.current}
            data={stepsWithVariables}
            options={options}
            onSuggestionClick={handleVariableSuggestionClick}
            onOptionClick={handleOptionClick}
            onTabChange={reset}
          />

          <FormHelperText
            variant="outlined"
            error={Boolean(fieldState.isTouched && fieldState.error)}
          >
            {fieldState.isTouched
              ? fieldState.error?.message || description
              : description}
          </FormHelperText>
        </ChildrenWrapper>
      </ClickAwayListener>
    </Slate>
  );
}

ControlledCustomAutocomplete.propTypes = {
  options: PropTypes.array,
  loading: PropTypes.bool.isRequired,
  showOptionValue: PropTypes.bool,
  dependsOn: PropTypes.arrayOf(PropTypes.string),
  defaultValue: PropTypes.string,
  name: PropTypes.string.isRequired,
  label: PropTypes.string,
  type: PropTypes.string,
  required: PropTypes.bool,
  readOnly: PropTypes.bool,
  description: PropTypes.string,
  docUrl: PropTypes.string,
  clickToCopy: PropTypes.bool,
  disabled: PropTypes.bool,
  shouldUnregister: PropTypes.bool,
};

export default ControlledCustomAutocomplete;
