import { useEffect, useRef, useState } from 'react'
import Quill from 'quill'
import 'quill/dist/quill.snow.css'
import { Select } from '@arco-design/web-react'
import { runChatMessage, consumeSSE, getDifyAppParameters } from '@/service/dify'

// 字号白名单：把 Quill 的 size 格式改为行内样式（font-size: xxpx）
const FONT_SIZES = ['12px', '14px', '16px', '18px', '20px', '24px', '28px', '32px']
const SizeStyle = Quill.import('attributors/style/size')
SizeStyle.whitelist = FONT_SIZES
Quill.register(SizeStyle, true)

// 正文初始统一 14px：载入时移除行内 font-size（标题大小由标题级别样式决定，正文基准字号由 CSS 提供）
const stripInlineFontSize = (html) => {
  if (!html) return html
  const tpl = document.createElement('template')
  tpl.innerHTML = html
  tpl.content.querySelectorAll('[style]').forEach((el) => {
    el.style.removeProperty('font-size')
    if (!el.getAttribute('style')) el.removeAttribute('style')
  })
  return tpl.innerHTML
}

// AI 图标（lucide 风格 sparkles）
const AiSparkIcon = () => (
  <svg
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth="2"
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <path d="M9.937 15.5A2 2 0 0 0 8.5 14.063l-6.135-1.582a.5.5 0 0 1 0-.962L8.5 9.936A2 2 0 0 0 9.937 8.5l1.582-6.135a.5.5 0 0 1 .963 0L14.063 8.5A2 2 0 0 0 15.5 9.937l6.135 1.581a.5.5 0 0 1 0 .964L15.5 14.063a2 2 0 0 0-1.437 1.437l-1.582 6.135a.5.5 0 0 1-.963 0z" />
    <path d="M20 3v4" />
    <path d="M22 5h-4" />
    <path d="M4 17v2" />
    <path d="M5 18H3" />
  </svg>
)

// 生成按钮图标（lucide 风格 arrow-up）
const SendIcon = () => (
  <svg
    viewBox="0 0 24 24"
    fill="none"
    stroke="currentColor"
    strokeWidth="2.4"
    strokeLinecap="round"
    strokeLinejoin="round"
  >
    <path d="m5 12 7-7 7 7" />
    <path d="M12 19V5" />
  </svg>
)

// 暂停按钮图标（实心方块 stop）
const StopIcon = () => (
  <svg viewBox="0 0 24 24" fill="currentColor">
    <rect x="7" y="7" width="10" height="10" rx="1.5" />
  </svg>
)

// AI 润色动作
// 润色动作：以「效果」命名（用户关心想要什么效果，而非扩写/缩写这类实现方式）
const AI_ACTIONS = [
  { value: 'polish', label: '修复润色', title: '修正语病与标点，优化措辞，不改变篇幅与原意' },
  { value: 'concise', label: '更简洁', title: '删减冗余、压缩篇幅，保留核心信息' },
  { value: 'richer', label: '更丰满', title: '补充细节与论据，让内容更充实饱满' },
  { value: 'professional', label: '更专业', title: '用词书面规范、术语准确、语气正式' },
  { value: 'vivid', label: '更生动', title: '增强画面感与感染力，表达更鲜活' },
  { value: 'casual', label: '更口语化', title: '轻松自然、通俗易懂，适合日常阅读' },
  { value: 'deai', label: '去AI味', title: '改掉机械腔调与套话，让文字像人写的' },
]
const DEFAULT_ACTION = 'polish'

// 「内容润色」工作流（Dify 工作流类型应用）的 API Key
// 定义在 public/config.js 的 window.APP_CONFIG.POLISH_API_KEY（打包后可直接修改该文件调整）；
// 也可通过 <RichTextEditor polishApiKey="app-xxx"> 属性传入，属性优先。
const getPolishApiKey = (prop) => prop || window.APP_CONFIG?.POLISH_API_KEY || ''

// 润色工作流「开始节点」输入变量映射的兜底默认值。
// 组件挂载（打开富文本）时会调用 GET /v1/parameters 自动识别实际变量并覆盖此映射。
const POLISH_INPUT_KEYS = {
  text: 'article',        // 段落原文
  action: '',             // 润色动作：扩写 / 缩写 / 重写（工作流未定义则不传）
  requirement: '',        // 补充要求（工作流未定义则不传）
}

// 解析 /v1/parameters 返回的 user_input_form，自动识别三类润色输入变量：
// 正文（article/text/content/文章/正文/内容）、动作（action/动作/方式/模式）、补充要求（requirement/要求/指令）
const parsePolishParams = (parameters) => {
  const form = Array.isArray(parameters?.user_input_form) ? parameters.user_input_form : []
  const fields = form
    .map((item) => {
      const typeKey = Object.keys(item || {})[0]
      const def = typeKey ? item[typeKey] : null
      if (!def || !def.variable) return null
      return { variable: String(def.variable), label: String(def.label || ''), type: typeKey }
    })
    .filter(Boolean)
  if (!fields.length) return null
  const hit = (f, re) => f && re.test(`${f.variable} ${f.label}`.toLowerCase())
  const textField = fields.find((f) => hit(f, /article|content|\btext\b|文章|正文|内容/)) || fields[0]
  const actionField = fields.find((f) => f !== textField && hit(f, /action|动作|方式|模式|类型|mode/))
  const reqField = fields.find(
    (f) => f !== textField && f !== actionField && hit(f, /requirement|要求|指令|提示|instruction|prompt/)
  )
  return {
    text: textField.variable,
    action: actionField?.variable || '',
    requirement: reqField?.variable || '',
  }
}

// 剥离思考内容，润色弹窗只展示最终结果
// 兼容多种标签变体：<think>/<thinking>/<thought>/<reasoning>，允许标签带属性，
// 并处理流式中尚未闭合的思考段（思考进行中不显示任何思考文字）
const THINK_TAG = 'think|thinking|thought|reasoning'
const stripThinkContent = (text) => {
  const source = String(text || '')
  let out = source.replace(new RegExp(`<(${THINK_TAG})\\b[^>]*>[\\s\\S]*?<\\/\\1\\s*>`, 'gi'), '')
  out = out.replace(new RegExp(`<(${THINK_TAG})\\b[^>]*>[\\s\\S]*$`, 'i'), '')
  return out.trim()
}

// Quill 富文本编辑器封装（基于 Quill 2，snow 主题）
// - value: 初始 HTML（仅在挂载时载入）
// - onChange(html): 内容变化时回调完整 HTML
// - placeholder: 占位文案
// - polishApiKey: 「内容润色」工作流的 API Key（可选，优先于文件顶部 POLISH_API_KEY 常量）
// - onAiParagraph({ text, element }): 点击段落前的 AI 按钮时回调
const RichTextEditor = ({ value, onChange, placeholder = '请输入内容...', polishApiKey, onAiParagraph }) => {
  const wrapperRef = useRef(null)
  const quillRef = useRef(null)
  const aiBtnRef = useRef(null)
  const activePRef = useRef(null)
  const hideTimerRef = useRef(null)
  const clearActivePRef = useRef(null)
  const onChangeRef = useRef(onChange)
  const onAiParagraphRef = useRef(onAiParagraph)
  onChangeRef.current = onChange
  onAiParagraphRef.current = onAiParagraph

  // ===== AI 润色弹窗 =====
  const aiPanelRef = useRef(null)      // 弹窗 DOM
  const aiPanelPRef = useRef(null)     // 弹窗当前作用的段落元素
  const aiAbortRef = useRef(null)      // 润色请求 AbortController
  const panelOpenRef = useRef(false)   // 弹窗打开期间锁定段落高亮清除
  const polishInputsRef = useRef(POLISH_INPUT_KEYS)  // 润色输入变量映射（由 /v1/parameters 动态识别）
  const [aiPos, setAiPos] = useState(null)                 // AI 按钮锚定层位置（null 隐藏）
  const [aiPanel, setAiPanel] = useState(null)             // 弹窗位置 { top, left, width }
  const [aiAction, setAiAction] = useState(DEFAULT_ACTION)  // 润色动作，见 AI_ACTIONS
  const [aiRequirement, setAiRequirement] = useState('')   // 补充要求
  const [aiResult, setAiResult] = useState('')             // 润色结果（流式累积）
  const [aiLoading, setAiLoading] = useState(false)
  const [aiError, setAiError] = useState('')

  // 取消延迟隐藏（鼠标回到段落 / 进入按钮时调用）
  const cancelHideP = () => {
    if (hideTimerRef.current) {
      clearTimeout(hideTimerRef.current)
      hideTimerRef.current = null
    }
  }
  // 延迟隐藏：鼠标彻底离开段落及按钮区域后，保留一小段时间再隐藏（兜底保险）
  const scheduleHideP = () => {
    if (hideTimerRef.current) return
    hideTimerRef.current = setTimeout(() => {
      hideTimerRef.current = null
      // 隐藏前校验悬停状态：鼠标仍在锚定层（按钮命中区）或当前段落上时，图标与高亮保持显示。
      // 浏览器派发顺序问题：段落 → 按钮切换时，root 的 mouseleave 可能晚于锚定层 mouseenter，
      // 导致延迟隐藏被重新挂起，这里在真正隐藏前再兜底校验一次
      if (aiBtnRef.current?.matches(':hover') || activePRef.current?.matches(':hover')) {
        scheduleHideP()
        return
      }
      clearActivePRef.current?.()
    }, 500)
  }

  // 关闭润色弹窗：终止请求、复位状态
  const closeAiPanel = () => {
    aiAbortRef.current?.abort()
    aiAbortRef.current = null
    panelOpenRef.current = false
    // 弹窗关闭时取消段落高亮（并隐藏段前 AI 按钮）
    clearActivePRef.current?.()
    aiPanelPRef.current = null
    setAiPanel(null)
    setAiLoading(false)
    setAiResult('')
    setAiError('')
  }

  // 暂停：中止流式接收，已生成的内容保留展示（可继续点「生成」重新发起）
  const pausePolish = () => {
    aiAbortRef.current?.abort()
    aiAbortRef.current = null
    setAiLoading(false)
  }

  // 组装润色请求：query = 动作 + 补充要求；inputs.article = 段落原文（变量名来自 /parameters）
  const buildPolishQuery = (actionLabel) => {
    const req = aiRequirement.trim()
    return req ? `${actionLabel}，${req}` : actionLabel
  }

  // 发起润色请求（点「生成」后调用 POST /chat-messages，流式累积结果）
  const runPolish = async () => {
    const target = aiPanelPRef.current
    const text = target?.textContent?.trim()
    if (!target || !text) return
    const key = getPolishApiKey(polishApiKey)
    if (!key) {
      setAiError('未配置润色 API Key（public/config.js 的 POLISH_API_KEY 或 polishApiKey 属性）')
      return
    }
    aiAbortRef.current?.abort()
    const controller = new AbortController()
    aiAbortRef.current = controller
    setAiLoading(true)
    setAiError('')
    setAiResult('')
    let acc = ''
    const applyResult = (chunk) => {
      acc += chunk
      // 展示时剥离思考内容，只保留最终润色结果
      const visible = stripThinkContent(acc)
      setAiResult(visible)
      return visible
    }
    try {
      const actionLabel = (AI_ACTIONS.find((a) => a.value === aiAction) || AI_ACTIONS[0]).label
      // inputs 严格按 /v1/parameters 识别出的变量传；工作流未定义的变量（映射为空）不传
      const keys = polishInputsRef.current
      const inputs = { [keys.text]: text }
      if (keys.action) inputs[keys.action] = actionLabel
      if (keys.requirement) inputs[keys.requirement] = aiRequirement.trim()
      const res = await runChatMessage(
        key,
        {
          query: buildPolishQuery(actionLabel),
          inputs,
          conversation_id: '',
        },
        undefined,
        { signal: controller.signal }
      )
      await consumeSSE(
        res,
        (evt) => {
          if (controller.signal.aborted) return
          const type = evt?.event
          // chat-messages 流式增量：message 事件的 answer 字段
          if (type === 'message' && evt.answer) {
            applyResult(evt.answer)
          } else if (type === 'text_chunk' && evt.data?.text) {
            // 兼容工作流 text_chunk 事件
            applyResult(evt.data.text)
          } else if (type === 'workflow_finished' && !stripThinkContent(acc)) {
            // 未产生流式块（或全部是思考内容）时，用结束节点 outputs 兜底（取第一个非空字符串字段）
            const outputs = evt.data?.outputs
            if (outputs && typeof outputs === 'object') {
              const first = Object.values(outputs).find((v) => typeof v === 'string' && v.trim())
              if (first) {
                acc = ''
                applyResult(stripThinkContent(first) || first)
              }
            }
          } else if (type === 'error') {
            setAiError(`润色失败：${evt.data?.message || evt.message || '接口执行出错'}`)
          }
        },
        () => { if (!controller.signal.aborted) setAiLoading(false) },
        (err) => {
          if (controller.signal.aborted) return
          setAiLoading(false)
          setAiError(`润色失败：${err?.message || '未知错误'}`)
        },
        { signal: controller.signal }
      )
    } catch (err) {
      if (controller.signal.aborted) return
      setAiLoading(false)
      setAiError(`润色失败：${err?.message || '未知错误'}`)
    } finally {
      if (aiAbortRef.current === controller) setAiLoading(false)
    }
  }

  // 打开润色弹窗：定位在段落下方（空间不足放上方），并自动生成一次
  const openAiPanel = (p) => {
    const wrap = wrapperRef.current
    if (!wrap) return
    const wrapRect = wrap.getBoundingClientRect()
    const pRect = p.getBoundingClientRect()
    const width = Math.min(520, Math.max(340, wrapRect.width - 16))
    const panelH = 330
    let top = pRect.bottom - wrapRect.top + 8
    if (top + panelH > wrapRect.height - 8) {
      top = Math.max(8, pRect.top - wrapRect.top - panelH - 8)
    }
    const left = Math.max(8, Math.min(pRect.left - wrapRect.left, wrapRect.width - width - 8))
    // 每次打开都是全新弹窗：终止旧请求并复位上次的结果/动作/要求，不携带任何残留
    aiAbortRef.current?.abort()
    aiAbortRef.current = null
    setAiResult('')
    setAiError('')
    setAiLoading(false)
    setAiAction(DEFAULT_ACTION)
    setAiRequirement('')
    aiPanelPRef.current = p
    panelOpenRef.current = true
    setAiPanel({ top, left, width })
    // 打开弹窗不自动调用接口，由用户点「生成」再发起请求
  }

  // 替换：把润色结果写回原段落（保留段落行内格式，user 触发以回调 onChange）
  const handleReplace = () => {
    const quill = quillRef.current
    const p = aiPanelPRef.current
    // 写入前再剥离一次思考内容，确保思考绝不进入富文本
    const text = stripThinkContent(aiResult)
    if (!quill || !p || !text) {
      closeAiPanel()
      return
    }
    const blot = Quill.find(p)
    if (blot) {
      const index = quill.getIndex(blot)
      const len = blot.length() // 含段落末尾换行符
      // 仅保留行内格式（加粗/斜体/颜色等），块级结构由 p 本身保证
      const fmt = quill.getFormat(index, Math.max(0, len - 1))
      const inlineFmt = {}
      ;['bold', 'italic', 'underline', 'strike', 'color', 'background', 'size', 'font', 'code'].forEach((k) => {
        if (fmt[k] !== undefined) inlineFmt[k] = fmt[k]
      })
      quill.deleteText(index, len - 1, Quill.sources.USER)
      quill.insertText(index, text, inlineFmt, Quill.sources.USER)
    }
    closeAiPanel()
  }

  useEffect(() => {
    const wrapper = wrapperRef.current
    if (!wrapper || quillRef.current) return
    // Quill 挂到内部动态节点上：工具栏与编辑区都收纳在 wrapper 内，
    // 卸载时清空 wrapper 即可整体移除（兼容 StrictMode 的二次挂载，避免出现双编辑器）
    const target = document.createElement('div')
    wrapper.appendChild(target)
    const quill = new Quill(target, {
      theme: 'snow',
      placeholder,
      modules: {
        toolbar: {
          container: [
            ['undo', 'redo'],
            [{ header: [1, 2, 3, 4, 5, false] }, { size: [...FONT_SIZES, false] }],
            ['bold', 'italic', 'underline', 'strike'],
            [{ color: [] }, { background: [] }],
            [{ list: 'ordered' }, { list: 'bullet' }],
            [{ align: [] }],
            ['blockquote', 'code-block'],
          ],
          // 撤销/重做非内置格式，转发到 history 模块（支持 Ctrl+Z / Ctrl+Y 同一历史栈）
          handlers: {
            undo() { this.quill.getModule('history')?.undo() },
            redo() { this.quill.getModule('history')?.redo() },
          },
        },
      },
    })
    quillRef.current = quill
    // 撤销/重做按钮图标：Quill 未内置，注入 lucide 风格 SVG（挂 ql-stroke 复用主题描边与 hover 配色）
    const undoBtn = wrapper.querySelector('button.ql-undo')
    const redoBtn = wrapper.querySelector('button.ql-redo')
    if (undoBtn) {
      undoBtn.innerHTML =
        '<svg viewBox="0 0 24 24"><path class="ql-stroke" d="M9 14 4 9l5-5" /><path class="ql-stroke" d="M4 9h10.5a5.5 5.5 0 0 1 5.5 5.5 5.5 5.5 0 0 1-5.5 5.5H11" /></svg>'
      undoBtn.title = '撤销'
    }
    if (redoBtn) {
      redoBtn.innerHTML =
        '<svg viewBox="0 0 24 24"><path class="ql-stroke" d="m15 14 5-5-5-5" /><path class="ql-stroke" d="M20 9H9.5A5.5 5.5 0 0 0 4 14.5 5.5 5.5 0 0 0 9.5 20H13" /></svg>'
      redoBtn.title = '重做'
    }
    // 初始内容：经 Quill 剪贴板管道规范化解析，避免直接写 innerHTML 结构不兼容
    if (value) quill.clipboard.dangerouslyPasteHTML(stripInlineFontSize(value))
    quill.on('text-change', (_delta, _oldContents, source) => {
      // 仅用户操作触发回调，程序化写入不回传
      if (source === 'user') onChangeRef.current?.(quill.root.innerHTML)
    })

    // ===== 段落 hover：高亮当前 p，并在段前显示 AI 按钮 =====
    const root = quill.root
    const clearActiveP = () => {
      // 弹窗打开期间锁定段落与按钮，避免鼠标移入弹窗时被清除
      if (panelOpenRef.current) return
      activePRef.current?.classList.remove('p-ai-active')
      activePRef.current = null
      setAiPos(null)
    }
    clearActivePRef.current = clearActiveP
    const setActiveP = (p) => {
      if (activePRef.current !== p) {
        activePRef.current?.classList.remove('p-ai-active')
        p.classList.add('p-ai-active')
        activePRef.current = p
      }
      // 锚定层覆盖「按钮 + 与段落的间隙 + 段落左缘 2px」，与 p 零缝隙衔接：
      // 鼠标从段落移出的瞬间 DOM 命中即落在锚定层上（root 收不到 mousemove），按钮不会被隐藏
      const wrapRect = wrapper.getBoundingClientRect()
      const pRect = p.getBoundingClientRect()
      setAiPos(prev => {
        const next = {
          top: pRect.top - wrapRect.top,
          left: pRect.left - wrapRect.left - 32,
          height: pRect.height,
        }
        if (prev && Math.abs(prev.top - next.top) < 1 && prev.left === next.left && prev.height === next.height) {
          return prev
        }
        return next
      })
    }
    const handleMouseMove = (e) => {
      // 弹窗打开期间冻结 hover 判定：鼠标穿过邻段（弹窗在上方时的必经间隙）也不切换高亮，
      // 锚定段落保持高亮直到弹窗关闭
      if (panelOpenRef.current) {
        cancelHideP()
        return
      }
      const el = e.target
      const p = el && el.closest ? el.closest('p') : null
      if (p && p.parentElement === root && p.textContent.trim()) {
        cancelHideP()
        setActiveP(p)
        return
      }
      if (aiBtnRef.current && aiBtnRef.current.contains(el)) {
        cancelHideP()
        return
      }
      // 坐标命中判定：鼠标在段落矩形、或段落左侧紧邻带（按钮所在区域，纵向外扩 8px）内时，
      // 无论 DOM target 是谁（如编辑器 padding 区）都保持显示，保证按钮可达
      const active = activePRef.current
      if (active && e.clientX != null) {
        const ar = active.getBoundingClientRect()
        const inPZone =
          e.clientX >= ar.left - 40 &&
          e.clientX <= ar.right &&
          e.clientY >= ar.top - 8 &&
          e.clientY <= ar.bottom + 8
        if (inPZone) {
          cancelHideP()
          return
        }
      }
      // 彻底离开段落及按钮区域：延迟隐藏兜底
      scheduleHideP()
    }
    // 注意：不能用「立即清除」——AI 按钮层在 DOM 上不是 .ql-editor 的后代，
    // 鼠标从段落移到按钮的瞬间 root 就会触发 mouseleave（按 DOM 子树判定，与视觉遮挡无关），
    // 立即清除会让按钮凭空消失。这里只启动延迟隐藏，进入按钮（mouseenter）会取消。
    const handleMouseLeave = () => scheduleHideP()
    root.addEventListener('mousemove', handleMouseMove)
    root.addEventListener('mouseleave', handleMouseLeave)

    return () => {
      root.removeEventListener('mousemove', handleMouseMove)
      root.removeEventListener('mouseleave', handleMouseLeave)
      cancelHideP()
      activePRef.current?.classList.remove('p-ai-active')
      activePRef.current = null
      clearActivePRef.current = null
      aiAbortRef.current?.abort()
      aiAbortRef.current = null
      panelOpenRef.current = false
      quillRef.current = null
      wrapper.innerHTML = ''
    }
    // 仅挂载时初始化一次，value 变化不回灌（避免输入时光标跳动）
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 打开富文本时调用 GET /v1/parameters，自动识别润色工作流的输入变量（失败则沿用兜底映射）
  useEffect(() => {
    const key = getPolishApiKey(polishApiKey)
    if (!key) return
    let cancelled = false
    getDifyAppParameters(key)
      .then((params) => {
        const mapping = parsePolishParams(params)
        if (!cancelled && mapping) {
          polishInputsRef.current = mapping
          if (typeof console !== 'undefined') console.log('[AI润色] /parameters 输入变量映射 →', mapping)
        }
      })
      .catch((err) => {
        if (typeof console !== 'undefined') console.warn('[AI润色] 获取 /parameters 失败，使用默认映射 →', err?.message || err)
      })
    return () => { cancelled = true }
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [])

  // 弹窗打开期间：点击弹窗与按钮以外区域时关闭
  useEffect(() => {
    if (!aiPanel) return
    const onDocMouseDown = (e) => {
      const t = e.target
      if (aiPanelRef.current?.contains(t)) return
      if (aiBtnRef.current?.contains(t)) return
      closeAiPanel()
    }
    document.addEventListener('mousedown', onDocMouseDown)
    return () => document.removeEventListener('mousedown', onDocMouseDown)
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [aiPanel])

  return (
    <div ref={wrapperRef} className="rich-editor">
      {aiPos && (
        <div
          ref={aiBtnRef}
          className="rich-editor-ai-anchor"
          style={{ top: aiPos.top, left: aiPos.left, height: aiPos.height }}
          onMouseEnter={cancelHideP}
          onMouseLeave={() => scheduleHideP()}
        >
          <button
            type="button"
            className="rich-editor-ai-btn"
            title="AI 优化"
            onClick={() => {
              const p = activePRef.current
              if (!p) return
              onAiParagraphRef.current?.({ text: p.textContent.trim(), element: p })
              openAiPanel(p)
            }}
          >
            <AiSparkIcon />
          </button>
        </div>
      )}

      {aiPanel && (
        <div
          ref={aiPanelRef}
          className="rich-editor-ai-panel"
          style={{ top: aiPanel.top, left: aiPanel.left, width: aiPanel.width }}
          onMouseEnter={cancelHideP}
        >
          <div className="rich-editor-ai-panel-head">
            <span className="rich-editor-ai-panel-title">
              <AiSparkIcon />
              AI 润色
            </span>
            <button
              type="button"
              className="rich-editor-ai-panel-close"
              aria-label="关闭"
              onClick={closeAiPanel}
            >
              ×
            </button>
          </div>

          {/* 顶部操作已隐藏：关闭走标题栏 ×，替换按钮移至底部表单行 */}

          <div className="rich-editor-ai-panel-result">
            {aiError && <div className="rich-editor-ai-panel-error">{aiError}</div>}
            {!aiError && aiResult && (
              <div className="rich-editor-ai-panel-text">
                {aiResult}
                {aiLoading && <span className="rich-editor-ai-cursor" />}
              </div>
            )}
            {!aiError && !aiResult && aiLoading && (
              <div className="rich-editor-ai-panel-empty rich-editor-ai-panel-loading">
                <span className="rich-editor-ai-panel-loading-spin" />
                正在生成，请稍候…
              </div>
            )}
            {!aiError && !aiResult && !aiLoading && (
              <div className="rich-editor-ai-panel-empty">点击输入框右下角的「生成」按钮开始润色</div>
            )}
          </div>

          {/* 底部表单：第一行 动作下拉 + 替换按钮，第二行 要求输入（右下角内嵌 生成/暂停 图标按钮） */}
          <div className="rich-editor-ai-panel-form">
            <div className="rich-editor-ai-panel-form-row">
              <Select
                className="rich-editor-ai-panel-select"
                size="small"
                value={aiAction}
                onChange={setAiAction}
                position="top"
                getPopupContainer={() => aiPanelRef.current || document.body}
              >
                {AI_ACTIONS.map((a) => (
                  <Select.Option key={a.value} value={a.value} title={a.title}>
                    {a.label}
                  </Select.Option>
                ))}
              </Select>
              <button
                type="button"
                className="rich-editor-ai-panel-replace"
                onClick={handleReplace}
                disabled={!aiResult.trim()}
              >
                替换
              </button>
            </div>
            <div className="rich-editor-ai-panel-input-wrap">
              <textarea
                className="rich-editor-ai-panel-input"
                value={aiRequirement}
                onChange={(e) => setAiRequirement(e.target.value)}
                onKeyDown={(e) => {
                  if (e.key === 'Enter' && !e.shiftKey) {
                    e.preventDefault()
                    aiLoading ? pausePolish() : runPolish()
                  }
                }}
                placeholder="输入润色要求（可选，Enter 生成，Shift+Enter 换行）"
              />
              <button
                type="button"
                className={`rich-editor-ai-panel-send${aiLoading ? ' is-pausing' : ''}`}
                title={aiLoading ? '暂停' : '生成'}
                aria-label={aiLoading ? '暂停' : '生成'}
                onClick={() => (aiLoading ? pausePolish() : runPolish())}
              >
                {aiLoading ? <StopIcon /> : <SendIcon />}
              </button>
            </div>
          </div>
        </div>
      )}
    </div>
  )
}

export default RichTextEditor
