import { parseVideoUrl } from '@typebot.io/schemas/features/blocks/bubbles/video/helpers'
import {
  BubbleBlock,
  Variable,
  ContinueChatResponse,
  Typebot,
} from '@typebot.io/schemas'
import { deepParseVariables } from '@typebot.io/variables/deepParseVariables'
import { isEmpty, isNotEmpty } from '@typebot.io/lib/utils'
import {
  getVariablesToParseInfoInText,
  parseVariables,
} from '@typebot.io/variables/parseVariables'
import { TDescendant } from '@udecode/plate-common'
import { BubbleBlockType } from '@typebot.io/schemas/features/blocks/bubbles/constants'
import { defaultVideoBubbleContent } from '@typebot.io/schemas/features/blocks/bubbles/video/constants'
import { convertMarkdownToRichText } from '@typebot.io/lib/markdown/convertMarkdownToRichText'

type Params = {
  version: 1 | 2
  typebotVersion: Typebot['version']
  variables: Variable[]
}

export type BubbleBlockWithDefinedContent = BubbleBlock & {
  content: NonNullable<BubbleBlock['content']>
}

export const parseBubbleBlock = (
  block: BubbleBlockWithDefinedContent,
  { version, variables, typebotVersion }: Params
): ContinueChatResponse['messages'][0] => {
  switch (block.type) {
    case BubbleBlockType.TEXT: {
      if (version === 1)
        return {
          ...block,
          content: {
            ...block.content,
            richText: (block.content?.richText ?? []).map(
              deepParseVariables(variables)
            ),
          },
        }
      return {
        ...block,
        content: {
          ...block.content,
          richText: parseVariablesInRichText(block.content?.richText ?? [], {
            variables,
            takeLatestIfList: typebotVersion !== '6',
          }),
        },
      }
    }

    case BubbleBlockType.EMBED: {
      const message = deepParseVariables(variables, {
        removeEmptyStrings: true,
      })(block)
      return {
        ...message,
        content: {
          ...message.content,
          height:
            typeof message.content?.height === 'string'
              ? parseFloat(message.content.height)
              : message.content?.height,
        },
      }
    }
    case BubbleBlockType.VIDEO: {
      const parsedContent = block.content
        ? deepParseVariables(variables, { removeEmptyStrings: true })(
            block.content
          )
        : undefined

      return {
        ...block,
        content: {
          ...parsedContent,
          ...(parsedContent?.url ? parseVideoUrl(parsedContent.url) : {}),
          height:
            typeof parsedContent?.height === 'string'
              ? parseFloat(parsedContent.height)
              : defaultVideoBubbleContent.height,
        },
      }
    }
    default:
      return deepParseVariables(variables, { removeEmptyStrings: true })(block)
  }
}

const parseVariablesInRichText = (
  elements: TDescendant[],
  {
    variables,
    takeLatestIfList,
  }: { variables: Variable[]; takeLatestIfList?: boolean }
): TDescendant[] => {
  const parsedElements: TDescendant[] = []
  for (const element of elements) {
    if ('text' in element) {
      const text = element.text as string
      if (isEmpty(text)) {
        parsedElements.push(element)
        continue
      }
      const variablesInText = getVariablesToParseInfoInText(text, {
        variables,
        takeLatestIfList,
      })
      if (variablesInText.length === 0) {
        parsedElements.push(element)
        continue
      }
      let lastTextEndIndex = 0
      let index = -1
      for (const variableInText of variablesInText) {
        index += 1
        const textBeforeVariable = text.substring(
          lastTextEndIndex,
          variableInText.startIndex
        )
        const textAfterVariable =
          index === variablesInText.length - 1
            ? text.substring(variableInText.endIndex)
            : undefined
        lastTextEndIndex = variableInText.endIndex
        const isStandaloneElement =
          isEmpty(textBeforeVariable) && isEmpty(textAfterVariable)
        const variableElements = convertMarkdownToRichText(
          isStandaloneElement
            ? variableInText.value
            : variableInText.value.replace(/[\n]+/g, ' ')
        )

        const variableElementsWithStyling = applyElementStyleToDescendants(
          variableElements,
          {
            bold: element.bold,
            italic: element.italic,
            underline: element.underline,
          }
        )

        if (
          isStandaloneElement &&
          !element.bold &&
          !element.italic &&
          !element.underline
        ) {
          parsedElements.push(...variableElementsWithStyling)
          continue
        }
        const children: TDescendant[] = []
        if (isNotEmpty(textBeforeVariable))
          children.push({
            ...element,
            text: textBeforeVariable,
          })
        children.push({
          type: 'inline-variable',
          children: variableElementsWithStyling,
        })
        if (isNotEmpty(textAfterVariable))
          children.push({
            ...element,
            text: textAfterVariable,
          })
        parsedElements.push(...children)
      }

      continue
    }

    const type =
      element.children.length === 1 &&
      'text' in element.children[0] &&
      (element.children[0].text as string).startsWith('{{') &&
      (element.children[0].text as string).endsWith('}}') &&
      element.type !== 'a'
        ? 'variable'
        : element.type

    parsedElements.push({
      ...element,
      url: element.url
        ? parseVariables(variables)(element.url as string)
        : undefined,
      type,
      children: parseVariablesInRichText(element.children as TDescendant[], {
        variables,
        takeLatestIfList,
      }),
    })
  }
  return parsedElements
}

const applyElementStyleToDescendants = (
  variableElements: TDescendant[],
  styles: { bold: unknown; italic: unknown; underline: unknown }
): TDescendant[] =>
  variableElements.map((variableElement) => {
    if ('text' in variableElement) return { ...styles, ...variableElement }
    return {
      ...variableElement,
      children: applyElementStyleToDescendants(
        variableElement.children,
        styles
      ),
    }
  })
