//  Package imports.
import PropTypes from 'prop-types';
import React from 'react';
import ImmutablePropTypes from 'react-immutable-proptypes';

const APPROX_HASHTAG_RE = /(?:^|[^\/\)\w])#(\S+)/i;

//  Actions.
import {
  cancelReplyCompose,
  changeCompose,
  changeComposeAdvancedOption,
  changeComposeSensitivity,
  changeComposeSpoilerText,
  changeComposeSpoilerness,
  changeComposeVisibility,
  changeUploadCompose,
  clearComposeSuggestions,
  fetchComposeSuggestions,
  insertEmojiCompose,
  mountCompose,
  selectComposeSuggestion,
  submitCompose,
  undoUploadCompose,
  unmountCompose,
  uploadCompose,
} from 'flavours/glitch/actions/compose';
import {
  closeModal,
  openModal,
} from 'flavours/glitch/actions/modal';

//  Components.
import ComposerOptions from './options';
import ComposerPublisher from './publisher';
import ComposerReply from './reply';
import ComposerSpoiler from './spoiler';
import ComposerTextarea from './textarea';
import ComposerUploadForm from './upload_form';
import ComposerWarning from './warning';
import ComposerHashtagWarning from './hashtag_warning';

//  Utils.
import { countableText } from 'flavours/glitch/util/counter';
import { me } from 'flavours/glitch/util/initial_state';
import { isMobile } from 'flavours/glitch/util/is_mobile';
import { assignHandlers } from 'flavours/glitch/util/react_helpers';
import { wrap } from 'flavours/glitch/util/redux_helpers';

//  State mapping.
function mapStateToProps (state) {
  const inReplyTo = state.getIn(['compose', 'in_reply_to']);
  return {
    acceptContentTypes: state.getIn(['media_attachments', 'accept_content_types']).toArray().join(','),
    advancedOptions: state.getIn(['compose', 'advanced_options']),
    amUnlocked: !state.getIn(['accounts', me, 'locked']),
    focusDate: state.getIn(['compose', 'focusDate']),
    isSubmitting: state.getIn(['compose', 'is_submitting']),
    isUploading: state.getIn(['compose', 'is_uploading']),
    layout: state.getIn(['local_settings', 'layout']),
    media: state.getIn(['compose', 'media_attachments']),
    preselectDate: state.getIn(['compose', 'preselectDate']),
    privacy: state.getIn(['compose', 'privacy']),
    progress: state.getIn(['compose', 'progress']),
    replyAccount: inReplyTo ? state.getIn(['statuses', inReplyTo, 'account']) : null,
    replyContent: inReplyTo ? state.getIn(['statuses', inReplyTo, 'contentHtml']) : null,
    resetFileKey: state.getIn(['compose', 'resetFileKey']),
    sideArm: state.getIn(['local_settings', 'side_arm']),
    sensitive: state.getIn(['compose', 'sensitive']),
    showSearch: state.getIn(['search', 'submitted']) && !state.getIn(['search', 'hidden']),
    spoiler: state.getIn(['compose', 'spoiler']),
    spoilerText: state.getIn(['compose', 'spoiler_text']),
    suggestionToken: state.getIn(['compose', 'suggestion_token']),
    suggestions: state.getIn(['compose', 'suggestions']),
    text: state.getIn(['compose', 'text']),
    anyMedia: state.getIn(['compose', 'media_attachments']).size > 0,
  };
};

//  Dispatch mapping.
const mapDispatchToProps = {
  onCancelReply: cancelReplyCompose,
  onChangeAdvancedOption: changeComposeAdvancedOption,
  onChangeDescription: changeUploadCompose,
  onChangeSensitivity: changeComposeSensitivity,
  onChangeSpoilerText: changeComposeSpoilerText,
  onChangeSpoilerness: changeComposeSpoilerness,
  onChangeText: changeCompose,
  onChangeVisibility: changeComposeVisibility,
  onClearSuggestions: clearComposeSuggestions,
  onCloseModal: closeModal,
  onFetchSuggestions: fetchComposeSuggestions,
  onInsertEmoji: insertEmojiCompose,
  onMount: mountCompose,
  onOpenActionsModal: openModal.bind(null, 'ACTIONS'),
  onOpenDoodleModal: openModal.bind(null, 'DOODLE', { noEsc: true }),
  onSelectSuggestion: selectComposeSuggestion,
  onSubmit: submitCompose,
  onUndoUpload: undoUploadCompose,
  onUnmount: unmountCompose,
  onUpload: uploadCompose,
};

//  Handlers.
const handlers = {

  //  Changes the text value of the spoiler.
  handleChangeSpoiler ({ target: { value } }) {
    const { onChangeSpoilerText } = this.props;
    if (onChangeSpoilerText) {
      onChangeSpoilerText(value);
    }
  },

  //  Inserts an emoji at the caret.
  handleEmoji (data) {
    const { textarea: { selectionStart } } = this;
    const { onInsertEmoji } = this.props;
    this.caretPos = selectionStart + data.native.length + 1;
    if (onInsertEmoji) {
      onInsertEmoji(selectionStart, data);
    }
  },

  //  Handles the secondary submit button.
  handleSecondarySubmit () {
    const { handleSubmit } = this.handlers;
    const {
      onChangeVisibility,
      sideArm,
    } = this.props;
    if (sideArm !== 'none' && onChangeVisibility) {
      onChangeVisibility(sideArm);
    }
    handleSubmit();
  },

  //  Selects a suggestion from the autofill.
  handleSelect (tokenStart, token, value) {
    const { onSelectSuggestion } = this.props;
    this.caretPos = null;
    if (onSelectSuggestion) {
      onSelectSuggestion(tokenStart, token, value);
    }
  },

  //  Submits the status.
  handleSubmit () {
    const { textarea: { value } } = this;
    const {
      onChangeText,
      onSubmit,
      isSubmitting,
      isUploading,
      anyMedia,
      text,
    } = this.props;

    //  If something changes inside the textarea, then we update the
    //  state before submitting.
    if (onChangeText && text !== value) {
      onChangeText(value);
    }

    // Submit disabled:
    if (isSubmitting || isUploading || (!!text.length && !text.trim().length && !anyMedia)) {
      return;
    }

    //  Submits the status.
    if (onSubmit) {
      onSubmit();
    }
  },

  //  Sets a reference to the textarea.
  handleRefTextarea (textareaComponent) {
    if (textareaComponent) {
      this.textarea = textareaComponent.textarea;
    }
  },
};

//  The component.
class Composer extends React.Component {

  //  Constructor.
  constructor (props) {
    super(props);
    assignHandlers(this, handlers);

    //  Instance variables.
    this.caretPos = null;
    this.textarea = null;
  }

  //  If this is the update where we've finished uploading,
  //  save the last caret position so we can restore it below!
  componentWillReceiveProps (nextProps) {
    const { textarea } = this;
    const { isUploading } = this.props;
    if (textarea && isUploading && !nextProps.isUploading) {
      this.caretPos = textarea.selectionStart;
    }
  }

  //  Tells our state the composer has been mounted.
  componentDidMount () {
    const { onMount } = this.props;
    if (onMount) {
      onMount();
    }
  }

  //  Tells our state the composer has been unmounted.
  componentWillUnmount () {
    const { onUnmount } = this.props;
    if (onUnmount) {
      onUnmount();
    }
  }

  //  This statement does several things:
  //  - If we're beginning a reply, and,
  //      - Replying to zero or one users, places the cursor at the end
  //        of the textbox.
  //      - Replying to more than one user, selects any usernames past
  //        the first; this provides a convenient shortcut to drop
  //        everyone else from the conversation.
  // - If we've just finished uploading an image, and have a saved
  //   caret position, restores the cursor to that position after the
  //   text changes.
  componentDidUpdate (prevProps) {
    const {
      caretPos,
      textarea,
    } = this;
    const {
      focusDate,
      isUploading,
      isSubmitting,
      preselectDate,
      text,
    } = this.props;
    let selectionEnd, selectionStart;

    //  Caret/selection handling.
    if (focusDate !== prevProps.focusDate || (prevProps.isUploading && !isUploading && !isNaN(caretPos) && caretPos !== null)) {
      switch (true) {
      case preselectDate !== prevProps.preselectDate:
        selectionStart = text.search(/\s/) + 1;
        selectionEnd = text.length;
        break;
      case !isNaN(caretPos) && caretPos !== null:
        selectionStart = selectionEnd = caretPos;
        break;
      default:
        selectionStart = selectionEnd = text.length;
      }
      if (textarea) {
        textarea.setSelectionRange(selectionStart, selectionEnd);
        textarea.focus();
      }

    //  Refocuses the textarea after submitting.
    } else if (textarea && prevProps.isSubmitting && !isSubmitting) {
      textarea.focus();
    }
  }

  render () {
    const {
      handleChangeSpoiler,
      handleEmoji,
      handleSecondarySubmit,
      handleSelect,
      handleSubmit,
      handleRefTextarea,
    } = this.handlers;
    const {
      acceptContentTypes,
      advancedOptions,
      amUnlocked,
      anyMedia,
      intl,
      isSubmitting,
      isUploading,
      layout,
      media,
      onCancelReply,
      onChangeAdvancedOption,
      onChangeDescription,
      onChangeSensitivity,
      onChangeSpoilerness,
      onChangeText,
      onChangeVisibility,
      onClearSuggestions,
      onCloseModal,
      onFetchSuggestions,
      onOpenActionsModal,
      onOpenDoodleModal,
      onUndoUpload,
      onUpload,
      privacy,
      progress,
      replyAccount,
      replyContent,
      resetFileKey,
      sensitive,
      showSearch,
      sideArm,
      spoiler,
      spoilerText,
      suggestions,
      text,
    } = this.props;

    let disabledButton = isSubmitting || isUploading || (!!text.length && !text.trim().length && !anyMedia);

    return (
      <div className='composer'>
        <ComposerSpoiler
          hidden={!spoiler}
          intl={intl}
          onChange={handleChangeSpoiler}
          onSubmit={handleSubmit}
          text={spoilerText}
        />
        {privacy === 'private' && amUnlocked ? <ComposerWarning /> : null}
        {privacy !== 'public' && APPROX_HASHTAG_RE.test(text) ? <ComposerHashtagWarning /> : null}
        {replyContent ? (
          <ComposerReply
            account={replyAccount}
            content={replyContent}
            intl={intl}
            onCancel={onCancelReply}
          />
        ) : null}
        <ComposerTextarea
          advancedOptions={advancedOptions}
          autoFocus={!showSearch && !isMobile(window.innerWidth, layout)}
          disabled={isSubmitting}
          intl={intl}
          onChange={onChangeText}
          onPaste={onUpload}
          onPickEmoji={handleEmoji}
          onSubmit={handleSubmit}
          onSuggestionsClearRequested={onClearSuggestions}
          onSuggestionsFetchRequested={onFetchSuggestions}
          onSuggestionSelected={handleSelect}
          ref={handleRefTextarea}
          suggestions={suggestions}
          value={text}
        />
        {isUploading || media && media.size ? (
          <ComposerUploadForm
            intl={intl}
            media={media}
            onChangeDescription={onChangeDescription}
            onRemove={onUndoUpload}
            progress={progress}
            uploading={isUploading}
          />
        ) : null}
        <ComposerOptions
          acceptContentTypes={acceptContentTypes}
          advancedOptions={advancedOptions}
          disabled={isSubmitting}
          full={media ? media.size >= 4 || media.some(
            item => item.get('type') === 'video'
          ) : false}
          hasMedia={media && !!media.size}
          intl={intl}
          onChangeAdvancedOption={onChangeAdvancedOption}
          onChangeSensitivity={onChangeSensitivity}
          onChangeVisibility={onChangeVisibility}
          onDoodleOpen={onOpenDoodleModal}
          onModalClose={onCloseModal}
          onModalOpen={onOpenActionsModal}
          onToggleSpoiler={onChangeSpoilerness}
          onUpload={onUpload}
          privacy={privacy}
          resetFileKey={resetFileKey}
          sensitive={sensitive}
          spoiler={spoiler}
        />
        <ComposerPublisher
          countText={`${spoilerText}${countableText(text)}${advancedOptions && advancedOptions.get('do_not_federate') ? ' 👁️' : ''}`}
          disabled={disabledButton}
          intl={intl}
          onSecondarySubmit={handleSecondarySubmit}
          onSubmit={handleSubmit}
          privacy={privacy}
          sideArm={sideArm}
        />
      </div>
    );
  }

}

//  Props.
Composer.propTypes = {
  intl: PropTypes.object.isRequired,

  //  State props.
  acceptContentTypes: PropTypes.string,
  advancedOptions: ImmutablePropTypes.map,
  amUnlocked: PropTypes.bool,
  focusDate: PropTypes.instanceOf(Date),
  isSubmitting: PropTypes.bool,
  isUploading: PropTypes.bool,
  layout: PropTypes.string,
  media: ImmutablePropTypes.list,
  preselectDate: PropTypes.instanceOf(Date),
  privacy: PropTypes.string,
  progress: PropTypes.number,
  replyAccount: PropTypes.string,
  replyContent: PropTypes.string,
  resetFileKey: PropTypes.number,
  sideArm: PropTypes.string,
  sensitive: PropTypes.bool,
  showSearch: PropTypes.bool,
  spoiler: PropTypes.bool,
  spoilerText: PropTypes.string,
  suggestionToken: PropTypes.string,
  suggestions: ImmutablePropTypes.list,
  text: PropTypes.string,

  //  Dispatch props.
  onCancelReply: PropTypes.func,
  onChangeAdvancedOption: PropTypes.func,
  onChangeDescription: PropTypes.func,
  onChangeSensitivity: PropTypes.func,
  onChangeSpoilerText: PropTypes.func,
  onChangeSpoilerness: PropTypes.func,
  onChangeText: PropTypes.func,
  onChangeVisibility: PropTypes.func,
  onClearSuggestions: PropTypes.func,
  onCloseModal: PropTypes.func,
  onFetchSuggestions: PropTypes.func,
  onInsertEmoji: PropTypes.func,
  onMount: PropTypes.func,
  onOpenActionsModal: PropTypes.func,
  onOpenDoodleModal: PropTypes.func,
  onSelectSuggestion: PropTypes.func,
  onSubmit: PropTypes.func,
  onUndoUpload: PropTypes.func,
  onUnmount: PropTypes.func,
  onUpload: PropTypes.func,
  anyMedia: PropTypes.bool,
};

//  Connecting and export.
export { Composer as WrappedComponent };
export default wrap(Composer, mapStateToProps, mapDispatchToProps, true);