<script setup lang="ts">
import { onMounted, onBeforeUnmount, ref, watch, computed, useSlots } from 'vue'
import { Editor, Extension, Node } from '@tiptap/core'
import Document from '@tiptap/extension-document'
import Paragraph from '@tiptap/extension-paragraph'
import HardBreak from '@tiptap/extension-hard-break'
import Text from '@tiptap/extension-text'
import Bold from '@tiptap/extension-bold'
import Italic from '@tiptap/extension-italic'
import Underline from '@tiptap/extension-underline'
import Strike from '@tiptap/extension-strike'
import Heading from '@tiptap/extension-heading'
import Code from '@tiptap/extension-code'
import CodeBlock from '@tiptap/extension-code-block'
import BulletList from '@tiptap/extension-bullet-list'
import OrderedList from '@tiptap/extension-ordered-list'
import ListItem from '@tiptap/extension-list-item'
import Blockquote from '@tiptap/extension-blockquote'
import Link from '@tiptap/extension-link'
import HorizontalRule from '@tiptap/extension-horizontal-rule'
import Dropcursor from '@tiptap/extension-dropcursor'
import Gapcursor from '@tiptap/extension-gapcursor'
import History from '@tiptap/extension-history'
import Placeholder from '@tiptap/extension-placeholder'
import TextAlign from '@tiptap/extension-text-align'
import Highlight from '@tiptap/extension-highlight'
import Color from '@tiptap/extension-color'
import TextStyle from '@tiptap/extension-text-style'
import TaskList from '@tiptap/extension-task-list'
import TaskItem from '@tiptap/extension-task-item'
import Table from '@tiptap/extension-table'
import TableRow from '@tiptap/extension-table-row'
import TableCell from '@tiptap/extension-table-cell'
import TableHeader from '@tiptap/extension-table-header'
import CharacterCount from '@tiptap/extension-character-count'
import Subscript from '@tiptap/extension-subscript'
import Superscript from '@tiptap/extension-superscript'
import { uploadFileToJingrow } from '@/shared/api/common'
import ImageResizable from '@/shared/extensions/image-resize'

const slots = useSlots()

interface Df {
  fieldname: string
  fieldtype: string
  label?: string
  description?: string
  [key: string]: any
}

const props = defineProps<{
  df: Df
  modelValue?: any
  disabled?: boolean
  record?: Record<string, any>
  canEdit?: boolean
  ctx?: any
}>()

// Label布局：上下结构(vertical) 或 左右结构(horizontal)
const labelLayout = computed(() => props.df.label_layout || 'vertical')

const emit = defineEmits<{
  (e: 'update:modelValue', v: any): void
  (e: 'onSave'): void
}>()

const mountEl = ref<HTMLElement | null>(null)
let editor: Editor | null = null
let sourceShowing = false
let sourceTextarea: HTMLTextAreaElement | null = null

// CustomDiv 扩展：支持 div 标签，避免被转换为 p 标签
// 关键：content 设置为 'block*' 允许包含其他块级元素（如 heading, paragraph, table 等）
const CustomDiv = Node.create({
  name: 'customDiv',
  group: 'block',
  content: 'block*',
  addAttributes() {
    return {
      class: {
        default: null,
        parseHTML: element => element.getAttribute('class'),
        renderHTML: attributes => attributes.class ? { class: attributes.class } : {},
      },
      style: {
        default: null,
        parseHTML: element => element.getAttribute('style'),
        renderHTML: attributes => attributes.style ? { style: attributes.style } : {},
      },
      id: {
        default: null,
        parseHTML: element => element.getAttribute('id'),
        renderHTML: attributes => attributes.id ? { id: attributes.id } : {},
      },
    }
  },
  parseHTML() {
    return [
      {
        tag: 'div',
      },
    ]
  },
  renderHTML({ HTMLAttributes }) {
    return ['div', HTMLAttributes, 0]
  },
})

// FontSize 扩展：对齐 jingrow 实现，基于 TextStyle，提供 setFontSize/unsetFontSize
// 保留实现以对齐 jingrow；如需启用，加入 extensions
const FontSize = Extension.create({
  name: 'fontSize',
  addGlobalAttributes() {
    return [
      {
        types: ['textStyle'],
        attributes: {
          fontSize: {
            default: null,
            parseHTML: element => {
              const size = (element as HTMLElement).style.fontSize
              return size || null
            },
            renderHTML: attributes => {
              if (!attributes.fontSize) return {}
              return { style: `font-size: ${attributes.fontSize}` }
            },
          },
        },
      },
    ]
  },
})

// SVG 图标 - 源自 @iconify-json/tabler，专业设计，像素优化，零外部依赖，无 FOUT
const SVG_ICONS: Record<string, string> = {
  // 标题 - "H" 样式，加粗笔画提升清晰度
  format_paragraph: '<path d="M6 4v16m0-8h12m0-8v16" stroke-width="2.8"/>',
  // 字号 - "Aa" 大小对比，主字加粗增强层次感
  format_size: '<text x="0.5" y="19" font-family="system-ui,-apple-system,sans-serif" font-size="20" font-weight="700" fill="currentColor" stroke="none">A</text><text x="13" y="19" font-family="system-ui,-apple-system,sans-serif" font-size="14" font-weight="600" fill="currentColor" stroke="none">a</text>',
  // 表格 - tabler:table
  table: '<path d="M3 5a2 2 0 0 1 2-2h14a2 2 0 0 1 2 2v14a2 2 0 0 1-2 2H5a2 2 0 0 1-2-2zm0 5h18M10 3v18"/>',
  // 左对齐 - tabler:align-left
  format_align_left: '<path d="M4 6h16M4 12h10M4 18h14"/>',
  // 居中 - tabler:align-center
  format_align_center: '<path d="M4 6h16M8 12h8M6 18h12"/>',
  // 右对齐 - tabler:align-right
  format_align_right: '<path d="M4 6h16m-10 6h10M6 18h14"/>',
  // 加粗 - tabler:bold
  format_bold: '<path d="M7 5h6a3.5 3.5 0 0 1 0 7H7zm6 7h1a3.5 3.5 0 0 1 0 7H7v-7"/>',
  // 斜体 - tabler:italic
  format_italic: '<path d="M11 5h6M7 19h6m1-14l-4 14"/>',
  // 下划线 - tabler:underline
  format_underlined: '<path d="M7 5v5a5 5 0 0 0 10 0V5M5 19h14"/>',
  // 删除线 - tabler:strikethrough
  strikethrough_s: '<path d="M5 12h14m-3-5.5A4 2 0 0 0 12 5h-1a3.5 3.5 0 0 0 0 7h2a3.5 3.5 0 0 1 0 7h-1.5a4 2 0 0 1-4-1.5"/>',
  // 行内代码 - tabler:code
  code: '<path d="m7 8l-4 4l4 4m10-8l4 4l-4 4M14 4l-4 16"/>',
  // 代码块 - tabler:source-code
  data_object: '<path d="M14.5 4H17a3 3 0 0 1 3 3v10a3 3 0 0 1-3 3H7a3 3 0 0 1-3-3v-5m2-7L4 7l2 2"/><path d="m10 9l2-2l-2-2"/>',
  // 引用 - tabler:quote
  format_quote: '<path d="M10 11H6a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v6q0 4-4 5m13-7h-4a1 1 0 0 1-1-1V7a1 1 0 0 1 1-1h3a1 1 0 0 1 1 1v6q0 4-4 5"/>',
  // 下标 - tabler:subscript
  subscript: '<path d="m5 7l8 10m-8 0l8-10m8 13h-4l3.5-4a1.73 1.73 0 0 0-3.5-2"/>',
  // 上标 - tabler:superscript
  superscript: '<path d="m5 7l8 10m-8 0l8-10m8 4h-4l3.5-4A1.73 1.73 0 0 0 17 5"/>',
  // 无序列表 - tabler:list
  format_list_bulleted: '<path d="M9 6h11M9 12h11M9 18h11M5 6v.01M5 12v.01M5 18v.01"/>',
  // 有序列表 - "1 2 3" + 横线，text 元素确保数字清晰完整
  format_list_numbered: '<text x="7" y="6" text-anchor="end" dy="0.35em" font-family="system-ui,-apple-system,sans-serif" font-size="8" font-weight="800" fill="currentColor" stroke="none">1</text><path d="M9 6h12" stroke-linecap="round"/><text x="7" y="12" text-anchor="end" dy="0.35em" font-family="system-ui,-apple-system,sans-serif" font-size="8" font-weight="800" fill="currentColor" stroke="none">2</text><path d="M9 12h12" stroke-linecap="round"/><text x="7" y="18" text-anchor="end" dy="0.35em" font-family="system-ui,-apple-system,sans-serif" font-size="8" font-weight="800" fill="currentColor" stroke="none">3</text><path d="M9 18h12" stroke-linecap="round"/>',
  // 任务列表 - tabler:list-check
  checklist: '<path d="M3.5 5.5L5 7l2.5-2.5m-4 7L5 13l2.5-2.5m-4 7L5 19l2.5-2.5M11 6h9m-9 6h9m-9 6h9"/>',
  // 链接 - tabler:link
  link: '<path d="m9 15l6-6m-4-3l.463-.536a5 5 0 0 1 7.071 7.072L18 13m-5 5l-.397.534a5.07 5.07 0 0 1-7.127 0a4.97 4.97 0 0 1 0-7.071L6 11"/>',
  // 图片 - tabler:photo
  image: '<path d="M15 8h.01M3 6a3 3 0 0 1 3-3h12a3 3 0 0 1 3 3v12a3 3 0 0 1-3 3H6a3 3 0 0 1-3-3z"/><path d="m3 16l5-5c.928-.893 2.072-.893 3 0l5 5"/><path d="m14 14l1-1c.928-.893 2.072-.893 3 0l3 3"/>',
  // 分割线 - tabler:minus
  horizontal_rule: '<path d="M5 12h14"/>',
  // 文字颜色 - A + 下方色带，Notion 风格统一设计语言
  format_color_text: '<text x="12" y="9.5" text-anchor="middle" dy="0.35em" font-family="system-ui,-apple-system,sans-serif" font-size="15" font-weight="700" fill="currentColor" stroke="none">A</text><rect x="4.5" y="17.5" width="15" height="3" rx="1.5" fill="currentColor" stroke="none"/>',
  // 背景色 - A + 中部高亮条，色带覆盖在文字之上表达"背景"语义
  format_color_fill: '<rect x="3" y="6" width="18" height="8" rx="2" fill="currentColor" opacity="0.25" stroke="none"/><text x="12" y="9.5" text-anchor="middle" dy="0.35em" font-family="system-ui,-apple-system,sans-serif" font-size="15" font-weight="700" fill="currentColor" stroke="none">A</text>',
  // 撤销 - tabler:arrow-back-up
  undo: '<path d="m9 14l-4-4l4-4"/><path d="M5 10h11a4 4 0 1 1 0 8h-1"/>',
  // 重做 - tabler:arrow-forward-up
  redo: '<path d="m15 14l4-4l-4-4"/><path d="M19 10H8a4 4 0 1 0 0 8h1"/>',
  // 源代码 - tabler:file-code
  code_blocks: '<path d="M14 3v4a1 1 0 0 0 1 1h4"/><path d="M17 21H7a2 2 0 0 1-2-2V5a2 2 0 0 1 2-2h7l5 5v11a2 2 0 0 1-2 2"/><path d="m10 13l-1 2l1 2m4-4l1 2l-1 2"/>',
  // 上传 - tabler:cloud-upload
  cloud_upload: '<path d="M7 18a4.6 4.4 0 0 1 0-9a5 4.5 0 0 1 11 2h1a3.5 3.5 0 0 1 0 7h-1"/><path d="m9 15l3-3l3 3m-3-3v9"/>',
  // 加载中 - tabler:loader
  hourglass_empty: '<path d="M12 6V3m4.25 4.75L18.4 5.6M18 12h3m-4.75 4.25l2.15 2.15M12 18v3m-4.25-4.75L5.6 18.4M6 12H3m4.75-4.25L5.6 5.6"/>',
}

function icon(name: string) {
  const span = document.createElement('span')
  span.className = 'jeditor-icon'
  span.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">${SVG_ICONS[name] || ''}</svg>`
  return span
}

function divider() {
  const el = document.createElement('span')
  el.className = 'tiptap-toolbar-divider'
  return el
}

function button(title: string, iconName: string, onClick: () => void) {
  const btn = document.createElement('button')
  btn.type = 'button'
  btn.className = 'tiptap-toolbar-btn'
  btn.title = title
  btn.appendChild(icon(iconName))
  btn.addEventListener('mousedown', (e) => { e.preventDefault(); onClick() })
  return btn
}

function dropdown(title: string, iconName: string, items: Array<{ label: string; onClick: () => void }>) {
  const wrap = document.createElement('div')
  wrap.className = 'tiptap-toolbar-dropdown'
  const btn = document.createElement('button')
  btn.type = 'button'
  btn.className = 'tiptap-toolbar-btn'
  btn.title = title
  btn.appendChild(icon(iconName))
  const caret = document.createElement('span')
  caret.className = 'tiptap-toolbar-caret'
  caret.textContent = ''
  wrap.appendChild(btn)
  const menu = document.createElement('div')
  menu.className = 'tiptap-toolbar-dropdown-menu'
  items.forEach(it => {
    const item = document.createElement('div')
    item.className = 'tiptap-toolbar-dropdown-item'
    item.textContent = it.label
    item.onmousedown = (e) => { e.preventDefault(); it.onClick(); menu.style.display = 'none' }
    menu.appendChild(item)
  })
  btn.onmousedown = (e) => { e.preventDefault(); menu.style.display = menu.style.display === 'block' ? 'none' : 'block' }
  document.addEventListener('mousedown', (e) => { if (!wrap.contains(e.target as globalThis.Node)) menu.style.display = 'none' })
  btn.appendChild(caret)
  wrap.appendChild(menu)
  return wrap
}

// 面板关闭
function closeAllPanels() {
  document.querySelectorAll('.tiptap-link-panel, .tiptap-image-panel, .tiptap-color-panel, .tiptap-highlight-panel').forEach(el => el.remove())
}

function showLinkPanel(toolbar: HTMLElement, e: MouseEvent, ed: Editor) {
  closeAllPanels()
  const panel = document.createElement('div')
  panel.className = 'tiptap-link-panel'
  panel.style.position = 'absolute'
  panel.style.zIndex = '20'
  panel.style.background = '#fff'
  panel.style.border = '1px solid #eee'
  panel.style.borderRadius = '8px'
  panel.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'
  panel.style.padding = '8px'
  panel.style.display = 'flex'
  panel.style.gap = '6px'
  const input = document.createElement('input')
  input.type = 'text'
  input.placeholder = 'https://...'
  input.style.width = '240px'
  const apply = document.createElement('button')
  apply.textContent = '应用'
  apply.className = 'tiptap-toolbar-btn'
  apply.onmousedown = (ev) => { ev.preventDefault(); const url = input.value.trim(); if (url) ed.chain().focus().setLink({ href: url }).run(); panel.remove() }
  panel.appendChild(input)
  panel.appendChild(apply)
  const btnEl = e.currentTarget as HTMLElement
  const btnRect = btnEl.getBoundingClientRect()
  const barRect = toolbar.getBoundingClientRect()
  panel.style.left = `${btnRect.left - barRect.left}px`
  panel.style.top = `${btnRect.bottom - barRect.top + 4}px`
  toolbar.appendChild(panel)
}

function showImagePanel(toolbar: HTMLElement, e: MouseEvent, ed: Editor) {
  closeAllPanels()
  const panel = document.createElement('div')
  panel.className = 'tiptap-image-panel'
  panel.style.position = 'absolute'
  panel.style.zIndex = '20'
  panel.style.background = '#fff'
  panel.style.border = '1px solid #eee'
  panel.style.borderRadius = '8px'
  panel.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'
  panel.style.padding = '8px'
  panel.style.display = 'flex'
  panel.style.gap = '6px'
  panel.style.alignItems = 'center'
  
  // 上传文件按钮
  const uploadBtn = document.createElement('button')
  uploadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">${SVG_ICONS['cloud_upload']}</svg>`
  uploadBtn.className = 'tiptap-toolbar-btn'
  uploadBtn.title = '上传图片'
  uploadBtn.style.padding = '6px'
  
  // 隐藏的文件输入
  const fileInput = document.createElement('input')
  fileInput.type = 'file'
  fileInput.accept = 'image/*'
  fileInput.style.display = 'none'
  
  uploadBtn.onmousedown = (ev) => {
    ev.preventDefault()
    fileInput.click()
  }
  
  fileInput.onchange = async (ev) => {
    const file = (ev.target as HTMLInputElement).files?.[0]
    if (!file) return
    
    try {
      uploadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">${SVG_ICONS['hourglass_empty']}</svg>`
      uploadBtn.disabled = true
      
      const result = await uploadFileToJingrow(file)
      
      if (result.success && result.file_url) {
        ed.chain().focus().insertContent({ type: 'imageResizable', attrs: { src: result.file_url } }).run()
        panel.remove()
        
        // 图片上传成功后自动保存文档
        emit('update:modelValue', ed.getHTML())
        // 触发保存整个记录
        emit('onSave')
      } else {
        alert(result.error || '上传失败')
        uploadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">${SVG_ICONS['cloud_upload']}</svg>`
        uploadBtn.disabled = false
      }
    } catch (error) {
      console.error('Upload error:', error)
      alert('上传失败')
      uploadBtn.innerHTML = `<svg xmlns="http://www.w3.org/2000/svg" width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" shape-rendering="geometricPrecision">${SVG_ICONS['cloud_upload']}</svg>`
      uploadBtn.disabled = false
    }
  }
  
  // URL输入框
  const input = document.createElement('input')
  input.type = 'text'
  input.placeholder = '图片地址 https://...'
  input.style.width = '200px'
  input.style.flex = '1'
  
  // 插入按钮
  const apply = document.createElement('button')
  apply.textContent = '插入'
  apply.className = 'tiptap-toolbar-btn'
  apply.onmousedown = (ev) => { 
    ev.preventDefault()
    const url = input.value.trim()
    if (url) {
      ed.chain().focus().insertContent({ type: 'imageResizable', attrs: { src: url } }).run()
      panel.remove()
      
      // 图片插入成功后自动保存文档
      emit('update:modelValue', ed.getHTML())
      // 触发保存整个记录
      emit('onSave')
    }
  }
  
  panel.appendChild(uploadBtn)
  panel.appendChild(fileInput)
  panel.appendChild(input)
  panel.appendChild(apply)
  
  const btnEl = e.currentTarget as HTMLElement
  const btnRect = btnEl.getBoundingClientRect()
  const barRect = toolbar.getBoundingClientRect()
  panel.style.left = `${btnRect.left - barRect.left}px`
  panel.style.top = `${btnRect.bottom - barRect.top + 4}px`
  toolbar.appendChild(panel)
}

function showColorPanel(toolbar: HTMLElement, e: MouseEvent, ed: Editor) {
  closeAllPanels()
  const panel = document.createElement('div')
  panel.className = 'tiptap-color-panel'
  panel.style.position = 'absolute'
  panel.style.zIndex = '20'
  panel.style.background = '#fff'
  panel.style.border = '1px solid #eee'
  panel.style.borderRadius = '8px'
  panel.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'
  panel.style.padding = '8px'
  const colors = ['#000000','#262626','#595959','#8c8c8c','#bfbfbf','#d9d9d9','#f0f0f0','#ffffff','#f5222d','#fa8c16','#fadb14','#52c41a','#13c2c2','#1890ff','#722ed1','#eb2f96']
  const row = document.createElement('div')
  row.style.display = 'flex'; row.style.flexWrap = 'wrap'; row.style.gap = '4px'
  colors.forEach(c => { const s = document.createElement('div'); s.style.width='20px'; s.style.height='20px'; s.style.border='1px solid #eee'; s.style.borderRadius='4px'; s.style.background=c; s.onmousedown=(ev)=>{ev.preventDefault(); ed.chain().focus().setColor(c).run(); panel.remove()}; row.appendChild(s) })
  panel.appendChild(row)
  const btnEl = e.currentTarget as HTMLElement
  const btnRect = btnEl.getBoundingClientRect()
  const barRect = toolbar.getBoundingClientRect()
  panel.style.left = `${btnRect.left - barRect.left}px`
  panel.style.top = `${btnRect.bottom - barRect.top + 4}px`
  toolbar.appendChild(panel)
}

function showHighlightPanel(toolbar: HTMLElement, e: MouseEvent, ed: Editor) {
  closeAllPanels()
  const panel = document.createElement('div')
  panel.className = 'tiptap-highlight-panel'
  panel.style.position = 'absolute'
  panel.style.zIndex = '20'
  panel.style.background = '#fff'
  panel.style.border = '1px solid #eee'
  panel.style.borderRadius = '8px'
  panel.style.boxShadow = '0 2px 8px rgba(0,0,0,0.08)'
  panel.style.padding = '8px'
  const colors = ['#fffbe6','#ffe7ba','#ffd591','#ffc069','#ffbb96','#ff9c6e','#ff7875','#eaff8f','#b7eb8f','#87e8de','#91d5ff']
  const row = document.createElement('div')
  row.style.display = 'flex'; row.style.flexWrap = 'wrap'; row.style.gap = '4px'
  colors.forEach(c => { const s = document.createElement('div'); s.style.width='20px'; s.style.height='20px'; s.style.border='1px solid #eee'; s.style.borderRadius='4px'; s.style.background=c; s.onmousedown=(ev)=>{ev.preventDefault(); ed.chain().focus().setHighlight({ color: c }).run(); panel.remove()}; row.appendChild(s) })
  panel.appendChild(row)
  const btnEl = e.currentTarget as HTMLElement
  const btnRect = btnEl.getBoundingClientRect()
  const barRect = toolbar.getBoundingClientRect()
  panel.style.left = `${btnRect.left - barRect.left}px`
  panel.style.top = `${btnRect.bottom - barRect.top + 4}px`
  toolbar.appendChild(panel)
}

let panelHandlersBound = false
function ensurePanelGlobalHandlers(_toolbar: HTMLElement) {
  if (panelHandlersBound) return
  panelHandlersBound = true
  document.addEventListener('mousedown', (ev) => {
    const target = ev.target as HTMLElement
    const isInPanel = !!target.closest('.tiptap-link-panel, .tiptap-image-panel, .tiptap-color-panel, .tiptap-highlight-panel')
    const isToolbarBtn = !!target.closest('.tiptap-toolbar-btn')
    if (!isInPanel && !isToolbarBtn) closeAllPanels()
  })
  document.addEventListener('keydown', (ev) => {
    if (ev.key === 'Escape') closeAllPanels()
  })
}

function togglePanel(toolbar: HTMLElement, e: MouseEvent, ed: Editor, type: 'link' | 'image' | 'color' | 'highlight') {
  const existing = document.querySelector(`.tiptap-${type}-panel`)
  if (existing) {
    existing.remove()
    return
  }
  switch (type) {
    case 'link':
      showLinkPanel(toolbar, e, ed)
      break
    case 'image':
      showImagePanel(toolbar, e, ed)
      break
    case 'color':
      showColorPanel(toolbar, e, ed)
      break
    case 'highlight':
      showHighlightPanel(toolbar, e, ed)
      break
  }
}

function buildToolbar(container: HTMLElement, ed: Editor) {
  const bar = document.createElement('div')
  bar.className = 'tiptap-toolbar'

  // 标题下拉：H1-H6 与 正文
  bar.appendChild(dropdown('标题', 'format_paragraph', [
    { label: '标题1', onClick: () => ed.chain().focus().toggleHeading({ level: 1 }).run() },
    { label: '标题2', onClick: () => ed.chain().focus().toggleHeading({ level: 2 }).run() },
    { label: '标题3', onClick: () => ed.chain().focus().toggleHeading({ level: 3 }).run() },
    { label: '标题4', onClick: () => ed.chain().focus().toggleHeading({ level: 4 }).run() },
    { label: '标题5', onClick: () => ed.chain().focus().toggleHeading({ level: 5 }).run() },
    { label: '标题6', onClick: () => ed.chain().focus().toggleHeading({ level: 6 }).run() },
    { label: '正文', onClick: () => ed.chain().focus().setParagraph().run() },
  ]))

  // 字号下拉（与 jingrow 对齐：TextStyle + setMark/unsetMark）
  bar.appendChild(dropdown('字号', 'format_size', [
    ...[8,10,12,14,16,18,20,24,28,32,36,48,64].map(sz => ({
      label: `${sz}px`,
      onClick: () => ed.chain().focus().setMark('textStyle', { fontSize: `${sz}px` }).run(),
    })),
    { label: '重置', onClick: () => ed.chain().focus().unsetMark('textStyle', { extendEmptyMarkRange: true }).run() }
  ]))
  // 分隔符
  bar.appendChild(divider())

  // 表格 & 对齐（顺序与 jingrow 对齐）
  bar.appendChild(dropdown('表格', 'table', [
    { label: '插入表格', onClick: () => ed.chain().focus().insertTable({ rows: 3, cols: 3, withHeaderRow: true }).run() },
    { label: '删除表格', onClick: () => ed.chain().focus().deleteTable().run() },
    { label: '插入行(下)', onClick: () => ed.chain().focus().addRowAfter().run() },
    { label: '插入列(右)', onClick: () => ed.chain().focus().addColumnAfter().run() },
    { label: '删除行', onClick: () => ed.chain().focus().deleteRow().run() },
    { label: '删除列', onClick: () => ed.chain().focus().deleteColumn().run() },
  ]))

  bar.appendChild(dropdown('对齐', 'format_align_left', [
    { label: '左对齐', onClick: () => ed.chain().focus().setTextAlign('left').run() },
    { label: '居中', onClick: () => ed.chain().focus().setTextAlign('center').run() },
    { label: '右对齐', onClick: () => ed.chain().focus().setTextAlign('right').run() },
  ]))

  bar.appendChild(divider())

  // 基础样式（含行内代码）
  bar.appendChild(button('加粗', 'format_bold', () => ed.chain().focus().toggleBold().run()))
  bar.appendChild(button('斜体', 'format_italic', () => ed.chain().focus().toggleItalic().run()))
  bar.appendChild(button('下划线', 'format_underlined', () => ed.chain().focus().toggleUnderline().run()))
  bar.appendChild(button('删除线', 'strikethrough_s', () => ed.chain().focus().toggleStrike().run()))
  bar.appendChild(button('行内代码', 'code', () => ed.chain().focus().toggleCode().run()))

  bar.appendChild(divider())

  // 高级格式：代码块/引用/上下标
  bar.appendChild(button('代码块', 'data_object', () => ed.chain().focus().toggleCodeBlock().run()))
  bar.appendChild(button('引用', 'format_quote', () => ed.chain().focus().toggleBlockquote().run()))
  bar.appendChild(button('下标', 'subscript', () => ed.chain().focus().toggleSubscript().run()))
  bar.appendChild(button('上标', 'superscript', () => ed.chain().focus().toggleSuperscript().run()))

  bar.appendChild(divider())

  // 列表
  bar.appendChild(button('无序列表', 'format_list_bulleted', () => ed.chain().focus().toggleBulletList().run()))
  bar.appendChild(button('有序列表', 'format_list_numbered', () => ed.chain().focus().toggleOrderedList().run()))
  bar.appendChild(button('任务列表', 'checklist', () => ed.chain().focus().toggleTaskList().run()))

  bar.appendChild(divider())

  // 链接/图片/分割线
  bar.appendChild(button('链接', 'link', () => {}))
  bar.lastElementChild?.addEventListener('mousedown', (e) => { e.preventDefault(); togglePanel(bar, e as MouseEvent, ed, 'link') })
  bar.appendChild(button('图片', 'image', () => {}))
  bar.lastElementChild?.addEventListener('mousedown', (e) => { e.preventDefault(); togglePanel(bar, e as MouseEvent, ed, 'image') })
  bar.appendChild(button('分割线', 'horizontal_rule', () => ed.chain().focus().setHorizontalRule().run()))

  bar.appendChild(divider())

  // 颜色
  bar.appendChild(button('文字颜色', 'format_color_text', () => {}))
  bar.lastElementChild?.addEventListener('mousedown', (e) => { e.preventDefault(); togglePanel(bar, e as MouseEvent, ed, 'color') })
  bar.appendChild(button('背景色', 'format_color_fill', () => {}))
  bar.lastElementChild?.addEventListener('mousedown', (e) => { e.preventDefault(); togglePanel(bar, e as MouseEvent, ed, 'highlight') })

  bar.appendChild(divider())

  bar.appendChild(button('撤销', 'undo', () => ed.chain().focus().undo().run()))
  bar.appendChild(button('重做', 'redo', () => ed.chain().focus().redo().run()))

  bar.appendChild(divider())

  // 源码模式
  const srcBtn = button('显示源代码', 'code_blocks', () => {
    toggleSourceMode()
  })
  bar.appendChild(srcBtn)

  container.appendChild(bar)
}

function toggleSourceMode() {
  if (!editor) return
  if (!sourceShowing) {
    // 进入源码模式：直接在编辑区域显示文本框
    const html = editor.getHTML()
    const host = (mountEl.value as HTMLElement).querySelector('.jeditor') as HTMLElement | null
    if (host) {
      // 获取编辑器容器的实际高度
      const editorHeight = host.offsetHeight
      
      const ta = document.createElement('textarea')
      ta.className = 'jeditor-source-textarea'
      ta.style.width = '100%'
      ta.style.height = `${editorHeight}px`  // 使用编辑器实际高度
      ta.style.fontFamily = '"SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace'
      ta.style.fontSize = '13px'
      ta.style.lineHeight = '1.6'
      ta.style.padding = '16px'
      ta.style.border = 'none'
      ta.style.borderRadius = '8px'
      ta.style.backgroundColor = '#fafbfc'
      ta.style.color = '#24292e'
      ta.style.whiteSpace = 'pre-wrap'
      ta.style.wordWrap = 'break-word'
      ta.style.resize = 'none'  // 禁用拖拽调整大小
      ta.style.outline = 'none'
      ta.style.boxShadow = 'inset 0 1px 3px rgba(0,0,0,0.1)'
      ta.style.transition = 'all 0.2s ease'
      ta.style.boxSizing = 'border-box'  // 确保 padding 包含在高度内
      // 异步格式化HTML
      formatHTML(html).then(formatted => {
        ta.value = formatted
      })
      
      host.style.display = 'none'
      // 插在编辑器后，位置与编辑区域一致
      host.insertAdjacentElement('afterend', ta)
      sourceTextarea = ta
      sourceShowing = true
    }
  } else {
    // 退出源码模式：移除文本框，恢复可视化编辑
    const html = sourceTextarea?.value || ''
    editor!.commands.setContent(html)
    const host = (mountEl.value as HTMLElement).querySelector('.jeditor') as HTMLElement | null
    if (sourceTextarea) {
      sourceTextarea.remove()
      sourceTextarea = null
    }
    if (host) host.style.display = ''
    sourceShowing = false
  }
}

// 使用Prettier格式化HTML，提高可读性（动态导入 + await）
async function formatHTML(html: string): Promise<string> {
  if (!html) return ''
  
  try {
    const prettier = await import('prettier/standalone')
    const parserHtml = await import('prettier/parser-html')
    
    const result = await prettier.format(html, {
      parser: 'html',
      plugins: [parserHtml],
      printWidth: 120,
      tabWidth: 2,
      useTabs: false,
      htmlWhitespaceSensitivity: 'ignore',
      bracketSameLine: false,
      singleAttributePerLine: false
    })
    
    return result
  } catch (error) {
    console.warn('Prettier formatting failed, using simple formatting:', error)
    // 回退到简单格式化
    return formatHTMLSimple(html)
  }
}

// 简单但有效的HTML格式化函数（回退方案）
function formatHTMLSimple(html: string): string {
  if (!html) return ''
  
  // 在标签之间添加换行
  let formatted = html.replace(/></g, '>\n<')
  
  // 分割成行并处理缩进
  const lines = formatted.split('\n')
  let indentLevel = 0
  const indentSize = 2
  
  const result = lines.map(line => {
    const trimmed = line.trim()
    if (!trimmed) return ''
    
    // 闭合标签减少缩进
    if (trimmed.match(/^<\/[^>]+>$/)) {
      indentLevel = Math.max(0, indentLevel - 1)
    }
    
    // 添加缩进
    const indented = ' '.repeat(indentLevel * indentSize) + trimmed
    
    // 开放标签增加缩进（排除自闭合标签）
    if (trimmed.match(/^<[^/][^>]*[^/]>$/) && !trimmed.match(/^<[^>]*\/>$/)) {
      indentLevel++
    }
    
    return indented
  })
  
  return result.join('\n')
}

function mountControl() {
  if (!mountEl.value) return
  mountEl.value.innerHTML = ''

  const toolbarHost = document.createElement('div')
  const editorHost = document.createElement('div')
  editorHost.className = 'jeditor'
  mountEl.value.appendChild(toolbarHost)
  mountEl.value.appendChild(editorHost)

  // Decode HTML entities / JSON escapes from modelValue
  let decodedContent = props.modelValue || ''
  if (decodedContent.includes('\\u') || decodedContent.includes('&lt;') || decodedContent.includes('&gt;') || decodedContent.includes('&amp;')) {
    try {
      const ta = document.createElement('textarea')
      ta.innerHTML = decodedContent
      decodedContent = ta.value
      decodedContent = decodedContent.replace(/\\u([0-9a-fA-F]{4})/g, (_m: string, hex: string) => String.fromCharCode(parseInt(hex, 16)))
    } catch { /* keep original */ }
  }

  editor = new Editor({
    element: editorHost,
    content: decodedContent,
    editable: !props.disabled && !slots.label,
    extensions: [
      Document.extend({
        content: '(paragraph | heading | blockquote | codeBlock | horizontalRule | customDiv | table | bulletList | orderedList | taskList)+',
      }),
      CustomDiv,
      HardBreak,
      Paragraph,
      Text,
      Bold,
      Italic,
      Underline,
      Strike,
      Heading,
      Code,
      CodeBlock,
      BulletList,
      OrderedList,
      ListItem,
      Blockquote,
      Link.configure({ openOnClick: true }),
      ImageResizable,
      HorizontalRule,
      Dropcursor,
      Gapcursor,
      History,
      Placeholder.configure({ placeholder: props.df?.label || props.df?.fieldname || '请输入内容...' }),
      TextAlign.configure({ types: ['heading', 'paragraph', 'imageResizable'] }),
      Color,
      TextStyle,
      FontSize,
      Highlight.configure({ multicolor: true }),
      TaskList,
      TaskItem,
      Table.configure({ resizable: true }),
      TableRow,
      TableHeader,
      TableCell,
      CharacterCount,
      Subscript,
      Superscript,
    ],
    onUpdate: ({ editor }) => {
      emit('update:modelValue', editor.getHTML())
    },
  })

  buildToolbar(toolbarHost, editor)
  ensurePanelGlobalHandlers(toolbarHost)
}

function unmountControl() {
  try {
    if (editor) editor.destroy()
  } catch {}
  editor = null
}

onMounted(() => {
  mountControl()
})

onBeforeUnmount(() => {
  unmountControl()
})

watch(() => props.modelValue, (val) => {
  if (!editor) return
  // Decode HTML entities / JSON escapes so content renders correctly
  let html = val || ''
  if (html.includes('\\u') || html.includes('&lt;') || html.includes('&gt;') || html.includes('&amp;')) {
    try {
      const ta = document.createElement('textarea')
      ta.innerHTML = html
      html = ta.value
      html = html.replace(/\\u([0-9a-fA-F]{4})/g, (_m: string, hex: string) => String.fromCharCode(parseInt(hex, 16)))
    } catch { /* keep original */ }
  }
  if (html !== editor.getHTML()) {
    editor.commands.setContent(html, false)
  }
})

watch(() => props.disabled, (disabled) => {
  if (editor) editor.setEditable(!disabled && !slots.label)
})
</script>

<template>
  <div :class="['field-wrapper', `layout-${labelLayout}`, { 'form-builder-control': slots.label }]">
    <!-- FormBuilder 模式: 使用 slot 渲染 label -->
    <div v-if="slots.label" class="field-controls">
      <slot name="label" />
      <slot name="actions" />
    </div>
    <!-- default_single.vue 模式: 自己渲染 label -->
    <label v-else class="field-label">
      {{ ctx?.t ? ctx.t(df.label || df.fieldname) : (df.label || df.fieldname) }}
      <span v-if="df.reqd" class="required">*</span>
    </label>
    <div class="jeditor-control">
      <div ref="mountEl"></div>
    </div>
  </div>
</template>

<style>
.jeditor-control :deep(.tiptap-toolbar) {
  margin-bottom: 8px;
}
.jeditor-control :deep(.jeditor) {
  min-height: 300px;
}
.jeditor-control,
.jeditor-control > div { width: 100%; }

.required {
  color: #ef4444;
  font-weight: bold;
}
.tiptap-toolbar {
    display: flex;
    flex-wrap: wrap;
    background: #fff;
    border-bottom: 1px solid #eee;
    padding: 6px 8px;
    margin-bottom: 4px;
    border-radius: 4px 4px 0 0;
    box-shadow: 0 1px 2px rgba(0, 0, 0, 0.03);
    align-items: center;
    gap: 2px;
    position: relative;
}
.tiptap-toolbar-btn {
  border: none;
  background: none;
  border-radius: 6px;
  padding: 0 6px;
  height: 32px;
  min-width: 32px;
  display: inline-flex;
  align-items: center;
  justify-content: center;
  cursor: pointer;
  color: #444;
  transition: all .15s cubic-bezier(0.2,0,0.2,1);
}
.tiptap-toolbar-btn:hover { background: #f0f2f5; color: #1fc76f; transform: translateY(-1px); box-shadow: 0 2px 4px rgba(0,0,0,0.08); }
.tiptap-toolbar-btn.active { background: rgba(31,199,111,0.1); color: #1fc76f; box-shadow: none; }
.tiptap-toolbar-divider {
  display: inline-block;
  width: 1px;
  height: 20px;
  background: linear-gradient(to bottom, transparent, #e5e6eb, transparent);
  margin: 0 6px;
  align-self: center;
}
.tiptap-toolbar-dropdown {
  position: relative;
  display: inline-block;
}
.tiptap-toolbar-dropdown-menu {
  display: none;
  position: absolute;
  top: 100%;
  left: 0;
  min-width: 160px;
  background: #fff;
  border: 1px solid #eee;
  border-radius: 6px;
  box-shadow: 0 4px 12px rgba(0,0,0,0.08);
  z-index: 20;
  padding: 6px 0;
}
.tiptap-toolbar-dropdown-item {
  padding: 6px 12px;
  cursor: pointer;
  white-space: nowrap;
}
.tiptap-toolbar-dropdown-item:hover {
  background: #f5f7fa;
}
.jeditor {
  border: 1px solid #e5e7eb;
  border-radius: 8px;
  padding: 12px;
  min-height: 300px;
  width: 100%;
  box-sizing: border-box;
  background: #fff;
  transition: all 0.2s ease;
  position: relative;
}
.jeditor:hover {
  border: 1.5px solid #1fc76f;
  box-shadow: 0 0 0 2px #1fc76f22;
}
.jeditor:focus, .jeditor:focus-within, .jeditor[contenteditable="true"]:focus {
    border: 1.5px solid #1fc76f;
    box-shadow: 0 0 0 2px #1fc76f22;
    outline: none;
}
.jeditor[contenteditable="true"],
.jeditor *:focus,
.jeditor *:focus-visible,
.jeditor *:active {
  outline: none;
  border: none;
  box-shadow: none;
}
/* 确保 ProseMirror 编辑器填满整个容器，可点击激活 */
.jeditor .ProseMirror {
  min-height: 276px; /* 300px - 12px padding * 2 */
  width: 100%;
  cursor: text;
  outline: none;
}
.tiptap-toolbar-caret { width: 0; height: 0; border-left: 4px solid transparent; border-right: 4px solid transparent; border-top: 6px solid currentColor; display: inline-block; margin-left: 4px; opacity: .8; }
.tiptap-toolbar .jeditor-icon { display: inline-flex; align-items: center; justify-content: center; width: 20px; height: 20px; }
.tiptap-toolbar .jeditor-icon svg { display: block; }
.jeditor-source-textarea { 
  width: 100% !important; 
  box-sizing: border-box; 
  display: block; 
  background: #fafbfc !important;
  color: #24292e !important;
  font-family: "SF Mono", "Monaco", "Inconsolata", "Roboto Mono", "Source Code Pro", monospace !important;
  font-size: 13px !important;
  line-height: 1.6 !important;
  padding: 16px !important;
  border: none !important;
  border-radius: 8px !important;
  outline: none !important;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.1) !important;
  transition: all 0.2s ease !important;
  resize: vertical !important;
}
.jeditor-source-textarea:focus {
  background: #ffffff !important;
  box-shadow: inset 0 1px 3px rgba(0,0,0,0.1), 0 0 0 3px rgba(59, 130, 246, 0.1) !important;
}
.jeditor pre {
    background: #f6f8fa;
    border-radius: 8px;
    padding: 14px 16px;
    font-family: 'JetBrains Mono', 'Fira Mono', 'Menlo', 'Consolas', 'Liberation Mono', monospace;
    font-size: 15px;
    line-height: 1.7;
    color: #222;
    overflow-x: auto;
    margin: 8px 0;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.04);
    border: 1px solid #e5e6eb;
    position: relative;
}
.jeditor pre code {
    background: none;
    padding: 0;
    border: none;
    font-family: inherit;
    font-size: inherit;
    color: inherit;
}
.jeditor code {
    background: #f6f8fa;
    color: #d6336c;
    font-family: 'JetBrains Mono', 'Fira Mono', 'Menlo', 'Consolas', 'Liberation Mono', monospace;
    font-size: 0.97em;
    border-radius: 4px;
    padding: 2px 6px;
    margin: 0 2px;
    border: none;
    word-break: break-all;
}
.jeditor blockquote {
    border-left: 4px solid #1fc76f;
    background: #f8fefb;
    margin: 8px 0;
    padding: 10px 18px;
    color: #444;
    font-size: 1.05em;
    border-radius: 4px;
    position: relative;
}
.jeditor img {
    max-width: 100%;
    height: auto;
    display: block;
    margin: 8px 0;
    border-radius: 4px;
    box-shadow: 0 1px 4px rgba(0, 0, 0, 0.1);
}

/* 图片选中效果样式 */
[data-type="imageResizable"] { 
    box-shadow: 0 0 0 1px #1fc76f33; 
    transition: box-shadow 0.2s; 
    position: relative;
}
[data-type="imageResizable"]:hover,
[data-type="imageResizable"].ProseMirror-selectednode { 
    box-shadow: 0 0 0 2px #1fc76f; 
}
[data-type="imageResizable"]:hover .image-drag-handle,
[data-type="imageResizable"].ProseMirror-selectednode .image-drag-handle {
    background: rgba(31, 199, 111, 0.1);
    border: 1px dashed #1fc76f;
}
[data-type="imageResizable"]:hover .image-resize-handle,
[data-type="imageResizable"].ProseMirror-selectednode .image-resize-handle {
    transform: scale(1.1);
    box-shadow: 0 0 4px #1fc76f;
}
/* 缩放手柄默认隐藏，只在悬浮时显示 */
.image-resize-handle {
    opacity: 0;
    transition: opacity 0.2s ease, transform 0.2s ease;
}
[data-type="imageResizable"]:hover .image-resize-handle,
[data-type="imageResizable"].ProseMirror-selectednode .image-resize-handle {
    opacity: 1;
}
.image-drag-handle {
    transition: all 0.2s ease;
    background: transparent;
    border: 1px dashed transparent;
}
.image-drag-handle:hover {
    background: rgba(31, 199, 111, 0.15) !important;
    border: 1px dashed #1fc76f !important;
}
.image-resize-handle {
    transition: all 0.2s ease;
}
.image-resize-handle:hover {
    transform: scale(1.2) !important;
    box-shadow: 0 0 6px #1fc76f !important;
}

/* 图片对齐样式 */
[data-type="imageResizable"] { 
    max-width: 100%;
    margin: 8px 0;
}
[data-type="imageResizable"][data-text-align="left"] {
    margin-left: 0;
    margin-right: auto;
}
[data-type="imageResizable"][data-text-align="center"] {
    margin-left: auto;
    margin-right: auto;
}
[data-type="imageResizable"][data-text-align="right"] {
    margin-left: auto;
    margin-right: 0;
}

.jeditor .ProseMirror [data-type="imageResizable"] {
    max-width: 100% !important;
    width: auto !important;
}

.jeditor table {
    border-collapse: collapse;
    margin: 8px 0;
    width: 100%;
    border: 1px solid #e5e6eb;
}
.jeditor table td,
.jeditor table th {
    border: 1px solid #e5e6eb;
    padding: 8px 12px;
}
.jeditor table th {
    background-color: #f6f8fa;
}

/* Link hover styles */
.jeditor a {
    color: #3b82f6;
    text-decoration: underline;
    text-underline-offset: 2px;
    cursor: pointer;
    transition: color 0.15s ease, opacity 0.15s ease;
}
.jeditor a:hover {
    color: #1d4ed8;
    opacity: 0.85;
}
.jeditor a:focus-visible {
    outline: 2px solid #3b82f6;
    outline-offset: 2px;
    border-radius: 2px;
}
</style>

