<!-- =====================================================================
  ElementView.vue — 单元素渲染（替代 editor.js 的 renderElement）
  editor / present / thumb 三处复用此组件
  ===================================================================== -->
<script setup lang="ts">
import { computed } from 'vue'
import type { SlideElement, BgKey } from '../../core/types'
import { store, resolveColor, isDarkBg } from '../../core/store'
import { resolveRef } from '../../core/assets'
import { segmentsToHtml, markdownToSegments, hasFormatting } from '../../core/richtext'
import ChartView from './ChartView.vue'

/* ---------- LaTeX 子集渲染（公式元素） ---------- */
const LATEX_SYMBOLS: Record<string, string> = {
  alpha: 'α', beta: 'β', gamma: 'γ', delta: 'δ', epsilon: 'ε', theta: 'θ', lambda: 'λ', mu: 'μ', pi: 'π', sigma: 'σ', omega: 'ω', phi: 'φ',
  sum: '∑', prod: '∏', int: '∫', infty: '∞',
  leq: '≤', geq: '≥', neq: '≠', times: '×', pm: '±', cdot: '·', div: '÷',
  rightarrow: '→', leftarrow: '←', Rightarrow: '⇒', in: '∈', notin: '∉', subset: '⊂', supset: '⊃', cup: '∪', cap: '∩',
  forall: '∀', exists: '∃', partial: '∂', nabla: '∇'
}

function escapeHtml(s: string): string {
  return s.replace(/&/g, '&amp;').replace(/</g, '&lt;').replace(/>/g, '&gt;')
}

function renderLatex(src: string): string {
  let s = escapeHtml(src)
  // 1. \frac{a}{b}
  s = s.replace(/\\frac\{([^{}]*)\}\{([^{}]*)\}/g, (_m, a: string, b: string) =>
    `<span class="frac"><span class="num">${a}</span><span class="den">${b}</span></span>`)
  // 2. \sqrt[n]{x} 和 \sqrt{x}
  s = s.replace(/\\sqrt\[([^\[\]]+)\]\{([^{}]*)\}/g, (_m, n: string, x: string) =>
    `<span class="sqrt"><span class="rad">${n}</span><span class="overline">${x}</span></span>`)
  s = s.replace(/\\sqrt\{([^{}]*)\}/g, (_m, x: string) =>
    `<span class="sqrt"><span class="overline">${x}</span></span>`)
  // 3. \symbol → Unicode
  s = s.replace(/\\([a-zA-Z]+)/g, (_m, name: string) => {
    if (Object.prototype.hasOwnProperty.call(LATEX_SYMBOLS, name)) return LATEX_SYMBOLS[name]
    return name
  })
  // 4. ^{...} 和 ^x
  s = s.replace(/\^\{([^{}]*)\}/g, (_m, x: string) => `<sup>${x}</sup>`)
  s = s.replace(/\^([0-9a-zA-Z])/g, (_m, x: string) => `<sup>${x}</sup>`)
  // 5. _{...} 和 _x
  s = s.replace(/_\{([^{}]*)\}/g, (_m, x: string) => `<sub>${x}</sub>`)
  s = s.replace(/_([0-9a-zA-Z])/g, (_m, x: string) => `<sub>${x}</sub>`)
  return s
}

const props = defineProps<{
  el: SlideElement
  bg: BgKey | string
  /** 是否为编辑态（启用 contenteditable） */
  edit?: boolean
  /** 是否显示八向缩放手柄 */
  showHandles?: boolean
  /** 是否为缩略图端（视频等重元素降级为静态渲染） */
  thumb?: boolean
}>()

const emit = defineEmits<{
  (e: 'blur', id: string, field: string, value: string): void
}>()

/** 当前页背景是否深色 → 文字是否需要反相（isDarkBg 复用 store 导出的统一实现） */
const dark = computed(() => {
  const d = store.state.deck ? (props.el.type === 'card' ? false : isDarkBg(props.bg)) : false
  return d
})

const s = computed(() => props.el.style || {})

const boxStyle = computed(() => {
  const st = s.value
  const css: Record<string, string> = {}
  if (st.opacity != null) css.opacity = String(st.opacity)
  if (st.fontSize) css.fontSize = st.fontSize + 'px'
  if (st.align) css.textAlign = st.align
  if (st.bold === true) css.fontWeight = '700'
  if (st.bold === false) css.fontWeight = '400'
  if (st.italic === true) css.fontStyle = 'italic'
  const color = resolveColor(st.color, dark.value)
  if (color) css.color = color
  return css
})

const dataList = computed(() => (props.el.content || '').split('\n'))

/** 媒体源解析：把 "asset:<id>" 引用解析为可渲染 URL（本地 ObjectURL 或云端 URL），其余原样 */
const mediaSrc = computed(() => resolveRef(props.el.content))

/** 表格解析 */
const tableRows = computed(() => {
  const lines = (props.el.content || '').split('\n').map(l => l.trim()).filter(Boolean)
  const rows: string[][] = []
  for (const line of lines) {
    if (/^\|?[\s:-]+\|[\s:-|]+$/.test(line)) continue
    const cells = line.replace(/^\||\|$/g, '').split('|').map(c => c.trim())
    rows.push(cells)
  }
  return rows
})

/** 公式渲染 */
const renderedFormula = computed(() => renderLatex(props.el.content || ''))

/** 形状背景 */
const shapeBg = computed(() => {
  const st = s.value
  const fill = resolveColor(st.fill, false)
  if (st.gradient) {
    return 'linear-gradient(135deg, ' + fill + ' 0%, ' + resolveColor('accent', false) + ' 100%)'
  }
  return fill
})

/** 各形状 clip-path（rect/circle 走 CSS 圆角，不走 clip） */
const SHAPE_CLIPS: Partial<Record<string, string>> = {
  triangle: 'polygon(50% 0, 100% 100%, 0 100%)',
  diamond: 'polygon(50% 0, 100% 50%, 50% 100%, 0 50%)',
  pentagon: 'polygon(50% 0, 100% 38%, 82% 100%, 18% 100%, 0 38%)',
  hexagon: 'polygon(25% 0, 75% 0, 100% 50%, 75% 100%, 25% 100%, 0 50%)',
  star: 'polygon(50% 0, 61% 35%, 98% 35%, 68% 57%, 79% 91%, 50% 70%, 21% 91%, 32% 57%, 2% 35%, 39% 35%)',
  arrow: 'polygon(0 20%, 75% 20%, 75% 0, 100% 50%, 75% 100%, 75% 80%, 0 80%)',
  chevron: 'polygon(0 0, 75% 0, 100% 50%, 75% 100%, 0 100%, 25% 50%)',
  bubble: 'polygon(0 0, 100% 0, 100% 75%, 25% 75%, 10% 100%, 15% 75%, 0 75%)'
}
const shapeClip = computed(() => SHAPE_CLIPS[s.value.shapeType || 'rect'] || null)

/** 形状文字（md 渲染；segments 存在时优先） */
const renderedShapeText = computed(() => {
  if (props.el.segments && props.el.segments.length && hasFormatting(props.el.segments)) {
    return segmentsToHtml(props.el.segments)
  }
  return segmentsToHtml(markdownToSegments(props.el.content || ''))
})

/** 形状文字层样式：对齐 + 未设色时默认白（填充色底） */
const shapeTextStyle = computed(() => {
  const css: Record<string, string> = { textAlign: s.value.align || 'center' }
  if (!s.value.color) css.color = '#fff'
  return css
})

const cardAccentColor = computed(() => resolveColor(s.value.accent, false))

/** 卡片：content 第一行=标题，其余=正文 */
const cardParts = computed(() => {
  const lines = (props.el.content || '').split('\n')
  return { title: lines[0] || '', body: lines.slice(1).join('\n') }
})

/** 卡片标题/正文失焦：从 DOM 读两块文字拼回 content（标题在前的兄弟节点）。
 *  标题空但正文非空时保留空行占位，避免正文首行漂移成标题。 */
function onBlurCard(e: Event) {
  const node = e.target as HTMLElement
  const card = node.closest('.el-card')
  if (!card) return
  const title = (card.querySelector('.card-title')?.textContent || '').trim()
  const body = (card.querySelector('.card-body')?.textContent || '').trim()
  if (!title && !body) { emit('blur', props.el.id, 'content', ''); return }
  if (body) emit('blur', props.el.id, 'content', title + '\n' + body)
  else emit('blur', props.el.id, 'content', title)
}

/* ---------- Rich text（segments 结构化富文本）---------- */
/** 是否有 segments（结构化富文本），优先于 content */
const hasSegments = computed(() => {
  return !!(props.el.segments && props.el.segments.length && hasFormatting(props.el.segments))
})

/** title/text/quote 的渲染 HTML */
const renderedContent = computed(() => {
  if (hasSegments.value) return segmentsToHtml(props.el.segments!)
  // 降级：检查 content 是否含 Markdown 语法（兼容旧数据）
  return null
})

/** list 每行的渲染 HTML（segments 或纯文本） */
const renderedListItems = computed(() => {
  if (hasSegments.value) {
    return props.el.segments!.map(line => segmentsToHtml([line]))
  }
  return null
})

/** contenteditable 失焦回调 */
function onBlur(e: Event, field: string) {
  const node = e.target as HTMLElement
  let val: string
  if (field === 'label') {
    val = node.textContent || ''
  } else if (props.el.type === 'list') {
    val = node.innerText.replace(/\r/g, '').trim()
  } else {
    val = node.textContent || ''
  }
  emit('blur', props.el.id, field, val)
}
</script>

<template>
  <div
    class="el"
    :data-id="el.id"
    :data-type="el.type"
    :data-anim="s.anim"
    :data-shape="el.type === 'shape' ? (s.shapeType || 'rect') : undefined"
    :style="{
      left: el.x + '%',
      top: el.y + '%',
      width: el.w + '%',
      height: el.h + '%',
      ...boxStyle
    }"
  >
    <!-- 标题 / 正文 / 金句 -->
    <template v-if="el.type === 'title' || el.type === 'text' || el.type === 'quote'">
      <!-- 编辑态：纯文本 contenteditable -->
      <div
        v-if="edit"
        class="el-text"
        style="white-space: pre-wrap; width: 100%"
        contenteditable="true"
        data-edit="content"
        @blur="onBlur($event, 'content')"
      >{{ el.content }}</div>
      <!-- 非编辑态 + 有 segments：渲染结构化富文本 -->
      <div
        v-else-if="renderedContent"
        class="el-text el-text-rich"
        v-html="renderedContent"
      ></div>
      <!-- 非编辑态 + 纯文本 -->
      <div v-else class="el-text" style="white-space: pre-wrap; width: 100%">{{ el.content }}</div>
    </template>

    <!-- 列表 -->
    <template v-else-if="el.type === 'list'">
      <!-- 编辑态 -->
      <div v-if="edit" class="el-list" contenteditable="true" data-edit="content" @blur="onBlur($event, 'content')">
        <div v-for="(line, i) in dataList" :key="i" class="li">{{ line }}</div>
      </div>
      <!-- 非编辑态 + 有 segments -->
      <div v-else-if="renderedListItems" class="el-list">
        <div v-for="(html, i) in renderedListItems" :key="i" class="li" v-html="html"></div>
      </div>
      <!-- 非编辑态 + 纯文本 -->
      <div v-else class="el-list">
        <div v-for="(line, i) in dataList" :key="i" class="li">{{ line }}</div>
      </div>
    </template>

    <!-- 数据 -->
    <template v-else-if="el.type === 'stat'">
      <div class="el-stat">
        <div
          class="num"
          :contenteditable="edit"
          data-edit="content"
          @blur="edit && onBlur($event, 'content')"
        >{{ el.content }}</div>
        <div
          class="label"
          :style="{ fontSize: (s.labelSize || 16) + 'px', color: resolveColor(s.labelColor, dark) }"
          :contenteditable="edit"
          data-edit="label"
          @blur="edit && onBlur($event, 'label')"
        >{{ s.label }}</div>
      </div>
    </template>

    <!-- 图片（无内容时显示占位提示，不渲染空 src 的裂图） -->
    <template v-else-if="el.type === 'image'">
      <div v-if="!el.content" class="el-image-empty">
        <span class="el-image-empty-icon">🖼</span>
        <span class="el-image-empty-text">拖入图片 · 属性面板「本地图片」或 AI 配图</span>
      </div>
      <img v-else class="el-image" :src="mediaSrc" draggable="false" />
    </template>

    <!-- 视频（缩略图端降级静态；preload=metadata 控制加载开销） -->
    <template v-else-if="el.type === 'video'">
      <div v-if="!el.content" class="el-image-empty">
        <span class="el-image-empty-icon">🎬</span>
        <span class="el-image-empty-text">属性面板选择本地视频或填入 URL</span>
      </div>
      <!-- 缩略图：poster 静态图（无 poster 黑底▶），不加载视频 -->
      <div v-else-if="thumb" class="el-video-thumb">
        <img v-if="s.poster" :src="s.poster" draggable="false" />
        <span v-else class="el-video-thumb-play">▶</span>
      </div>
      <!-- 编辑/演示：可播放（点选拖动交给画布 mousedown 冒泡，不阻断） -->
      <video
        v-else
        class="el-video"
        :src="mediaSrc"
        :poster="s.poster || undefined"
        controls
        preload="metadata"
        :autoplay="!!s.autoplay"
        :loop="!!s.loop"
        :muted="!!s.muted"
        :style="{ objectFit: s.fit || 'contain' }"
        draggable="false"
        @click.stop
      ></video>
    </template>

    <!-- 形状 -->
    <template v-else-if="el.type === 'shape'">
      <div v-if="s.shapeType === 'circle' || s.shapeType === 'ellipse'" class="el-shape" :class="{ gradient: s.gradient }" :style="{ borderRadius: '50%', background: shapeBg }"></div>
      <div v-else-if="shapeClip" class="el-shape" :class="{ gradient: s.gradient }" :style="{ background: shapeBg, clipPath: shapeClip }"></div>
      <div v-else class="el-shape" :class="{ gradient: s.gradient }" :style="{ background: shapeBg, borderRadius: (s.radius != null ? s.radius : 12) + 'px' }"></div>
      <!-- 形状文字层：编辑态 contenteditable，非编辑态渲染 md；未设色时默认白（填充色底） -->
      <div
        v-if="edit"
        class="el-shape-text"
        :style="!s.color ? { color: '#fff' } : {}"
        contenteditable="true"
        data-edit="content"
        @blur="onBlur($event, 'content')"
      >{{ el.content }}</div>
      <div
        v-else-if="el.content"
        class="el-shape-text"
        :style="shapeTextStyle"
        v-html="renderedShapeText"
      ></div>
    </template>

    <!-- 图表 -->
    <template v-else-if="el.type === 'chart'">
      <ChartView :content="el.content" :style="s" :dark="dark" />
    </template>

    <!-- 卡片 -->
    <template v-else-if="el.type === 'card'">
      <div class="card-bar" :style="{ background: cardAccentColor }"></div>
      <div class="el-card">
        <div v-if="s.icon" class="card-icon">{{ s.icon }}</div>
        <div
          class="card-title"
          :contenteditable="edit"
          data-edit="content"
          @blur="edit && onBlurCard($event)"
        >{{ cardParts.title }}</div>
        <div
          class="card-body"
          :contenteditable="edit"
          data-edit="content"
          @blur="edit && onBlurCard($event)"
        >{{ cardParts.body }}</div>
      </div>
    </template>

    <!-- 表格 -->
    <template v-else-if="el.type === 'table'">
      <table class="el-table">
        <thead v-if="s.header !== false && tableRows.length">
          <tr><th v-for="(c, i) in tableRows[0]" :key="i">{{ c }}</th></tr>
        </thead>
        <tbody>
          <tr v-for="(row, ri) in tableRows.slice(s.header !== false ? 1 : 0)" :key="ri">
            <td v-for="(c, ci) in row" :key="ci">{{ c }}</td>
          </tr>
        </tbody>
      </table>
    </template>

    <!-- 代码 -->
    <template v-else-if="el.type === 'code'">
      <pre class="el-code">
        <code>{{ el.content }}</code>
        <span v-if="s.lang" class="code-lang">{{ s.lang }}</span>
      </pre>
    </template>

    <!-- 公式 -->
    <template v-else-if="el.type === 'formula'">
      <div class="el-formula" v-html="renderedFormula"></div>
    </template>

    <!-- 八向缩放手柄（仅编辑态选中时） -->
    <template v-if="showHandles">
      <div
        v-for="h in ['tl','tm','tr','lm','rm','bl','bm','br']"
        :key="h"
        class="handle"
        :class="h"
        :data-handle="h"
      ></div>
    </template>
  </div>
</template>
