/**
 * Copyright (c) Facebook, Inc. and its affiliates.
 *
 * This source code is licensed under the MIT license found in the
 * LICENSE file in the root directory of this source tree.
 *
 * @format
 * @flow
 * @emails oncall+draft_js
 *
 * This file is a fork of ContentBlock adding support for nesting references by
 * providing links to children, parent, prevSibling, and nextSibling.
 *
 * 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 { BlockNode, BlockNodeConfig, BlockNodeKey } from "./BlockNode";
import type { DraftBlockType } from "./DraftBlockType";
import type { DraftInlineStyle } from "./DraftInlineStyle";

const CharacterMetadata = require("./CharacterMetadata");

const findRangesImmutable = require("./findRangesImmutable");

const Immutable = require("immutable");

const {
  List,
  Map,
  OrderedSet,
  Record,
  Repeat
} = Immutable;
type ContentBlockNodeConfig = BlockNodeConfig & {
  children?: List<BlockNodeKey>,
  parent?: ?BlockNodeKey,
  prevSibling?: ?BlockNodeKey,
  nextSibling?: ?BlockNodeKey,
  ...
};
const EMPTY_SET = OrderedSet();
const defaultRecord: ContentBlockNodeConfig = {
  parent: null,
  characterList: List(),
  data: Map(),
  depth: 0,
  key: '',
  text: '',
  type: 'unstyled',
  children: List(),
  prevSibling: null,
  nextSibling: null
};

const haveEqualStyle = (charA: CharacterMetadata, charB: CharacterMetadata): boolean => charA.getStyle() === charB.getStyle();

const haveEqualEntity = (charA: CharacterMetadata, charB: CharacterMetadata): boolean => charA.getEntity() === charB.getEntity();

const decorateCharacterList = (config: ContentBlockNodeConfig): ContentBlockNodeConfig => {
  if (!config) {
    return config;
  }

  const {
    characterList,
    text
  } = config;

  if (text && !characterList) {
    config.characterList = List(Repeat(CharacterMetadata.EMPTY, text.length));
  }

  return config;
};

class ContentBlockNode extends (Record(defaultRecord): any) implements BlockNode {
  constructor(props: ContentBlockNodeConfig = defaultRecord) {
    /* eslint-disable-next-line constructor-super */
    super(decorateCharacterList(props));
  }

  getKey(): BlockNodeKey {
    return this.get('key');
  }

  getType(): DraftBlockType {
    return this.get('type');
  }

  getText(): string {
    return this.get('text');
  }

  getCharacterList(): List<CharacterMetadata> {
    return this.get('characterList');
  }

  getLength(): number {
    return this.getText().length;
  }

  getDepth(): number {
    return this.get('depth');
  }

  getData(): Map<any, any> {
    return this.get('data');
  }

  getInlineStyleAt(offset: number): DraftInlineStyle {
    const character = this.getCharacterList().get(offset);
    return character ? character.getStyle() : EMPTY_SET;
  }

  getEntityAt(offset: number): ?string {
    const character = this.getCharacterList().get(offset);
    return character ? character.getEntity() : null;
  }

  getChildKeys(): List<BlockNodeKey> {
    return this.get('children');
  }

  getParentKey(): ?BlockNodeKey {
    return this.get('parent');
  }

  getPrevSiblingKey(): ?BlockNodeKey {
    return this.get('prevSibling');
  }

  getNextSiblingKey(): ?BlockNodeKey {
    return this.get('nextSibling');
  }

  findStyleRanges(filterFn: (value: CharacterMetadata) => boolean, callback: (start: number, end: number) => void): void {
    findRangesImmutable(this.getCharacterList(), haveEqualStyle, filterFn, callback);
  }

  findEntityRanges(filterFn: (value: CharacterMetadata) => boolean, callback: (start: number, end: number) => void): void {
    findRangesImmutable(this.getCharacterList(), haveEqualEntity, filterFn, callback);
  }

}

module.exports = ContentBlockNode;