import {
  $createLineBreakNode,
  $createParagraphNode,
  $createTextNode,
  $getRoot,
  $isTextNode,
  LexicalNode,
  TextFormatType,
} from 'lexical';
import { $createCodeHighlightNode, $createCodeNode } from '@lexical/code';
import { $createLinkNode } from '@lexical/link';
import { $createListItemNode, $createListNode } from '@lexical/list';
import { $createHorizontalRuleNode } from '@lexical/react/LexicalHorizontalRuleNode';
import { $createHeadingNode, $createQuoteNode } from '@lexical/rich-text';
import { $createTableCellNode, $createTableNode, $createTableRowNode } from '@lexical/table';

import type { CrystallizeRichTextNode, CrystallizeRichText } from '../types/crystallize-rich-text-types';
import { parseInitialState } from './parse-initial-state';

export function composeInitialState({ richText }: { richText: CrystallizeRichText }) {
  return function setLexicalState() {
    const root = $getRoot();

    function handleNode({
      crystallizeContentNode,
      lexicalParent,
    }: {
      crystallizeContentNode: CrystallizeRichTextNode;
      lexicalParent: LexicalNode;
    }) {
      const { textContent, kind, type, children, metadata } = crystallizeContentNode;
      let lexicalNode: LexicalNode | null = null;

      let insertedTextContent = false;

      const customTextFormat = getLexicalTextFormat(crystallizeContentNode);
      if (customTextFormat) {
        lexicalNode = $createTextNode(textContent).toggleFormat(customTextFormat);
        insertedTextContent = true;
      } else {
        switch (type) {
          case 'heading1': {
            lexicalNode = $createHeadingNode('h1');
            break;
          }
          case 'heading2': {
            lexicalNode = $createHeadingNode('h2');
            break;
          }
          case 'heading3': {
            lexicalNode = $createHeadingNode('h3');
            break;
          }
          case 'heading4': {
            lexicalNode = $createHeadingNode('h4');
            break;
          }
          case 'heading5': {
            lexicalNode = $createHeadingNode('h5');
            break;
          }
          case 'heading6': {
            lexicalNode = $createHeadingNode('h6');
            break;
          }
          case 'paragraph': {
            lexicalNode = $createParagraphNode();
            break;
          }
          case 'horizontal-line': {
            lexicalNode = $createHorizontalRuleNode();
            break;
          }
          case 'code': {
            const language = metadata && typeof metadata.language === 'string' ? metadata.language : null;
            lexicalNode = $createCodeNode(language);
            lexicalNode.append($createCodeHighlightNode(textContent ?? ''));
            insertedTextContent = true;

            break;
          }
          case 'ordered-list': {
            lexicalNode = $createListNode('number');
            break;
          }
          case 'unordered-list': {
            lexicalNode = $createListNode('bullet');
            break;
          }
          case 'list-item': {
            lexicalNode = $createListItemNode();
            break;
          }
          case 'line-break': {
            lexicalNode = $createLineBreakNode();
            break;
          }
          case 'link': {
            const href = metadata && typeof metadata.href === 'string' ? metadata.href : '';
            const target = metadata && typeof metadata.target === 'string' ? metadata.target : null;
            const rel = metadata && typeof metadata.rel === 'string' ? metadata.rel : null;

            lexicalNode = $createLinkNode(href, {
              target,
              rel,
            });
            break;
          }
          case 'quote': {
            lexicalNode = $createQuoteNode();
            break;
          }
          case 'table': {
            lexicalNode = $createTableNode();
            break;
          }
          case 'table-row': {
            lexicalNode = $createTableRowNode();
            break;
          }
          case 'table-cell':
          case 'table-head-cell': {
            // ** colSpan / rowSpan has not yet been implemented **
            // const colSpanFromMeta = metadata?.colSpan;
            // const colSpanParsed =
            //   typeof colSpanFromMeta === 'number'
            //     ? colSpanFromMeta
            //     : parseInt(colSpanFromMeta as unknown as string, 10);
            // const colSpan = !isNaN(colSpanParsed) ? colSpanParsed : undefined;
            const colSpan = undefined;

            lexicalNode = $createTableCellNode(
              // 2 indicates NO_HEADER_STATUS
              // See TableCellHeaderStates in @lexical/table/LexicalTableCellNode for details
              0,
              colSpan,
            );
            break;
          }
          case 'preformatted': {
            /**
             * We do not deal with this tag. It should just be skipped.
             */
            lexicalNode = lexicalParent;
            if (children) {
              children.forEach(n => handleNode({ crystallizeContentNode: n, lexicalParent }));
            }
            return;
          }
        }

        if (!insertedTextContent && typeof textContent === 'string') {
          const textNode = $createTextNode(textContent);
          if (!lexicalNode && kind === 'inline') {
            lexicalNode = textNode;
          } else if (lexicalNode) {
            lexicalNode.append(textNode);
          }
        }
      }

      if (lexicalNode) {
        if (Array.isArray(children)) {
          /**
           * Support multiple nested inline text formats. A text can have both
           * a strong _and_ underlined (and more) formats applied.
           */
          if ($isTextNode(lexicalNode)) {
            const handleInlineTextChild = (child: CrystallizeRichTextNode) => {
              if ($isTextNode(lexicalNode)) {
                const textFormat = getLexicalTextFormat(child);

                if (textFormat && !lexicalNode.hasFormat(textFormat)) {
                  lexicalNode.toggleFormat(textFormat);
                }

                // Text content will be stored at the deepest nested child
                if (child.textContent) {
                  lexicalNode.setTextContent(child.textContent);
                }
              }

              if ('children' in child) {
                child.children?.forEach(handleInlineTextChild);
              }
            };

            children.forEach(handleInlineTextChild);
          } else {
            children.forEach(n => handleNode({ crystallizeContentNode: n, lexicalParent: lexicalNode as LexicalNode }));
          }
        }

        lexicalParent.append(lexicalNode);
      }
    }

    parseInitialState({ richText }).forEach(crystallizeContentNode =>
      handleNode({ crystallizeContentNode, lexicalParent: root }),
    );
  };
}

function getLexicalTextFormat(node: CrystallizeRichTextNode): null | TextFormatType {
  switch (node.type) {
    case 'strong':
      return 'bold';
    case 'emphasized':
      return 'italic';
    case 'underlined':
      return 'underline';
    case 'subscripted':
      return 'subscript';
    case 'superscripted':
      return 'superscript';
    case 'deleted':
      return 'strikethrough';
    case 'code': {
      if (node.kind === 'inline') {
        return 'code';
      }
      break;
    }
  }
  return null;
}
