<script setup>
import { computed, onMounted, ref } from 'vue'
import { adminApi } from '@/api/adminApi'
import { useAuthStore } from '@/stores/auth'
import { useDialog } from '@/composables/dialog'
import IconBtn from '@/components/ui/IconBtn.vue'

const auth = useAuthStore()
const dialog = useDialog()
const myId = computed(() => auth.user?.id)

const members = ref([])
const loading = ref(false)
const error = ref(null)
const keyword = ref('')

const filtered = computed(() => {
  const k = keyword.value.trim().toLowerCase()
  if (!k) return members.value
  return members.value.filter(
    (m) =>
      (m.loginId || '').toLowerCase().includes(k) ||
      (m.name || '').toLowerCase().includes(k) ||
      (m.email || '').toLowerCase().includes(k),
  )
})

function fmtDate(v) {
  if (!v) return '-'
  const d = new Date(v)
  return Number.isNaN(d.getTime()) ? v : `${d.getFullYear()}.${String(d.getMonth() + 1).padStart(2, '0')}.${String(d.getDate()).padStart(2, '0')}`
}

async function load() {
  loading.value = true
  error.value = null
  try {
    members.value = await adminApi.members()
  } catch (e) {
    error.value = e.response?.data?.message || '불러오지 못했습니다.'
  } finally {
    loading.value = false
  }
}

// 회원가입 허용 설정
const signupEnabled = ref(true)
const signupSaving = ref(false)
async function loadSignupSetting() {
  try {
    const res = await adminApi.getSignupSetting()
    signupEnabled.value = !!res.enabled
  } catch {
    /* 무시 — 기본 허용 표시 */
  }
}
async function toggleSignup() {
  if (signupSaving.value) return
  const next = !signupEnabled.value
  signupSaving.value = true
  try {
    const res = await adminApi.setSignupSetting(next)
    signupEnabled.value = !!res.enabled
  } catch (e) {
    alert(e.response?.data?.message || '설정 변경에 실패했습니다.')
  } finally {
    signupSaving.value = false
  }
}

async function changeRole(m, role) {
  if (role === m.role) return
  try {
    const updated = await adminApi.updateMemberRole(m.id, role)
    Object.assign(m, updated)
  } catch (e) {
    alert(e.response?.data?.message || '역할 변경 실패')
    await load()
  }
}

async function changeStatus(m, status) {
  if (status === m.status) return
  try {
    const updated = await adminApi.updateMemberStatus(m.id, status)
    Object.assign(m, updated)
  } catch (e) {
    alert(e.response?.data?.message || '상태 변경 실패')
    await load()
  }
}

async function remove(m) {
  if (!(await dialog.confirm(`'${m.name}(${m.loginId})' 회원을 삭제할까요?\n되돌릴 수 없습니다.`, { title: '회원 삭제', danger: true }))) return
  try {
    await adminApi.removeMember(m.id)
    members.value = members.value.filter((x) => x.id !== m.id)
  } catch (e) {
    alert(e.response?.data?.message || '삭제 실패')
  }
}

onMounted(() => {
  load()
  loadSignupSetting()
})
</script>

<template>
  <main class="members">
    <header class="head">
      <h1>회원 관리</h1>
      <div class="head-actions">
        <input v-model="keyword" type="search" class="search" placeholder="아이디·이름·이메일 검색" />
        <IconBtn icon="refresh" title="새로고침" size="sm" :disabled="loading" @click="load" />
      </div>
    </header>
    <p class="hint">가입한 회원의 역할·상태를 관리합니다. (관리자 전용)</p>

    <!-- 회원가입 허용 토글 -->
    <div class="signup-toggle" :class="{ off: !signupEnabled }">
      <div class="st-text">
        <strong>회원가입 {{ signupEnabled ? '허용' : '제한' }}</strong>
        <span class="st-desc">{{ signupEnabled ? '누구나 회원가입할 수 있습니다.' : '신규 회원가입이 차단됩니다.' }}</span>
      </div>
      <button
        type="button" class="switch" :class="{ on: signupEnabled }"
        :disabled="signupSaving" role="switch" :aria-checked="signupEnabled"
        @click="toggleSignup"
      ><span class="knob"></span></button>
    </div>

    <p v-if="error" class="msg error">{{ error }}</p>
    <p v-if="loading" class="msg">불러오는 중...</p>

    <table v-else-if="filtered.length" class="member-table">
      <thead>
        <tr>
          <th class="c-id">ID</th>
          <th>아이디 / 이름</th>
          <th class="c-email">이메일</th>
          <th class="c-role">역할</th>
          <th class="c-status">상태</th>
          <th class="c-date">가입일</th>
          <th class="c-act"></th>
        </tr>
      </thead>
      <tbody>
        <tr v-for="m in filtered" :key="m.id" :class="{ me: m.id === myId, suspended: m.status !== 'ACTIVE' }">
          <td class="c-id">{{ m.id }}</td>
          <td class="c-user">
            <div class="login">{{ m.loginId || '(소셜)' }}<span v-if="m.id === myId" class="badge-me">나</span></div>
            <div class="name">{{ m.name }}<span v-if="m.provider && m.provider !== 'LOCAL'" class="prov">{{ m.provider }}</span></div>
          </td>
          <td class="c-email">{{ m.email || '-' }}</td>
          <td class="c-role">
            <select :value="m.role" :disabled="m.id === myId" @change="changeRole(m, $event.target.value)">
              <option value="USER">USER</option>
              <option value="ADMIN">ADMIN</option>
            </select>
          </td>
          <td class="c-status">
            <select
              :value="m.status" :disabled="m.id === myId"
              class="status-sel" :class="m.status.toLowerCase()"
              @change="changeStatus(m, $event.target.value)"
            >
              <option value="ACTIVE">활성</option>
              <option value="SUSPENDED">정지</option>
              <option value="WITHDRAWN">탈퇴</option>
            </select>
          </td>
          <td class="c-date">{{ fmtDate(m.createdAt) }}</td>
          <td class="c-act">
            <IconBtn icon="trash" title="삭제" variant="danger" size="sm" :disabled="m.id === myId" @click="remove(m)" />
          </td>
        </tr>
      </tbody>
    </table>
    <p v-else-if="!loading" class="msg">{{ keyword ? '검색 결과가 없습니다.' : '회원이 없습니다.' }}</p>
  </main>
</template>

<style scoped>
.members {
  max-width: 900px;
  margin: 0 auto;
}
.head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.5rem;
  flex-wrap: wrap;
}
h1 {
  font-size: 1.5rem;
}
.head-actions {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.search {
  padding: 0.45rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
  min-width: 12rem;
}
.hint {
  font-size: 0.85rem;
  opacity: 0.7;
  margin: 0.5rem 0 1rem;
}
.signup-toggle {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 1rem;
  padding: 0.8rem 1rem;
  margin-bottom: 1rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-background-soft);
}
.signup-toggle.off {
  border-color: #c0392b;
  background: rgba(192, 57, 43, 0.06);
}
.st-text {
  display: flex;
  flex-direction: column;
  gap: 0.15rem;
  min-width: 0;
}
.st-text strong {
  font-size: 0.95rem;
}
.st-desc {
  font-size: 0.8rem;
  opacity: 0.7;
}
.switch {
  flex-shrink: 0;
  width: 48px;
  height: 28px;
  border: 0;
  border-radius: 999px;
  background: #b0b0b0;
  position: relative;
  cursor: pointer;
  transition: background 0.18s;
  padding: 0;
}
.switch.on {
  background: hsla(160, 100%, 37%, 1);
}
.switch:disabled {
  opacity: 0.6;
  cursor: default;
}
.switch .knob {
  position: absolute;
  top: 3px;
  left: 3px;
  width: 22px;
  height: 22px;
  border-radius: 50%;
  background: #fff;
  transition: transform 0.18s;
}
.switch.on .knob {
  transform: translateX(20px);
}
.member-table {
  width: 100%;
  border-collapse: collapse;
  font-size: 0.9rem;
}
.member-table th,
.member-table td {
  padding: 0.5rem 0.5rem;
  border-bottom: 1px solid var(--color-border);
  text-align: left;
  vertical-align: middle;
}
.c-id {
  width: 48px;
}
.c-role,
.c-status {
  width: 90px;
}
.c-date {
  width: 100px;
  white-space: nowrap;
}
.c-act {
  width: 48px;
}
.member-table tr.me {
  background: hsla(160, 100%, 37%, 0.06);
}
.member-table tr.suspended .name,
.member-table tr.suspended .login {
  opacity: 0.6;
}
.login {
  font-weight: 600;
}
.badge-me {
  margin-left: 0.35rem;
  font-size: 0.68rem;
  border: 1px solid hsla(160, 100%, 37%, 1);
  color: hsla(160, 100%, 37%, 1);
  border-radius: 3px;
  padding: 0.02rem 0.3rem;
}
.name {
  font-size: 0.82rem;
  opacity: 0.75;
}
.prov {
  margin-left: 0.3rem;
  font-size: 0.7rem;
  opacity: 0.6;
}
.member-table select {
  padding: 0.3rem 0.4rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
  font-size: 0.82rem;
}
.member-table select:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
.status-sel.active {
  color: #2e7d32;
}
.status-sel.suspended {
  color: #e67e22;
}
.status-sel.withdrawn {
  color: #c0392b;
}
.msg {
  margin: 1rem 0;
}
.msg.error {
  color: #c0392b;
}

/* 모바일: 2줄 카드 */
@media (max-width: 768px) {
  .members {
    max-width: 100%;
  }
  .member-table thead {
    display: none;
  }
  .member-table,
  .member-table tbody,
  .member-table tr,
  .member-table td {
    display: block;
  }
  .member-table tr {
    display: flex;
    flex-wrap: wrap;
    align-items: center;
    column-gap: 0.5rem;
    row-gap: 0.3rem;
    padding: 0.6rem 0.2rem;
    border-bottom: 1px solid var(--color-border);
  }
  .member-table td {
    border: 0;
    padding: 0;
    width: auto;
  }
  .c-user {
    flex: 1 1 100%;
  }
  .c-id::before {
    content: '#';
  }
  .c-id {
    opacity: 0.6;
    font-size: 0.8rem;
  }
  .c-email {
    flex: 1 1 100%;
    font-size: 0.8rem;
    opacity: 0.75;
  }
  .c-date {
    font-size: 0.78rem;
    opacity: 0.65;
  }
  .c-act {
    margin-left: auto;
  }
}
</style>
