<script lang="ts" setup>
import { nextTick, ref, watch } from 'vue';

import {
  AlignCenter,
  AlignLeft,
  AlignRight,
  Bold,
  Eraser,
  Heading1,
  Image,
  Italic,
  Link,
  List,
  ListOrdered,
  Quote,
  Redo2,
  Table,
  Underline,
  Undo2,
} from '@vben/icons';

import {
  ElButton,
  ElButtonGroup,
  ElColorPicker,
  ElDivider,
  ElInput,
  ElMessage,
  ElPopover,
  ElSelect,
  ElOption,
} from 'element-plus';

const props = withDefaults(
  defineProps<{
    disabled?: boolean;
    maxHeight?: number;
    minHeight?: number;
    modelValue?: string;
    placeholder?: string;
    readonly?: boolean;
  }>(),
  {
    disabled: false,
    maxHeight: 500,
    minHeight: 200,
    modelValue: '',
    placeholder: '',
    readonly: false,
  },
);

const emit = defineEmits<{
  blur: [event: FocusEvent];
  change: [value: string];
  focus: [event: FocusEvent];
  'update:modelValue': [value: string];
}>();

const editorRef = ref<HTMLElement>();
const linkPopoverVisible = ref(false);
const imagePopoverVisible = ref(false);
const tablePopoverVisible = ref(false);
const linkUrl = ref('');
const imageUrl = ref('');
const tableRows = ref(3);
const tableCols = ref(3);
const currentColor = ref('#1f2937');
const currentBlock = ref('P');
let isSyncing = false;

function syncHtml(value = props.modelValue || '') {
  if (!editorRef.value || editorRef.value.innerHTML === value) return;
  isSyncing = true;
  editorRef.value.innerHTML = value;
  nextTick(() => {
    isSyncing = false;
  });
}

watch(
  () => props.modelValue,
  (value) => syncHtml(value || ''),
);

function emitChange() {
  if (isSyncing || !editorRef.value) return;
  const html = editorRef.value.innerHTML;
  emit('update:modelValue', html);
  emit('change', html);
}

function focusEditor() {
  editorRef.value?.focus();
}

function exec(command: string, value?: string) {
  if (props.disabled || props.readonly) return;
  focusEditor();
  document.execCommand(command, false, value);
  emitChange();
}

function applyBlock(value: string) {
  currentBlock.value = value;
  exec('formatBlock', value);
}

function applyColor(value: string | null) {
  if (!value) return;
  currentColor.value = value;
  exec('foreColor', value);
}

function insertLink() {
  const url = linkUrl.value.trim();
  if (!url) {
    ElMessage.warning('请输入链接地址');
    return;
  }
  exec('createLink', url);
  linkPopoverVisible.value = false;
  linkUrl.value = '';
}

function insertImage() {
  const url = imageUrl.value.trim();
  if (!url) {
    ElMessage.warning('请输入图片地址');
    return;
  }
  exec('insertHTML', `<img src="${url}" alt="" />`);
  imagePopoverVisible.value = false;
  imageUrl.value = '';
}

function insertTable() {
  const rows = Math.max(1, Math.min(10, tableRows.value));
  const cols = Math.max(1, Math.min(8, tableCols.value));
  const cells = Array.from({ length: cols })
    .map(() => '<td><br></td>')
    .join('');
  const body = Array.from({ length: rows })
    .map(() => `<tr>${cells}</tr>`)
    .join('');
  exec('insertHTML', `<table><tbody>${body}</tbody></table><p><br></p>`);
  tablePopoverVisible.value = false;
}

function handlePaste(event: ClipboardEvent) {
  const html = event.clipboardData?.getData('text/html');
  const text = event.clipboardData?.getData('text/plain');
  if (!html && !text) return;
  event.preventDefault();
  exec('insertHTML', html || (text || '').replaceAll('\n', '<br>'));
}

function handleFocus(event: FocusEvent) {
  emit('focus', event);
}

function handleBlur(event: FocusEvent) {
  emitChange();
  emit('blur', event);
}

nextTick(() => syncHtml());
</script>

<template>
  <div class="rich-text-editor" :class="{ 'is-disabled': disabled || readonly }">
    <div class="rich-text-toolbar">
      <ElSelect
        v-model="currentBlock"
        size="small"
        class="block-select"
        :disabled="disabled || readonly"
        @change="applyBlock"
      >
        <ElOption label="正文" value="P" />
        <ElOption label="标题 1" value="H1" />
        <ElOption label="标题 2" value="H2" />
        <ElOption label="标题 3" value="H3" />
      </ElSelect>

      <ElDivider direction="vertical" />

      <ElButtonGroup>
        <ElButton
          :icon="Undo2"
          size="small"
          text
          title="撤销"
          @click="exec('undo')"
        />
        <ElButton
          :icon="Redo2"
          size="small"
          text
          title="重做"
          @click="exec('redo')"
        />
      </ElButtonGroup>

      <ElDivider direction="vertical" />

      <ElButtonGroup>
        <ElButton
          :icon="Bold"
          size="small"
          text
          title="加粗"
          @click="exec('bold')"
        />
        <ElButton
          :icon="Italic"
          size="small"
          text
          title="斜体"
          @click="exec('italic')"
        />
        <ElButton
          :icon="Underline"
          size="small"
          text
          title="下划线"
          @click="exec('underline')"
        />
      </ElButtonGroup>

      <ElDivider direction="vertical" />

      <ElButtonGroup>
        <ElButton
          :icon="AlignLeft"
          size="small"
          text
          title="左对齐"
          @click="exec('justifyLeft')"
        />
        <ElButton
          :icon="AlignCenter"
          size="small"
          text
          title="居中"
          @click="exec('justifyCenter')"
        />
        <ElButton
          :icon="AlignRight"
          size="small"
          text
          title="右对齐"
          @click="exec('justifyRight')"
        />
      </ElButtonGroup>

      <ElDivider direction="vertical" />

      <ElButtonGroup>
        <ElButton
          :icon="List"
          size="small"
          text
          title="无序列表"
          @click="exec('insertUnorderedList')"
        />
        <ElButton
          :icon="ListOrdered"
          size="small"
          text
          title="有序列表"
          @click="exec('insertOrderedList')"
        />
        <ElButton
          :icon="Quote"
          size="small"
          text
          title="引用"
          @click="exec('formatBlock', 'BLOCKQUOTE')"
        />
      </ElButtonGroup>

      <ElDivider direction="vertical" />

      <ElColorPicker
        v-model="currentColor"
        size="small"
        :disabled="disabled || readonly"
        @change="applyColor"
      />

      <ElDivider direction="vertical" />

      <ElPopover v-model:visible="linkPopoverVisible" width="300" trigger="click">
        <template #reference>
          <ElButton :icon="Link" size="small" text title="插入链接" />
        </template>
        <div class="popover-form">
          <ElInput v-model="linkUrl" placeholder="https://example.com" />
          <ElButton type="primary" size="small" @click="insertLink">插入链接</ElButton>
        </div>
      </ElPopover>

      <ElPopover v-model:visible="imagePopoverVisible" width="300" trigger="click">
        <template #reference>
          <ElButton :icon="Image" size="small" text title="插入图片" />
        </template>
        <div class="popover-form">
          <ElInput v-model="imageUrl" placeholder="图片 URL" />
          <ElButton type="primary" size="small" @click="insertImage">插入图片</ElButton>
        </div>
      </ElPopover>

      <ElPopover v-model:visible="tablePopoverVisible" width="260" trigger="click">
        <template #reference>
          <ElButton :icon="Table" size="small" text title="插入表格" />
        </template>
        <div class="table-popover">
          <ElInput v-model.number="tableRows" type="number" min="1" max="10">
            <template #prepend>行</template>
          </ElInput>
          <ElInput v-model.number="tableCols" type="number" min="1" max="8">
            <template #prepend>列</template>
          </ElInput>
          <ElButton type="primary" size="small" @click="insertTable">插入表格</ElButton>
        </div>
      </ElPopover>

      <ElButton
        :icon="Eraser"
        size="small"
        text
        title="清除格式"
        @click="exec('removeFormat')"
      />
      <ElButton
        :icon="Heading1"
        size="small"
        text
        title="标题"
        @click="applyBlock('H2')"
      />
    </div>

    <div
      ref="editorRef"
      class="rich-text-content"
      :contenteditable="!disabled && !readonly"
      :data-placeholder="placeholder"
      :style="{ minHeight: `${minHeight}px`, maxHeight: `${maxHeight}px` }"
      role="textbox"
      aria-multiline="true"
      @blur="handleBlur"
      @focus="handleFocus"
      @input="emitChange"
      @paste="handlePaste"
    ></div>
  </div>
</template>

<style scoped>
.rich-text-editor {
  border: 1px solid var(--el-border-color);
  border-radius: 6px;
  background: var(--el-bg-color);
}

.rich-text-editor.is-disabled {
  opacity: 0.72;
}

.rich-text-toolbar {
  display: flex;
  min-height: 42px;
  align-items: center;
  gap: 2px;
  flex-wrap: wrap;
  border-bottom: 1px solid var(--el-border-color-lighter);
  padding: 5px 8px;
}

.block-select {
  width: 96px;
}

.rich-text-content {
  overflow: auto;
  padding: 12px 14px;
  outline: none;
  line-height: 1.7;
}

.rich-text-content:empty::before {
  color: var(--el-text-color-placeholder);
  content: attr(data-placeholder);
}

.rich-text-content :deep(h1),
.rich-text-content :deep(h2),
.rich-text-content :deep(h3) {
  margin: 0.8em 0 0.45em;
  font-weight: 650;
  line-height: 1.35;
}

.rich-text-content :deep(h1) {
  font-size: 24px;
}

.rich-text-content :deep(h2) {
  font-size: 20px;
}

.rich-text-content :deep(h3) {
  font-size: 17px;
}

.rich-text-content :deep(p) {
  margin: 0.45em 0;
}

.rich-text-content :deep(blockquote) {
  margin: 8px 0;
  border-left: 4px solid var(--el-border-color);
  padding-left: 14px;
  color: var(--el-text-color-secondary);
}

.rich-text-content :deep(img) {
  max-width: 100%;
  border-radius: 4px;
}

.rich-text-content :deep(table) {
  width: 100%;
  margin: 8px 0;
  border-collapse: collapse;
}

.rich-text-content :deep(td),
.rich-text-content :deep(th) {
  min-width: 48px;
  border: 1px solid var(--el-border-color);
  padding: 6px 8px;
}

.rich-text-content :deep(a) {
  color: var(--el-color-primary);
  text-decoration: underline;
}

.popover-form,
.table-popover {
  display: grid;
  gap: 8px;
}
</style>
