import {
  allowedNodeTypes,
  blockNodeType,
  blockquoteNodeType,
  codeNodeType,
  headingNodeType,
  inlineBlockNodeType,
  inlineItemNodeType,
  inlineNodeTypes,
  itemLinkNodeType,
  linkNodeType,
  listItemNodeType,
  listNodeType,
  paragraphNodeType,
  rootNodeType,
  spanNodeType,
  thematicBreakNodeType,
} from './definitions';
import {
  Block,
  Blockquote,
  Code,
  Document,
  Heading,
  InlineBlock,
  InlineItem,
  InlineNode,
  ItemLink,
  Link,
  List,
  ListItem,
  Node,
  NodeType,
  Paragraph,
  Record as DatoCmsRecord,
  Root,
  Span,
  StructuredText,
  ThematicBreak,
  WithChildrenNode,
} from './types';

export function hasChildren(node: Node): node is WithChildrenNode {
  return 'children' in node;
}

export function isInlineNode(node: Node): node is InlineNode {
  return (inlineNodeTypes as NodeType[]).includes(node.type);
}

export function isHeading(node: Node): node is Heading {
  return node.type === headingNodeType;
}

export function isSpan(node: Node): node is Span {
  return node.type === spanNodeType;
}

export function isRoot(node: Node): node is Root {
  return node.type === rootNodeType;
}

export function isParagraph(node: Node): node is Paragraph {
  return node.type === paragraphNodeType;
}

export function isList(node: Node): node is List {
  return node.type === listNodeType;
}

export function isListItem(node: Node): node is ListItem {
  return node.type === listItemNodeType;
}

export function isBlockquote(node: Node): node is Blockquote {
  return node.type === blockquoteNodeType;
}

export function isBlock(node: Node): node is Block {
  return node.type === blockNodeType;
}

export function isInlineBlock(node: Node): node is InlineBlock {
  return node.type === inlineBlockNodeType;
}

export function isCode(node: Node): node is Code {
  return node.type === codeNodeType;
}

export function isLink(node: Node): node is Link {
  return node.type === linkNodeType;
}

export function isItemLink(node: Node): node is ItemLink {
  return node.type === itemLinkNodeType;
}

export function isInlineItem(node: Node): node is InlineItem {
  return node.type === inlineItemNodeType;
}

export function isThematicBreak(node: Node): node is ThematicBreak {
  return node.type === thematicBreakNodeType;
}

function isObject(obj: unknown): obj is Record<string, unknown> {
  return Boolean(typeof obj === 'object' && obj);
}

export function isNodeType(value: string): value is NodeType {
  return allowedNodeTypes.includes(value as NodeType);
}

export function isNode(obj: unknown): obj is Node {
  return Boolean(
    isObject(obj) &&
      'type' in obj &&
      typeof obj.type === 'string' &&
      isNodeType(obj.type),
  );
}

export function isStructuredText<
  BlockRecord extends DatoCmsRecord,
  LinkRecord extends DatoCmsRecord,
  InlineBlockRecord extends DatoCmsRecord
>(
  obj: unknown,
): obj is StructuredText<BlockRecord, LinkRecord, InlineBlockRecord> {
  return Boolean(isObject(obj) && 'value' in obj && isDocument(obj.value));
}

export function isDocument(obj: unknown): obj is Document {
  return Boolean(
    isObject(obj) &&
      'schema' in obj &&
      'document' in obj &&
      obj.schema === 'dast',
  );
}

export function isEmptyDocument(obj: unknown): boolean {
  if (!obj) {
    return true;
  }

  const document =
    isStructuredText(obj) && isDocument(obj.value)
      ? obj.value
      : isDocument(obj)
      ? obj
      : null;

  if (!document) {
    throw new Error(
      'Passed object is neither null, a Structured Text value or a DAST document',
    );
  }

  return (
    document.document.children.length === 1 &&
    document.document.children[0].type === 'paragraph' &&
    document.document.children[0].children.length === 1 &&
    document.document.children[0].children[0].type === 'span' &&
    document.document.children[0].children[0].value === ''
  );
}
