<script setup>
import { computed, onBeforeUnmount, onMounted, reactive, ref, watch, nextTick } from 'vue'
import Sortable from 'sortablejs'
import { accountApi } from '@/api/accountApi'
import IconBtn from '@/components/ui/IconBtn.vue'


const categories = ref([])
const rows = ref([]) // 현재 탭(activeType)의 분류 — 드래그 정렬 대상
const loading = ref(false)
const error = ref(null)
const activeType = ref('EXPENSE') // EXPENSE / INCOME
const newName = ref('')
const newParent = ref('') // 추가 시 대분류('' = 대분류로 추가)

// 인앱 확인 모달 (네이티브 confirm 대신 — PC 팝업 제목 'sb_pt' 노출 방지)
const confirmState = reactive({ open: false, title: '', message: '', onOk: null })
function askConfirm(title, message, onOk) {
  confirmState.title = title
  confirmState.message = message
  confirmState.onOk = onOk
  confirmState.open = true
}
function confirmOk() {
  const fn = confirmState.onOk
  confirmState.open = false
  if (fn) fn()
}
// 액션 오류도 네이티브 alert 대신 화면 상단 메시지로
function showError(e, fallback) {
  error.value = e.response?.data?.message || fallback
}

// categories/activeType 변경 시 현재 탭 목록 동기화 (백엔드가 sort_order 순 정렬)
function syncRows() {
  rows.value = categories.value.filter((c) => c.type === activeType.value)
}
watch([categories, activeType], syncRows, { immediate: true })

// 현재 탭의 대분류(parent 없는 것) 목록 — 추가 폼 대분류 선택용
const majors = computed(() => rows.value.filter((c) => c.parentId == null))
// 대분류 → 소분류 트리 (major/children 모두 실제 row 참조 유지 → v-model 바인딩 가능)
const tree = computed(() =>
  majors.value.map((m) => ({ major: m, children: rows.value.filter((c) => c.parentId === m.id) })),
)

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

// ===== 계층 드래그 정렬 (대분류 블록 / 대분류 내 소분류) =====
const majorListEl = ref(null)
let sortables = []
function destroySortables() {
  sortables.forEach((s) => s.destroy())
  sortables = []
}
function initSortable() {
  destroySortables()
  if (!majorListEl.value) return
  // 대분류 순서 — 블록 단위라 소분류가 함께 이동
  sortables.push(
    Sortable.create(majorListEl.value, {
      handle: '.major-handle',
      draggable: '.major-block',
      animation: 150,
      onEnd: () => {
        const ids = [...majorListEl.value.querySelectorAll(':scope > .major-block')].map((li) => Number(li.dataset.id))
        persistOrder(ids)
      },
    }),
  )
  // 각 대분류의 소분류 순서 — 고유 group 으로 다른 대분류로는 이동 불가
  majorListEl.value.querySelectorAll('.sub-list').forEach((ul) => {
    sortables.push(
      Sortable.create(ul, {
        handle: '.sub-handle',
        draggable: '.sub-item',
        group: 'sub-' + ul.dataset.parent,
        animation: 150,
        onEnd: () => {
          const ids = [...ul.querySelectorAll(':scope > .sub-item')].map((li) => Number(li.dataset.id))
          if (ids.length) persistOrder(ids)
        },
      }),
    )
  })
}
// 로컬 순서 즉시 반영(스냅백 방지) 후 백엔드 저장
function reorderLocal(ids) {
  const idSet = new Set(ids)
  const moved = ids.map((id) => categories.value.find((c) => c.id === id)).filter(Boolean)
  let k = 0
  categories.value = categories.value.map((c) => (idSet.has(c.id) ? moved[k++] : c))
  syncRows()
}
async function persistOrder(ids) {
  reorderLocal(ids)
  try {
    await accountApi.reorderCategories(activeType.value, ids)
  } catch (e) {
    showError(e, '순서 저장에 실패했습니다.')
  }
  await reload()
}

async function addCategory() {
  const name = newName.value.trim()
  if (!name) return
  error.value = null
  try {
    await accountApi.createCategory({ type: activeType.value, name, parentId: newParent.value || null })
    newName.value = ''
    await reload()
  } catch (e) {
    showError(e, '추가에 실패했습니다.')
  }
}

async function saveCategory(c) {
  const name = (c.name || '').trim()
  if (!name) return
  error.value = null
  try {
    // parentId 는 항상 현재값을 함께 전송(미전송 시 대분류로 풀림)
    await accountApi.updateCategory(c.id, { type: c.type, name, parentId: c.parentId || null })
    await reload()
  } catch (e) {
    showError(e, '수정에 실패했습니다.')
  }
}

function removeCategory(c) {
  askConfirm('분류 삭제', `'${c.name}' 분류를 삭제할까요?\n기존 내역의 분류명은 그대로 유지됩니다.`, async () => {
    error.value = null
    try {
      await accountApi.removeCategory(c.id)
      await reload()
    } catch (e) {
      showError(e, '삭제에 실패했습니다.')
    }
  })
}

function importDefaults() {
  askConfirm('기본 분류 불러오기', '관리자가 설정한 기본 분류를 내 분류로 가져올까요?\n같은 이름은 건너뜁니다.', async () => {
    error.value = null
    try {
      categories.value = await accountApi.importCategories()
      await nextTick()
      initSortable()
    } catch (e) {
      showError(e, '불러오기에 실패했습니다.')
    }
  })
}

// 탭(수입/지출) 전환 시 재렌더 → 드래그 재초기화
watch(activeType, async () => {
  await nextTick()
  initSortable()
})

onMounted(async () => {
  await reload()
})
onBeforeUnmount(destroySortables)
</script>

<template>
  <section class="cat">
    <p class="hint">내역·예산에서 선택할 분류를 관리합니다. <b>대분류 아래 소분류</b>로 묶을 수 있고(2단계), 예산·통계는 소분류 기준입니다. 이름을 바꾸면 기존 내역·예산에도 반영됩니다.</p>

    <div class="tabs-row">
      <div class="tabs">
        <button type="button" :class="{ active: activeType === 'EXPENSE' }" @click="activeType = 'EXPENSE'">지출 분류</button>
        <button type="button" :class="{ active: activeType === 'INCOME' }" @click="activeType = 'INCOME'">수입 분류</button>
      </div>
      <button type="button" class="text-btn" @click="importDefaults">기본 분류 불러오기</button>
    </div>

    <form class="new-cat" @submit.prevent="addCategory">
      <select v-model="newParent" class="parent-sel" title="대분류 선택">
        <option value="">대분류로 추가</option>
        <option v-for="m in majors" :key="m.id" :value="m.id">{{ m.name }}</option>
      </select>
      <div class="nc-line">
        <input v-model="newName" type="text" :placeholder="activeType === 'EXPENSE' ? '예: 식비, 교통' : '예: 급여, 용돈'" />
        <IconBtn icon="plus" title="추가" variant="primary" type="submit" size="sm" />
      </div>
    </form>
    <p class="hint sm">≡ 손잡이로 순서 변경 — <b>대분류</b>는 소분류와 함께, <b>소분류</b>는 그 대분류 안에서만 이동합니다.</p>

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

    <ul v-show="!loading && tree.length" ref="majorListEl" class="major-list">
      <li v-for="g in tree" :key="g.major.id" :data-id="g.major.id" class="major-block">
        <div class="cat-row major-row">
          <div class="cat-main">
            <span class="drag-handle major-handle" title="대분류 순서 변경(소분류 함께 이동)">≡</span>
            <span class="major-badge">대</span>
            <input v-model="g.major.name" class="cat-name" @keyup.enter="saveCategory(g.major)" />
            <IconBtn icon="check" title="저장" size="sm" @click="saveCategory(g.major)" />
            <IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(g.major)" />
          </div>
        </div>
        <ul class="sub-list" :data-parent="g.major.id">
          <li v-for="s in g.children" :key="s.id" :data-id="s.id" class="sub-item cat-row child">
            <div class="cat-main">
              <span class="drag-handle sub-handle" title="소분류 순서 변경">≡</span>
              <span class="sub-mark">↳</span>
              <input v-model="s.name" class="cat-name" @keyup.enter="saveCategory(s)" />
              <IconBtn icon="check" title="저장" size="sm" @click="saveCategory(s)" />
              <IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="removeCategory(s)" />
            </div>
          </li>
        </ul>
      </li>
    </ul>
    <p v-if="!loading && !tree.length" class="msg">
      등록된 {{ activeType === 'EXPENSE' ? '지출' : '수입' }} 분류가 없습니다.
    </p>

    <!-- 인앱 확인 모달 (네이티브 confirm 대체 — 제목 'sb_pt' 노출 방지) -->
    <Teleport to="body">
      <Transition name="fade">
        <div v-if="confirmState.open" class="modal-backdrop" @click.self="confirmState.open = false">
          <div class="modal confirm-modal" role="dialog" aria-modal="true">
            <h2>{{ confirmState.title }}</h2>
            <p class="confirm-body">{{ confirmState.message }}</p>
            <div class="buttons">
              <IconBtn icon="close" title="취소" @click="confirmState.open = false" />
              <IconBtn icon="check" title="확인" variant="primary" @click="confirmOk" />
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </section>
</template>

<style scoped>
.cat {
  max-width: 560px;
  margin: 0 auto;
}
.head {
  display: flex;
  align-items: center;
  justify-content: space-between;
}
.head-actions {
  display: flex;
  gap: 0.5rem;
}
h1 {
  font-size: 1.5rem;
}
.hint {
  font-size: 0.85rem;
  opacity: 0.7;
  margin: 0.5rem 0 1rem;
}
input {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
}
button {
  padding: 0.45rem 0.85rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
  cursor: pointer;
}
button.danger {
  border-color: #c0392b;
  color: #c0392b;
}
.tabs-row {
  display: flex;
  align-items: flex-end;
  justify-content: space-between;
  gap: 0.75rem;
  margin-bottom: 1rem;
}
.tabs-row .tabs {
  margin-bottom: 0;
}
.tabs {
  display: flex;
  gap: 0.25rem;
  border-bottom: 1px solid var(--color-border);
  margin-bottom: 1rem;
}
.tabs button {
  border: 0;
  border-bottom: 2px solid transparent;
  border-radius: 0;
  background: transparent;
  padding: 0.6rem 1rem;
}
.tabs button.active {
  border-bottom-color: hsla(160, 100%, 37%, 1);
  color: hsla(160, 100%, 37%, 1);
  font-weight: 600;
}
.new-cat {
  display: flex;
  flex-direction: column;
  gap: 0.5rem;
  margin-bottom: 1rem;
}
.nc-line {
  display: flex;
  gap: 0.5rem;
}
.nc-line input {
  flex: 1;
  min-width: 0;
}
.major-list {
  list-style: none;
  padding-left: 0;
  margin: 0;
}
.major-block {
  border: 1px solid var(--color-border);
  border-radius: 8px;
  margin-bottom: 0.5rem;
  overflow: hidden;
}
.major-row {
  background: var(--color-background-soft);
  border-bottom: 0 !important;
}
.major-badge {
  flex: 0 0 auto;
  font-size: 0.62rem;
  font-weight: 700;
  color: hsla(160, 100%, 37%, 1);
  border: 1px solid hsla(160, 100%, 37%, 0.6);
  border-radius: 4px;
  padding: 0.05rem 0.25rem;
}
.sub-list {
  list-style: none;
  padding: 0 0 0 0.4rem;
  margin: 0;
}
.sub-item:last-child {
  border-bottom: 0;
}
.cat-row {
  display: flex;
  flex-direction: column;
  gap: 0.35rem;
  padding: 0.5rem 0.5rem;
  border-bottom: 1px solid var(--color-border);
  background: var(--color-background);
}
.cat-main {
  display: flex;
  gap: 0.4rem;
  align-items: center;
}
.cat-parent {
  display: flex;
  align-items: center;
  gap: 0.4rem;
  padding-left: 1.4rem;
  font-size: 0.8rem;
  opacity: 0.85;
}
.cat-parent .pl {
  flex: 0 0 auto;
  opacity: 0.6;
}
.cat-parent .parent-sel {
  flex: 1;
  min-width: 0;
}
.drag-handle {
  cursor: grab;
  user-select: none;
  opacity: 0.5;
  font-size: 1.1rem;
  padding: 0 0.2rem;
  touch-action: none;
}
.drag-handle:active {
  cursor: grabbing;
}
.cat-name {
  flex: 1;
  min-width: 0;
}
.cat-row.child {
  padding-left: 1.2rem;
}
.cat-row.child .cat-name {
  background: var(--color-background-soft);
}
.sub-mark {
  opacity: 0.45;
}
.hint.sm {
  font-size: 0.78rem;
  margin: 0 0 0.75rem;
}
.msg {
  margin: 0.75rem 0;
}
.msg.error {
  color: #c0392b;
}
/* 인앱 확인 모달 */
.modal-backdrop {
  position: fixed;
  inset: 0;
  z-index: 1000;
  display: flex;
  align-items: center;
  justify-content: center;
  background: rgba(0, 0, 0, 0.5);
  padding: 1rem;
}
.modal {
  position: relative;
  width: 100%;
  max-width: 340px;
  padding: 1.5rem 1.25rem calc(1.25rem + env(safe-area-inset-bottom));
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: 12px;
}
.modal h2 {
  font-size: 1.05rem;
  margin: 0 0 0.6rem;
}
.confirm-body {
  margin: 0 0 1rem;
  font-size: 0.9rem;
  line-height: 1.5;
  white-space: pre-line;
}
.buttons {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.15s;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}
</style>
