<script setup lang="ts">
import QuickNoteEditor from './QuickNoteEditor.vue'
import {
  createQuickNoteModalState,
  markSaveFailed,
  markSaveSucceeded,
  updateDraftContent,
} from './quick-note-modal-state'
import {
  clampModalRect,
  createDefaultModalRect,
} from './quick-note-modal-layout'

interface QuickNotePayload {
  quickNote: {
    content: string
    updatedAt: string | null
  }
}

const open = defineModel<boolean>('open', { default: false })
const router = useRouter()

const { fetchData, getApiErrorMessage } = useClientApi()
const toast = useToast()

const modalState = ref(createQuickNoteModalState(''))
const quickNoteEditorRef = ref<null | { flushValue: () => void }>(null)
const loading = ref(false)
const saving = ref(false)
const loadError = ref('')
const fullScreen = ref(false)
const isMobileViewport = ref(false)
const lastSavedAt = ref<string | null>(null)
const allowSilentClose = ref(false)
let removeRouteGuard: (() => void) | null = null
let viewportMql: MediaQueryList | null = null
let onViewportChange: ((event: MediaQueryListEvent) => void) | null = null

const modalRect = reactive({ left: 0, top: 0, width: 0, height: 0 })
const dragging = ref(false)
const resizing = ref(false)
type ResizeDirection = 'n' | 's' | 'e' | 'w' | 'ne' | 'nw' | 'se' | 'sw'
let resizeDirection: ResizeDirection = 'se'
let dragStartPointerX = 0
let dragStartPointerY = 0
let dragStartLeft = 0
let dragStartTop = 0
let resizeStartPointerX = 0
let resizeStartPointerY = 0
let resizeStartWidth = 0
let resizeStartHeight = 0
let resizeStartLeft = 0
let resizeStartTop = 0

const MODAL_MARGIN_PX = 24
const MIN_MODAL_WIDTH_PX = 640
const MIN_MODAL_HEIGHT_PX = 420

const draftContent = computed({
  get: () => modalState.value.draftContent,
  set: (value: string) => {
    modalState.value = updateDraftContent(modalState.value, value)
  },
})

const isDirty = computed(() => modalState.value.isDirty)
const effectiveFullscreen = computed(() => fullScreen.value || isMobileViewport.value)
const editorResizeSignal = computed(() => `${modalRect.width}x${modalRect.height}-${effectiveFullscreen.value ? 'full' : 'windowed'}`)
const editorReloadToken = ref(0)

function syncDraftFromEditor() {
  quickNoteEditorRef.value?.flushValue()
}

function handleEditorContentChange(nextValue: string) {
  if (nextValue === modalState.value.draftContent) {
    return
  }
  modalState.value = updateDraftContent(modalState.value, nextValue)
}

function reloadEditor() {
  syncDraftFromEditor()
  editorReloadToken.value += 1
  toast.add({
    title: '编辑器已重新加载',
    color: 'info',
  })
}

const cardStyle = computed(() => {
  if (effectiveFullscreen.value) {
    return {}
  }
  return {
    left: `${modalRect.left}px`,
    top: `${modalRect.top}px`,
    width: `${modalRect.width}px`,
    height: `${modalRect.height}px`,
  }
})

const cardClass = computed(() => {
  if (effectiveFullscreen.value) {
    return 'h-full w-full rounded-none'
  }
  return 'absolute max-w-none rounded-xl'
})

const overlayClass = computed(() => {
  return 'fixed inset-0 z-[60] bg-black/35 backdrop-blur-[1px] pointer-events-none'
})

function stopDragging() {
  if (!dragging.value) {
    return
  }
  dragging.value = false
  if (import.meta.client) {
    window.removeEventListener('pointermove', handlePointerMove)
    window.removeEventListener('pointerup', stopDragging)
  }
}

function updateModalSizeFromViewport() {
  if (!import.meta.client) {
    return
  }
  if (isMobileViewport.value) {
    modalRect.left = 0
    modalRect.top = 0
    modalRect.width = window.innerWidth
    modalRect.height = window.innerHeight
    return
  }
  const next = createDefaultModalRect({
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight,
    margin: MODAL_MARGIN_PX,
    minWidth: MIN_MODAL_WIDTH_PX,
    minHeight: MIN_MODAL_HEIGHT_PX,
  })
  const clamped = clampModalRect({
    left: modalRect.left || next.left,
    top: modalRect.top || next.top,
    width: modalRect.width || next.width,
    height: modalRect.height || next.height,
    minWidth: MIN_MODAL_WIDTH_PX,
    minHeight: MIN_MODAL_HEIGHT_PX,
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight,
    margin: MODAL_MARGIN_PX,
  })
  modalRect.left = clamped.left
  modalRect.top = clamped.top
  modalRect.width = clamped.width
  modalRect.height = clamped.height
}

function handlePointerMove(event: PointerEvent) {
  if (resizing.value && !effectiveFullscreen.value) {
    if (!import.meta.client) {
      return
    }
    const deltaX = event.clientX - resizeStartPointerX
    const deltaY = event.clientY - resizeStartPointerY
    let nextLeft = resizeStartLeft
    let nextTop = resizeStartTop
    let nextWidth = resizeStartWidth
    let nextHeight = resizeStartHeight

    if (resizeDirection.includes('e')) {
      nextWidth = resizeStartWidth + deltaX
    }
    if (resizeDirection.includes('s')) {
      nextHeight = resizeStartHeight + deltaY
    }
    if (resizeDirection.includes('w')) {
      nextLeft = resizeStartLeft + deltaX
      nextWidth = resizeStartWidth - deltaX
    }
    if (resizeDirection.includes('n')) {
      nextTop = resizeStartTop + deltaY
      nextHeight = resizeStartHeight - deltaY
    }

    const clampedRect = clampModalRect({
      left: nextLeft,
      top: nextTop,
      width: nextWidth,
      height: nextHeight,
      minWidth: MIN_MODAL_WIDTH_PX,
      minHeight: MIN_MODAL_HEIGHT_PX,
      viewportWidth: window.innerWidth,
      viewportHeight: window.innerHeight,
      margin: MODAL_MARGIN_PX,
    })
    modalRect.left = clampedRect.left
    modalRect.top = clampedRect.top
    modalRect.width = clampedRect.width
    modalRect.height = clampedRect.height
    return
  }
  if (!dragging.value || effectiveFullscreen.value) {
    return
  }
  const clamped = clampModalRect({
    left: dragStartLeft + (event.clientX - dragStartPointerX),
    top: dragStartTop + (event.clientY - dragStartPointerY),
    width: modalRect.width,
    height: modalRect.height,
    minWidth: MIN_MODAL_WIDTH_PX,
    minHeight: MIN_MODAL_HEIGHT_PX,
    viewportWidth: window.innerWidth,
    viewportHeight: window.innerHeight,
    margin: MODAL_MARGIN_PX,
  })
  modalRect.left = clamped.left
  modalRect.top = clamped.top
}

function startDragging(event: PointerEvent) {
  if (effectiveFullscreen.value || event.button !== 0) {
    return
  }
  dragging.value = true
  dragStartPointerX = event.clientX
  dragStartPointerY = event.clientY
  dragStartLeft = modalRect.left
  dragStartTop = modalRect.top
  if (import.meta.client) {
    window.addEventListener('pointermove', handlePointerMove)
    window.addEventListener('pointerup', stopDragging)
  }
}

function stopResizing() {
  if (!resizing.value) {
    return
  }
  resizing.value = false
  if (import.meta.client) {
    window.removeEventListener('pointermove', handlePointerMove)
    window.removeEventListener('pointerup', stopResizing)
  }
}

function startResizing(event: PointerEvent, direction: ResizeDirection) {
  if (effectiveFullscreen.value || event.button !== 0 || !import.meta.client) {
    return
  }
  resizing.value = true
  resizeDirection = direction
  resizeStartPointerX = event.clientX
  resizeStartPointerY = event.clientY
  resizeStartLeft = modalRect.left
  resizeStartTop = modalRect.top
  resizeStartWidth = modalRect.width
  resizeStartHeight = modalRect.height
  window.addEventListener('pointermove', handlePointerMove)
  window.addEventListener('pointerup', stopResizing)
}

function toggleFullScreen() {
  if (isMobileViewport.value) {
    return
  }
  fullScreen.value = !fullScreen.value
  if (fullScreen.value) {
    stopDragging()
    stopResizing()
  } else if (modalRect.width === 0 || modalRect.height === 0) {
    updateModalSizeFromViewport()
  }
}

async function loadQuickNote() {
  loading.value = true
  loadError.value = ''
  try {
    const data = await fetchData<QuickNotePayload>('/api/me/quick-note')
    modalState.value = createQuickNoteModalState(data.quickNote.content ?? '')
    lastSavedAt.value = data.quickNote.updatedAt
  } catch (error) {
    loadError.value = getApiErrorMessage(error)
    modalState.value = createQuickNoteModalState('')
    toast.add({
      title: `速记加载失败：${loadError.value}`,
      color: 'error',
    })
  } finally {
    loading.value = false
  }
}

async function saveQuickNote() {
  if (saving.value) {
    return
  }
  saving.value = true
  try {
    const data = await fetchData<QuickNotePayload>('/api/me/quick-note', {
      method: 'PUT',
      body: {
        content: modalState.value.draftContent,
      },
    })
    modalState.value = markSaveSucceeded(modalState.value)
    lastSavedAt.value = data.quickNote.updatedAt
    toast.add({
      title: '速记已保存',
      color: 'success',
    })
  } catch (error) {
    modalState.value = markSaveFailed(modalState.value)
    toast.add({
      title: `保存失败：${getApiErrorMessage(error)}`,
      color: 'error',
    })
  } finally {
    saving.value = false
  }
}

function requestClose() {
  syncDraftFromEditor()
  if (isDirty.value && !confirmDiscard()) {
    return
  }
  closeModal()
}

function confirmDiscard(): boolean {
  if (!import.meta.client) {
    return false
  }
  return window.confirm('当前速记有未保存内容，确认后将丢失本次修改。')
}

function closeModal() {
  allowSilentClose.value = true
  open.value = false
}

function handleBeforeUnload(event: BeforeUnloadEvent) {
  syncDraftFromEditor()
  if (!open.value || !isDirty.value) {
    return
  }
  event.preventDefault()
  event.returnValue = ''
}

watch(
  () => open.value,
  (isOpen, wasOpen) => {
    if (wasOpen && !isOpen && !allowSilentClose.value && isDirty.value) {
      if (confirmDiscard()) {
        allowSilentClose.value = true
        return
      }
      open.value = true
      return
    }
    allowSilentClose.value = false
    if (!isOpen) {
      fullScreen.value = false
      stopDragging()
      stopResizing()
      return
    }
    updateModalSizeFromViewport()
    void loadQuickNote()
  },
)

onMounted(() => {
  if (!import.meta.client) {
    return
  }
  viewportMql = window.matchMedia('(max-width: 767px)')
  isMobileViewport.value = viewportMql.matches
  onViewportChange = (event: MediaQueryListEvent) => {
    isMobileViewport.value = event.matches
    updateModalSizeFromViewport()
  }
  viewportMql.addEventListener('change', onViewportChange)
  removeRouteGuard = router.beforeEach((to) => {
    syncDraftFromEditor()
    if (!open.value || !isDirty.value) {
      return true
    }
    return confirmDiscard()
  })
  window.addEventListener('beforeunload', handleBeforeUnload)
  window.addEventListener('resize', updateModalSizeFromViewport)
})

onBeforeUnmount(() => {
  if (import.meta.client) {
    if (viewportMql && onViewportChange) {
      viewportMql.removeEventListener('change', onViewportChange)
    }
    onViewportChange = null
    viewportMql = null
    removeRouteGuard?.()
    removeRouteGuard = null
    window.removeEventListener('beforeunload', handleBeforeUnload)
    window.removeEventListener('resize', updateModalSizeFromViewport)
  }
  stopDragging()
  stopResizing()
})
</script>

<template>
  <Teleport to="body">
    <div
      v-if="open"
      :class="overlayClass"
    >
      <div
        :class="cardClass"
        :style="cardStyle"
        class="relative pointer-events-auto"
      >
        <UCard
          class="h-full w-full"
          :ui="{
            body: 'h-[calc(100%-7rem)] p-3 md:p-4',
            footer: 'border-t border-default/80',
          }"
        >
        <template #header>
          <div
            class="flex items-center justify-between gap-3 select-none"
            :class="effectiveFullscreen ? 'cursor-default' : 'cursor-move'"
            @pointerdown="startDragging"
          >
            <div class="min-w-0">
              <p class="truncate text-sm font-semibold text-highlighted">
                速记
              </p>
              <p class="truncate text-xs text-muted">
                {{ lastSavedAt ? `上次保存：${new Date(lastSavedAt).toLocaleString()}` : '尚未保存过内容' }}
              </p>
            </div>
            <div class="flex items-center gap-2" @pointerdown.stop>
              <UButton
                color="neutral"
                variant="ghost"
                size="sm"
                icon="i-lucide-refresh-cw"
                aria-label="重新加载编辑器"
                @click="reloadEditor"
              />
              <UButton
                v-if="!isMobileViewport"
                color="neutral"
                variant="ghost"
                size="sm"
                :icon="fullScreen ? 'i-lucide-minimize-2' : 'i-lucide-maximize-2'"
                :aria-label="fullScreen ? '退出全屏' : '全屏'"
                @click="toggleFullScreen"
              />
              <UButton
                color="neutral"
                variant="ghost"
                size="sm"
                icon="i-lucide-x"
                aria-label="关闭速记"
                @click="requestClose"
              />
            </div>
          </div>
        </template>

        <div class="flex h-full min-h-0 flex-col gap-3">
          <UAlert
            v-if="loadError"
            color="error"
            variant="subtle"
            title="速记内容加载失败"
            :description="loadError"
          />
          <UAlert
            v-if="modalState.lastSaveFailed"
            color="warning"
            variant="subtle"
            title="最近一次保存失败"
            description="请检查网络后重试保存。"
          />
          <div v-if="loading" class="flex min-h-0 flex-1 items-center justify-center rounded-lg border border-default bg-elevated/40">
            <UIcon name="i-lucide-loader-2" class="size-5 animate-spin text-primary" />
          </div>
          <div v-else class="min-h-0 flex-1">
            <QuickNoteEditor
              :key="editorReloadToken"
              ref="quickNoteEditorRef"
              v-model="draftContent"
              :resize-signal="editorResizeSignal"
              @content-change="handleEditorContentChange"
            />
          </div>
        </div>

        <template #footer>
          <div class="flex items-center justify-between gap-2">
            <span class="text-xs text-muted">
              {{ isDirty ? '有未保存修改' : '已保存' }}
            </span>
            <div class="flex items-center gap-2">
              <UButton
                color="neutral"
                variant="outline"
                size="sm"
                :disabled="loading || saving"
                @click="requestClose"
              >
                关闭
              </UButton>
              <UButton
                color="primary"
                size="sm"
                :loading="saving"
                :disabled="loading"
                @click="saveQuickNote"
              >
                保存
              </UButton>
            </div>
          </div>
        </template>
        </UCard>
      <template v-if="!effectiveFullscreen">
        <button
          type="button"
          class="absolute left-0 top-0 z-10 h-full w-1 cursor-w-resize"
          aria-label="从左侧调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'w')"
        />
        <button
          type="button"
          class="absolute right-0 top-0 z-10 h-full w-1 cursor-e-resize"
          aria-label="从右侧调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'e')"
        />
        <button
          type="button"
          class="absolute left-0 top-0 z-10 h-1 w-full cursor-n-resize"
          aria-label="从上侧调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'n')"
        />
        <button
          type="button"
          class="absolute bottom-0 left-0 z-10 h-1 w-full cursor-s-resize"
          aria-label="从下侧调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 's')"
        />
        <button
          type="button"
          class="absolute left-0 top-0 z-20 h-3 w-3 cursor-nw-resize"
          aria-label="从左上角调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'nw')"
        />
        <button
          type="button"
          class="absolute right-0 top-0 z-20 h-3 w-3 cursor-ne-resize"
          aria-label="从右上角调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'ne')"
        />
        <button
          type="button"
          class="absolute bottom-0 left-0 z-20 h-3 w-3 cursor-sw-resize"
          aria-label="从左下角调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'sw')"
        />
        <button
          type="button"
          class="absolute bottom-0 right-0 z-20 h-3 w-3 cursor-se-resize"
          aria-label="从右下角调整速记弹框尺寸"
          @pointerdown.prevent.stop="startResizing($event, 'se')"
        />
      </template>
      </div>
    </div>
  </Teleport>
</template>
