/**
 * Add container.
 * @module components/manage/Add/Add
 */

import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { connect } from 'react-redux';
import { compose } from 'redux';
import keys from 'lodash/keys';
import isEmpty from 'lodash/isEmpty';
import { defineMessages, injectIntl } from 'react-intl';
import { Button, Grid, Menu } from 'semantic-ui-react';
import { createPortal } from 'react-dom';
import { v4 as uuid } from 'uuid';
import qs from 'query-string';
import { toast } from 'react-toastify';

import { createContent } from '@plone/volto/actions/content/content';
import { getSchema } from '@plone/volto/actions/schema/schema';
import { changeLanguage } from '@plone/volto/actions/language/language';
import { setFormData } from '@plone/volto/actions/form/form';

import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import Sidebar from '@plone/volto/components/manage/Sidebar/Sidebar';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import TranslationObject from '@plone/volto/components/manage/Multilingual/TranslationObject';
import { Form } from '@plone/volto/components/manage/Form';

import { getBaseUrl, flattenToAppURL } from '@plone/volto/helpers/Url/Url';
import {
  hasBlocksData,
  getBlocksFieldname,
  getBlocksLayoutFieldname,
} from '@plone/volto/helpers/Blocks/Blocks';
import { getLanguageIndependentFields } from '@plone/volto/helpers/Content/Content';
import langmap from '@plone/volto/helpers/LanguageMap/LanguageMap';
import { toGettextLang } from '@plone/volto/helpers/Utils/Utils';
import {
  getSimpleDefaultBlocks,
  getDefaultBlocks,
} from '@plone/volto/helpers/Blocks/defaultBlocks';
import {
  tryParseJSON,
  extractInvariantErrors,
} from '@plone/volto/helpers/FormValidation/FormValidation';
import BodyClass from '@plone/volto/helpers/BodyClass/BodyClass';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';

import { preloadLazyLibs } from '@plone/volto/helpers/Loadable';

import config from '@plone/volto/registry';

import saveSVG from '@plone/volto/icons/save.svg';
import clearSVG from '@plone/volto/icons/clear.svg';

const messages = defineMessages({
  add: {
    id: 'Add {type}',
    defaultMessage: 'Add {type}',
  },
  save: {
    id: 'Save',
    defaultMessage: 'Save',
  },
  cancel: {
    id: 'Cancel',
    defaultMessage: 'Cancel',
  },
  error: {
    id: 'Error',
    defaultMessage: 'Error',
  },
  translateTo: {
    id: 'Translate to {lang}',
    defaultMessage: 'Translate to {lang}',
  },
  someErrors: {
    id: 'There are some errors.',
    defaultMessage: 'There are some errors.',
  },
});

/**
 * Add class.
 * @class Add
 * @extends Component
 */
class Add extends Component {
  /**
   * Property types.
   * @property {Object} propTypes Property types.
   * @static
   */
  static propTypes = {
    createContent: PropTypes.func.isRequired,
    getSchema: PropTypes.func.isRequired,
    pathname: PropTypes.string.isRequired,
    schema: PropTypes.objectOf(PropTypes.any),
    content: PropTypes.shape({
      // eslint-disable-line react/no-unused-prop-types
      '@id': PropTypes.string,
      '@type': PropTypes.string,
    }),
    returnUrl: PropTypes.string,
    createRequest: PropTypes.shape({
      loading: PropTypes.bool,
      loaded: PropTypes.bool,
    }).isRequired,
    schemaRequest: PropTypes.shape({
      loading: PropTypes.bool,
      loaded: PropTypes.bool,
    }).isRequired,
    type: PropTypes.string,
    location: PropTypes.objectOf(PropTypes.any),
  };

  /**
   * Default properties
   * @property {Object} defaultProps Default properties.
   * @static
   */
  static defaultProps = {
    schema: null,
    content: null,
    returnUrl: null,
    type: 'Default',
  };

  /**
   * Constructor
   * @method constructor
   * @param {Object} props Component properties
   * @constructs WysiwygEditor
   */
  constructor(props) {
    super(props);
    this.onCancel = this.onCancel.bind(this);
    this.onSubmit = this.onSubmit.bind(this);

    this.state = {
      isClient: false,
      error: null,
      formSelected: 'addForm',
    };
  }

  /**
   * Component did mount
   * @method componentDidMount
   * @returns {undefined}
   */
  componentDidMount() {
    this.props.getSchema(this.props.type, getBaseUrl(this.props.pathname));
    this.setState({ isClient: true });
  }

  /**
   * Component will receive props
   * @method componentWillReceiveProps
   * @param {Object} nextProps Next properties
   * @returns {undefined}
   */
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (
      this.props.createRequest.loading &&
      nextProps.createRequest.loaded &&
      nextProps.content['@type'] === this.props.type
    ) {
      this.props.setFormData({});
      this.props.history.push(
        this.props.returnUrl || flattenToAppURL(nextProps.content['@id']),
      );
    }

    if (this.props.createRequest.loading && nextProps.createRequest.error) {
      const message =
        nextProps.createRequest.error.response?.body?.message ||
        nextProps.createRequest.error.response?.text;

      const error =
        new DOMParser().parseFromString(message, 'text/html')?.all[0]
          ?.textContent || message;

      const errorsList = tryParseJSON(error);
      let erroMessage;
      if (Array.isArray(errorsList)) {
        const invariantErrors = extractInvariantErrors(errorsList);
        if (invariantErrors.length > 0) {
          // Plone invariant validation message.
          erroMessage = invariantErrors.join(' - ');
        } else {
          // Error in specific field.
          erroMessage = this.props.intl.formatMessage(messages.someErrors);
        }
      } else {
        erroMessage = errorsList?.error?.message || error;
      }

      this.setState({ error: error });

      toast.error(
        <Toast
          error
          title={this.props.intl.formatMessage(messages.error)}
          content={erroMessage}
        />,
      );
    }
  }

  /**
   * Submit handler
   * @method onSubmit
   * @param {object} data Form data.
   * @returns {undefined}
   */
  onSubmit(data) {
    this.props.createContent(getBaseUrl(this.props.pathname), {
      ...data,
      '@static_behaviors': this.props.schema.definitions
        ? keys(this.props.schema.definitions)
        : null,
      '@type': this.props.type,
      ...(this.props.location?.state?.translationOf && {
        translation_of: this.props.location.state.translationOf,
        language: this.props.location.state.language,
      }),
    });
  }

  /**
   * Cancel handler
   * @method onCancel
   * @returns {undefined}
   */
  onCancel() {
    this.props.setFormData({});
    if (this.props.location?.state?.translationOf) {
      const language = this.props.location.state.languageFrom;
      const langFileName = toGettextLang(language);
      import(/* @vite-ignore */ '@root/../locales/' + langFileName + '.json')
        .then((locale) => {
          this.props.changeLanguage(language, locale.default);
        })
        .catch(() => {
          this.props.changeLanguage(language, {});
        });
      this.props.history.push(this.props.location?.state?.translationOf);
    } else {
      this.props.history.push(getBaseUrl(this.props.pathname));
    }
  }

  form = React.createRef();

  /**
   * Render method.
   * @method render
   * @returns {string} Markup for the component.
   */
  render() {
    if (this.props.schemaRequest.loaded) {
      const visual = hasBlocksData(this.props.schema.properties);
      const blocksFieldname = getBlocksFieldname(this.props.schema.properties);
      const blocksLayoutFieldname = getBlocksLayoutFieldname(
        this.props.schema.properties,
      );
      const translationObject = this.props.location?.state?.translationObject;
      const translateTo = translationObject
        ? langmap?.[this.props.location?.state?.language]?.nativeName ||
          this.props.location?.state?.language
        : null;

      // Get initial blocks from local config, if any
      let initialBlocks, initialBlocksLayout;
      const initialContentTypeBlocks =
        config.blocks?.initialBlocks[this.props.type];
      if (initialContentTypeBlocks) {
        if (typeof initialContentTypeBlocks?.[0] === 'string') {
          // Simple (legacy) default blocks definition
          [initialBlocks, initialBlocksLayout] = getSimpleDefaultBlocks(
            initialContentTypeBlocks,
          );
        } else {
          [initialBlocks, initialBlocksLayout] = getDefaultBlocks(
            initialContentTypeBlocks,
          );
        }
      }

      // Lookup initialBlocks and initialBlocksLayout within schema, if any
      const schemaBlocks =
        this.props.schema.properties[blocksFieldname]?.default;
      const schemaBlocksLayout =
        this.props.schema.properties[blocksLayoutFieldname]?.default?.items;

      if (!isEmpty(schemaBlocksLayout) && !isEmpty(schemaBlocks)) {
        initialBlocks = {};
        initialBlocksLayout = [];
        schemaBlocksLayout.forEach((value) => {
          if (!isEmpty(schemaBlocks[value])) {
            let newUid = uuid();
            initialBlocksLayout.push(newUid);
            initialBlocks[newUid] = schemaBlocks[value];
            initialBlocks[newUid].block = newUid;

            // Layout ID - keep a reference to the original block id within layout
            initialBlocks[newUid]['@layout'] = value;
          }
        });
      }

      //copy blocks from translationObject
      if (translationObject && blocksFieldname && blocksLayoutFieldname) {
        initialBlocks = {};
        initialBlocksLayout = [];
        const originalBlocks = JSON.parse(
          JSON.stringify(translationObject[blocksFieldname]),
        );
        const originalBlocksLayout =
          translationObject[blocksLayoutFieldname].items;

        originalBlocksLayout.forEach((value) => {
          if (!isEmpty(originalBlocks[value])) {
            let newUid = uuid();
            initialBlocksLayout.push(newUid);
            initialBlocks[newUid] = originalBlocks[value];
            initialBlocks[newUid].block = newUid;

            // Layout ID - keep a reference to the original block id within layout
            initialBlocks[newUid]['@canonical'] = value;
          }
        });
      }

      const lifData = () => {
        const data = {};
        if (translationObject) {
          getLanguageIndependentFields(this.props.schema).forEach(
            (lif) => (data[lif] = translationObject[lif]),
          );
        }
        return data;
      };

      const pageAdd = (
        <div id="page-add">
          <Helmet
            title={this.props.intl.formatMessage(messages.add, {
              type: this.props?.schema?.title || this.props.type,
            })}
          />
          <Form
            ref={this.form}
            key="translated-or-new-content-form"
            navRoot={
              this.props.content?.['@components']?.navroot?.navroot || {}
            }
            schema={this.props.schema}
            type={this.props.type}
            formData={
              this.props.location?.state?.initialFormData || {
                ...(blocksFieldname && {
                  [blocksFieldname]:
                    initialBlocks ||
                    this.props.schema.properties[blocksFieldname]?.default,
                }),
                ...(blocksLayoutFieldname && {
                  [blocksLayoutFieldname]: {
                    items:
                      initialBlocksLayout ||
                      this.props.schema.properties[blocksLayoutFieldname]
                        ?.default?.items,
                  },
                }),
                // Copy the Language Independent Fields values from the to-be translated content
                // into the default values of the translated content Add form.
                ...lifData(),
                parent: {
                  '@id': this.props.content?.['@id'] || '',
                },
              }
            }
            requestError={this.state.error}
            onSubmit={this.onSubmit}
            hideActions
            pathname={this.props.pathname}
            visual={visual}
            title={
              this.props?.schema?.title
                ? this.props.intl.formatMessage(messages.add, {
                    type: this.props.schema.title,
                  })
                : null
            }
            loading={this.props.createRequest.loading}
            isFormSelected={this.state.formSelected === 'addForm'}
            onSelectForm={() => {
              this.setState({ formSelected: 'addForm' });
            }}
            global
            // Properties to pass to the BlocksForm to match the View ones
            history={this.props.history}
            location={this.props.location}
            token={this.props.token}
          />
          {this.state.isClient &&
            createPortal(
              <Toolbar
                pathname={this.props.pathname}
                hideDefaultViewButtons
                inner={
                  <>
                    <Button
                      id="toolbar-save"
                      className="save"
                      aria-label={this.props.intl.formatMessage(messages.save)}
                      onClick={() => this.form.current.onSubmit()}
                      loading={this.props.createRequest.loading}
                      disabled={this.props.createRequest.loading}
                    >
                      <Icon
                        name={saveSVG}
                        className="circled"
                        size="30px"
                        title={this.props.intl.formatMessage(messages.save)}
                      />
                    </Button>
                    <Button
                      className="cancel"
                      onClick={() => this.onCancel()}
                      type="button"
                    >
                      <Icon
                        name={clearSVG}
                        className="circled"
                        aria-label={this.props.intl.formatMessage(
                          messages.cancel,
                        )}
                        size="30px"
                        title={this.props.intl.formatMessage(messages.cancel)}
                      />
                    </Button>
                  </>
                }
              />,
              document.getElementById('toolbar'),
            )}
          {visual &&
            this.state.isClient &&
            createPortal(<Sidebar />, document.getElementById('sidebar'))}
        </div>
      );

      return translationObject ? (
        <>
          <BodyClass className="babel-view" />
          <Grid
            celled="internally"
            stackable
            columns={2}
            id="page-add-translation"
          >
            <Grid.Column className="source-object">
              <TranslationObject
                translationObject={translationObject}
                schema={this.props.schema}
                pathname={this.props.pathname}
                visual={visual}
                isFormSelected={
                  this.state.formSelected === 'translationObjectForm'
                }
                onSelectForm={() => {
                  this.setState({
                    formSelected: 'translationObjectForm',
                  });
                }}
              />
            </Grid.Column>
            <Grid.Column>
              <div className="new-translation">
                <Menu pointing secondary attached tabular>
                  <Menu.Item
                    name={translateTo?.toUpperCase() || ''}
                    active={true}
                  >
                    {`${this.props.intl.formatMessage(messages.translateTo, {
                      lang: translateTo || '',
                    })}`}
                  </Menu.Item>
                </Menu>
                {pageAdd}
              </div>
            </Grid.Column>
          </Grid>
        </>
      ) : (
        pageAdd
      );
    }
    return <div />;
  }
}

export default compose(
  injectIntl,
  connect(
    (state, props) => ({
      createRequest: state.content.create,
      schemaRequest: state.schema,
      content: state.content.data,
      schema: state.schema.schema,
      pathname: props.location.pathname,
      returnUrl: qs.parse(props.location.search).return_url,
      type: qs.parse(props.location.search).type,
    }),
    { createContent, getSchema, changeLanguage, setFormData },
  ),
  preloadLazyLibs('cms'),
)(Add);
