import {pick} from 'lodash'

import createPreviewGetter from '../../preview/createPreviewGetter'
import {OWN_PROPS_NAME} from '../constants'
import {hiddenGetter, lazyGetter} from '../utils'
import {
  BLOCK_STYLES,
  DEFAULT_BLOCK_STYLES,
  DEFAULT_DECORATORS,
  DEFAULT_LINK_ANNOTATION,
  DEFAULT_LIST_TYPES,
  DEFAULT_MARKS_FIELD,
  DEFAULT_TEXT_FIELD,
} from './defaults'

const INHERITED_FIELDS = [
  'type',
  'name',
  'title',
  'jsonType',
  'description',
  'options',
  'fieldsets',
  'icon',
]

const BLOCK_CORE = {
  name: 'block',
  title: 'Block',
  type: null,
  jsonType: 'object',
}

const DEFAULT_OPTIONS = {}

export const BlockType = {
  get() {
    return BLOCK_CORE
  },
  extend(subTypeDef: any, extendMember: any) {
    const options = {...(subTypeDef.options || DEFAULT_OPTIONS)}

    const {marks, styles, lists, of, ...rest} = subTypeDef

    const childrenField = createChildrenField(marks, of)
    const styleField = createStyleField(styles)
    const listItemField = createListItemField(lists)

    const markDefsField = {
      name: 'markDefs',
      title: 'Mark definitions',
      type: 'array',
      of: marks?.annotations || DEFAULT_ANNOTATIONS,
    }

    const levelField = {
      name: 'level',
      title: 'Indentation',
      type: 'number',
    }

    // NOTE: if you update this (EVEN THE ORDER OF FIELDS) you _NEED TO_ also
    // update `BlockSchemaType`, `isBlockSchemaType` and similar in `@sanity/types`
    const fields = [childrenField, styleField, listItemField, markDefsField, levelField].concat(
      subTypeDef.fields || [],
    )

    const ownProps = {...rest, options}

    const parsed = Object.assign(pick(BLOCK_CORE, INHERITED_FIELDS), ownProps, {
      type: BLOCK_CORE,
    })

    lazyGetter(parsed, 'fields', () => {
      return fields.map((fieldDef) => {
        const {name, ...type} = fieldDef
        return {
          name: name,
          type: extendMember(type),
        }
      })
    })

    lazyGetter(parsed, 'preview', createPreviewGetter(subTypeDef))

    lazyGetter(
      parsed,
      OWN_PROPS_NAME,
      () => ({
        ...ownProps,
        fields: parsed.fields,
        preview: parsed.preview,
      }),
      {enumerable: false, writable: false},
    )

    return subtype(parsed)

    function subtype(parent: any) {
      return {
        get() {
          return parent
        },
        extend: (extensionDef: any) => {
          if (extensionDef.fields) {
            throw new Error('Cannot override `fields` of subtypes of "block"')
          }
          const subOwnProps = pick(extensionDef, INHERITED_FIELDS)
          const current = Object.assign({}, parent, subOwnProps, {
            type: parent,
          })
          hiddenGetter(current, OWN_PROPS_NAME, subOwnProps)
          return subtype(current)
        },
      }
    }
  },
}

function ensureNormalStyle(styles: any) {
  return styles.some((style: any) => style.value === 'normal')
    ? styles
    : [BLOCK_STYLES.normal, ...styles]
}

function createStyleField(styles: any) {
  return {
    name: 'style',
    title: 'Style',
    type: 'string',
    options: {
      list: ensureNormalStyle(styles || DEFAULT_BLOCK_STYLES),
    },
  }
}

function createListItemField(lists: any) {
  return {
    name: 'listItem',
    title: 'List type',
    type: 'string',
    options: {
      list: lists || DEFAULT_LIST_TYPES,
    },
  }
}

const DEFAULT_ANNOTATIONS = [DEFAULT_LINK_ANNOTATION]

function createChildrenField(marks: any, of = []) {
  return {
    name: 'children',
    title: 'Content',
    type: 'array',
    of: [
      {
        type: 'span',
        fields: [DEFAULT_TEXT_FIELD, DEFAULT_MARKS_FIELD],
        annotations: marks && marks.annotations ? marks.annotations : DEFAULT_ANNOTATIONS,
        decorators: marks && marks.decorators ? marks.decorators : DEFAULT_DECORATORS,
      },
      ...of.filter((memberType: any) => memberType.type !== 'span'),
    ],
  }
}
