<script setup>
const props = defineProps({
  member: {
    type: Object,
    default: () => null
  },
  mode: {
    type: String,
    default: 'edit'
  }
})

const emit = defineEmits(['saved', 'deleted'])

const isNewMember = computed(() => props.mode === 'new')
const saveMessage = ref('')
const saveError = ref('')
const isSaving = ref(false)
const savedMemberSnapshot = ref('')
const avatarInputRef = ref(null)
const isUploadingAvatar = ref(false)
const actionMenuOpen = ref(false)
const passwordModalOpen = ref(false)
const deleteModalOpen = ref(false)
const isUpdatingPassword = ref(false)
const isDeletingMember = ref(false)
const actionMessage = ref('')
const actionError = ref('')

const roleOptions = [
  { value: 'owner', label: '소유자' },
  { value: 'admin', label: '관리자' },
  { value: 'vip', label: 'VIP' },
  { value: 'member', label: '멤버' }
]

const form = reactive({
  username: '',
  email: '',
  avatarUrl: '',
  roleCode: 'member',
  labelsText: '',
  note: ''
})

const passwordForm = reactive({
  password: '',
  passwordConfirm: ''
})

const deleteForm = reactive({
  confirmText: ''
})

/**
 * 회원 폼 값을 현재 회원 정보로 동기화한다.
 * @returns {void}
 */
const syncMemberForm = () => {
  const member = props.member || {}
  form.username = member.username || ''
  form.email = member.email || ''
  form.avatarUrl = member.avatarUrl || ''
  form.roleCode = member.roleCode || 'member'
  form.labelsText = Array.isArray(member.labels) ? member.labels.join(', ') : ''
  form.note = member.note || ''
}

watch(() => props.member, syncMemberForm, { immediate: true })

const pageTitle = computed(() => {
  if (isNewMember.value) {
    return '새 멤버'
  }

  return form.username || props.member?.email || '멤버'
})

const memberInitial = computed(() => String(form.username || form.email || '?').slice(0, 1).toUpperCase())
const noteLength = computed(() => form.note.length)

const normalizedLabels = computed(() => [...new Set(
  form.labelsText
    .split(',')
    .map((label) => label.trim())
    .filter(Boolean)
)])

const currentRoleLabel = computed(() => roleOptions.find((option) => option.value === form.roleCode)?.label || '멤버')

/**
 * 회원 저장 요청 본문을 문자열로 직렬화한다.
 * @returns {string} 직렬화된 회원 입력값
 */
const serializeMemberPayload = () => JSON.stringify({
  ...getMemberPayload(),
  roleCode: form.roleCode
})

/**
 * 날짜 표시 형식 변환
 * @param {string | null} value - ISO 날짜 문자열
 * @returns {string} 화면 표시 날짜
 */
const formatDate = (value) => {
  if (!value) {
    return '-'
  }

  const date = new Date(value)
  if (Number.isNaN(date.getTime())) {
    return '-'
  }

  const year = date.getFullYear()
  const month = String(date.getMonth() + 1).padStart(2, '0')
  const day = String(date.getDate()).padStart(2, '0')

  return `${year}.${month}.${day}`
}

/**
 * 최근 활동 시각을 상대 시간으로 표시한다.
 * @param {string | null} value - ISO 시각
 * @returns {string} 상대 시간
 */
const formatRelativeTime = (value) => {
  if (!value) {
    return '최근 활동 없음'
  }

  const date = new Date(value)
  if (Number.isNaN(date.getTime())) {
    return '최근 활동 없음'
  }

  const diffMs = Date.now() - date.getTime()
  const minute = 1000 * 60
  const hour = minute * 60
  const day = hour * 24

  if (diffMs < minute) {
    return '방금 전'
  }

  if (diffMs < hour) {
    return `${Math.floor(diffMs / minute)}분 전`
  }

  if (diffMs < day) {
    return `${Math.floor(diffMs / hour)}시간 전`
  }

  if (diffMs < day * 30) {
    return `${Math.floor(diffMs / day)}일 전`
  }

  return formatDate(value)
}

/**
 * 회원 저장 요청 본문을 만든다.
 * @returns {{ username: string, email: string, avatarUrl: string, labels: string[], note: string }} 저장 본문
 */
const getMemberPayload = () => ({
  username: form.username.trim(),
  email: form.email.trim(),
  avatarUrl: form.avatarUrl.trim(),
  labels: normalizedLabels.value,
  note: form.note
})

const hasUnsavedMemberChanges = computed(() => serializeMemberPayload() !== savedMemberSnapshot.value)

const {
  isUnsavedModalOpen,
  stayOnUnsavedPage,
  leaveUnsavedPage
} = useAdminUnsavedChangesGuard(hasUnsavedMemberChanges)

/**
 * 썸네일 파일 선택창을 연다.
 * @returns {void}
 */
const openAvatarFilePicker = () => {
  avatarInputRef.value?.click()
}

/**
 * 회원 썸네일 파일을 업로드하고 폼에 반영한다.
 * @param {Event} event - 파일 선택 이벤트
 * @returns {Promise<void>}
 */
const uploadAvatar = async (event) => {
  const target = event.target instanceof HTMLInputElement ? event.target : null
  const file = target?.files?.[0]

  if (!file || isUploadingAvatar.value) {
    return
  }

  isUploadingAvatar.value = true
  saveError.value = ''
  saveMessage.value = ''

  try {
    const formData = new FormData()
    formData.append('file', file)
    const result = isNewMember.value
      ? await $fetch('/admin/api/member-avatar', {
        method: 'POST',
        body: formData
      })
      : await $fetch(`/admin/api/members/${props.member.id}/avatar`, {
        method: 'POST',
        body: formData
      })
    form.avatarUrl = result.avatarUrl || ''

    if (!isNewMember.value) {
      emit('saved', result)
      savedMemberSnapshot.value = serializeMemberPayload()
      saveMessage.value = '썸네일이 변경되었습니다.'
    }
  } catch (error) {
    saveError.value = error?.data?.message || '썸네일 업로드에 실패했습니다.'
  } finally {
    isUploadingAvatar.value = false
    if (target) {
      target.value = ''
    }
  }
}

/**
 * 회원 썸네일 연결을 제거한다.
 * @returns {void}
 */
const removeAvatar = () => {
  form.avatarUrl = ''
}

/**
 * 회원 작업 메뉴를 토글한다.
 * @returns {void}
 */
const toggleActionMenu = () => {
  actionMenuOpen.value = !actionMenuOpen.value
}

/**
 * 회원 작업 메뉴를 닫는다.
 * @returns {void}
 */
const closeActionMenu = () => {
  actionMenuOpen.value = false
}

/**
 * 비밀번호 변경 모달을 연다.
 * @returns {void}
 */
const openPasswordModal = () => {
  passwordForm.password = ''
  passwordForm.passwordConfirm = ''
  actionMessage.value = ''
  actionError.value = ''
  passwordModalOpen.value = true
  closeActionMenu()
}

/**
 * 회원 삭제 모달을 연다.
 * @returns {void}
 */
const openDeleteModal = () => {
  deleteForm.confirmText = ''
  actionMessage.value = ''
  actionError.value = ''
  deleteModalOpen.value = true
  closeActionMenu()
}

/**
 * 비밀번호 변경 모달을 닫는다.
 * @returns {void}
 */
const closePasswordModal = () => {
  if (isUpdatingPassword.value) {
    return
  }

  passwordModalOpen.value = false
}

/**
 * 회원 삭제 모달을 닫는다.
 * @returns {void}
 */
const closeDeleteModal = () => {
  if (isDeletingMember.value) {
    return
  }

  deleteModalOpen.value = false
}

/**
 * 관리자 권한으로 회원 비밀번호를 변경한다.
 * @returns {Promise<void>}
 */
const updateMemberPassword = async () => {
  actionMessage.value = ''
  actionError.value = ''

  if (!passwordForm.password || passwordForm.password.length < 8 || passwordForm.password.length > 32) {
    actionError.value = '새 비밀번호는 8~32자로 입력해 주세요.'
    return
  }

  if (passwordForm.password !== passwordForm.passwordConfirm) {
    actionError.value = '새 비밀번호와 확인 값이 일치하지 않습니다.'
    return
  }

  isUpdatingPassword.value = true
  try {
    await $fetch(`/admin/api/members/${props.member.id}/password`, {
      method: 'PUT',
      body: {
        password: passwordForm.password
      }
    })
    passwordModalOpen.value = false
    passwordForm.password = ''
    passwordForm.passwordConfirm = ''
    actionMessage.value = '비밀번호가 변경되었습니다.'
  } catch (error) {
    actionError.value = error?.data?.message || '비밀번호 변경에 실패했습니다.'
  } finally {
    isUpdatingPassword.value = false
  }
}

/**
 * 관리자 권한으로 회원을 삭제한다.
 * @returns {Promise<void>}
 */
const deleteMember = async () => {
  actionMessage.value = ''
  actionError.value = ''

  if (deleteForm.confirmText !== form.email) {
    actionError.value = '삭제하려면 회원 이메일을 정확히 입력해 주세요.'
    return
  }

  isDeletingMember.value = true
  try {
    await $fetch(`/admin/api/members/${props.member.id}`, {
      method: 'DELETE'
    })
    emit('deleted')
  } catch (error) {
    actionError.value = error?.data?.message || '회원 삭제에 실패했습니다.'
  } finally {
    isDeletingMember.value = false
  }
}

/**
 * 회원 기본 정보를 저장한다.
 * @returns {Promise<void>}
 */
const saveMember = async () => {
  if (isSaving.value) {
    return
  }

  saveMessage.value = ''
  saveError.value = ''
  isSaving.value = true

  try {
    const payload = getMemberPayload()
    if (!isNewMember.value && form.roleCode !== props.member?.roleCode) {
      await $fetch(`/admin/api/members/${props.member.id}/role`, {
        method: 'PUT',
        body: {
          role: form.roleCode
        }
      })
    }

    const saved = isNewMember.value
      ? await $fetch('/admin/api/members', {
        method: 'POST',
        body: payload
      })
      : await $fetch(`/admin/api/members/${props.member.id}`, {
        method: 'PUT',
        body: payload
      })

    savedMemberSnapshot.value = serializeMemberPayload()
    emit('saved', saved)
    saveMessage.value = '저장되었습니다.'
  } catch (error) {
    saveError.value = error?.data?.message || '저장에 실패했습니다.'
  } finally {
    isSaving.value = false
  }
}

watch(() => props.member, () => {
  savedMemberSnapshot.value = serializeMemberPayload()
}, { immediate: true, flush: 'post' })
</script>

<template>
  <section class="admin-member-form bg-paper p-6">
    <div class="admin-member-form__header sticky top-0 z-10 -mx-6 -mt-6 border-b border-line bg-paper/95 px-6 py-5 backdrop-blur">
      <div class="admin-member-form__header-inner flex flex-col gap-4 xl:flex-row xl:items-center xl:justify-between">
        <div class="admin-member-form__title-block">
          <div class="admin-member-form__breadcrumb flex items-center gap-2 text-sm text-[#8a95a5]">
            <NuxtLink class="admin-member-form__breadcrumb-link text-[#3f4650] hover:text-[#15171a]" to="/admin/members">
              멤버
            </NuxtLink>
            <svg class="h-3 w-3" viewBox="0 0 18 27" aria-hidden="true">
              <path d="M2.397 25.426l13.143-11.5-13.143-11.5" stroke-width="3" stroke="currentColor" fill="none" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
            <span>{{ isNewMember ? '새 멤버' : '멤버 편집' }}</span>
          </div>
          <h1 class="admin-member-form__title mt-4 text-3xl font-semibold tracking-[-0.01em] text-[#15171a]">
            {{ pageTitle }}
          </h1>
        </div>
        <div class="admin-member-form__actions flex items-center gap-3">
          <div v-if="!isNewMember" class="admin-member-form__action-menu relative">
            <button class="admin-member-form__action-icon flex h-11 w-11 items-center justify-center rounded-md border border-line bg-white text-[#3f4650] transition hover:border-[#c5ccd5] hover:bg-[#f4f6f8]" type="button" aria-label="멤버 작업" :aria-expanded="actionMenuOpen" @click="toggleActionMenu">
              <svg class="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M10.546 2.438a1.957 1.957 0 002.908 0L14.4 1.4a1.959 1.959 0 013.41 1.413l-.071 1.4a1.958 1.958 0 002.051 2.054l1.4-.071a1.959 1.959 0 011.41 3.41l-1.042.94a1.96 1.96 0 000 2.909l1.042.94a1.959 1.959 0 01-1.413 3.41l-1.4-.071a1.958 1.958 0 00-2.056 2.056l.071 1.4A1.959 1.959 0 0114.4 22.6l-.941-1.041a1.959 1.959 0 00-2.908 0L9.606 22.6A1.959 1.959 0 016.2 21.192l.072-1.4a1.958 1.958 0 00-2.056-2.056l-1.4.071A1.958 1.958 0 011.4 14.4l1.041-.94a1.96 1.96 0 000-2.909L1.4 9.606A1.958 1.958 0 012.809 6.2l1.4.071a1.958 1.958 0 002.058-2.06L6.2 2.81A1.959 1.959 0 019.606 1.4z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
                <circle cx="12" cy="12.001" r="4.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
              </svg>
            </button>
            <div v-if="actionMenuOpen" class="admin-member-form__action-popover absolute right-0 top-12 z-20 grid w-52 overflow-hidden rounded-xl border border-line bg-white py-2 text-sm text-[#3f4650] shadow-[0_16px_44px_rgba(15,23,42,0.16)]">
              <button class="admin-member-form__action-item px-4 py-2.5 text-left hover:bg-[#f3f5f7]" type="button" @click="openPasswordModal">
                비밀번호 변경
              </button>
              <button class="admin-member-form__action-item px-4 py-2.5 text-left text-[#d21a26] hover:bg-[#fff1f2]" type="button" @click="openDeleteModal">
                멤버 삭제
              </button>
            </div>
          </div>
          <button v-else class="admin-member-form__action-icon flex h-11 w-11 items-center justify-center rounded-md border border-line bg-white text-[#3f4650]" type="button" aria-label="멤버 작업" disabled>
            <svg class="h-5 w-5" viewBox="0 0 24 24" aria-hidden="true">
              <path d="M10.546 2.438a1.957 1.957 0 002.908 0L14.4 1.4a1.959 1.959 0 013.41 1.413l-.071 1.4a1.958 1.958 0 002.051 2.054l1.4-.071a1.959 1.959 0 011.41 3.41l-1.042.94a1.96 1.96 0 000 2.909l1.042.94a1.959 1.959 0 01-1.413 3.41l-1.4-.071a1.958 1.958 0 00-2.056 2.056l.071 1.4A1.959 1.959 0 0114.4 22.6l-.941-1.041a1.959 1.959 0 00-2.908 0L9.606 22.6A1.959 1.959 0 016.2 21.192l.072-1.4a1.958 1.958 0 00-2.056-2.056l-1.4.071A1.958 1.958 0 011.4 14.4l1.041-.94a1.96 1.96 0 000-2.909L1.4 9.606A1.958 1.958 0 012.809 6.2l1.4.071a1.958 1.958 0 002.058-2.06L6.2 2.81A1.959 1.959 0 019.606 1.4z" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
              <circle cx="12" cy="12.001" r="4.5" fill="none" stroke="currentColor" stroke-linecap="round" stroke-linejoin="round" stroke-width="1.5" />
            </svg>
          </button>
          <button class="admin-member-form__save h-11 rounded-md bg-[#15171a] px-5 text-sm font-semibold text-white transition hover:bg-[#2b2f35] disabled:opacity-50" type="button" :disabled="isSaving" @click="saveMember">
            {{ isSaving ? '저장 중' : '저장' }}
          </button>
        </div>
      </div>
    </div>

    <div class="admin-member-form__body grid gap-8 py-8 xl:grid-cols-3">
      <aside class="admin-member-form__summary">
        <div class="admin-member-form__identity flex items-center gap-4">
          <div class="admin-member-form__avatar-control group relative h-20 w-20 shrink-0">
            <button
              class="admin-member-form__avatar-button relative h-20 w-20 overflow-hidden rounded-full bg-[#15171a] text-white"
              type="button"
              :aria-label="form.avatarUrl ? '썸네일 변경' : '썸네일 등록'"
              @click="openAvatarFilePicker"
            >
              <img
                v-if="form.avatarUrl"
                class="admin-member-form__avatar h-full w-full object-cover"
                :src="form.avatarUrl"
                :alt="pageTitle"
              >
              <span v-else class="admin-member-form__avatar flex h-full w-full items-center justify-center text-2xl font-semibold">
                {{ memberInitial }}
              </span>
              <span class="admin-member-form__avatar-caption absolute inset-x-0 bottom-0 flex min-h-8 items-end justify-center bg-gradient-to-t from-black/80 via-black/35 to-transparent px-2 pb-2 text-center text-[11px] font-semibold leading-tight text-white opacity-0 transition-opacity group-hover:opacity-100 group-focus-within:opacity-100">
                {{ isUploadingAvatar ? '업로드 중' : form.avatarUrl ? '썸네일 변경' : '썸네일 등록' }}
              </span>
            </button>
            <button
              v-if="form.avatarUrl"
              class="admin-member-form__avatar-remove absolute right-0 top-0 grid size-6 -translate-y-1 translate-x-1 place-items-center rounded-full bg-black/85 text-white opacity-0 shadow-sm transition hover:bg-[#d21a26] group-hover:opacity-100 group-focus-within:opacity-100"
              type="button"
              aria-label="썸네일 제거"
              @click.stop="removeAvatar"
            >
              <svg class="h-3 w-3" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M12.707 12L23.854.854a.5.5 0 00-.707-.707L12 11.293.854.146a.5.5 0 00-.707.707L11.293 12 .146 23.146a.5.5 0 00.708.708L12 12.707l11.146 11.146a.5.5 0 10.708-.706L12.707 12z" fill="currentColor" />
              </svg>
            </button>
            <input ref="avatarInputRef" class="sr-only" type="file" accept="image/*" @change="uploadAvatar">
          </div>
          <div class="min-w-0">
            <h2 class="truncate text-lg font-semibold text-[#15171a]">{{ pageTitle }}</h2>
            <p class="mt-1 truncate text-sm text-[#657080]">{{ form.email || '이메일 없음' }}</p>
            <p class="mt-1 text-xs font-semibold uppercase tracking-[0.04em] text-[#9aa4b2]">{{ currentRoleLabel }}</p>
          </div>
        </div>

        <div v-if="!isNewMember" class="admin-member-form__meta mt-10 space-y-3 text-sm text-[#4d5663]">
          <p class="flex items-center gap-2">
            <svg class="h-4 w-4 text-[#8a95a5]" fill="none" viewBox="0 0 24 26" aria-hidden="true">
              <path d="M12 14.75a4 4 0 100-8 4 4 0 000 8z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
              <path d="M21 10.75c0 7.9-6.932 12.331-8.629 13.3a.751.751 0 01-.743 0C9.931 23.08 3 18.648 3 10.75a9 9 0 1118 0z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
            {{ member?.lastSeenIp || '접속 IP 없음' }}
          </p>
          <p class="flex items-center gap-2">
            <svg class="h-4 w-4 text-[#8a95a5]" fill="none" viewBox="0 0 26 24" aria-hidden="true">
              <path d="M13 5.001c-4.03-.078-8.2 3.157-10.82 6.47-.276.35-.428.805-.428 1.277 0 .472.152.928.427 1.278C4.743 17.27 8.9 20.578 13 20.5c4.1.079 8.258-3.23 10.824-6.473.275-.35.428-.806.428-1.278s-.153-.927-.428-1.278C21.2 8.158 17.031 4.923 13 5.001z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
              <path d="M16.75 12.751a3.75 3.75 0 11-7.5-.002 3.75 3.75 0 017.5.002z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
            {{ formatRelativeTime(member?.lastSeenAt) }}
          </p>
        </div>

        <div v-if="!isNewMember" class="admin-member-form__side-section mt-12 border-t border-line pt-6">
          <h3 class="text-xs font-semibold uppercase tracking-[0.04em] text-[#15171a]">가입 정보</h3>
          <p class="mt-5 flex items-center gap-2 text-sm text-[#4d5663]">
            <svg class="h-4 w-4 text-[#8a95a5]" fill="none" viewBox="0 0 24 24" aria-hidden="true">
              <path d="M11.5 12c-2.824 0-2.83.024-4.5.53-3.5 1.058-5 3.176-5 6.386V21h10m7-5v6m-3-3h6m-10.5-7a5.5 5.5 0 100-11 5.5 5.5 0 000 11z" stroke="currentColor" stroke-width="2" stroke-linecap="round" stroke-linejoin="round" />
            </svg>
            생성됨 — <strong>{{ formatDate(member?.createdAt) }}</strong>
          </p>
        </div>

        <div v-if="!isNewMember" class="admin-member-form__side-section mt-12 border-t border-line pt-6">
          <h3 class="text-xs font-semibold uppercase tracking-[0.04em] text-[#15171a]">참여도</h3>
          <p class="mt-5 text-sm leading-6 text-[#8a95a5]">
            댓글 작성 {{ member?.commentCount || 0 }}개
          </p>
        </div>
      </aside>

      <div class="admin-member-form__content space-y-8 xl:col-span-2">
        <form class="admin-member-form__card rounded-xl border border-line bg-white p-5 md:p-6" @submit.prevent="saveMember">
          <div class="grid gap-5 md:grid-cols-2">
            <label class="admin-member-form__field block">
              <span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">이름</span>
              <input v-model="form.username" class="admin-member-form__input h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="text" maxlength="60" required>
            </label>
            <label class="admin-member-form__field block">
              <span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">이메일</span>
              <input v-model="form.email" class="admin-member-form__input h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="email" maxlength="254" required>
            </label>
          </div>

          <label class="admin-member-form__field mt-5 block">
            <span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">멤버 등급</span>
            <select
              v-model="form.roleCode"
              class="admin-member-form__select h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8] disabled:opacity-60"
              :disabled="isNewMember || isSaving"
            >
              <option v-for="option in roleOptions" :key="option.value" :value="option.value">
                {{ option.label }}
              </option>
            </select>
            <span class="admin-member-form__hint mt-2 block text-sm text-[#8a95a5]">
              VIP 이상 등급은 멤버십 게시물을 볼 수 있습니다.
            </span>
          </label>

          <label class="admin-member-form__field mt-5 block">
            <span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">레이블</span>
            <input v-model="form.labelsText" class="admin-member-form__input h-12 w-full rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="text" placeholder="쉼표로 구분">
          </label>

          <label class="admin-member-form__field mt-5 block">
            <span class="admin-member-form__label mb-2 block text-sm font-semibold text-[#15171a]">
              노트 <span class="font-normal text-[#657080]">(멤버에게 보이지 않음)</span>
            </span>
            <textarea v-model="form.note" class="admin-member-form__textarea min-h-32 w-full resize-y rounded-md border border-transparent bg-[#eef1f4] px-4 py-3 text-sm text-[#15171a] outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" maxlength="500" />
            <span class="admin-member-form__count mt-2 block text-sm text-[#8a95a5]">
              최대 500자. 현재 {{ noteLength }}자
            </span>
          </label>

          <p v-if="saveMessage" class="admin-member-form__message mt-5 rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">{{ saveMessage }}</p>
          <p v-if="saveError" class="admin-member-form__error mt-5 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ saveError }}</p>
          <p v-if="actionMessage" class="admin-member-form__message mt-5 rounded-md border border-green-200 bg-green-50 px-4 py-3 text-sm text-green-700">{{ actionMessage }}</p>
          <p v-if="actionError && !passwordModalOpen && !deleteModalOpen" class="admin-member-form__error mt-5 rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ actionError }}</p>
        </form>

        <section v-if="!isNewMember" class="admin-member-form__activity">
          <h2 class="admin-member-form__section-title mb-4 text-xs font-semibold uppercase tracking-[0.04em] text-[#15171a]">활동</h2>
          <div class="admin-member-form__activity-card rounded-xl border border-line bg-white px-5 md:px-6">
            <div class="admin-member-form__activity-row flex items-center justify-between gap-4 border-b border-line py-5 text-sm last:border-b-0">
              <span class="flex items-center gap-3 text-[#3f4650]">
                <svg class="h-5 w-5 text-[#6c747d]" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                  <path d="M4 12h10.31m-3.076-3.076L14.31 12l-3.076 3.077" stroke="currentColor" stroke-width="1.714" stroke-linecap="round" stroke-linejoin="round" />
                  <path d="M4.998 16.308a7.69 7.69 0 003.733 3.182 7.238 7.238 0 004.8.189 7.608 7.608 0 003.949-2.88A8.283 8.283 0 0018.998 12c0-1.73-.533-3.414-1.518-4.798a7.607 7.607 0 00-3.949-2.88 7.237 7.237 0 00-4.8.188 7.69 7.69 0 00-3.733 3.182" stroke="currentColor" stroke-width="1.714" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
                로그인
              </span>
              <span class="text-[#9aa4b2]">{{ formatRelativeTime(member?.lastSeenAt) }}</span>
            </div>
            <div class="admin-member-form__activity-row flex items-center justify-between gap-4 py-5 text-sm">
              <span class="flex items-center gap-3 text-[#3f4650]">
                <svg class="h-5 w-5 text-[#6c747d]" viewBox="0 0 24 24" fill="none" aria-hidden="true">
                  <path d="M11.246 12.144a4.242 4.242 0 100-8.484 4.242 4.242 0 000 8.484zM4 18.761a8.484 8.484 0 0110.5-3.42" stroke="currentColor" stroke-width="1.714" stroke-linecap="round" stroke-linejoin="round" />
                  <path d="M17.54 16.077V23m-3.463-3.46H21" stroke="#30CF43" stroke-width="1.5" stroke-linecap="round" stroke-linejoin="round" />
                </svg>
                가입
              </span>
              <span class="text-[#9aa4b2]">{{ formatRelativeTime(member?.createdAt) }}</span>
            </div>
          </div>
        </section>
      </div>
    </div>

    <AdminUnsavedChangesModal
      :open="isUnsavedModalOpen"
      @stay="stayOnUnsavedPage"
      @leave="leaveUnsavedPage"
    />

    <Teleport to="body">
      <div v-if="passwordModalOpen" class="admin-member-form__modal fixed inset-0 z-50 flex items-start justify-center bg-black/35 px-4 pt-10">
        <section class="admin-member-form__modal-panel w-full max-w-[520px] rounded-xl bg-white text-[#15171a] shadow-[0_22px_70px_rgba(15,23,42,0.25)]">
          <header class="flex items-center justify-between border-b border-line px-6 py-5">
            <h2 class="text-xl font-semibold">비밀번호 변경</h2>
            <button class="grid h-8 w-8 place-items-center rounded-md text-[#657080] hover:bg-[#f3f5f7]" type="button" aria-label="닫기" @click="closePasswordModal">
              <svg class="h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M12.707 12L23.854.854a.5.5 0 00-.707-.707L12 11.293.854.146a.5.5 0 00-.707.707L11.293 12 .146 23.146a.5.5 0 00.708.708L12 12.707l11.146 11.146a.5.5 0 10.708-.706L12.707 12z" fill="currentColor" />
              </svg>
            </button>
          </header>
          <div class="grid gap-4 px-6 py-5">
            <p class="text-sm leading-6 text-[#657080]">
              이메일 전송이 불가능한 상황을 대비해 관리자가 직접 새 비밀번호를 설정합니다.
            </p>
            <label class="grid gap-2 text-sm font-semibold">
              새 비밀번호
              <input v-model="passwordForm.password" class="h-11 rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="password" autocomplete="new-password">
            </label>
            <label class="grid gap-2 text-sm font-semibold">
              새 비밀번호 확인
              <input v-model="passwordForm.passwordConfirm" class="h-11 rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="password" autocomplete="new-password">
            </label>
            <p v-if="actionError" class="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ actionError }}</p>
          </div>
          <footer class="flex justify-end gap-2 border-t border-line px-6 py-4">
            <button class="h-10 rounded-md border border-line bg-white px-4 text-sm font-semibold text-[#3f4650]" type="button" @click="closePasswordModal">
              취소
            </button>
            <button class="h-10 rounded-md bg-[#15171a] px-4 text-sm font-semibold text-white disabled:opacity-50" type="button" :disabled="isUpdatingPassword" @click="updateMemberPassword">
              {{ isUpdatingPassword ? '변경 중' : '변경' }}
            </button>
          </footer>
        </section>
      </div>
    </Teleport>

    <Teleport to="body">
      <div v-if="deleteModalOpen" class="admin-member-form__modal fixed inset-0 z-50 flex items-start justify-center bg-black/35 px-4 pt-10">
        <section class="admin-member-form__modal-panel w-full max-w-[520px] rounded-xl bg-white text-[#15171a] shadow-[0_22px_70px_rgba(15,23,42,0.25)]">
          <header class="flex items-center justify-between border-b border-line px-6 py-5">
            <h2 class="text-xl font-semibold">멤버 삭제</h2>
            <button class="grid h-8 w-8 place-items-center rounded-md text-[#657080] hover:bg-[#f3f5f7]" type="button" aria-label="닫기" @click="closeDeleteModal">
              <svg class="h-4 w-4" viewBox="0 0 24 24" aria-hidden="true">
                <path d="M12.707 12L23.854.854a.5.5 0 00-.707-.707L12 11.293.854.146a.5.5 0 00-.707.707L11.293 12 .146 23.146a.5.5 0 00.708.708L12 12.707l11.146 11.146a.5.5 0 10.708-.706L12.707 12z" fill="currentColor" />
              </svg>
            </button>
          </header>
          <div class="grid gap-4 px-6 py-5">
            <p class="text-sm leading-6 text-[#657080]">
              삭제하면 멤버 계정과 작성 댓글이 함께 삭제됩니다. 계속하려면 아래에 <strong class="text-[#15171a]">{{ form.email }}</strong> 을 입력해 주세요.
            </p>
            <input v-model="deleteForm.confirmText" class="h-11 rounded-md border border-transparent bg-[#eef1f4] px-4 text-sm outline-none focus:border-[#c5ccd5] focus:bg-white focus:ring-2 focus:ring-[#dce2e8]" type="text" autocomplete="off">
            <p v-if="actionError" class="rounded-md border border-red-200 bg-red-50 px-4 py-3 text-sm text-red-700">{{ actionError }}</p>
          </div>
          <footer class="flex justify-end gap-2 border-t border-line px-6 py-4">
            <button class="h-10 rounded-md border border-line bg-white px-4 text-sm font-semibold text-[#3f4650]" type="button" @click="closeDeleteModal">
              취소
            </button>
            <button class="h-10 rounded-md bg-[#d21a26] px-4 text-sm font-semibold text-white disabled:opacity-50" type="button" :disabled="isDeletingMember" @click="deleteMember">
              {{ isDeletingMember ? '삭제 중' : '삭제' }}
            </button>
          </footer>
        </section>
      </div>
    </Teleport>
  </section>
</template>
