<template>
  <div class="art-full-height admin-guide-page">
    <ElCard class="art-card-xs flex flex-col h-full mt-0" shadow="never">
      <template #header>
        <div class="flex flex-wrap items-center justify-between gap-3">
          <div>
            <b>{{ $t('page.title') }}</b>
            <div v-if="meta.filePath" class="mt-1 text-xs text-g-500">
              {{ $t('page.meta.filePath') }}：{{ meta.filePath }}
              <span v-if="meta.updateTime" class="ml-3">
                {{ $t('page.meta.updateTime') }}：{{ meta.updateTime }}
              </span>
            </div>
          </div>
          <ElSpace wrap>
            <ElButton
              v-permission="'system:admin_guide:index:read'"
              :loading="loading"
              @click="loadContent"
            >
              <template #icon>
                <ArtSvgIcon icon="ri:refresh-line" />
              </template>
              {{ $t('page.toolbar.refresh') }}
            </ElButton>
            <ElButton
              v-if="!isEditing"
              v-permission="'system:admin_guide:index:edit'"
              type="primary"
              @click="startEdit"
            >
              <template #icon>
                <ArtSvgIcon icon="ri:pencil-line" />
              </template>
              {{ $t('page.toolbar.edit') }}
            </ElButton>
            <template v-if="isEditing">
              <ElButton @click="handleCancel">
                {{ $t('page.toolbar.cancel') }}
              </ElButton>
              <ElButton
                v-permission="'system:admin_guide:index:save'"
                type="primary"
                :loading="saving"
                @click="handleSave"
              >
                <template #icon>
                  <ArtSvgIcon icon="ri:save-line" />
                </template>
                {{ $t('page.toolbar.save') }}
              </ElButton>
            </template>
          </ElSpace>
        </div>
      </template>

      <div v-loading="loading" class="admin-guide-body flex-1 min-h-0">
        <SaMdEditor
          v-if="isEditing"
          v-model="editContent"
          class="admin-guide-editor"
          height="calc(100vh - 220px)"
          min-height="480px"
        />
        <div v-else class="admin-guide-read flex h-full min-h-0">
          <aside class="admin-guide-catalog flex-shrink-0">
            <div class="catalog-title">{{ $t('page.catalog.title') }}</div>
            <ElScrollbar class="catalog-scroll">
              <nav v-if="tocList.length" class="guide-toc">
                <button
                  v-for="(item, index) in tocList"
                  :key="`${item.level}-${item.text}-${index}`"
                  type="button"
                  class="guide-toc-item"
                  :class="[
                    `guide-toc-level-${item.level}`,
                    { 'is-active': activeTocIndex === index }
                  ]"
                  :title="item.text"
                  @click="scrollToHeading(item, index)"
                >
                  {{ item.text }}
                </button>
              </nav>
              <div v-else class="guide-toc-empty">{{ $t('page.catalog.empty') }}</div>
            </ElScrollbar>
          </aside>
          <div
            id="admin-guide-scroll"
            ref="previewScrollRef"
            class="admin-guide-preview-wrap flex-1 min-h-0 overflow-auto"
          >
            <MdPreview
              :editor-id="previewEditorId"
              :model-value="previewContent"
              :theme="previewTheme"
              preview-theme="github"
              no-img-zoom-in
              class="admin-guide-preview"
              @on-html-changed="handlePreviewHtmlReady"
            />
          </div>
        </div>
      </div>
    </ElCard>

    <ElImageViewer
      v-if="imageViewerVisible"
      :key="imageViewerIndex"
      :url-list="previewImageUrls"
      :initial-index="imageViewerIndex"
      :hide-on-click-modal="true"
      :z-index="3000"
      teleported
      @close="closeImageViewer"
    />
  </div>
</template>

<script setup lang="ts">
  import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
  import { useI18n } from 'vue-i18n'
  import { ElImageViewer, ElMessageBox } from 'element-plus'
  import { MdPreview } from 'md-editor-v3'
  import 'md-editor-v3/lib/preview.css'
  import SaMdEditor from '@/components/sai/sa-md-editor/index.vue'
  import { useSettingStore } from '@/store/modules/setting'
  import api from '@/api/system/admin_guide'

  defineOptions({ name: 'SystemAdminGuide' })

  interface GuideTocItem {
    level: number
    text: string
  }

  const { t } = useI18n()
  const settingStore = useSettingStore()

  const previewEditorId = 'admin-guide-preview'
  const previewScrollRef = ref<HTMLElement | null>(null)
  const loading = ref(false)
  const saving = ref(false)
  const isEditing = ref(false)
  const previewContent = ref('')
  const editContent = ref('')
  const originalContent = ref('')
  const meta = ref<{ filePath?: string; updateTime?: string }>({})
  const tocList = ref<GuideTocItem[]>([])
  const activeTocIndex = ref(-1)
  const previewImageUrls = ref<string[]>([])
  const imageViewerVisible = ref(false)
  const imageViewerIndex = ref(0)

  let previewImageClickHandler: ((event: Event) => void) | null = null
  let previewEnhanceTimer: ReturnType<typeof setTimeout> | null = null

  const previewTheme = computed(() => (settingStore.isDark ? 'dark' : 'light'))

  const getStaticFileBase = (): string => {
    const apiUrl = import.meta.env.VITE_API_URL || ''
    if (apiUrl.startsWith('http')) {
      return apiUrl.replace(/\/$/, '')
    }
    const proxyUrl = import.meta.env.VITE_API_PROXY_URL || ''
    if (proxyUrl) {
      return String(proxyUrl).replace(/\/$/, '')
    }
    return ''
  }

  const resolveGuideImages = (content: string): string => {
    const staticBase = getStaticFileBase()
    if (!staticBase) {
      return content
    }
    return content.replace(/!\[([^\]]*)\]\((\/docs\/picture\/[^)]+)\)/g, (_match, alt, path) => {
      return `![${alt}](${staticBase}${path})`
    })
  }

  const parseTocFromMarkdown = (content: string): GuideTocItem[] => {
    const items: GuideTocItem[] = []
    for (const line of content.split('\n')) {
      const match = line.match(/^(#{1,6})\s+(.+)$/)
      if (!match) {
        continue
      }
      items.push({
        level: match[1].length,
        text: match[2].trim()
      })
    }
    return items
  }

  const refreshTocList = () => {
    tocList.value = parseTocFromMarkdown(originalContent.value)
    activeTocIndex.value = -1
  }

  const collectPreviewImages = () => {
    const container = previewScrollRef.value
    if (!container) {
      previewImageUrls.value = []
      return
    }
    const imgs = container.querySelectorAll('img')
    previewImageUrls.value = Array.from(imgs).map((img) => {
      const el = img as HTMLImageElement
      return el.currentSrc || el.src
    })
  }

  const findImageIndex = (img: HTMLImageElement): number => {
    collectPreviewImages()
    const targetSrc = img.currentSrc || img.src
    let index = previewImageUrls.value.indexOf(targetSrc)
    if (index >= 0) {
      return index
    }
    index = previewImageUrls.value.findIndex((url) => targetSrc.endsWith(url) || url.endsWith(targetSrc))
    if (index >= 0) {
      return index
    }
    previewImageUrls.value = [...previewImageUrls.value, targetSrc]
    return previewImageUrls.value.length - 1
  }

  const openImageViewerByImg = (img: HTMLImageElement) => {
    const index = findImageIndex(img)
    openImageViewer(index)
  }

  const openImageViewer = (index: number) => {
    if (index < 0 || index >= previewImageUrls.value.length) {
      return
    }
    imageViewerIndex.value = index
    imageViewerVisible.value = true
  }

  const closeImageViewer = () => {
    imageViewerVisible.value = false
  }

  const unbindPreviewImageClick = (clearTimer = true) => {
    const container = previewScrollRef.value
    if (container && previewImageClickHandler) {
      container.removeEventListener('click', previewImageClickHandler, true)
    }
    previewImageClickHandler = null
    if (clearTimer && previewEnhanceTimer) {
      clearTimeout(previewEnhanceTimer)
      previewEnhanceTimer = null
    }
  }

  const bindPreviewImageClick = () => {
    unbindPreviewImageClick(false)
    const container = previewScrollRef.value
    if (!container) {
      return
    }

    collectPreviewImages()
    previewImageClickHandler = (event: Event) => {
      const imgEl = (event.target as Element | null)?.closest('img')
      if (!(imgEl instanceof HTMLImageElement)) {
        return
      }
      if (!container.contains(imgEl)) {
        return
      }
      event.preventDefault()
      event.stopPropagation()
      openImageViewerByImg(imgEl)
    }
    container.addEventListener('click', previewImageClickHandler, true)

    const imgs = container.querySelectorAll('img')
    imgs.forEach((img) => {
      const el = img as HTMLImageElement
      el.style.cursor = 'zoom-in'
      el.title = t('page.image.zoom')
    })
  }

  const handlePreviewHtmlReady = () => {
    if (previewEnhanceTimer) {
      clearTimeout(previewEnhanceTimer)
    }
    previewEnhanceTimer = setTimeout(() => {
      bindPreviewImageClick()
      previewEnhanceTimer = null
    }, 50)
  }

  const setupPreviewEnhancements = async () => {
    await nextTick()
    refreshTocList()
    handlePreviewHtmlReady()
  }

  const scrollToHeading = (item: GuideTocItem, index: number) => {
    const container = previewScrollRef.value
    if (!container) {
      return
    }
    const headings = container.querySelectorAll('h1, h2, h3, h4, h5, h6')
    for (const heading of Array.from(headings)) {
      if (heading.textContent?.trim() === item.text) {
        activeTocIndex.value = index
        heading.scrollIntoView({ behavior: 'smooth', block: 'start' })
        return
      }
    }
  }

  const loadContent = async () => {
    loading.value = true
    unbindPreviewImageClick()
    try {
      const res = await api.read()
      const data = res as { content?: string; file_path?: string; update_time?: string }
      const rawContent = data.content ?? ''
      editContent.value = rawContent
      originalContent.value = rawContent
      previewContent.value = resolveGuideImages(rawContent)
      meta.value = {
        filePath: data.file_path,
        updateTime: data.update_time
      }
      await setupPreviewEnhancements()
    } catch {
      // 错误由 http 工具统一处理
    } finally {
      loading.value = false
    }
  }

  const startEdit = () => {
    editContent.value = originalContent.value
    isEditing.value = true
    unbindPreviewImageClick()
  }

  const handleCancel = async () => {
    if (editContent.value !== originalContent.value) {
      try {
        await ElMessageBox.confirm(t('page.message.cancelConfirm'), {
          type: 'warning'
        })
      } catch {
        return
      }
    }
    editContent.value = originalContent.value
    isEditing.value = false
    await setupPreviewEnhancements()
  }

  const handleSave = async () => {
    if (!isEditing.value) {
      return
    }
    saving.value = true
    try {
      const res = await api.save({ content: editContent.value })
      const data = res as { content?: string; file_path?: string; update_time?: string }
      const savedContent = data.content ?? editContent.value
      editContent.value = savedContent
      originalContent.value = savedContent
      previewContent.value = resolveGuideImages(savedContent)
      meta.value = {
        filePath: data.file_path ?? meta.value.filePath,
        updateTime: data.update_time ?? meta.value.updateTime
      }
      isEditing.value = false
      await setupPreviewEnhancements()
    } catch {
      // 错误由 http 工具统一处理
    } finally {
      saving.value = false
    }
  }

  watch(previewContent, async () => {
    if (!isEditing.value) {
      await nextTick()
      handlePreviewHtmlReady()
    }
  })

  onMounted(() => {
    loadContent()
  })

  onBeforeUnmount(() => {
    unbindPreviewImageClick()
  })
</script>

<style scoped lang="scss">
  .admin-guide-page {
    :deep(.el-card__body) {
      display: flex;
      flex: 1;
      flex-direction: column;
      min-height: 0;
      padding-top: 12px;
    }
  }

  .admin-guide-body {
    width: 100%;
    min-height: 0;
  }

  .admin-guide-read {
    min-height: 0;
    gap: 0;
  }

  .admin-guide-catalog {
    display: flex;
    flex-direction: column;
    width: 240px;
    min-height: 0;
    padding: 4px 12px 12px 4px;
    border-right: 1px solid var(--el-border-color-lighter);
  }

  .catalog-title {
    flex-shrink: 0;
    margin-bottom: 10px;
    font-size: 14px;
    font-weight: 600;
    color: var(--el-text-color-primary);
  }

  .catalog-scroll {
    flex: 1;
    min-height: 0;
  }

  .guide-toc {
    display: flex;
    flex-direction: column;
    gap: 2px;
    padding-right: 8px;
  }

  .guide-toc-item {
    display: block;
    width: 100%;
    padding: 5px 8px;
    overflow: hidden;
    font-size: 13px;
    line-height: 1.5;
    color: var(--el-text-color-regular);
    text-align: left;
    text-overflow: ellipsis;
    white-space: nowrap;
    cursor: pointer;
    background: transparent;
    border: none;
    border-radius: 4px;
    transition: color 0.2s, background-color 0.2s;

    &:hover,
    &.is-active {
      color: var(--el-color-primary);
      background-color: var(--el-color-primary-light-9);
    }
  }

  .guide-toc-level-1 {
    padding-left: 8px;
    font-weight: 600;
  }

  .guide-toc-level-2 {
    padding-left: 20px;
  }

  .guide-toc-level-3 {
    padding-left: 32px;
    font-size: 12px;
  }

  .guide-toc-level-4 {
    padding-left: 44px;
    font-size: 12px;
  }

  .guide-toc-level-5,
  .guide-toc-level-6 {
    padding-left: 56px;
    font-size: 12px;
  }

  .guide-toc-empty {
    padding: 8px;
    font-size: 13px;
    color: var(--el-text-color-secondary);
  }

  .admin-guide-preview-wrap {
    position: relative;
    padding: 8px 12px 8px 16px;
  }

  .admin-guide-preview {
    padding: 0;
  }

  .admin-guide-editor {
    width: 100%;
  }

  @media (max-width: 768px) {
    .admin-guide-read {
      flex-direction: column;
    }

    .admin-guide-catalog {
      width: 100%;
      max-height: 200px;
      padding-right: 0;
      border-right: none;
      border-bottom: 1px solid var(--el-border-color-lighter);
    }
  }
</style>

<style lang="scss">
  .admin-guide-preview-wrap .md-editor-preview-wrapper img,
  .admin-guide-preview-wrap .md-editor-preview img,
  .admin-guide-editor .md-editor-preview-wrapper img,
  .admin-guide-editor .md-editor-preview img {
    display: block;
    width: 50% !important;
    max-width: 50% !important;
    height: auto !important;
    margin: 8px 0;
    cursor: zoom-in;
  }
</style>
