<script setup lang="ts">
import { TextAlign } from '@tiptap/extension-text-align'
import { TextStyleKit } from '@tiptap/extension-text-style'
import Image from '@tiptap/extension-image'
import StarterKit from '@tiptap/starter-kit'
import { EditorContent, useEditor } from '@tiptap/vue-3'
import { toEditorHtml } from '~/utils/rich-text'

const props = defineProps<{ modelValue: string }>()
const emit = defineEmits<{ 'update:modelValue': [value: string] }>()
const linkMessage = ref('')
const imageInput = ref<HTMLInputElement>()
const imageMessage = ref('')
const imageBusy = ref(false)

const editor = useEditor({
  content: toEditorHtml(props.modelValue),
  extensions: [
    StarterKit.configure({
      heading: { levels: [2, 3] },
      link: {
        openOnClick: false,
        autolink: true,
        HTMLAttributes: { target: '_blank', rel: 'noopener noreferrer' },
      },
    }),
    TextAlign.configure({ types: ['heading', 'paragraph'] }),
    TextStyleKit,
    Image.configure({ allowBase64: false }),
  ],
  editorProps: {
    attributes: {
      class: 'rich-text-surface',
      'aria-label': '正文编辑区域',
      spellcheck: 'true',
    },
  },
  onUpdate: ({ editor: instance }) => {
    emit('update:modelValue', instance.isEmpty ? '' : instance.getHTML())
  },
})

watch(() => props.modelValue, (value) => {
  if (!editor.value) return
  const next = toEditorHtml(value)
  if (editor.value.getHTML() !== next) editor.value.commands.setContent(next, { emitUpdate: false })
})

function selectBlock(event: Event) {
  const value = (event.target as HTMLSelectElement).value
  if (value === 'paragraph') editor.value?.chain().focus().setParagraph().run()
  else editor.value?.chain().focus().toggleHeading({ level: Number(value) as 2 | 3 }).run()
}

function selectFontFamily(event: Event) {
  const value = (event.target as HTMLSelectElement).value
  if (value) editor.value?.chain().focus().setFontFamily(value).run()
  else editor.value?.chain().focus().unsetFontFamily().run()
}

function selectFontSize(event: Event) {
  const value = (event.target as HTMLSelectElement).value
  if (value) editor.value?.chain().focus().setFontSize(value).run()
  else editor.value?.chain().focus().unsetFontSize().run()
}

function selectLineHeight(event: Event) {
  const value = (event.target as HTMLSelectElement).value
  if (value) editor.value?.chain().focus().setLineHeight(value).run()
  else editor.value?.chain().focus().unsetLineHeight().run()
}

function setColor(event: Event, background = false) {
  const value = (event.target as HTMLInputElement).value
  const chain = editor.value?.chain().focus()
  if (background) chain?.setBackgroundColor(value).run()
  else chain?.setColor(value).run()
}

function setLink() {
  const previous = editor.value?.getAttributes('link').href || ''
  const href = window.prompt('请输入链接地址（http、https、mailto 或 tel）', previous)
  if (href === null) return
  if (!href.trim()) {
    editor.value?.chain().focus().extendMarkRange('link').unsetLink().run()
    return
  }
  if (!/^(https?:\/\/|mailto:|tel:)/i.test(href.trim())) {
    linkMessage.value = '链接需以 http://、https://、mailto: 或 tel: 开头'
    return
  }
  linkMessage.value = ''
  editor.value?.chain().focus().extendMarkRange('link').setLink({ href: href.trim(), target: '_blank', rel: 'noopener noreferrer' }).run()
}

function insertImageUrl() {
  const src = window.prompt('请输入图片地址（建议使用素材库中的图片地址）')
  if (!src?.trim()) return
  if (!/^https?:\/\//i.test(src.trim()) && !src.trim().startsWith('/')) {
    imageMessage.value = '图片地址需以 http://、https:// 或 / 开头'
    return
  }
  imageMessage.value = ''
  editor.value?.chain().focus().setImage({ src: src.trim() }).run()
}

async function uploadImage(event: Event) {
  const file = (event.target as HTMLInputElement).files?.[0]
  if (!file) return
  imageBusy.value = true
  imageMessage.value = ''
  try {
    const body = new FormData()
    body.append('file', file)
    const result = await $fetch<{ url: string }>('/api/admin/media', { method: 'POST', body })
    editor.value?.chain().focus().setImage({ src: result.url, alt: file.name }).run()
  } catch (error: any) {
    imageMessage.value = error?.data?.statusMessage || '图片上传失败，请检查图片格式和大小'
  } finally {
    imageBusy.value = false
    ;(event.target as HTMLInputElement).value = ''
  }
}
</script>

<template>
  <div class="rich-text-editor">
    <div v-if="editor" class="rich-text-toolbar" role="toolbar" aria-label="正文排版工具栏">
      <select aria-label="段落样式" title="段落样式" @change="selectBlock">
        <option value="paragraph">正文</option>
        <option value="2">二级标题</option>
        <option value="3">三级标题</option>
      </select>
      <select aria-label="字体" title="字体" @change="selectFontFamily">
        <option value="">默认字体</option>
        <option value="Noto Serif SC, serif">宋体风格</option>
        <option value="Noto Sans SC, sans-serif">黑体风格</option>
        <option value="Arial, sans-serif">Arial</option>
      </select>
      <select aria-label="字号" title="字号" @change="selectFontSize">
        <option value="">默认字号</option>
        <option v-for="size in [14,16,18,20,24,28,32]" :key="size" :value="`${size}px`">{{ size }} px</option>
      </select>
      <select aria-label="行距" title="行距" @change="selectLineHeight">
        <option value="">默认行距</option>
        <option value="1.5">1.5 倍</option>
        <option value="1.75">1.75 倍</option>
        <option value="2">2 倍</option>
        <option value="2.25">2.25 倍</option>
      </select>

      <span class="toolbar-divider" />
      <button type="button" :class="{ active: editor.isActive('bold') }" title="加粗" aria-label="加粗" @click="editor.chain().focus().toggleBold().run()"><b>B</b></button>
      <button type="button" :class="{ active: editor.isActive('italic') }" title="斜体" aria-label="斜体" @click="editor.chain().focus().toggleItalic().run()"><i>I</i></button>
      <button type="button" :class="{ active: editor.isActive('underline') }" title="下划线" aria-label="下划线" @click="editor.chain().focus().toggleUnderline().run()"><u>U</u></button>
      <button type="button" :class="{ active: editor.isActive('strike') }" title="删除线" aria-label="删除线" @click="editor.chain().focus().toggleStrike().run()"><s>S</s></button>
      <label class="toolbar-color" title="文字颜色">A<input type="color" value="#17364e" aria-label="文字颜色" @input="setColor($event)"></label>
      <label class="toolbar-color highlight" title="背景颜色">A<input type="color" value="#fff1c9" aria-label="背景颜色" @input="setColor($event, true)"></label>

      <span class="toolbar-divider" />
      <button type="button" :class="{ active: editor.isActive({ textAlign: 'left' }) }" title="左对齐" aria-label="左对齐" @click="editor.chain().focus().setTextAlign('left').run()">左</button>
      <button type="button" :class="{ active: editor.isActive({ textAlign: 'center' }) }" title="居中对齐" aria-label="居中对齐" @click="editor.chain().focus().setTextAlign('center').run()">中</button>
      <button type="button" :class="{ active: editor.isActive({ textAlign: 'right' }) }" title="右对齐" aria-label="右对齐" @click="editor.chain().focus().setTextAlign('right').run()">右</button>
      <button type="button" :class="{ active: editor.isActive({ textAlign: 'justify' }) }" title="两端对齐" aria-label="两端对齐" @click="editor.chain().focus().setTextAlign('justify').run()">齐</button>
      <button type="button" :class="{ active: editor.isActive('bulletList') }" title="项目符号" aria-label="项目符号" @click="editor.chain().focus().toggleBulletList().run()">• 列表</button>
      <button type="button" :class="{ active: editor.isActive('orderedList') }" title="编号列表" aria-label="编号列表" @click="editor.chain().focus().toggleOrderedList().run()">1. 列表</button>
      <button type="button" :class="{ active: editor.isActive('blockquote') }" title="引用" aria-label="引用" @click="editor.chain().focus().toggleBlockquote().run()">引用</button>
      <button type="button" :class="{ active: editor.isActive('link') }" title="添加或编辑链接" aria-label="添加或编辑链接" @click="setLink">链接</button>
      <button type="button" title="插入图片地址" aria-label="插入图片地址" @click="insertImageUrl">图片地址</button>
      <button type="button" title="上传图片" aria-label="上传图片" :disabled="imageBusy" @click="imageInput?.click()">{{ imageBusy ? '上传中…' : '上传图片' }}</button>
      <input ref="imageInput" class="visually-hidden" type="file" accept="image/png,image/jpeg,image/webp" @change="uploadImage">
      <button type="button" :class="{ active: editor.isActive('codeBlock') }" title="代码块" aria-label="代码块" @click="editor.chain().focus().toggleCodeBlock().run()">代码</button>
      <button type="button" title="分隔线" aria-label="分隔线" @click="editor.chain().focus().setHorizontalRule().run()">分隔线</button>
      <button type="button" title="清除格式" aria-label="清除格式" @click="editor.chain().focus().clearNodes().unsetAllMarks().run()">清除格式</button>
      <button type="button" title="撤销" aria-label="撤销" :disabled="!editor.can().undo()" @click="editor.chain().focus().undo().run()">↶</button>
      <button type="button" title="重做" aria-label="重做" :disabled="!editor.can().redo()" @click="editor.chain().focus().redo().run()">↷</button>
    </div>
    <EditorContent :editor="editor" />
    <p v-if="linkMessage" class="rich-text-message">{{ linkMessage }}</p>
    <p v-if="imageMessage" class="rich-text-message">{{ imageMessage }}</p>
    <div class="rich-text-footer"><span>支持从 Word 粘贴，发布时会自动清理不安全代码</span><span>{{ modelValue.length }} 字符</span></div>
  </div>
</template>
