<script setup lang="ts">
import Placeholder from '@tiptap/extension-placeholder'
import StarterKit from '@tiptap/starter-kit'
import Mention from '@tiptap/extension-mention'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import tippy from 'tippy.js'
import { type ColumnType, UITypes } from 'nocodb-sdk'
import FieldList from '~/helpers/tiptapExtensions/mention/FieldList'
import suggestion from '~/helpers/tiptapExtensions/mention/suggestion.ts'

const props = withDefaults(
  defineProps<{
    modelValue: string
    options?: ColumnType[]
    autoFocus?: boolean
    promptFieldTagClassName?: string
    suggestionIconClassName?: string
    placeholder?: string
    readOnly?: boolean
  }>(),
  {
    options: () => [],
    autoFocus: true,
    promptFieldTagClassName: '',
    suggestionIconClassName: '',
    /**
     * Use \n to show placeholder as preline
     * @example: :placeholder="`Enter prompt here...\n\neg : Categorise this {Notes}`"
     */
    placeholder: 'Write your prompt here...',
    readOnly: false,
  },
)

const emits = defineEmits(['update:modelValue'])

const vModel = computed({
  get: () => props.modelValue,
  set: (v) => {
    emits('update:modelValue', v)
  },
})

const { autoFocus, readOnly } = toRefs(props)

const debouncedLoadMentionFieldTagTooltip = useDebounceFn(loadMentionFieldTagTooltip, 1000)

const editor = useEditor({
  content: vModel.value,
  extensions: [
    StarterKit.configure({
      heading: false,
    }) as any,
    Placeholder.configure({
      emptyEditorClass: 'is-editor-empty',
      placeholder: props.placeholder,
    }),
    Mention.configure({
      suggestion: {
        ...suggestion(FieldList),
        items: ({ query }) => {
          if (query.length === 0) return props.options ?? []
          return (
            props.options?.filter(
              (o) =>
                o.title?.toLowerCase()?.includes(query.toLowerCase()) || `${o.title?.toLowerCase()}}` === query.toLowerCase(),
            ) ?? []
          )
        },
        char: '{',
        allowSpaces: true,
      },
      renderHTML: ({ node }) => {
        const matchedOption = props.options?.find((option) => option.title === node.attrs.id)
        const isAttachment = matchedOption?.uidt === UITypes.Attachment
        return [
          'span',
          {
            'class': `prompt-field-tag ${isAttachment ? '!bg-green-200' : ''} ${props.promptFieldTagClassName}`,
            'style': 'max-width: 100px; white-space: nowrap; overflow: hidden; display: inline-block; text-overflow: ellipsis;', // Enforces truncation
            'data-tooltip': node.attrs.id, // Tooltip content
          },
          `${node.attrs.id}`,
        ]
      },
    }),
  ],
  onUpdate: ({ editor }) => {
    let text = ''

    // replace all mentions with id & prepare vModel
    editor.state.doc.descendants((node) => {
      if (node.type.name === 'mention') {
        text += `{${node.attrs.id}}`
      } else if (node.text) {
        text += node.text
      } else if (node.type.name === 'paragraph') {
        text += '\n'
      } else if (node.type.name === 'hardBreak') {
        text += '\n'
      }
    })

    // remove leading & trailing new lines
    text = text.trim()

    vModel.value = text

    debouncedLoadMentionFieldTagTooltip()
  },
  editable: !readOnly.value,
  autofocus: autoFocus.value,
  editorProps: { scrollThreshold: 100 },
})

const newFieldSuggestionNode = () => {
  if (!editor.value) return

  const { $from } = editor.value.state.selection
  const textBefore = editor.value.state.doc.textBetween($from.pos - 1, $from.pos, '\n', '\n')

  const lastCharacter = editor.value.state.doc.textBetween($from.pos - 1, $from.pos)

  // Check if the text before cursor contains a newline
  const hasNewlineBefore = textBefore.includes('\n')

  if (lastCharacter === '{') {
    editor.value
      .chain()
      .deleteRange({ from: $from.pos - 1, to: $from.pos })
      .run()
  } else if (lastCharacter !== ' ' && $from.pos !== 1 && !hasNewlineBefore) {
    editor.value?.commands.insertContent(' {')
    editor.value?.chain().focus().run()
  } else {
    editor.value?.commands.insertContent('{')
    editor.value?.chain().focus().run()
  }
}

onMounted(async () => {
  await until(() => vModel.value !== null && vModel.value !== undefined).toBeTruthy()

  // replace {id} with <span data-type="mention" data-id="id"></span>
  const renderContent = vModel.value
    .replace(/\{(.*?)\}/g, '<span data-type="mention" data-id="$1"></span>')
    .trim()
    .replace(/\n/g, '<br>')

  editor.value?.commands.setContent(renderContent)

  if (autoFocus.value) {
    setTimeout(() => {
      editor.value?.chain().focus().setTextSelection(vModel.value.length).run()
    }, 100)
  }
})

const tooltipInstances: any[] = []

function loadMentionFieldTagTooltip() {
  document.querySelectorAll('.nc-ai-prompt-with-fields .prompt-field-tag').forEach((el) => {
    const tooltip = Object.values(el.attributes).find((attr) => attr.name === 'data-tooltip')
    if (!tooltip || el.scrollWidth <= el.clientWidth) return
    // Show tooltip only on truncate
    const instance = tippy(el, {
      content: `<div class="tooltip text-xs">${tooltip.value}</div>`,
      placement: 'top',
      allowHTML: true,
      arrow: true,
      animation: 'fade',
      duration: 0,
      maxWidth: '200px',
    })

    tooltipInstances.push(instance)
  })
}

onMounted(() => {
  debouncedLoadMentionFieldTagTooltip()
})

onBeforeUnmount(() => {
  tooltipInstances.forEach((instance) => instance?.destroy())
  tooltipInstances.length = 0
})
</script>

<template>
  <div class="nc-ai-prompt-with-fields w-full">
    <EditorContent ref="editorDom" :editor="editor" @keydown.alt.enter.stop @keydown.shift.enter.stop />

    <NcButton
      size="xs"
      type="text"
      class="nc-prompt-with-field-suggestion-btn !px-1"
      :disabled="readOnly"
      @click.stop="newFieldSuggestionNode"
    >
      <slot name="triggerIcon">
        <GeneralIcon
          icon="ncPlusSquareSolid"
          class="text-nc-content-brand"
          :class="[
            `${suggestionIconClassName}`,
            {
              'opacity-75': readOnly,
            },
          ]"
        />
      </slot>
    </NcButton>
  </div>
</template>

<style lang="scss">
.nc-ai-prompt-with-fields {
  @apply relative;

  .nc-prompt-with-field-suggestion-btn {
    @apply absolute top-[2px] right-[1px];
  }

  .prompt-field-tag {
    @apply bg-gray-100 rounded-md px-1 align-middle;
  }

  .ProseMirror {
    @apply px-3 pb-3 pt-2 h-[120px] min-h-[120px] overflow-y-auto nc-scrollbar-thin outline-none border-1 border-gray-200 bg-white text-nc-content-gray rounded-lg !rounded-b-none transition-shadow ease-linear -mx-[1px] -mt-[1px];
    resize: vertical;
    min-width: 100%;
    max-height: min(800px, calc(100vh - 200px)) !important;

    & > p {
      @apply mr-3;
    }
  }

  .ProseMirror-focused {
    @apply !rounded-b-lg outline-none border-nc-fill-purple-medium shadow-selected-ai;
  }

  .tiptap p.is-editor-empty:first-child::before {
    @apply text-gray-500;
    content: attr(data-placeholder);
    white-space: pre-line; /* Preserve line breaks */
    float: left;
    height: 0;
    pointer-events: none;
  }

  p {
    @apply !mb-1;
  }
}
</style>
