/**
 * Form component.
 * @module components/manage/Form/Form
 */

import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import { Field, BlocksForm } from '@plone/volto/components/manage/Form';
import BlocksToolbar from '@plone/volto/components/manage/Form/BlocksToolbar';
import UndoToolbar from '@plone/volto/components/manage/Form/UndoToolbar';
import { difference } from '@plone/volto/helpers/Utils/Utils';
import FormValidation from '@plone/volto/helpers/FormValidation/FormValidation';
import {
  getBlocksFieldname,
  getBlocksLayoutFieldname,
  hasBlocksData,
} from '@plone/volto/helpers/Blocks/Blocks';
import { applySchemaEnhancer } from '@plone/volto/helpers/Extensions/withBlockSchemaEnhancer';
import { messages } from '@plone/volto/helpers/MessageLabels/MessageLabels';
import aheadSVG from '@plone/volto/icons/ahead.svg';
import clearSVG from '@plone/volto/icons/clear.svg';
import upSVG from '@plone/volto/icons/up-key.svg';
import downSVG from '@plone/volto/icons/down-key.svg';
import findIndex from 'lodash/findIndex';
import isEmpty from 'lodash/isEmpty';
import isEqual from 'lodash/isEqual';
import keys from 'lodash/keys';
import map from 'lodash/map';
import mapValues from 'lodash/mapValues';
import pickBy from 'lodash/pickBy';
import without from 'lodash/without';
import cloneDeep from 'lodash/cloneDeep';
import xor from 'lodash/xor';
import isBoolean from 'lodash/isBoolean';
import PropTypes from 'prop-types';
import React, { Component } from 'react';
import { injectIntl } from 'react-intl';
import { createPortal } from 'react-dom';
import { connect } from 'react-redux';
import {
  Accordion,
  Button,
  Container as SemanticContainer,
  Form as UiForm,
  Message,
  Segment,
  Tab,
} from 'semantic-ui-react';
import { v4 as uuid } from 'uuid';
import { toast } from 'react-toastify';
import {
  setMetadataFieldsets,
  resetMetadataFocus,
  setSidebarTab,
} from '@plone/volto/actions/sidebar/sidebar';
import { setFormData, setUIState } from '@plone/volto/actions/form/form';
import { compose } from 'redux';
import config from '@plone/volto/registry';
import SlotRenderer from '@plone/volto/components/theme/SlotRenderer/SlotRenderer';

/**
 * Form container class.
 * @class Form
 * @extends Component
 */
class Form extends Component {
  /**
   * Property types.
   * @property {Object} propTypes Property types.
   * @static
   */
  static propTypes = {
    schema: PropTypes.shape({
      fieldsets: PropTypes.arrayOf(
        PropTypes.shape({
          fields: PropTypes.arrayOf(PropTypes.string),
          id: PropTypes.string,
          title: PropTypes.string,
        }),
      ),
      properties: PropTypes.objectOf(PropTypes.any),
      definitions: PropTypes.objectOf(PropTypes.any),
      required: PropTypes.arrayOf(PropTypes.string),
    }),
    widgets: PropTypes.objectOf(PropTypes.any),
    component: PropTypes.any,
    formData: PropTypes.objectOf(PropTypes.any),
    globalData: PropTypes.objectOf(PropTypes.any),
    metadataFieldsets: PropTypes.arrayOf(PropTypes.string),
    metadataFieldFocus: PropTypes.string,
    pathname: PropTypes.string,
    onSubmit: PropTypes.func,
    onCancel: PropTypes.func,
    submitLabel: PropTypes.string,
    cancelLabel: PropTypes.string,
    textButtons: PropTypes.bool,
    buttonComponent: PropTypes.any,
    resetAfterSubmit: PropTypes.bool,
    resetOnCancel: PropTypes.bool,
    isEditForm: PropTypes.bool,
    isAdminForm: PropTypes.bool,
    title: PropTypes.string,
    error: PropTypes.shape({
      message: PropTypes.string,
    }),
    loading: PropTypes.bool,
    hideActions: PropTypes.bool,
    description: PropTypes.string,
    visual: PropTypes.bool,
    blocks: PropTypes.arrayOf(PropTypes.object),
    isFormSelected: PropTypes.bool,
    onSelectForm: PropTypes.func,
    editable: PropTypes.bool,
    onChangeFormData: PropTypes.func,
    requestError: PropTypes.string,
    allowedBlocks: PropTypes.arrayOf(PropTypes.string),
    showRestricted: PropTypes.bool,
    global: PropTypes.bool,
  };

  /**
   * Default properties.
   * @property {Object} defaultProps Default properties.
   * @static
   */
  static defaultProps = {
    formData: null,
    widgets: null,
    component: null,
    onSubmit: null,
    onCancel: null,
    submitLabel: null,
    cancelLabel: null,
    textButtons: false,
    buttonComponent: null,
    resetAfterSubmit: false,
    resetOnCancel: false,
    isEditForm: false,
    isAdminForm: false,
    title: null,
    description: null,
    error: null,
    loading: null,
    hideActions: false,
    visual: false,
    blocks: [],
    pathname: '',
    schema: {},
    isFormSelected: true,
    onSelectForm: null,
    editable: true,
    requestError: null,
    allowedBlocks: null,
    global: false,
  };

  /**
   * Constructor
   * @method constructor
   * @param {Object} props Component properties
   * @constructs Form
   */
  constructor(props) {
    super(props);
    const ids = {
      title: uuid(),
      text: uuid(),
    };
    let { formData, schema: originalSchema } = props;
    const blocksFieldname = getBlocksFieldname(formData);
    const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);

    const schema = this.removeBlocksLayoutFields(originalSchema);

    this.props.setMetadataFieldsets(
      schema?.fieldsets ? schema.fieldsets.map((fieldset) => fieldset.id) : [],
    );

    if (!props.isEditForm) {
      // It's a normal (add form), get defaults from schema
      formData = {
        ...mapValues(props.schema.properties, 'default'),
        ...formData,
      };
    }

    // We initialize the formData snapshot in here, before the initial data checks
    const initialFormData = cloneDeep(formData);

    // Adding fallback in case the fields are empty, so we are sure that the edit form
    // shows at least the default blocks
    if (
      formData.hasOwnProperty(blocksFieldname) &&
      formData.hasOwnProperty(blocksLayoutFieldname)
    ) {
      if (
        !formData[blocksLayoutFieldname] ||
        isEmpty(formData[blocksLayoutFieldname].items)
      ) {
        formData[blocksLayoutFieldname] = {
          items: [ids.title, ids.text],
        };
      }
      if (!formData[blocksFieldname] || isEmpty(formData[blocksFieldname])) {
        formData[blocksFieldname] = {
          [ids.title]: {
            '@type': 'title',
          },
          [ids.text]: {
            '@type': config.settings.defaultBlockType,
          },
        };
      }
    }

    let selectedBlock = null;
    if (
      formData.hasOwnProperty(blocksLayoutFieldname) &&
      formData[blocksLayoutFieldname].items.length > 0
    ) {
      if (config.blocks?.initialBlocksFocus === null) {
        selectedBlock = null;
      } else if (this.props.type in config.blocks?.initialBlocksFocus) {
        // Default selected is not the first block, but the one from config.
        // TODO Select first block and not an arbitrary one.
        Object.keys(formData[blocksFieldname]).forEach((b_key) => {
          if (
            formData[blocksFieldname][b_key]['@type'] ===
            config.blocks?.initialBlocksFocus?.[this.props.type]
          ) {
            selectedBlock = b_key;
          }
        });
      } else {
        selectedBlock = formData[blocksLayoutFieldname].items[0];
      }
    }

    // Sync state to global state
    if (this.props.global) {
      this.props.setFormData(formData);
    }

    this.props.setUIState({
      selected: selectedBlock,
      multiSelected: [],
      hovered: null,
    });

    // Set initial state
    this.state = {
      formData,
      initialFormData,
      errors: {},
      isClient: false,
      // Ensure focus remain in field after change
      inFocus: {},
      sidebarMetadataIsAvailable: false,
    };
    this.onChangeField = this.onChangeField.bind(this);
    this.onSelectBlock = this.onSelectBlock.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.onCancel = this.onCancel.bind(this);
    this.onTabChange = this.onTabChange.bind(this);
    this.onBlurField = this.onBlurField.bind(this);
    this.onClickInput = this.onClickInput.bind(this);
    this.onToggleMetadataFieldset = this.onToggleMetadataFieldset.bind(this);
  }

  /**
   * On updates caused by props change
   * if errors from Backend come, these will be shown to their corresponding Fields
   * also the first Tab to have any errors will be selected
   * @param {Object} prevProps
   */
  async componentDidUpdate(prevProps, prevState) {
    let { requestError } = this.props;
    let errors = {};
    let activeIndex = 0;

    if (!this.props.isFormSelected && prevProps.isFormSelected) {
      this.props.setUIState({
        selected: null,
      });
    }

    if (requestError && prevProps.requestError !== requestError) {
      errors =
        FormValidation.giveServerErrorsToCorrespondingFields(requestError);
      activeIndex = FormValidation.showFirstTabWithErrors({
        errors,
        schema: this.props.schema,
      });

      this.setState({
        errors,
        activeIndex,
      });
    }

    if (this.props.onChangeFormData) {
      if (!isEqual(prevState?.formData, this.state.formData)) {
        this.props.onChangeFormData(this.state.formData);
      }
    }
    if (
      this.props.global &&
      !isEqual(this.props.globalData, prevProps.globalData)
    ) {
      this.setState({
        formData: this.props.globalData,
      });
    }

    if (!isEqual(prevProps.schema, this.props.schema)) {
      this.props.setMetadataFieldsets(
        this.removeBlocksLayoutFields(this.props.schema).fieldsets.map(
          (fieldset) => fieldset.id,
        ),
      );
    }

    if (
      this.props.metadataFieldFocus !== '' &&
      !isEqual(prevProps.metadataFieldFocus, this.props.metadataFieldFocus)
    ) {
      // Scroll into view
      document
        .querySelector(`.field-wrapper-${this.props.metadataFieldFocus}`)
        .scrollIntoView();

      // Set focus to first input if available
      document
        .querySelector(`.field-wrapper-${this.props.metadataFieldFocus} input`)
        ?.focus();

      // Reset focus field
      this.props.resetMetadataFocus();
    }

    if (
      !this.state.sidebarMetadataIsAvailable &&
      document.getElementById('sidebar-metadata')
    ) {
      this.setState(() => ({ sidebarMetadataIsAvailable: true }));
    }
  }

  /**
   * Tab selection is done only by setting activeIndex in state
   */
  onTabChange(e, { activeIndex }) {
    const defaultFocus = this.props.schema.fieldsets[activeIndex].fields[0];
    this.setState({
      activeIndex,
      ...(defaultFocus ? { inFocus: { [defaultFocus]: true } } : {}),
    });
  }

  /**
   * If user clicks on input, the form will be not considered pristine
   * this will avoid onBlur effects without interaction with the form
   * @param {Object} e event
   */
  onClickInput(e) {
    this.setState({ isFormPristine: false });
  }

  /**
   * Validate fields on blur
   * @method onBlurField
   * @param {string} id Id of the field
   * @param {*} value Value of the field
   * @returns {undefined}
   */
  onBlurField(id, value) {
    if (!this.state.isFormPristine) {
      const errors = FormValidation.validateFieldsPerFieldset({
        schema: this.props.schema,
        formData: this.state.formData,
        formatMessage: this.props.intl.formatMessage,
        touchedField: { [id]: value },
      });

      this.setState({
        errors,
      });
    }
  }

  /**
   * Component did mount
   * @method componentDidMount
   * @returns {undefined}
   */
  componentDidMount() {
    this.setState({ isClient: true });
  }

  /**
   * Change field handler
   * Remove errors for changed field
   * @method onChangeField
   * @param {string} id Id of the field
   * @param {*} value Value of the field
   * @returns {undefined}
   */
  onChangeField(id, value) {
    this.setState((prevState) => {
      const { errors, formData } = prevState;
      const newFormData = {
        ...formData,
        // We need to catch also when the value equals false this fixes #888
        [id]: value || (value !== undefined && isBoolean(value)) ? value : null,
      };
      delete errors[id];
      if (this.props.global) {
        this.props.setFormData(newFormData);
      }
      return {
        errors,
        formData: newFormData,
        // Changing the form data re-renders the select widget which causes the
        // focus to get lost. To circumvent this, we set the focus back to
        // the input.
        // This could fix other widgets too but currently targeted
        // against the select widget only.
        // Ensure field to be in focus after the change
        inFocus: { [id]: true },
      };
    });
  }

  /**
   * Select block handler
   * @method onSelectBlock
   * @param {string} id Id of the field
   * @param {string} isMultipleSelection true if multiple blocks are selected
   * @returns {undefined}
   */
  onSelectBlock(id, isMultipleSelection, event) {
    let multiSelected = [];
    let selected = id;
    const formData = this.state.formData;

    if (isMultipleSelection) {
      selected = null;
      const blocksLayoutFieldname = getBlocksLayoutFieldname(formData);

      const blocks_layout = formData[blocksLayoutFieldname].items;

      if (event.shiftKey) {
        const anchor =
          this.props.uiState.multiSelected.length > 0
            ? blocks_layout.indexOf(this.props.uiState.multiSelected[0])
            : blocks_layout.indexOf(this.props.uiState.selected);
        const focus = blocks_layout.indexOf(id);

        if (anchor === focus) {
          multiSelected = [id];
        } else if (focus > anchor) {
          multiSelected = [...blocks_layout.slice(anchor, focus + 1)];
        } else {
          multiSelected = [...blocks_layout.slice(focus, anchor + 1)];
        }
        window.getSelection().empty();
      }

      if ((event.ctrlKey || event.metaKey) && !event.shiftKey) {
        multiSelected = this.props.uiState.multiSelected || [];
        if (!this.props.uiState.multiSelected.includes(this.state.selected)) {
          multiSelected = [...multiSelected, this.props.uiState.selected];
          selected = null;
        }
        if (this.props.uiState.multiSelected.includes(id)) {
          selected = null;
          multiSelected = without(multiSelected, id);
        } else {
          multiSelected = [...multiSelected, id];
        }
      }
    }

    this.props.setUIState({
      selected,
      multiSelected,
      gridSelected: null,
    });

    if (this.props.onSelectForm) {
      if (event) event.nativeEvent.stopImmediatePropagation();
      this.props.onSelectForm();
    }
  }

  /**
   * Cancel handler
   * It prevents event from triggering submit, reset form if props.resetAfterSubmit
   * and calls this.props.onCancel
   * @method onCancel
   * @param {Object} event Event object.
   * @returns {undefined}
   */
  onCancel(event) {
    if (event) {
      event.preventDefault();
    }
    if (this.props.resetOnCancel || this.props.resetAfterSubmit) {
      this.setState({
        formData: this.props.formData,
      });
      if (this.props.global) {
        this.props.setFormData(this.props.formData);
      }
    }
    this.props.onCancel(event);
  }

  /**
   * Submit handler also validate form and collect errors
   * @method onSubmit
   * @param {Object} event Event object.
   * @returns {undefined}
   */
  onSubmit(event) {
    const formData = this.state.formData;

    if (event) {
      event.preventDefault();
    }

    const errors = this.props.schema
      ? FormValidation.validateFieldsPerFieldset({
          schema: this.props.schema,
          formData,
          formatMessage: this.props.intl.formatMessage,
        })
      : {};

    let blocksErrors = {};

    if (hasBlocksData(formData)) {
      // Validate blocks
      const blocks = this.state.formData[getBlocksFieldname(formData)];
      const blocksLayout =
        this.state.formData[getBlocksLayoutFieldname(formData)];
      const defaultSchema = {
        properties: {},
        fieldsets: [],
        required: [],
      };
      blocksLayout.items.forEach((block) => {
        let blockSchema =
          config.blocks.blocksConfig[blocks[block]['@type']].blockSchema ||
          defaultSchema;
        if (typeof blockSchema === 'function') {
          blockSchema = blockSchema({
            intl: this.props.intl,
            formData: blocks[block],
          });
        }

        if (config.blocks.blocksConfig[blocks[block]['@type']].blockSchema) {
          blockSchema = applySchemaEnhancer({
            schema: blockSchema,
            formData: blocks[block],
            intl: this.props.intl,
            blocksConfig: config.blocks.blocksConfig,
            navRoot: this.props.navRoot,
            contentType: this.props.content['@type'],
          });
        }

        const blockErrors = FormValidation.validateFieldsPerFieldset({
          schema: blockSchema,
          formData: blocks[block],
          formatMessage: this.props.intl.formatMessage,
        });
        if (keys(blockErrors).length > 0) {
          blocksErrors = {
            ...blocksErrors,
            [block]: { ...blockErrors },
          };
        }
      });
    }

    if (keys(errors).length > 0 || keys(blocksErrors).length > 0) {
      const activeIndex = FormValidation.showFirstTabWithErrors({
        errors,
        schema: this.props.schema,
      });

      this.setState({
        errors: {
          ...errors,
          ...(!isEmpty(blocksErrors) && { blocks: blocksErrors }),
        },
        activeIndex,
      });

      if (keys(errors).length > 0) {
        // Changes the focus to the metadata tab in the sidebar if error
        Object.keys(errors).forEach((err) =>
          toast.error(
            <Toast
              error
              title={this.props.schema.properties[err].title || err}
              content={errors[err].join(', ')}
            />,
          ),
        );
        this.props.setSidebarTab(0);
      } else if (keys(blocksErrors).length > 0) {
        const errorField = Object.entries(
          Object.entries(blocksErrors)[0][1],
        )[0][0];
        const errorMessage = Object.entries(
          Object.entries(blocksErrors)[0][1],
        )[0][1];
        const errorFieldTitle = errorMessage.title || errorField;

        toast.error(
          <Toast
            error
            title={this.props.intl.formatMessage(
              messages.blocksFieldsErrorTitle,
              { errorField: errorFieldTitle },
            )}
            content={errorMessage}
          />,
        );
        this.props.setSidebarTab(1);
        this.props.setUIState({
          selected: Object.keys(blocksErrors)[0],
          multiSelected: [],
          hovered: null,
        });
      }
    } else {
      // Get only the values that have been modified (Edit forms), send all in case that
      // it's an add form
      if (this.props.isEditForm) {
        this.props.onSubmit(this.getOnlyFormModifiedValues());
      } else {
        this.props.onSubmit(formData);
      }
      if (this.props.resetAfterSubmit) {
        this.setState({
          formData: this.props.formData,
        });
        if (this.props.global) {
          this.props.setFormData(this.props.formData);
        }
      }
    }
  }

  /**
   * getOnlyFormModifiedValues handler
   * It returns only the values of the fields that are have really changed since the
   * form was loaded. Useful for edit forms and PATCH operations, when we only want to
   * send the changed data.
   * @method getOnlyFormModifiedValues
   * @param {Object} event Event object.
   * @returns {undefined}
   */
  getOnlyFormModifiedValues = () => {
    const formData = this.state.formData;

    const fieldsModified = Object.keys(
      difference(formData, this.state.initialFormData),
    );
    return {
      ...pickBy(formData, (value, key) => fieldsModified.includes(key)),
      ...(formData['@static_behaviors'] && {
        '@static_behaviors': formData['@static_behaviors'],
      }),
    };
  };

  /**
   * Removed blocks and blocks_layout fields from the form.
   * @method removeBlocksLayoutFields
   * @param {object} schema The schema definition of the form.
   * @returns A modified copy of the given schema.
   */
  removeBlocksLayoutFields = (schema) => {
    const newSchema = { ...schema };
    const layoutFieldsetIndex = findIndex(
      newSchema.fieldsets,
      (fieldset) => fieldset.id === 'layout',
    );
    if (layoutFieldsetIndex > -1) {
      const layoutFields = newSchema.fieldsets[layoutFieldsetIndex].fields;
      newSchema.fieldsets[layoutFieldsetIndex].fields = layoutFields.filter(
        (field) => field !== 'blocks' && field !== 'blocks_layout',
      );
      if (newSchema.fieldsets[layoutFieldsetIndex].fields.length === 0) {
        newSchema.fieldsets = [
          ...newSchema.fieldsets.slice(0, layoutFieldsetIndex),
          ...newSchema.fieldsets.slice(layoutFieldsetIndex + 1),
        ];
      }
    }
    return newSchema;
  };

  /**
   * Toggle metadata fieldset handler
   * @method onToggleMetadataFieldset
   * @param {Object} event Event object.
   * @param {Object} blockProps Block properties.
   * @returns {undefined}
   */
  onToggleMetadataFieldset(event, blockProps) {
    const { index } = blockProps;
    this.props.setMetadataFieldsets(xor(this.props.metadataFieldsets, [index]));
  }

  /**
   * Render method.
   * @method render
   * @returns {string} Markup for the component.
   */
  render() {
    const { settings } = config;
    const {
      schema: originalSchema,
      onCancel,
      onSubmit,
      navRoot,
      type,
      metadataFieldsets,
      component,
      buttonComponent,
    } = this.props;
    const formData = this.state.formData;
    const schema = this.removeBlocksLayoutFields(originalSchema);
    const Container =
      config.getComponent({ name: 'Container' }).component || SemanticContainer;
    const FormComponent = component || UiForm;
    const ButtonComponent = buttonComponent || Button;

    return this.props.visual ? (
      // Removing this from SSR is important, since react-beautiful-dnd supports SSR,
      // but draftJS don't like it much and the hydration gets messed up
      this.state.isClient && (
        <>
          <SlotRenderer
            name="aboveContent"
            content={this.props.content}
            navRoot={navRoot}
          />

          <Container>
            <>
              <BlocksToolbar
                formData={formData}
                selectedBlock={this.props.uiState.selected}
                selectedBlocks={this.props.uiState.multiSelected}
                onChangeBlocks={(newBlockData) => {
                  const newFormData = {
                    ...formData,
                    ...newBlockData,
                  };
                  this.setState({
                    formData: newFormData,
                  });
                  if (this.props.global) {
                    this.props.setFormData(newFormData);
                  }
                }}
                onSetSelectedBlocks={(blockIds) =>
                  this.props.setUIState({ multiSelected: blockIds })
                }
                onSelectBlock={this.onSelectBlock}
              />
              <UndoToolbar
                state={{
                  formData,
                  selected: this.props.uiState.selected,
                  multiSelected: this.props.uiState.multiSelected,
                }}
                enableHotKeys
                onUndoRedo={({ state }) => {
                  if (this.props.global) {
                    this.props.setFormData(state.formData);
                  }
                  return this.setState(state);
                }}
              />
              <BlocksForm
                onChangeFormData={(newData) => {
                  const newFormData = {
                    ...formData,
                    ...newData,
                  };
                  this.setState({
                    formData: newFormData,
                  });
                  if (this.props.global) {
                    this.props.setFormData(newFormData);
                  }
                }}
                onChangeField={this.onChangeField}
                onSelectBlock={this.onSelectBlock}
                properties={formData}
                navRoot={navRoot}
                type={type}
                pathname={this.props.pathname}
                selectedBlock={this.props.uiState.selected}
                multiSelected={this.props.uiState.multiSelected}
                manage={this.props.isAdminForm}
                allowedBlocks={this.props.allowedBlocks}
                showRestricted={this.props.showRestricted}
                editable={this.props.editable}
                isMainForm={this.props.editable}
                // Properties to pass to the BlocksForm to match the View ones
                history={this.props.history}
                location={this.props.location}
                token={this.props.token}
                errors={this.state.errors}
                blocksErrors={this.state.errors.blocks}
              />
              {this.state.isClient &&
                this.state.sidebarMetadataIsAvailable &&
                this.props.editable &&
                createPortal(
                  <UiForm
                    method="post"
                    onSubmit={this.onSubmit}
                    error={keys(this.state.errors).length > 0}
                  >
                    {schema &&
                      map(schema.fieldsets, (fieldset) => (
                        <Accordion
                          fluid
                          styled
                          className="form"
                          key={fieldset.title}
                        >
                          <div
                            key={fieldset.id}
                            id={`metadataform-fieldset-${fieldset.id}`}
                          >
                            <Accordion.Title
                              active={metadataFieldsets.includes(fieldset.id)}
                              index={fieldset.id}
                              onClick={this.onToggleMetadataFieldset}
                            >
                              {fieldset.title}
                              {metadataFieldsets.includes(fieldset.id) ? (
                                <Icon name={upSVG} size="20px" />
                              ) : (
                                <Icon name={downSVG} size="20px" />
                              )}
                            </Accordion.Title>
                            <Accordion.Content
                              active={metadataFieldsets.includes(fieldset.id)}
                            >
                              <Segment className="attached">
                                {map(fieldset.fields, (field, index) => (
                                  <Field
                                    {...schema.properties[field]}
                                    id={field}
                                    fieldSet={fieldset.title.toLowerCase()}
                                    formData={formData}
                                    focus={
                                      this.state.isClient &&
                                      document
                                        .getElementById('sidebar-metadata')
                                        ?.contains(document.activeElement)
                                        ? this.state.inFocus[field]
                                        : false
                                    }
                                    value={formData?.[field]}
                                    required={
                                      schema.required.indexOf(field) !== -1
                                    }
                                    onChange={this.onChangeField}
                                    onBlur={this.onBlurField}
                                    onClick={this.onClickInput}
                                    key={field}
                                    error={this.state.errors[field]}
                                  />
                                ))}
                              </Segment>
                            </Accordion.Content>
                          </div>
                        </Accordion>
                      ))}
                  </UiForm>,
                  document.getElementById('sidebar-metadata'),
                )}

              <SlotRenderer
                name="belowContent"
                content={this.props.content}
                navRoot={navRoot}
              />
            </>
          </Container>
        </>
      )
    ) : (
      <Container>
        <FormComponent
          method="post"
          onSubmit={this.onSubmit}
          error={keys(this.state.errors).length > 0}
          className={settings.verticalFormTabs ? 'vertical-form' : ''}
        >
          <fieldset className="invisible">
            <Segment.Group raised>
              {schema && schema.fieldsets.length > 1 && (
                <>
                  {settings.verticalFormTabs && this.props.title && (
                    <Segment secondary attached key={this.props.title}>
                      {this.props.title}
                    </Segment>
                  )}
                  <Tab
                    menu={{
                      secondary: true,
                      pointing: true,
                      attached: true,
                      tabular: true,
                      className: 'formtabs',
                      vertical: settings.verticalFormTabs,
                    }}
                    grid={{ paneWidth: 9, tabWidth: 3, stackable: true }}
                    onTabChange={this.onTabChange}
                    activeIndex={this.state.activeIndex}
                    panes={map(schema.fieldsets, (item) => ({
                      menuItem: item.title,
                      render: () => [
                        !settings.verticalFormTabs && this.props.title && (
                          <Segment secondary attached key={this.props.title}>
                            {this.props.title}
                          </Segment>
                        ),
                        item.description && (
                          <Message attached="bottom">
                            {item.description}
                          </Message>
                        ),
                        ...map(item.fields, (field, index) => (
                          <Field
                            widgets={this.props.widgets}
                            {...schema.properties[field]}
                            id={field}
                            formData={formData}
                            fieldSet={item.id}
                            focus={this.state.inFocus[field]}
                            value={formData?.[field]}
                            required={schema.required.indexOf(field) !== -1}
                            onChange={
                              this.props.editable
                                ? this.onChangeField
                                : () => {}
                            }
                            onBlur={this.onBlurField}
                            onClick={this.onClickInput}
                            key={field}
                            error={this.state.errors[field]}
                          />
                        )),
                      ],
                    }))}
                  />
                </>
              )}
              {schema && schema.fieldsets.length === 1 && (
                <Segment>
                  {this.props.title && (
                    <Segment className="primary">
                      <h1 style={{ fontSize: '16px' }}> {this.props.title}</h1>
                    </Segment>
                  )}
                  {this.props.description && (
                    <Segment secondary>{this.props.description}</Segment>
                  )}
                  {keys(this.state.errors).length > 0 && (
                    <Message
                      icon="warning"
                      negative
                      attached
                      header={this.props.intl.formatMessage(messages.error)}
                      content={this.props.intl.formatMessage(
                        messages.thereWereSomeErrors,
                      )}
                    />
                  )}
                  {this.props.error && (
                    <Message
                      icon="warning"
                      negative
                      attached
                      header={this.props.intl.formatMessage(messages.error)}
                      content={this.props.error.message}
                    />
                  )}
                  {map(schema.fieldsets[0].fields, (field) => (
                    <Field
                      widgets={this.props.widgets}
                      {...schema.properties[field]}
                      id={field}
                      value={formData?.[field]}
                      required={schema.required.indexOf(field) !== -1}
                      onChange={this.onChangeField}
                      onBlur={this.onBlurField}
                      onClick={this.onClickInput}
                      key={field}
                      error={this.state.errors[field]}
                    />
                  ))}
                </Segment>
              )}
              {!this.props.hideActions && (
                <Segment className="actions" clearing>
                  {onSubmit &&
                    (this.props.textButtons ? (
                      <ButtonComponent
                        primary
                        floated="right"
                        type="submit"
                        aria-label={
                          this.props.submitLabel
                            ? this.props.submitLabel
                            : this.props.intl.formatMessage(messages.save)
                        }
                        title={
                          this.props.submitLabel
                            ? this.props.submitLabel
                            : this.props.intl.formatMessage(messages.save)
                        }
                        loading={this.props.loading}
                      >
                        {this.props.submitLabel
                          ? this.props.submitLabel
                          : this.props.intl.formatMessage(messages.save)}
                      </ButtonComponent>
                    ) : (
                      <ButtonComponent
                        basic
                        primary
                        floated="right"
                        type="submit"
                        aria-label={
                          this.props.submitLabel
                            ? this.props.submitLabel
                            : this.props.intl.formatMessage(messages.save)
                        }
                        title={
                          this.props.submitLabel
                            ? this.props.submitLabel
                            : this.props.intl.formatMessage(messages.save)
                        }
                        loading={this.props.loading}
                      >
                        <Icon className="circled" name={aheadSVG} size="30px" />
                      </ButtonComponent>
                    ))}
                  {onCancel &&
                    (this.props.textButtons ? (
                      <ButtonComponent
                        secondary
                        type="button"
                        aria-label={
                          this.props.cancelLabel
                            ? this.props.cancelLabel
                            : this.props.intl.formatMessage(messages.cancel)
                        }
                        title={
                          this.props.cancelLabel
                            ? this.props.cancelLabel
                            : this.props.intl.formatMessage(messages.cancel)
                        }
                        floated="right"
                        onClick={this.onCancel}
                      >
                        {this.props.cancelLabel
                          ? this.props.cancelLabel
                          : this.props.intl.formatMessage(messages.cancel)}
                      </ButtonComponent>
                    ) : (
                      <ButtonComponent
                        basic
                        secondary
                        type="button"
                        aria-label={
                          this.props.cancelLabel
                            ? this.props.cancelLabel
                            : this.props.intl.formatMessage(messages.cancel)
                        }
                        title={
                          this.props.cancelLabel
                            ? this.props.cancelLabel
                            : this.props.intl.formatMessage(messages.cancel)
                        }
                        floated="right"
                        onClick={this.onCancel}
                      >
                        <Icon className="circled" name={clearSVG} size="30px" />
                      </ButtonComponent>
                    ))}
                </Segment>
              )}
            </Segment.Group>
          </fieldset>
        </FormComponent>
      </Container>
    );
  }
}

const FormIntl = injectIntl(Form, { forwardRef: true });

export default compose(
  connect(
    (state, props) => ({
      content: state.content.data,
      globalData: state.form?.global,
      uiState: state.form?.ui,
      metadataFieldsets: state.sidebar?.metadataFieldsets,
      metadataFieldFocus: state.sidebar?.metadataFieldFocus,
    }),
    {
      setMetadataFieldsets,
      setSidebarTab,
      setFormData,
      setUIState,
      resetMetadataFocus,
    },
    null,
    { forwardRef: true },
  ),
)(FormIntl);
