<template>
  <div v-if="visible" class="modal-mask">
    <div
      ref="dialogRef"
      class="format-dialog scale-in"
      role="dialog"
      aria-modal="true"
      aria-labelledby="article-format-dialog-title"
      aria-describedby="article-format-dialog-desc"
      tabindex="-1"
      @keydown="handleKeydown"
    >
      <!-- 覆盖确认 / 应用守卫的进行中态：整块大转圈，按钮排消失（还原 enspeak :461-480） -->
      <div v-if="loading && mode !== 'paste'" class="dialog-loading">
        <svg class="spinner spinner-lg" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5" stroke-linecap="round">
          <path d="M12 3a9 9 0 1 0 9 9" />
        </svg>
        <div id="article-format-dialog-title" class="dialog-title">{{ lang.ssArticleAutoFormatting }}</div>
        <div id="article-format-dialog-desc" class="dialog-hint">{{ lang.ssArticleAutoFormattingHint }}</div>
      </div>

      <template v-else>
        <div class="dialog-body">
          <div id="article-format-dialog-title" class="dialog-title">{{ title }}</div>
          <div id="article-format-dialog-desc" class="dialog-message">{{ bodyText }}</div>
        </div>

        <div class="dialog-actions">
          <button class="dialog-btn btn-secondary" :disabled="loading" @click="emit('cancel')">
            {{ cancelLabel }}
          </button>
          <button class="dialog-btn btn-primary" :disabled="loading" @click="emit('confirm')">
            <!-- 粘贴弹窗的进行中态：确认键内联小转圈（还原 enspeak :443-450） -->
            <svg
              v-if="loading"
              class="spinner spinner-sm"
              viewBox="0 0 24 24"
              fill="none"
              stroke="currentColor"
              stroke-width="2.5"
              stroke-linecap="round"
            >
              <path d="M12 3a9 9 0 1 0 9 9" />
            </svg>
            {{ loading ? lang.ssArticleAutoFormatting : confirmLabel }}
          </button>
        </div>
      </template>
    </div>
  </div>
</template>

<script lang="ts" setup>
import { computed, nextTick, onBeforeUnmount, ref, watch } from 'vue'
import { lang } from '@/main'

const props = defineProps<{
  visible: boolean
  mode: 'paste' | 'overwrite' | 'guard'
  loading: boolean
}>()

const emit = defineEmits<{
  (e: 'confirm'): void
  (e: 'cancel'): void
}>()

// 焦点陷阱 / 键盘行为：与同目录 TaskHintModal.vue 同一套实现，两个弹框统一风格
const dialogRef = ref<HTMLElement | null>(null)
const previouslyFocusedElement = ref<HTMLElement | null>(null)

const focusableSelector = [
  'a[href]',
  'button:not([disabled])',
  'textarea:not([disabled])',
  'input:not([disabled])',
  'select:not([disabled])',
  '[tabindex]:not([tabindex="-1"])',
].join(',')

const getFocusableElements = () => {
  const dialog = dialogRef.value
  if (!dialog) return []

  return Array.from(dialog.querySelectorAll<HTMLElement>(focusableSelector)).filter((el) => {
    if (el.hasAttribute('disabled') || el.getAttribute('aria-hidden') === 'true') return false
    return el.offsetParent !== null || el === document.activeElement
  })
}

const restorePreviousFocus = () => {
  const element = previouslyFocusedElement.value
  previouslyFocusedElement.value = null

  if (element && document.contains(element)) {
    element.focus()
  }
}

const focusDialog = async () => {
  previouslyFocusedElement.value = document.activeElement instanceof HTMLElement
    ? document.activeElement
    : null

  await nextTick()
  dialogRef.value?.focus()
}

const containTabFocus = (event: KeyboardEvent) => {
  const dialog = dialogRef.value
  if (!dialog) return

  const focusableElements = getFocusableElements()
  if (!focusableElements.length) {
    event.preventDefault()
    dialog.focus()
    return
  }

  const firstElement = focusableElements[0]
  const lastElement = focusableElements[focusableElements.length - 1]
  const activeElement = document.activeElement

  if (event.shiftKey) {
    if (activeElement === dialog || activeElement === firstElement || !dialog.contains(activeElement)) {
      event.preventDefault()
      lastElement.focus()
    }
    return
  }

  if (activeElement === dialog) {
    event.preventDefault()
    firstElement.focus()
    return
  }

  if (activeElement === lastElement) {
    event.preventDefault()
    firstElement.focus()
  }
}

const handleKeydown = (event: KeyboardEvent) => {
  if (event.key === 'Escape') {
    if (props.loading) return // 进行中态 Escape 失效，与取消按钮 :disabled="loading" 一致
    event.preventDefault()
    emit('cancel')
    return
  }

  if (event.key === 'Tab') {
    containTabFocus(event)
  }
}

watch(
  () => props.visible,
  (visible) => {
    if (visible) {
      void focusDialog()
      return
    }

    restorePreviousFocus()
  },
  { flush: 'post', immediate: true },
)

onBeforeUnmount(() => {
  restorePreviousFocus()
})

// 文案按 mode 分档。必须使用静态key访问（避免动态下标）
// 扫描脚本依赖正则表达式检测源码中的key引用
const title = computed(() => {
  if (props.mode === 'paste') return lang.ssArticlePasteFormatTitle as string
  if (props.mode === 'overwrite') return lang.ssArticleOverwriteFormatTitle as string
  return lang.ssArticleGuardTitle as string
})

const bodyText = computed(() => {
  if (props.mode === 'paste') return lang.ssArticlePasteFormatMessage as string
  if (props.mode === 'overwrite') return lang.ssArticleOverwriteFormatMessage as string
  return lang.ssArticleGuardMessage as string
})

const cancelLabel = computed(() =>
  (props.mode === 'guard' ? lang.ssArticleGuardManual : lang.ssArticleFormatCancel) as string
)

const confirmLabel = computed(() =>
  (props.mode === 'guard' ? lang.ssArticleAutoFormat : lang.ssArticleFormatConfirm) as string
)
</script>

<style lang="scss" scoped>
/* 值抄自同仓 TaskHintModal.vue（scoped 无法跨组件复用，故复制）= enspeak bg-black/30 + inset-0 + z-50 */
.modal-mask {
  position: fixed;
  inset: 0;
  z-index: 50;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 16px;
  background: rgba(0, 0, 0, 0.3);
  backdrop-filter: blur(2px);
}

/* enspeak: bg-white rounded-xl shadow-2xl w-[380px] p-6 */
.format-dialog {
  width: 380px;
  max-width: 100%;
  padding: 24px;
  background: #fff;
  border-radius: 16px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.15);
  box-sizing: border-box;
}

/* enspeak: animate-in fade-in zoom-in duration-200 */
.scale-in {
  animation: scale-in 0.18s ease-out;
}

@keyframes scale-in {
  from {
    opacity: 0;
    transform: scale(0.96);
  }

  to {
    opacity: 1;
    transform: scale(1);
  }
}

.dialog-body {
  text-align: center;
  margin-bottom: 24px;
}

/* enspeak: text-[15px] font-semibold text-gray-800 mb-2 */
.dialog-title {
  font-size: 15px;
  font-weight: 600;
  color: #1f2937;
  margin-bottom: 8px;
}

/* enspeak: text-[13px] text-gray-600 */
.dialog-message {
  font-size: 13px;
  line-height: 1.6;
  color: #4b5563;
}

.dialog-loading {
  text-align: center;
  padding: 8px 0;
}

/* enspeak: text-[13px] text-gray-500 */
.dialog-hint {
  font-size: 13px;
  color: #6b7280;
}

/* enspeak: flex gap-3 */
.dialog-actions {
  display: flex;
  gap: 12px;
}

/* enspeak: flex-1 h-10 rounded-lg text-[13px] font-medium */
.dialog-btn {
  flex: 1;
  height: 40px;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 6px;
  border-radius: 8px;
  font-size: 13px;
  font-weight: 500;
  cursor: pointer;
  transition: background 0.2s;

  &:disabled {
    cursor: not-allowed;
  }
}

/* enspeak: border-gray-200 text-gray-700 hover:bg-gray-50 disabled:opacity-50 */
.btn-secondary {
  border: 1px solid #e5e7eb;
  background: #fff;
  color: #374151;

  &:hover:not(:disabled) {
    background: #f9fafb;
  }

  &:disabled {
    opacity: 0.5;
  }
}

/* enspeak: bg-orange-500 text-white hover:bg-orange-600 */
.btn-primary {
  border: none;
  background: #f97316;
  color: #fff;

  &:hover:not(:disabled) {
    background: #ea580c;
  }
}

.spinner {
  animation: spin 0.8s linear infinite;
  flex-shrink: 0;
}

/* enspeak: LoaderIcon w-4 h-4 */
.spinner-sm {
  width: 16px;
  height: 16px;
}

/* enspeak: LoaderIcon w-8 h-8 text-orange-500 mx-auto mb-4 */
.spinner-lg {
  width: 32px;
  height: 32px;
  color: #f97316;
  display: block;
  margin: 0 auto 16px;
}

@keyframes spin {
  to {
    transform: rotate(360deg);
  }
}
</style>
