import PropTypes from 'prop-types';
import { omit } from 'ramda';
import React, { Component } from 'react';
import { Form } from 'semantic-ui-react';

import { withSite } from '../../addons/Site';
import Context from './Context';

/* Interface */
const propTypes = {
  ...Form.propTypes,

  /** Identity provider to use for resetting the password. */
  identityProvider: PropTypes.string,

  /** Marks the form as invalid (incomplete). */
  invalid: PropTypes.bool,

  /** Event handler called when reset is successful. */
  onResetResponse: PropTypes.func,

  /**
   * StoryShare `Site`.
   * @private
   */
  site: PropTypes.object.isRequired,

  /** Reset token query parameter */
  token: PropTypes.string.isRequired,
};

/* Defaults */
const defaultProps = {
  identityProvider: 'email',
};

/**
 * A password reset response form
 * Renders an SUI `<Form>` that can contain other components to build the
 * dynamic form.
 * Reset response forms must include the `<ResetResponseForm.PasswordInput>`
 * component.
 * We recommend including the `<ResetResponseForm.ErrorMessage>` compnoent to
 * display error details.
 * Any submit button will work, but `<ResetResponseForm.Button>` disables itself
 * until the required data is entered.
 * You can also include a `<ResetResponseForm.PasswordStrength>` component to
 * display a password strength meter.
 * All props are passed down to SUI's `<Form>`
 * @returns {Object} component
 */
export class ResetResponseForm extends Component {
  /**
   * Handles input changes by updating value in local state
   * The following components have `<Input>` boxes that need to propagate their
   * changes to the top level:
   *  - `<ResetResponseForm.PasswordInput>`
   * @param {ChangeEvent} event - React's original SyntheticEvent.
   * @param {Object} data - All props and a proposed value.
   * @param {string} data.name - `<Input>` field name.
   * @param {value} data.value - Proposed value.
   * @returns {void}
   */
  handleChange = (event, { name, value }) => this.setState({ [name]: value });

  /**
   * Handles password validation information
   * Used by `<ResetResponseForm.PasswordStrength>` to report password strength.
   * @param {bool} isValid - Whether the password is valid.
   * @returns {void}
   */
  handlePasswordValidation = (isValid) =>
    this.setState({ isPasswordValid: isValid });

  /**
   * Checks whether the password is correctly entered
   * Used internally before submission, but also by `<ResetResponseForm.Button>`
   * which disables itself when the form is incomplete.
   * @returns {bool} whether the form is complete
   */
  isFormValid = () => {
    const { invalid } = this.props;
    const { isPasswordValid, password } = this.state;
    return !!(!invalid && isPasswordValid && password);
  };

  /**
   * Component state
   * Also provides the public API to child components using `Context`.
   * N.B.: must be defined _after_ `handleChange`, `handlePasswordValidation`
   * and `isFormValid` as it exposes those class properties but they aren't
   * hoisted.
   */
  state = {
    /**
     * Error message
     * Used by `<ResetResponseForm.ErrorMessage>`.
     * @public
     * @type {'error'|null}
     */
    error: null,

    /**
     * Handles input changes by updating value in local state
     * Exposed in `state` for use by `<ResetResponseForm.PasswordInput>`.
     * @public
     * @type {func}
     */
    handleChange: this.handleChange,

    /**
     * Handles password validation information
     * Used by `<ResetResponseForm.PasswordStrength>` to report password
     * strength.
     * @public
     */
    handlePasswordValidation: this.handlePasswordValidation,

    /**
     * Checks whether the password is correctly entered
     * Exposed in `state` for use by `<ResetResponseForm.Button>`.
     * @public
     * @type {func}
     */
    isFormValid: this.isFormValid,

    /**
     * Whether or not we're in the process of requesting reset
     * @private
     * @type {bool}
     */
    isLoading: false,

    /**
     * Whether or not the password is valid
     * Defaults to `true` in case there's no strength meter in use.
     * @private
     * @type {bool}
     */
    isPasswordValid: true,

    /**
     * Entered password
     * Used by `<ResetResponseForm.PasswordInput>`
     * @public
     * @type {string}
     */
    password: '',
  };

  /**
   * Handles form submission (requests a password reset)
   * @returns {Promise<void>} resolves when complete
   */
  handleSubmit = async () => {
    if (!this.isFormValid()) {
      return;
    }

    // New submission attempt: reset error state and display loading indicator
    this.setState({ error: null, isLoading: true });

    const { identityProvider, onResetResponse, site, token } = this.props;
    const { password } = this.state;

    try {
      await site.req(`identityprovider/${identityProvider}/resetresponse`)
        .setParams({
          password,
          token,
        })
        .promise()
        .create();

      this.setState({ isLoading: false });

      if (onResetResponse) {
        onResetResponse();
      }
    } catch (err) {
      this.setState({ error: 'error', isLoading: false });
    }
  };

  render = () => (
    <Context.Provider value={ this.state }>
      <Form
        { ...omit([
          'identityProvider',
          'invalid',
          'onResetResponse',
          'site',
          'token',
        ], this.props) }
        error={ this.props.error || !!this.state.error }
        loading={ this.props.loading || this.state.isLoading }
        onSubmit={ this.handleSubmit }
      />
    </Context.Provider>
  );
}

ResetResponseForm.defaultProps = defaultProps;
ResetResponseForm.propTypes = propTypes;

export default withSite(ResetResponseForm);
