/**
 * HTML редактор с подсветкой синтаксиса через contenteditable
 * @module HTMLEditor
 */

import { createEffect, onMount, untrack, createSignal } from 'solid-js'
import Prism from 'prismjs'
import 'prismjs/components/prism-markup'
import 'prismjs/themes/prism.css'
import styles from '../styles/Form.module.css'

interface HTMLEditorProps {
  value: string
  onInput: (value: string) => void
  placeholder?: string
  rows?: number
  class?: string
  disabled?: boolean
}

/**
 * Компонент HTML редактора с contenteditable и подсветкой синтаксиса
 */
const HTMLEditor = (props: HTMLEditorProps) => {
  let editorElement: HTMLDivElement | undefined
  const [isUpdating, setIsUpdating] = createSignal(false)

  // Функция для принудительного обновления подсветки
  const forceHighlight = (element?: Element) => {
    if (!element) return

    // Многократная попытка подсветки для надежности
    const attemptHighlight = (attempts = 0) => {
      if (attempts > 3) return // Максимум 3 попытки

      if (typeof window !== 'undefined' && window.Prism && element) {
        try {
          Prism.highlightElement(element)
        } catch (error) {
          console.warn('Prism highlight failed, retrying...', error)
          setTimeout(() => attemptHighlight(attempts + 1), 50)
        }
      } else {
        setTimeout(() => attemptHighlight(attempts + 1), 50)
      }
    }

    attemptHighlight()
  }

  onMount(() => {
    if (editorElement) {
      // Устанавливаем начальное содержимое сразу
      updateContentWithoutCursor()

      // Принудительно перезапускаем подсветку через короткий таймаут
      setTimeout(() => {
        if (editorElement) {
          const codeElement = editorElement.querySelector('code')
          if (codeElement) {
            forceHighlight(codeElement)
          }
        }
      }, 50)

      // Устанавливаем фокус в конец если есть содержимое
      if (props.value) {
        setTimeout(() => {
          const range = document.createRange()
          const selection = window.getSelection()
          const codeElement = editorElement?.querySelector('code')
          if (codeElement && codeElement.firstChild) {
            range.setStart(codeElement.firstChild, codeElement.firstChild.textContent?.length || 0)
            range.setEnd(codeElement.firstChild, codeElement.firstChild.textContent?.length || 0)
            selection?.removeAllRanges()
            selection?.addRange(range)
          }
        }, 100)
      }
    }
  })

  // Обновляем содержимое при изменении props.value извне
  createEffect(() => {
    const newValue = props.value

    untrack(() => {
      if (editorElement && !isUpdating()) {
        const currentText = getPlainText()

        // Обновляем только если значение действительно изменилось извне
        // и элемент не в фокусе (чтобы не мешать вводу)
        if (newValue !== currentText && document.activeElement !== editorElement) {
          updateContentWithoutCursor()
        }
      }
    })
  })

  const updateContent = () => {
    if (!editorElement || isUpdating()) return

    const value = untrack(() => props.value) || ''

    // Сохраняем позицию курсора более надежно
    const selection = window.getSelection()
    let savedRange: Range | null = null
    let cursorOffset = 0

    if (selection && selection.rangeCount > 0 && document.activeElement === editorElement) {
      const range = selection.getRangeAt(0)
      savedRange = range.cloneRange()

      // Вычисляем общий offset относительно всего текстового содержимого
      const walker = document.createTreeWalker(
        editorElement,
        NodeFilter.SHOW_TEXT,
        null
      )

      let node
      let totalOffset = 0

      while (node = walker.nextNode()) {
        if (node === range.startContainer) {
          cursorOffset = totalOffset + range.startOffset
          break
        }
        totalOffset += node.textContent?.length || 0
      }
    }

    if (value.trim()) {
      // Экранируем HTML для безопасности
      const escapedValue = value
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')

      editorElement.innerHTML = `<code class="language-html">${escapedValue}</code>`

      // Применяем подсветку с дополнительной проверкой
      const codeElement = editorElement.querySelector('code')
      if (codeElement) {
        forceHighlight(codeElement)

        // Восстанавливаем позицию курсора только если элемент в фокусе
        if (cursorOffset > 0 && document.activeElement === editorElement) {
          setTimeout(() => {
            const walker = document.createTreeWalker(
              codeElement,
              NodeFilter.SHOW_TEXT,
              null
            )
            let currentOffset = 0
            let node

            while (node = walker.nextNode()) {
              const nodeLength = node.textContent?.length || 0
              if (currentOffset + nodeLength >= cursorOffset) {
                try {
                  const range = document.createRange()
                  const newSelection = window.getSelection()
                  const targetOffset = Math.min(cursorOffset - currentOffset, nodeLength)

                  range.setStart(node, targetOffset)
                  range.setEnd(node, targetOffset)
                  newSelection?.removeAllRanges()
                  newSelection?.addRange(range)
                } catch (e) {
                  // Игнорируем ошибки позиционирования курсора
                }
                break
              }
              currentOffset += nodeLength
            }
          }, 0)
        }
      }
    } else {
      // Для пустого содержимого просто очищаем
      editorElement.innerHTML = ''
    }
  }

  const updateContentWithoutCursor = () => {
    if (!editorElement) return

    const value = props.value || ''

    if (value.trim()) {
      // Экранируем HTML для безопасности
      const escapedValue = value
        .replace(/&/g, '&amp;')
        .replace(/</g, '&lt;')
        .replace(/>/g, '&gt;')
        .replace(/"/g, '&quot;')
        .replace(/'/g, '&#39;')

      editorElement.innerHTML = `<code class="language-html">${escapedValue}</code>`

      // Применяем подсветку с дополнительной проверкой
      const codeElement = editorElement.querySelector('code')
      if (codeElement) {
        forceHighlight(codeElement)
      }
    } else {
      // Для пустого содержимого просто очищаем
      editorElement.innerHTML = ''
    }
  }

  const getPlainText = (): string => {
    if (!editorElement) return ''

    // Получаем текстовое содержимое с правильной обработкой новых строк
    let text = ''

    const processNode = (node: Node): void => {
      if (node.nodeType === Node.TEXT_NODE) {
        text += node.textContent || ''
      } else if (node.nodeType === Node.ELEMENT_NODE) {
        const element = node as Element

        // Обрабатываем элементы, которые должны создавать новые строки
        if (element.tagName === 'DIV' || element.tagName === 'P') {
          // Добавляем новую строку перед содержимым div/p (кроме первого)
          if (text && !text.endsWith('\n')) {
            text += '\n'
          }

          // Обрабатываем дочерние элементы
          for (const child of Array.from(element.childNodes)) {
            processNode(child)
          }

          // Добавляем новую строку после содержимого div/p
          if (!text.endsWith('\n')) {
            text += '\n'
          }
        } else if (element.tagName === 'BR') {
          text += '\n'
        } else {
          // Для других элементов просто обрабатываем содержимое
          for (const child of Array.from(element.childNodes)) {
            processNode(child)
          }
        }
      }
    }

    try {
      processNode(editorElement)
    } catch (e) {
      // В случае ошибки возвращаем базовый textContent
      return editorElement.textContent || ''
    }

    // Убираем лишние новые строки в конце
    return text.replace(/\n+$/, '')
  }

  const handleInput = () => {
    if (!editorElement || isUpdating()) return

    setIsUpdating(true)

    const text = untrack(() => getPlainText())

    // Обновляем значение через props, используя untrack для избежания циклических обновлений
    untrack(() => props.onInput(text))

    // Отложенное обновление подсветки без влияния на курсор
    setTimeout(() => {
      // Используем untrack для всех операций чтения состояния
      untrack(() => {
        if (document.activeElement === editorElement && !isUpdating()) {
          const currentText = getPlainText()
          if (currentText === text && editorElement) {
            updateContent()
          }
        }
        setIsUpdating(false)
      })
    }, 100)  // Ещё меньше задержка
  }

  const handleKeyDown = (e: KeyboardEvent) => {
    // Поддержка Tab для отступов
    if (e.key === 'Tab') {
      e.preventDefault()
      const selection = window.getSelection()
      const range = selection?.getRangeAt(0)

      if (range) {
        const textNode = document.createTextNode('  ')
        range.insertNode(textNode)
        range.setStartAfter(textNode)
        range.setEndAfter(textNode)
        selection?.removeAllRanges()
        selection?.addRange(range)
      }

      // Обновляем содержимое без задержки для Tab
      untrack(() => {
        const text = getPlainText()
        props.onInput(text)
      })
      return
    }

    // Для Enter не делаем ничего - полностью полагаемся на handleInput
    if (e.key === 'Enter') {
      // Полностью доверяем handleInput обработать изменение
      return
    }
  }

  const handlePaste = (e: ClipboardEvent) => {
    e.preventDefault()
    const text = e.clipboardData?.getData('text/plain') || ''
    document.execCommand('insertText', false, text)

    // Обновляем значение после вставки
    setTimeout(() => {
      untrack(() => {
        const newText = getPlainText()
        props.onInput(newText)
      })
    }, 10)
  }

  return (
    <div
      ref={editorElement}
      class={`${styles.htmlEditorContenteditable} ${props.class || ''}`}
      contenteditable={!props.disabled}
      data-placeholder={props.placeholder}
      onInput={handleInput}
      onKeyDown={handleKeyDown}
      onPaste={handlePaste}
      style={{
        'min-height': `${(props.rows || 6) * 1.6}em`
      }}
    />
  )
}

export default HTMLEditor
