/**
 * Copyright (c) 2013-present, Facebook, Inc.
 * All rights reserved.
 *
 * This source code is licensed under the BSD-style license found in the
 * LICENSE file in the root directory of this source tree. An additional grant
 * of patent rights can be found in the PATENTS file in the same directory.
 *
 * @providesModule DraftEditorContentsExperimental.react
 * @format
 * @flow
 *
 * This file is a fork of DraftEditorContents.react.js for tree nodes
 *
 * This is unstable and not part of the public API and should not be used by
 * production systems. This file may be update/removed without notice.
 */

'use strict';

import type { BlockNodeRecord } from './BlockNodeRecord';
import type { DraftBlockRenderMap } from './DraftBlockRenderMap';
import type { DraftInlineStyle } from './DraftInlineStyle';
import type { BidiDirection } from 'fbjs/lib/UnicodeBidiDirection';

const DraftEditorBlockNode = require('./DraftEditorBlockNode.react');
const DraftOffsetKey = require('./DraftOffsetKey');
const EditorState = require('./EditorState');
const React = require('react');

const nullthrows = require('fbjs/lib/nullthrows');

type Props = {
  blockRenderMap: DraftBlockRenderMap;
  blockRendererFn: (block: BlockNodeRecord) => ?Object;
  blockStyleFn?: (block: BlockNodeRecord) => string;
  customStyleFn?: (style: DraftInlineStyle, block: BlockNodeRecord) => ?Object;
  customStyleMap?: Object;
  editorKey?: string;
  editorState: EditorState;
  textDirectionality?: BidiDirection;
};

/**
 * `DraftEditorContents` is the container component for all block components
 * rendered for a `DraftEditor`. It is optimized to aggressively avoid
 * re-rendering blocks whenever possible.
 *
 * This component is separate from `DraftEditor` because certain props
 * (for instance, ARIA props) must be allowed to update without affecting
 * the contents of the editor.
 */
class DraftEditorContentsExperimental extends React.Component<Props> {
  shouldComponentUpdate(nextProps: Props): boolean {
    const prevEditorState = this.props.editorState;
    const nextEditorState = nextProps.editorState;

    const prevDirectionMap = prevEditorState.getDirectionMap();
    const nextDirectionMap = nextEditorState.getDirectionMap();

    // Text direction has changed for one or more blocks. We must re-render.
    if (prevDirectionMap !== nextDirectionMap) {
      return true;
    }

    const didHaveFocus = prevEditorState.getSelection().getHasFocus();
    const nowHasFocus = nextEditorState.getSelection().getHasFocus();

    if (didHaveFocus !== nowHasFocus) {
      return true;
    }

    const nextNativeContent = nextEditorState.getNativelyRenderedContent();

    const wasComposing = prevEditorState.isInCompositionMode();
    const nowComposing = nextEditorState.isInCompositionMode();

    // If the state is unchanged or we're currently rendering a natively
    // rendered state, there's nothing new to be done.
    if (prevEditorState === nextEditorState || nextNativeContent !== null && nextEditorState.getCurrentContent() === nextNativeContent || wasComposing && nowComposing) {
      return false;
    }

    const prevContent = prevEditorState.getCurrentContent();
    const nextContent = nextEditorState.getCurrentContent();
    const prevDecorator = prevEditorState.getDecorator();
    const nextDecorator = nextEditorState.getDecorator();
    return wasComposing !== nowComposing || prevContent !== nextContent || prevDecorator !== nextDecorator || nextEditorState.mustForceSelection();
  }

  render(): React.Node {
    const {
      blockRenderMap,
      blockRendererFn,
      blockStyleFn,
      customStyleMap,
      customStyleFn,
      editorState,
      editorKey,
      textDirectionality
    } = this.props;

    const content = editorState.getCurrentContent();
    const selection = editorState.getSelection();
    const forceSelection = editorState.mustForceSelection();
    const decorator = editorState.getDecorator();
    const directionMap = nullthrows(editorState.getDirectionMap());

    const blocksAsArray = content.getBlocksAsArray();
    const rootBlock = blocksAsArray[0];
    const processedBlocks = [];

    let nodeBlock = rootBlock;

    while (nodeBlock) {
      const blockKey = nodeBlock.getKey();
      const blockProps = {
        blockRenderMap,
        blockRendererFn,
        blockStyleFn,
        contentState: content,
        customStyleFn,
        customStyleMap,
        decorator,
        editorKey,
        editorState,
        forceSelection,
        selection,
        block: nodeBlock,
        direction: textDirectionality ? textDirectionality : directionMap.get(blockKey),
        tree: editorState.getBlockTree(blockKey)
      };

      const configForType = blockRenderMap.get(nodeBlock.getType()) || blockRenderMap.get('unstyled');
      const wrapperTemplate = configForType.wrapper;
      processedBlocks.push({
        block: <DraftEditorBlockNode key={blockKey} {...blockProps} />,
        wrapperTemplate,
        key: blockKey,
        offsetKey: DraftOffsetKey.encode(blockKey, 0, 0)
      });

      const nextBlockKey = nodeBlock.getNextSiblingKey();
      nodeBlock = nextBlockKey ? content.getBlockForKey(nextBlockKey) : null;
    }

    // Group contiguous runs of blocks that have the same wrapperTemplate
    const outputBlocks = [];
    for (let ii = 0; ii < processedBlocks.length;) {
      const info: any = processedBlocks[ii];
      if (info.wrapperTemplate) {
        const blocks = [];
        do {
          blocks.push(processedBlocks[ii].block);
          ii++;
        } while (ii < processedBlocks.length && processedBlocks[ii].wrapperTemplate === info.wrapperTemplate);
        const wrapperElement = React.cloneElement(info.wrapperTemplate, {
          key: info.key + '-wrap',
          'data-offset-key': info.offsetKey
        }, blocks);
        outputBlocks.push(wrapperElement);
      } else {
        outputBlocks.push(info.block);
        ii++;
      }
    }

    return <div data-contents="true">{outputBlocks}</div>;
  }
}

module.exports = DraftEditorContentsExperimental;