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 { validateEmail } from '../../lib';
import Context from './Context';

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

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

  /** Event handler called when login is successful. */
  onLogin: PropTypes.func,

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

/**
 * A login form
 * Renders an SUI `<Form>` that can contain other components to build the
 * dynamic form.
 * Login forms must include a `<LoginForm.EmailInput>` and
 * a `<LoginForm.PasswordInput>`.
 * We recommend including `<LoginForm.InvalidCredentialsMessage>` and
 * `<LoginForm.InvalidEmailMessage>` to display error details.
 * Any submit button will work, but `<LoginForm.Button>` disables itself until
 * an email address and password are entered.
 * All props are passed down to SUI's `<Form>`
 * @returns {Object} component
 */
export class LoginForm extends Component {
  /**
   * Handles input changes by updating value in local state
   * Both `<LoginForm.EmailInput>` and `<LoginForm.PasswordInput>` have
   * `<Input>` boxes that need to propagate their changes to the top level.
   * @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 });

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

  /**
   * Component state
   * Also provides the public API to child components using `Context`.
   * N.B.: must be defined _after_ `handleChange` and `isFormValid` as it
   * exposes those class properties but they aren't hoisted.
   */
  state = {
    /**
     * Entered email address
     * Used by `<LoginForm.EmailInput>`
     * @public
     * @type {string}
     */
    email: '',

    /**
     * Error message
     * Used by `<LoginForm.InvalidCredentialsMessage>` and
     * `<LoginForm.InvalidEmailMessage>`.
     * @public
     * @type {'email'|'credentials'|null}
     */
    error: null,

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

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

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

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

  /**
   * Handles form submission (runs login)
   * Calls the `onLogin` event handler when complete.
   * @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 { onLogin, site } = this.props;
    const { email, password } = this.state;

    if (!validateEmail(email.trim())) {
      this.setState({ error: 'email', isLoading: false });
      return;
    }

    try {
      await site.login({ email: email.trim(), password });
      this.setState({ isLoading: false });
      if (onLogin) {
        onLogin();
      }
    } catch (err) {
      this.setState({ error: 'credentials', isLoading: false });
    }
  };

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

LoginForm.propTypes = propTypes;

export default withSite(LoginForm);
