/**
 * Sharing container.
 * @module components/manage/Sharing/Sharing
 */
import React, { Component } from 'react';
import PropTypes from 'prop-types';
import { Plug, Pluggable } from '@plone/volto/components/manage/Pluggable';
import Helmet from '@plone/volto/helpers/Helmet/Helmet';
import { connect } from 'react-redux';
import { compose } from 'redux';
import { Link, withRouter } from 'react-router-dom';
import find from 'lodash/find';
import isEqual from 'lodash/isEqual';
import map from 'lodash/map';
import { createPortal } from 'react-dom';
import {
  Button,
  Checkbox,
  Container as SemanticContainer,
  Form,
  Icon as IconOld,
  Input,
  Segment,
  Table,
} from 'semantic-ui-react';
import jwtDecode from 'jwt-decode';
import { FormattedMessage, defineMessages, injectIntl } from 'react-intl';

import {
  updateSharing,
  getSharing,
} from '@plone/volto/actions/sharing/sharing';
import { getBaseUrl } from '@plone/volto/helpers/Url/Url';
import Icon from '@plone/volto/components/theme/Icon/Icon';
import Toolbar from '@plone/volto/components/manage/Toolbar/Toolbar';
import Toast from '@plone/volto/components/manage/Toast/Toast';
import { toast } from 'react-toastify';
import config from '@plone/volto/registry';

import aheadSVG from '@plone/volto/icons/ahead.svg';
import clearSVG from '@plone/volto/icons/clear.svg';
import backSVG from '@plone/volto/icons/back.svg';

const messages = defineMessages({
  searchForUserOrGroup: {
    id: 'Search for user or group',
    defaultMessage: 'Search for user or group',
  },
  search: {
    id: 'Search',
    defaultMessage: 'Search',
  },
  inherit: {
    id: 'Inherit permissions from higher levels',
    defaultMessage: 'Inherit permissions from higher levels',
  },
  save: {
    id: 'Save',
    defaultMessage: 'Save',
  },
  cancel: {
    id: 'Cancel',
    defaultMessage: 'Cancel',
  },
  back: {
    id: 'Back',
    defaultMessage: 'Back',
  },
  sharing: {
    id: 'Sharing',
    defaultMessage: 'Sharing',
  },
  user: {
    id: 'User',
    defaultMessage: 'User',
  },
  group: {
    id: 'Group',
    defaultMessage: 'Group',
  },
  globalRole: {
    id: 'Global role',
    defaultMessage: 'Global role',
  },
  inheritedValue: {
    id: 'Inherited value',
    defaultMessage: 'Inherited value',
  },
  permissionsUpdated: {
    id: 'Permissions updated',
    defaultMessage: 'Permissions updated',
  },
  permissionsUpdatedSuccessfully: {
    id: 'Permissions have been updated successfully',
    defaultMessage: 'Permissions have been updated successfully',
  },
  assignNewRoles: {
    id: 'Assign the {role} role to {entry}',
    defaultMessage: 'Assign the {role} role to {entry}',
  },
});

/**
 * SharingComponent class.
 * @class SharingComponent
 * @extends Component
 */
class SharingComponent extends Component {
  /**
   * Property types.
   * @property {Object} propTypes Property types.
   * @static
   */
  static propTypes = {
    updateSharing: PropTypes.func.isRequired,
    getSharing: PropTypes.func.isRequired,
    updateRequest: PropTypes.shape({
      loading: PropTypes.bool,
      loaded: PropTypes.bool,
    }).isRequired,
    pathname: PropTypes.string.isRequired,
    entries: PropTypes.arrayOf(
      PropTypes.shape({
        id: PropTypes.string,
        login: PropTypes.string,
        roles: PropTypes.object,
        title: PropTypes.string,
        type: PropTypes.string,
      }),
    ).isRequired,
    available_roles: PropTypes.arrayOf(PropTypes.object).isRequired,
    inherit: PropTypes.bool,
    title: PropTypes.string.isRequired,
    login: PropTypes.string,
  };

  /**
   * Default properties
   * @property {Object} defaultProps Default properties.
   * @static
   */
  static defaultProps = {
    inherit: null,
    login: '',
  };

  /**
   * Constructor
   * @method constructor
   * @param {Object} props Component properties
   * @constructs Sharing
   */
  constructor(props) {
    super(props);
    this.onCancel = this.onCancel.bind(this);
    this.onChange = this.onChange.bind(this);
    this.onChangeSearch = this.onChangeSearch.bind(this);
    this.onSearch = this.onSearch.bind(this);
    this.onSubmit = this.onSubmit.bind(this);
    this.onToggleInherit = this.onToggleInherit.bind(this);
    this.state = {
      search: '',
      isLoading: false,
      inherit: props.inherit,
      entries: props.entries,
      isClient: false,
    };
  }

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

  /**
   * Component will receive props
   * @method componentWillReceiveProps
   * @param {Object} nextProps Next properties
   * @returns {undefined}
   */
  UNSAFE_componentWillReceiveProps(nextProps) {
    if (this.props.updateRequest.loading && nextProps.updateRequest.loaded) {
      this.props.getSharing(getBaseUrl(this.props.pathname), this.state.search);
      toast.success(
        <Toast
          success
          title={this.props.intl.formatMessage(messages.permissionsUpdated)}
          content={this.props.intl.formatMessage(
            messages.permissionsUpdatedSuccessfully,
          )}
        />,
      );
    }
    this.setState({
      inherit:
        this.props.inherit === null ? nextProps.inherit : this.state.inherit,
      entries: map(nextProps.entries, (entry) => {
        const values = find(this.state.entries, { id: entry.id });
        return {
          ...entry,
          roles: values ? values.roles : entry.roles,
        };
      }),
    });
  }

  /**
   * Submit handler
   * @method onSubmit
   * @param {object} event Event object.
   * @returns {undefined}
   */
  onSubmit(event) {
    const data = { entries: [] };
    event.preventDefault();
    if (this.props.inherit !== this.state.inherit) {
      data.inherit = this.state.inherit;
    }
    for (let i = 0; i < this.props.entries.length; i += 1) {
      if (!isEqual(this.props.entries[i].roles, this.state.entries[i].roles)) {
        data.entries.push({
          id: this.state.entries[i].id,
          type: this.state.entries[i].type,
          roles: this.state.entries[i].roles,
        });
      }
    }
    this.props.updateSharing(getBaseUrl(this.props.pathname), data);
  }

  /**
   * Search handler
   * @method onSearch
   * @param {object} event Event object.
   * @returns {undefined}
   */
  onSearch(event) {
    event.preventDefault();
    this.setState({ isLoading: true });
    this.props
      .getSharing(getBaseUrl(this.props.pathname), this.state.search)
      .then(() => {
        this.setState({ isLoading: false });
      })
      .catch((error) => {
        this.setState({ isLoading: false });
        // eslint-disable-next-line no-console
        console.error('Error searching users or groups', error);
      });
  }

  /**
   * On change search handler
   * @method onChangeSearch
   * @param {object} event Event object.
   * @returns {undefined}
   */
  onChangeSearch(event) {
    this.setState({
      search: event.target.value,
    });
  }

  /**
   * On toggle inherit handler
   * @method onToggleInherit
   * @returns {undefined}
   */
  onToggleInherit() {
    this.setState((state) => ({
      inherit: !state.inherit,
    }));
  }

  /**
   * On change handler
   * @method onChange
   * @param {object} event Event object
   * @param {string} value Entry value
   * @returns {undefined}
   */
  onChange(event, { value }) {
    const [principal, role] = value.split(':');
    this.setState({
      entries: map(this.state.entries, (entry) => ({
        ...entry,
        roles:
          entry.id === principal
            ? {
                ...entry.roles,
                [role]: !entry.roles[role],
              }
            : entry.roles,
      })),
    });
  }

  /**
   * Cancel handler
   * @method onCancel
   * @returns {undefined}
   */
  onCancel() {
    this.props.history.push(getBaseUrl(this.props.pathname));
  }

  /**
   * Render method.
   * @method render
   * @returns {string} Markup for the component.
   */
  render() {
    const Container =
      config.getComponent({ name: 'Container' }).component || SemanticContainer;

    return (
      <Container id="page-sharing">
        <Helmet title={this.props.intl.formatMessage(messages.sharing)} />
        <Segment.Group raised>
          <Pluggable
            name="sharing-component"
            params={{ isLoading: this.state.isLoading }}
          />
          <Plug pluggable="sharing-component" id="sharing-component-title">
            <Segment className="primary">
              <FormattedMessage
                id="Sharing for {title}"
                defaultMessage="Sharing for {title}"
                values={{ title: <q>{this.props.title}</q> }}
              />
            </Segment>
          </Plug>
          <Plug
            pluggable="sharing-component"
            id="sharing-component-description"
          >
            <Segment secondary>
              <FormattedMessage
                id="You can control who can view and edit your item using the list below."
                defaultMessage="You can control who can view and edit your item using the list below."
              />
            </Segment>
          </Plug>
          <Plug pluggable="sharing-component" id="sharing-component-search">
            {({ isLoading }) => {
              return (
                <Segment>
                  <Form onSubmit={this.onSearch}>
                    <Form.Field>
                      <Input
                        name="SearchableText"
                        action={{
                          icon: 'search',
                          loading: isLoading,
                          disabled: isLoading,
                          'aria-label': this.props.intl.formatMessage(
                            messages.search,
                          ),
                        }}
                        placeholder={this.props.intl.formatMessage(
                          messages.searchForUserOrGroup,
                        )}
                        onChange={this.onChangeSearch}
                        id="sharing-component-search"
                      />
                    </Form.Field>
                  </Form>
                </Segment>
              );
            }}
          </Plug>
          <Plug
            pluggable="sharing-component"
            id="sharing-component-form"
            dependencies={[this.state.entries, this.props.available_roles]}
          >
            <Form onSubmit={this.onSubmit}>
              <Table celled padded striped attached>
                <Table.Header>
                  <Table.Row>
                    <Table.HeaderCell>
                      <FormattedMessage id="Name" defaultMessage="Name" />
                    </Table.HeaderCell>
                    {this.props.available_roles?.map((role) => (
                      <Table.HeaderCell key={role.id}>
                        {role.title}
                      </Table.HeaderCell>
                    ))}
                  </Table.Row>
                </Table.Header>
                <Table.Body>
                  {this.state.entries?.map((entry) => (
                    <Table.Row key={entry.id}>
                      <Table.Cell>
                        <IconOld
                          name={entry.type === 'user' ? 'user' : 'users'}
                          title={
                            entry.type === 'user'
                              ? this.props.intl.formatMessage(messages.user)
                              : this.props.intl.formatMessage(messages.group)
                          }
                        />{' '}
                        {entry.title}
                        {entry.login && ` (${entry.login})`}
                      </Table.Cell>
                      {this.props.available_roles?.map((role) => (
                        <Table.Cell key={role.id}>
                          {entry.roles[role.id] === 'global' && (
                            <IconOld
                              name="check circle outline"
                              title={this.props.intl.formatMessage(
                                messages.globalRole,
                              )}
                              color="blue"
                            />
                          )}
                          {entry.roles[role.id] === 'acquired' && (
                            <IconOld
                              name="check circle outline"
                              color="green"
                              title={this.props.intl.formatMessage(
                                messages.inheritedValue,
                              )}
                            />
                          )}
                          {typeof entry.roles[role.id] === 'boolean' && (
                            <Checkbox
                              name={this.props.intl.formatMessage(
                                messages.assignNewRoles,
                                {
                                  entry: entry.title,
                                  role: role.id,
                                },
                              )}
                              aria-label={this.props.intl.formatMessage(
                                messages.assignNewRoles,
                                {
                                  entry: entry.title,
                                  role: role.id,
                                },
                              )}
                              onChange={this.onChange}
                              value={`${entry.id}:${role.id}`}
                              checked={entry.roles[role.id]}
                              disabled={entry.login === this.props.login}
                            />
                          )}
                        </Table.Cell>
                      ))}
                    </Table.Row>
                  ))}
                </Table.Body>
              </Table>
              <Segment attached>
                <Form.Field>
                  <Checkbox
                    id="inherit-permissions-checkbox"
                    name="inherit-permissions-checkbox"
                    defaultChecked={this.state.inherit}
                    onChange={this.onToggleInherit}
                    label={
                      <label htmlFor="inherit-permissions-checkbox">
                        {this.props.intl.formatMessage(messages.inherit)}
                      </label>
                    }
                  />
                </Form.Field>
                <p className="help">
                  <FormattedMessage
                    id="By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator."
                    defaultMessage="By default, permissions from the container of this item are inherited. If you disable this, only the explicitly defined sharing permissions will be valid. In the overview, the symbol {inherited} indicates an inherited value. Similarly, the symbol {global} indicates a global role, which is managed by the site administrator."
                    values={{
                      inherited: (
                        <IconOld name="check circle outline" color="green" />
                      ),
                      global: (
                        <IconOld name="check circle outline" color="blue" />
                      ),
                    }}
                  />
                </p>
              </Segment>
              <Segment className="actions" attached clearing>
                <Button
                  basic
                  primary
                  floated="right"
                  type="submit"
                  aria-label={this.props.intl.formatMessage(messages.save)}
                  title={this.props.intl.formatMessage(messages.save)}
                  loading={this.props.updateRequest.loading}
                  onClick={this.onSubmit}
                >
                  <Icon className="circled" name={aheadSVG} size="30px" />
                </Button>
                <Button
                  basic
                  secondary
                  aria-label={this.props.intl.formatMessage(messages.cancel)}
                  title={this.props.intl.formatMessage(messages.cancel)}
                  floated="right"
                  onClick={this.onCancel}
                >
                  <Icon className="circled" name={clearSVG} size="30px" />
                </Button>
              </Segment>
            </Form>
          </Plug>
        </Segment.Group>
        {this.state.isClient &&
          createPortal(
            <Toolbar
              pathname={this.props.pathname}
              hideDefaultViewButtons
              inner={
                <Link
                  to={`${getBaseUrl(this.props.pathname)}`}
                  className="item"
                >
                  <Icon
                    name={backSVG}
                    className="contents circled"
                    size="30px"
                    title={this.props.intl.formatMessage(messages.back)}
                  />
                </Link>
              }
            />,
            document.getElementById('toolbar'),
          )}
      </Container>
    );
  }
}

export default compose(
  withRouter,
  injectIntl,
  connect(
    (state, props) => ({
      entries: state.sharing.data.entries,
      inherit: state.sharing.data.inherit,
      available_roles: state.sharing.data.available_roles,
      updateRequest: state.sharing.update,
      pathname: props.location.pathname,
      title: state.content.data.title,
      login: state.userSession.token
        ? jwtDecode(state.userSession.token).sub
        : '',
    }),
    { updateSharing, getSharing },
  ),
)(SharingComponent);
