content: import Immutable from 'immutable';
  import React from 'react';
  import { connect } from 'react-redux';
  import PropTypes from 'prop-types';
  import classNames from 'classnames';
  import ImmutablePropTypes from 'react-immutable-proptypes';
  import { createSelector } from 'reselect';
  import { fetchStatus } from '../../actions/statuses';
  import MissingIndicator from '../../components/missing_indicator';
  import DetailedStatus from './components/detailed_status';
  import ActionBar from './components/action_bar';
  import Column from '../ui/components/column';
  import {
    favourite,
    unfavourite,
    bookmark,
    unbookmark,
    reblog,
    unreblog,
    pin,
    unpin,
  } from '../../actions/interactions';
  import {
    replyCompose,
    mentionCompose,
    directCompose,
  } from '../../actions/compose';
  import {
    muteStatus,
    unmuteStatus,
    deleteStatus,
    hideStatus,
    revealStatus,
  } from '../../actions/statuses';
  import {
    unblockAccount,
    unmuteAccount,
  } from '../../actions/accounts';
  import {
    blockDomain,
    unblockDomain,
  } from '../../actions/domain_blocks';
  import { initMuteModal } from '../../actions/mutes';
  import { initBlockModal } from '../../actions/blocks';
  import { initReport } from '../../actions/reports';
  import { makeGetStatus } from '../../selectors';
  import { ScrollContainer } from 'react-router-scroll-4';
  import ColumnBackButton from '../../components/column_back_button';
  import ColumnHeader from '../../components/column_header';
  import StatusContainer from '../../containers/status_container';
  import { openModal } from '../../actions/modal';
  import { defineMessages, injectIntl, FormattedMessage } from 'react-intl';
  import ImmutablePureComponent from 'react-immutable-pure-component';
  import { HotKeys } from 'react-hotkeys';
  import { boostModal, deleteModal } from '../../initial_state';
  import { attachFullscreenListener, detachFullscreenListener, isFullscreen } from '../ui/util/fullscreen';
  import { textForScreenReader, defaultMediaVisibility } from '../../components/status';
  import Icon from 'mastodon/components/icon';
  
  const messages = defineMessages({
    deleteConfirm: { id: 'confirmations.delete.confirm', defaultMessage: 'Delete' },
    deleteMessage: { id: 'confirmations.delete.message', defaultMessage: 'Are you sure you want to delete this status?' },
    redraftConfirm: { id: 'confirmations.redraft.confirm', defaultMessage: 'Delete & redraft' },
    redraftMessage: { id: 'confirmations.redraft.message', defaultMessage: 'Are you sure you want to delete this status and re-draft it? Favourites and boosts will be lost, and replies to the original post will be orphaned.' },
    revealAll: { id: 'status.show_more_all', defaultMessage: 'Show more for all' },
    hideAll: { id: 'status.show_less_all', defaultMessage: 'Show less for all' },
    detailedStatus: { id: 'status.detailed_status', defaultMessage: 'Detailed conversation view' },
    replyConfirm: { id: 'confirmations.reply.confirm', defaultMessage: 'Reply' },
    replyMessage: { id: 'confirmations.reply.message', defaultMessage: 'Replying now will overwrite the message you are currently composing. Are you sure you want to proceed?' },
    blockDomainConfirm: { id: 'confirmations.domain_block.confirm', defaultMessage: 'Hide entire domain' },
  });
  
  const makeMapStateToProps = () => {
    const getStatus = makeGetStatus();
  
    const getAncestorsIds = createSelector([
      (_, { id }) => id,
      state => state.getIn(['contexts', 'inReplyTos']),
    ], (statusId, inReplyTos) => {
      let ancestorsIds = Immutable.List();
      ancestorsIds = ancestorsIds.withMutations(mutable => {
        let id = statusId;
  
        while (id) {
          mutable.unshift(id);
          id = inReplyTos.get(id);
        }
      });
  
      return ancestorsIds;
    });
  
    const getDescendantsIds = createSelector([
      (_, { id }) => id,
      state => state.getIn(['contexts', 'replies']),
      state => state.get('statuses'),
    ], (statusId, contextReplies, statuses) => {
      let descendantsIds = [];
      const ids = [statusId];
  
      while (ids.length > 0) {
        let id        = ids.shift();
        const replies = contextReplies.get(id);
  
        if (statusId !== id) {
          descendantsIds.push(id);
        }
  
        if (replies) {
          replies.reverse().forEach(reply => {
            ids.unshift(reply);
          });
        }
      }
  
      let insertAt = descendantsIds.findIndex((id) => statuses.get(id).get('in_reply_to_account_id') !== statuses.get(id).get('account'));
      if (insertAt !== -1) {
        descendantsIds.forEach((id, idx) => {
          if (idx > insertAt && statuses.get(id).get('in_reply_to_account_id') === statuses.get(id).get('account')) {
            descendantsIds.splice(idx, 1);
            descendantsIds.splice(insertAt, 0, id);
            insertAt += 1;
          }
        });
      }
  
      return Immutable.List(descendantsIds);
    });
  
    const mapStateToProps = (state, props) => {
      const status = getStatus(state, { id: props.params.statusId });
      let ancestorsIds = Immutable.List();
      let descendantsIds = Immutable.List();
  
      if (status) {
        ancestorsIds = getAncestorsIds(state, { id: status.get('in_reply_to_id') });
        descendantsIds = getDescendantsIds(state, { id: status.get('id') });
      }
  
      return {
        status,
        ancestorsIds,
        descendantsIds,
        askReplyConfirmation: state.getIn(['compose', 'text']).trim().length !== 0,
        domain: state.getIn(['meta', 'domain']),
      };
    };
  
    return mapStateToProps;
  };
  
  export default @injectIntl
  @connect(makeMapStateToProps)
  class Status extends ImmutablePureComponent {
  
    static contextTypes = {
      router: PropTypes.object,
    };
  
    static propTypes = {
      params: PropTypes.object.isRequired,
      dispatch: PropTypes.func.isRequired,
      status: ImmutablePropTypes.map,
      ancestorsIds: ImmutablePropTypes.list,
      descendantsIds: ImmutablePropTypes.list,
      intl: PropTypes.object.isRequired,
      askReplyConfirmation: PropTypes.bool,
      multiColumn: PropTypes.bool,
      domain: PropTypes.string.isRequired,
    };
  
    state = {
      fullscreen: false,
      showMedia: defaultMediaVisibility(this.props.status),
      loadedStatusId: undefined,
    };
  
    componentWillMount () {
      this.props.dispatch(fetchStatus(this.props.params.statusId));
    }
  
    componentDidMount () {
      attachFullscreenListener(this.onFullScreenChange);
    }
  
    componentWillReceiveProps (nextProps) {
      if (nextProps.params.statusId !== this.props.params.statusId && nextProps.params.statusId) {
        this._scrolledIntoView = false;
        this.props.dispatch(fetchStatus(nextProps.params.statusId));
      }
  
      if (nextProps.status && nextProps.status.get('id') !== this.state.loadedStatusId) {
        this.setState({ showMedia: defaultMediaVisibility(nextProps.status), loadedStatusId: nextProps.status.get('id') });
      }
    }
  
    handleToggleMediaVisibility = () => {
      this.setState({ showMedia: !this.state.showMedia });
    }
  
    handleFavouriteClick = (status) => {
      if (status.get('favourited')) {
        this.props.dispatch(unfavourite(status));
      } else {
        this.props.dispatch(favourite(status));
      }
    }
  
    handlePin = (status) => {
      if (status.get('pinned')) {
        this.props.dispatch(unpin(status));
      } else {
        this.props.dispatch(pin(status));
      }
    }
  
    handleReplyClick = (status) => {
      let { askReplyConfirmation, dispatch, intl } = this.props;
      if (askReplyConfirmation) {
        dispatch(openModal('CONFIRM', {
          message: intl.formatMessage(messages.replyMessage),
          confirm: intl.formatMessage(messages.replyConfirm),
          onConfirm: () => dispatch(replyCompose(status, this.context.router.history)),
        }));
      } else {
        dispatch(replyCompose(status, this.context.router.history));
      }
    }
  
    handleModalReblog = (status) => {
      this.props.dispatch(reblog(status));
    }
  
    handleReblogClick = (status, e) => {
      if (status.get('reblogged')) {
        this.props.dispatch(unreblog(status));
      } else {
        if ((e && e.shiftKey) || !boostModal) {
          this.handleModalReblog(status);
        } else {
          this.props.dispatch(openModal('BOOST', { status, onReblog: this.handleModalReblog }));
        }
      }
    }
  
    handleBookmarkClick = (status) => {
      if (status.get('bookmarked')) {
        this.props.dispatch(unbookmark(status));
      } else {
        this.props.dispatch(bookmark(status));
      }
    }
  
    handleDeleteClick = (status, history, withRedraft = false) => {
      const { dispatch, intl } = this.props;
  
      if (!deleteModal) {
        dispatch(deleteStatus(status.get('id'), history, withRedraft));
      } else {
        dispatch(openModal('CONFIRM', {
          message: intl.formatMessage(withRedraft ? messages.redraftMessage : messages.deleteMessage),
          confirm: intl.formatMessage(withRedraft ? messages.redraftConfirm : messages.deleteConfirm),
          onConfirm: () => dispatch(deleteStatus(status.get('id'), history, withRedraft)),
        }));
      }
    }
  
    handleDirectClick = (account, router) => {
      this.props.dispatch(directCompose(account, router));
    }
  
    handleMentionClick = (account, router) => {
      this.props.dispatch(mentionCompose(account, router));
    }
  
    handleOpenMedia = (media, index) => {
      this.props.dispatch(openModal('MEDIA', { media, index }));
    }
  
    handleOpenVideo = (media, time) => {
      this.props.dispatch(openModal('VIDEO', { media, time }));
    }
  
    handleHotkeyOpenMedia = e => {
      const status = this._properStatus();
  
      e.preventDefault();
  
      if (status.get('media_attachments').size > 0) {
        if (status.getIn(['media_attachments', 0, 'type']) === 'audio') {
          // TODO: toggle play/paused?
        } else if (status.getIn(['media_attachments', 0, 'type']) === 'video') {
          this.handleOpenVideo(status.getIn(['media_attachments', 0]), 0);
        } else {
          this.handleOpenMedia(status.get('media_attachments'), 0);
        }
      }
    }
  
    handleMuteClick = (account) => {
      this.props.dispatch(initMuteModal(account));
    }
  
    handleConversationMuteClick = (status) => {
      if (status.get('muted')) {
        this.props.dispatch(unmuteStatus(status.get('id')));
      } else {
        this.props.dispatch(muteStatus(status.get('id')));
      }
    }
  
    handleToggleHidden = (status) => {
      if (status.get('hidden')) {
        this.props.dispatch(revealStatus(status.get('id')));
      } else {
        this.props.dispatch(hideStatus(status.get('id')));
      }
    }
  
    handleToggleAll = () => {
      const { status, ancestorsIds, descendantsIds } = this.props;
      const statusIds = [status.get('id')].concat(ancestorsIds.toJS(), descendantsIds.toJS());
  
      if (status.get('hidden')) {
        this.props.dispatch(revealStatus(statusIds));
      } else {
        this.props.dispatch(hideStatus(statusIds));
      }
    }
  
    handleBlockClick = (status) => {
      const { dispatch } = this.props;
      const account = status.get('account');
      dispatch(initBlockModal(account));
    }
  
    handleReport = (status) => {
      this.props.dispatch(initReport(status.get('account'), status));
    }
  
    handleEmbed = (status) => {
      this.props.dispatch(openModal('EMBED', { url: status.get('url') }));
    }
  
    handleUnmuteClick = account => {
      this.props.dispatch(unmuteAccount(account.get('id')));
    }
  
    handleUnblockClick = account => {
      this.props.dispatch(unblockAccount(account.get('id')));
    }
  
    handleBlockDomainClick = domain => {
      this.props.dispatch(openModal('CONFIRM', {
        message: <FormattedMessage id='confirmations.domain_block.message' defaultMessage='Are you really, really sure you want to block the entire {domain}? In most cases a few targeted blocks or mutes are sufficient and preferable. You will not see content from that domain in any public timelines or your notifications. Your followers from that domain will be removed.' values={{ domain: <strong>{domain}</strong> }} />,
        confirm: this.props.intl.formatMessage(messages.blockDomainConfirm),
        onConfirm: () => this.props.dispatch(blockDomain(domain)),
      }));
    }
  
    handleUnblockDomainClick = domain => {
      this.props.dispatch(unblockDomain(domain));
    }
  
  
    handleHotkeyMoveUp = () => {
      this.handleMoveUp(this.props.status.get('id'));
    }
  
    handleHotkeyMoveDown = () => {
      this.handleMoveDown(this.props.status.get('id'));
    }
  
    handleHotkeyReply = e => {
      e.preventDefault();
      this.handleReplyClick(this.props.status);
    }
  
    handleHotkeyFavourite = () => {
      this.handleFavouriteClick(this.props.status);
    }
  
    handleHotkeyBoost = () => {
      this.handleReblogClick(this.props.status);
    }
  
    handleHotkeyMention = e => {
      e.preventDefault();
      this.handleMentionClick(this.props.status.get('account'));
    }
  
    handleHotkeyOpenProfile = () => {
      this.context.router.history.push(`/accounts/${this.props.status.getIn(['account', 'id'])}`);
    }
  
    handleHotkeyToggleHidden = () => {
      this.handleToggleHidden(this.props.status);
    }
  
    handleHotkeyToggleSensitive = () => {
      this.handleToggleMediaVisibility();
    }
  
    handleMoveUp = id => {
      const { status, ancestorsIds, descendantsIds } = this.props;
  
      if (id === status.get('id')) {
        this._selectChild(ancestorsIds.size - 1, true);
      } else {
        let index = ancestorsIds.indexOf(id);
  
        if (index === -1) {
          index = descendantsIds.indexOf(id);
          this._selectChild(ancestorsIds.size + index, true);
        } else {
          this._selectChild(index - 1, true);
        }
      }
    }
  
    handleMoveDown = id => {
      const { status, ancestorsIds, descendantsIds } = this.props;
  
      if (id === status.get('id')) {
        this._selectChild(ancestorsIds.size + 1, false);
      } else {
        let index = ancestorsIds.indexOf(id);
  
        if (index === -1) {
          index = descendantsIds.indexOf(id);
          this._selectChild(ancestorsIds.size + index + 2, false);
        } else {
          this._selectChild(index + 1, false);
        }
      }
    }
  
    _selectChild (index, align_top) {
      const container = this.node;
      const element = container.querySelectorAll('.focusable')[index];
  
      if (element) {
        if (align_top && container.scrollTop > element.offsetTop) {
          element.scrollIntoView(true);
        } else if (!align_top && container.scrollTop + container.clientHeight < element.offsetTop + element.offsetHeight) {
          element.scrollIntoView(false);
        }
        element.focus();
      }
    }
  
    renderChildren (list) {
      return list.map(id => (
        <StatusContainer
          key={id}
          id={id}
          onMoveUp={this.handleMoveUp}
          onMoveDown={this.handleMoveDown}
          contextType='thread'
        />
      ));
    }
  
    setRef = c => {
      this.node = c;
    }
  
    componentDidUpdate () {
      if (this._scrolledIntoView) {
        return;
      }
  
      const { status, ancestorsIds } = this.props;
  
      if (status && ancestorsIds && ancestorsIds.size > 0) {
        const element = this.node.querySelectorAll('.focusable')[ancestorsIds.size - 1];
  
        window.requestAnimationFrame(() => {
          element.scrollIntoView(true);
        });
        this._scrolledIntoView = true;
      }
    }
  
    componentWillUnmount () {
      detachFullscreenListener(this.onFullScreenChange);
    }
  
    onFullScreenChange = () => {
      this.setState({ fullscreen: isFullscreen() });
    }
  
    render () {
      let ancestors, descendants;
      const { shouldUpdateScroll, status, ancestorsIds, descendantsIds, intl, domain, multiColumn } = this.props;
      const { fullscreen } = this.state;
  
      if (status === null) {
        return (
          <Column>
            <ColumnBackButton multiColumn={multiColumn} />
            <MissingIndicator />
          </Column>
        );
      }
  
      if (ancestorsIds && ancestorsIds.size > 0) {
        ancestors = <div>{this.renderChildren(ancestorsIds)}</div>;
      }
  
      if (descendantsIds && descendantsIds.size > 0) {
        descendants = <div>{this.renderChildren(descendantsIds)}</div>;
      }
  
      const handlers = {
        moveUp: this.handleHotkeyMoveUp,
        moveDown: this.handleHotkeyMoveDown,
        reply: this.handleHotkeyReply,
        favourite: this.handleHotkeyFavourite,
        boost: this.handleHotkeyBoost,
        mention: this.handleHotkeyMention,
        openProfile: this.handleHotkeyOpenProfile,
        toggleHidden: this.handleHotkeyToggleHidden,
        toggleSensitive: this.handleHotkeyToggleSensitive,
        openMedia: this.handleHotkeyOpenMedia,
      };
  
      return (
        <Column bindToDocument={!multiColumn} label={intl.formatMessage(messages.detailedStatus)}>
          <ColumnHeader
            showBackButton
            multiColumn={multiColumn}
            extraButton={(
              <button className='column-header__button' title={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} aria-label={intl.formatMessage(status.get('hidden') ? messages.revealAll : messages.hideAll)} onClick={this.handleToggleAll} aria-pressed={status.get('hidden') ? 'false' : 'true'}><Icon id={status.get('hidden') ? 'eye-slash' : 'eye'} /></button>
            )}
          />
  
          <ScrollContainer scrollKey='thread' shouldUpdateScroll={shouldUpdateScroll}>
            <div className={classNames('scrollable', { fullscreen })} ref={this.setRef}>
              {ancestors}
  
              <HotKeys handlers={handlers}>
                <div className={classNames('focusable', 'detailed-status__wrapper')} tabIndex='0' aria-label={textForScreenReader(intl, status, false)}>
                  <DetailedStatus
                    key={`details-${status.get('id')}`}
                    status={status}
                    onOpenVideo={this.handleOpenVideo}
                    onOpenMedia={this.handleOpenMedia}
                    onToggleHidden={this.handleToggleHidden}
                    domain={domain}
                    showMedia={this.state.showMedia}
                    onToggleMediaVisibility={this.handleToggleMediaVisibility}
                  />
  
                  <ActionBar
                    key={`action-bar-${status.get('id')}`}
                    status={status}
                    onReply={this.handleReplyClick}
                    onFavourite={this.handleFavouriteClick}
                    onReblog={this.handleReblogClick}
                    onBookmark={this.handleBookmarkClick}
                    onDelete={this.handleDeleteClick}
                    onDirect={this.handleDirectClick}
                    onMention={this.handleMentionClick}
                    onMute={this.handleMuteClick}
                    onUnmute={this.handleUnmuteClick}
                    onMuteConversation={this.handleConversationMuteClick}
                    onBlock={this.handleBlockClick}
                    onUnblock={this.handleUnblockClick}
                    onBlockDomain={this.handleBlockDomainClick}
                    onUnblockDomain={this.handleUnblockDomainClick}
                    onReport={this.handleReport}
                    onPin={this.handlePin}
                    onEmbed={this.handleEmbed}
                  />
                </div>
              </HotKeys>
  
              {descendants}
            </div>
          </ScrollContainer>
        </Column>
      );
    }
  
  }
  