<script setup>
import { computed, nextTick, onMounted, reactive, ref } from 'vue'
import { accountApi } from '@/api/accountApi'
import IconBtn from '@/components/ui/IconBtn.vue'
import { imageToBlob, parseReceiptText } from '@/utils/receiptOcr'
import { cardNotif } from '@/native/cardNotif'
import { Capacitor } from '@capacitor/core'
// @capacitor/camera 는 네이티브에서만 동적 로드 (웹은 file input 사용 — 정적 import 시 모바일 웹 청크 로드 실패 유발)

const now = new Date()
const year = ref(now.getFullYear())
const month = ref(now.getMonth() + 1)

const entries = ref([])
const summary = ref({ totalIncome: 0, totalExpense: 0, balance: 0 })
const loading = ref(false)
const error = ref(null)
const pendingCount = ref(0) // 확인 필요(카드 알림 자동인식) 건수
const editingPending = ref(false) // 수정 중인 항목이 미확인 건인지

// 카드 결제 알림 자동인식 권한 (네이티브 전용)
const notifNative = cardNotif.isNative()
const notifEnabled = ref(true) // 미허용일 때만 배너 노출
async function checkNotifPermission() {
  if (notifNative) notifEnabled.value = await cardNotif.isEnabled()
}
function openNotifSettings() {
  cardNotif.openSettings()
}

async function loadPendingCount() {
  try {
    pendingCount.value = (await accountApi.pendingCount()).count || 0
  } catch {
    pendingCount.value = 0
  }
}
// 미확인 내역 즉시 확정(분류 미지정으로 수락)
async function confirmRow(e) {
  try {
    await accountApi.confirmEntry(e.id, {})
    e.pending = false // UI 즉시 반영(목록 재로딩 전)
    await load()
    await loadPendingCount()
  } catch (err) {
    const st = err.response?.status
    alert(err.response?.data?.message || `확인 처리에 실패했습니다.${st ? ' (HTTP ' + st + ')' : ' (네트워크 오류)'}`)
  }
}

// 검색·필터
const filterOpen = ref(false)
const filters = reactive({ keyword: '', type: '', category: '', walletId: '', tagId: '' })
const hasFilter = computed(() => !!(filters.keyword || filters.type || filters.category || filters.walletId || filters.tagId))
function filterParams() {
  const p = {}
  if (filters.keyword) p.keyword = filters.keyword.trim()
  if (filters.type) p.type = filters.type
  if (filters.category) p.category = filters.category
  if (filters.walletId) p.walletId = filters.walletId
  if (filters.tagId) p.tagId = filters.tagId
  return p
}
function applyFilters() {
  load()
}
function resetFilters() {
  filters.keyword = ''
  filters.type = ''
  filters.category = ''
  filters.walletId = ''
  filters.tagId = ''
  load()
}

// 추가/수정 모달
const formOpen = ref(false)
const editId = ref(null)
const form = reactive({ entryDate: '', type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: '', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, installmentMonths: '' })
const isRepayment = computed(() => form.type === 'REPAYMENT')
// 카드 지출일 때만 할부 입력 노출 (2~24개월, 일시불은 빈값)
const showInstallment = computed(() => form.type === 'EXPENSE' && form.walletKind === 'CARD')
const installmentMonthly = computed(() => {
  const m = Number(form.installmentMonths)
  const amt = Number(form.amount)
  return m >= 2 && amt > 0 ? Math.round(amt / m) : 0
})
const liabilityWallets = computed(() => wallets.value.filter((w) => w.type === 'LOAN' || w.type === 'CARD'))
const submitting = ref(false)
const formError = ref(null)

// 영수증 OCR (온디바이스)
const receiptInput = ref(null)
const ocrRunning = ref(false)
const ocrResult = ref(null) // { amount, date, store }
const receiptPickerOpen = ref(false) // 카메라/갤러리 선택 레이어
const webCapture = ref(false) // 웹: 카메라 버튼이면 capture 활성
// 영수증 등록: 커스텀 레이어 팝업(카메라/갤러리 선택)
function pickReceipt() {
  ocrResult.value = null
  receiptPickerOpen.value = true
}
async function chooseCamera() {
  receiptPickerOpen.value = false
  if (Capacitor.isNativePlatform()) {
    await captureFrom('CAMERA')
  } else {
    webCapture.value = true
    await nextTick()
    receiptInput.value?.click()
  }
}
async function chooseGallery() {
  receiptPickerOpen.value = false
  if (Capacitor.isNativePlatform()) {
    await captureFrom('PHOTOS')
  } else {
    webCapture.value = false
    await nextTick()
    receiptInput.value?.click()
  }
}
// 네이티브 전용 — 카메라 플러그인을 이 시점에만 동적 로드
async function captureFrom(sourceKey) {
  try {
    const { Camera, CameraResultType, CameraSource } = await import('@capacitor/camera')
    const photo = await Camera.getPhoto({
      source: sourceKey === 'CAMERA' ? CameraSource.Camera : CameraSource.Photos,
      resultType: CameraResultType.DataUrl,
      quality: 70,
      correctOrientation: true,
    })
    if (photo?.dataUrl) await runOcr(photo.dataUrl)
  } catch {
    // 사용자가 취소 → 무시
  }
}
async function onReceiptFile(e) {
  const file = e.target.files?.[0]
  e.target.value = '' // 같은 파일 재선택 허용
  if (file) await runOcr(file)
}
async function runOcr(image) {
  ocrRunning.value = true
  ocrResult.value = null
  formError.value = null
  try {
    const blob = await imageToBlob(image)
    const { text } = await accountApi.ocrReceipt(blob)
    const r = parseReceiptText(text)
    // 추출값이 있을 때만 폼에 채움 (메모는 비어있을 때만)
    if (r.amount) form.amount = r.amount
    if (r.date) form.entryDate = r.date
    if (r.store && !form.memo) form.memo = r.store
    // 카드 결제 영수증이면 등록된 카드 자동 선택 (카드사 매칭)
    let cardName = null
    if (form.type === 'EXPENSE') {
      const card = r.cardIssuer ? matchCardWallet(r.cardIssuer) : null
      if (card) {
        form.walletKind = 'CARD'
        form.walletId = card.id
        cardName = card.name
      } else if (r.isCard) {
        form.walletKind = 'CARD' // 카드결제지만 매칭 실패 → 카드 목록만 좁혀줌
        form.walletId = ''
      }
    }
    ocrResult.value = { amount: r.amount, date: r.date, store: r.store, card: cardName }
    if (!r.amount && !r.date) {
      formError.value = '영수증에서 정보를 충분히 인식하지 못했습니다. 직접 입력하거나 더 선명한 사진으로 다시 시도하세요.'
    }
  } catch (e) {
    formError.value = e.response?.data?.message || '영수증 인식에 실패했습니다. 다시 시도해 주세요.'
  } finally {
    ocrRunning.value = false
  }
}

// 계좌/카드
const wallets = ref([])
async function loadWallets() {
  try {
    wallets.value = await accountApi.wallets()
  } catch {
    wallets.value = []
  }
}

// 계좌 종류(콤보) → 해당 종류만 목록에
const WALLET_KINDS = [
  { value: 'BANK', label: '계좌' },
  { value: 'CARD', label: '카드' },
  { value: 'LOAN', label: '대출' },
  { value: 'INVEST', label: '증권' },
]
const walletsOfKind = computed(() => wallets.value.filter((w) => w.type === form.walletKind))
const toWalletsOfKind = computed(() => wallets.value.filter((w) => w.type === form.toWalletKind))
function walletKindOf(id) {
  const w = wallets.value.find((x) => x.id === id)
  return w ? w.type : ''
}
// 영수증에서 감지한 카드사명으로 등록된 카드(CARD) 찾기
function matchCardWallet(issuer) {
  if (!issuer) return null
  return wallets.value.find(
    (w) => w.type === 'CARD' && ((w.issuer || '').includes(issuer) || (w.name || '').includes(issuer)),
  )
}
function onWalletKindChange() {
  form.walletId = ''
}
function onToWalletKindChange() {
  form.toWalletId = ''
}

// 분류(카테고리)
const categories = ref([])
async function loadCategories() {
  try {
    categories.value = await accountApi.categories()
  } catch {
    categories.value = []
  }
}
// 현재 구분(수입/지출)에 맞는 분류 + 편집 중 현재 값 보존
const categoryOptions = computed(() => {
  const names = categories.value.filter((c) => c.type === form.type).map((c) => c.name)
  if (form.category && !names.includes(form.category)) names.unshift(form.category)
  return names
})
// 필터용 전체 분류명 (중복 제거)
const allCategoryNames = computed(() => [...new Set(categories.value.map((c) => c.name))])

// 모달 내 분류 인라인 추가
const addingCategory = ref(false)
const newCategoryName = ref('')
const catSubmitting = ref(false)
function startAddCategory() {
  newCategoryName.value = ''
  addingCategory.value = true
}
function cancelAddCategory() {
  addingCategory.value = false
  newCategoryName.value = ''
}
async function confirmAddCategory() {
  const name = newCategoryName.value.trim()
  if (!name) return
  catSubmitting.value = true
  try {
    await accountApi.createCategory({ type: form.type, name })
    await loadCategories()
    form.category = name
    cancelAddCategory()
  } catch (e) {
    if (e.response?.status === 409) {
      // 이미 있는 분류면 그대로 선택
      form.category = name
      cancelAddCategory()
    } else {
      alert(e.response?.data?.message || '분류 추가에 실패했습니다.')
    }
  } finally {
    catSubmitting.value = false
  }
}

// 태그 (태그 관리에 등록된 태그 선택)
const tagOptions = ref([]) // [{ id, name }]
const selectedTagIds = ref([])

function toggleTag(id) {
  const i = selectedTagIds.value.indexOf(id)
  if (i >= 0) selectedTagIds.value.splice(i, 1)
  else selectedTagIds.value.push(id)
}

async function loadTagOptions() {
  try {
    tagOptions.value = await accountApi.tags()
  } catch {
    tagOptions.value = []
  }
}

const periodLabel = computed(() => `${year.value}년 ${String(month.value).padStart(2, '0')}월`)

function won(n) {
  return (n ?? 0).toLocaleString('ko-KR')
}
function dotClass(type) {
  return type === 'INCOME' ? 'income' : type === 'TRANSFER' ? 'transfer' : 'expense'
}
function amountClass(type) {
  return type === 'INCOME' ? 'income' : type === 'TRANSFER' ? 'transfer' : 'expense'
}
function amountSign(type) {
  return type === 'INCOME' ? '+' : type === 'TRANSFER' ? '' : '-'
}

// 일별 그룹 + 일 합계 (백엔드가 날짜 내림차순 정렬 → 순서 유지)
const entriesByDay = computed(() => {
  const groups = []
  const idx = {}
  for (const e of entries.value) {
    const d = (e.entryDate || '').slice(0, 10)
    if (idx[d] === undefined) {
      idx[d] = groups.length
      groups.push({ date: d, income: 0, expense: 0, items: [] })
    }
    const g = groups[idx[d]]
    g.items.push(e)
    if (e.type === 'INCOME') g.income += e.amount
    else if (e.type === 'EXPENSE') g.expense += e.amount
  }
  return groups
})
function dayLabel(d) {
  const dt = new Date(d)
  if (Number.isNaN(dt.getTime())) return d
  const wd = ['일', '월', '화', '수', '목', '금', '토'][dt.getDay()]
  return `${dt.getMonth() + 1}월 ${dt.getDate()}일 (${wd})`
}

async function load() {
  loading.value = true
  error.value = null
  try {
    const listParams = { year: year.value, month: month.value, ...filterParams() }
    const sumParams = { year: year.value, month: month.value }
    const [list, sum] = await Promise.all([accountApi.list(listParams), accountApi.summary(sumParams)])
    entries.value = list
    summary.value = sum
  } catch (e) {
    error.value = e.response?.data?.message || '내역을 불러오지 못했습니다.'
  } finally {
    loading.value = false
  }
}

function prevMonth() {
  if (month.value === 1) {
    month.value = 12
    year.value -= 1
  } else {
    month.value -= 1
  }
  load()
}
function nextMonth() {
  if (month.value === 12) {
    month.value = 1
    year.value += 1
  } else {
    month.value += 1
  }
  load()
}

function todayStr() {
  const d = new Date()
  return `${d.getFullYear()}-${String(d.getMonth() + 1).padStart(2, '0')}-${String(d.getDate()).padStart(2, '0')}`
}

function openCreate() {
  editId.value = null
  editingPending.value = false
  Object.assign(form, { entryDate: todayStr(), type: 'EXPENSE', category: '', amount: null, memo: '', walletKind: 'BANK', walletId: '', toWalletKind: '', toWalletId: '', principal: null, interest: null, installmentMonths: '' })
  selectedTagIds.value = []
  cancelAddCategory()
  formError.value = null
  formOpen.value = true
}
function openEdit(e) {
  editId.value = e.id
  editingPending.value = !!e.pending
  Object.assign(form, {
    entryDate: e.entryDate,
    type: e.type,
    category: e.category || '',
    amount: e.amount,
    memo: e.memo || '',
    // 알림 자동인식(pending): 매칭 실패 시 수입(입금)=은행, 지출(카드결제)=카드로 기본 선택(현금 X)
    walletKind: e.walletId
      ? walletKindOf(e.walletId)
      : (e.pending ? (e.type === 'INCOME' ? 'BANK' : 'CARD') : (e.type === 'TRANSFER' ? '' : 'CASH')),
    walletId: e.walletId || '',
    toWalletKind: walletKindOf(e.toWalletId),
    toWalletId: e.toWalletId || '',
    installmentMonths: e.installmentMonths || '',
  })
  // 태그 이름 → id 매핑 (현재 태그 목록 기준)
  const nameToId = {}
  tagOptions.value.forEach((t) => (nameToId[t.name] = t.id))
  selectedTagIds.value = (e.tags || []).map((n) => nameToId[n]).filter(Boolean)
  cancelAddCategory()
  formError.value = null
  formOpen.value = true
}

async function submit() {
  formError.value = null
  if (!form.entryDate) {
    formError.value = '거래일을 입력하세요.'
    return
  }
  // 상환/납부: 원금=이체, 이자=지출 자동 분리
  if (isRepayment.value) {
    if (!form.walletId || !form.toWalletId) {
      formError.value = '출금 계좌와 대상(대출/카드)을 선택하세요.'
      return
    }
    if (form.walletId === form.toWalletId) {
      formError.value = '출금/대상 계좌가 같을 수 없습니다.'
      return
    }
    const principal = Number(form.principal) || 0
    const interest = Number(form.interest) || 0
    if (principal <= 0 && interest <= 0) {
      formError.value = '원금 또는 이자를 입력하세요.'
      return
    }
    submitting.value = true
    try {
      await accountApi.repayment({
        entryDate: form.entryDate,
        fromWalletId: form.walletId,
        targetWalletId: form.toWalletId,
        principal,
        interest,
        memo: form.memo || null,
      })
      formOpen.value = false
      await load()
    } catch (e) {
      formError.value = e.response?.data?.message || '저장에 실패했습니다.'
    } finally {
      submitting.value = false
    }
    return
  }
  if (form.amount == null || form.amount < 0) {
    formError.value = '금액을 올바르게 입력하세요.'
    return
  }
  if (form.type === 'TRANSFER') {
    if (!form.walletId || !form.toWalletId) {
      formError.value = '이체는 출금/입금 계좌를 모두 선택하세요.'
      return
    }
    if (form.walletId === form.toWalletId) {
      formError.value = '출금/입금 계좌가 같을 수 없습니다.'
      return
    }
  }
  submitting.value = true
  const payload = {
    entryDate: form.entryDate,
    type: form.type,
    category: form.category || null,
    amount: Number(form.amount),
    memo: form.memo || null,
    walletId: form.walletId || null,
    toWalletId: form.type === 'TRANSFER' ? form.toWalletId || null : null,
    installmentMonths: showInstallment.value && Number(form.installmentMonths) >= 2 ? Number(form.installmentMonths) : null,
    tagIds: selectedTagIds.value,
  }
  try {
    if (editId.value) {
      await accountApi.update(editId.value, payload)
      // 미확인(카드 알림) 건이면 수정 저장 후 확정 처리
      if (editingPending.value) {
        await accountApi.confirmEntry(editId.value, {})
      }
    } else {
      await accountApi.create(payload)
    }
    formOpen.value = false
    await load()
    await loadPendingCount()
  } catch (e) {
    formError.value = e.response?.data?.message || '저장에 실패했습니다.'
  } finally {
    submitting.value = false
  }
}

async function remove(e) {
  if (!confirm('이 내역을 삭제하시겠습니까?')) return
  try {
    await accountApi.remove(e.id)
    await load()
  } catch (err) {
    alert(err.response?.data?.message || '삭제에 실패했습니다.')
  }
}

onMounted(async () => {
  // 진입 시 밀린 정기 거래를 먼저 반영한 뒤 목록을 불러온다 (중복 없이)
  try {
    await accountApi.runRecurrings()
  } catch {
    // 정기 거래 반영 실패는 가계부 조회를 막지 않는다
  }
  load()
  loadTagOptions()
  loadWallets()
  loadCategories()
  loadPendingCount()
  checkNotifPermission()
})
</script>

<template>
  <section class="account">
    <header class="account-head">
      <h1>가계부<span v-if="pendingCount" class="pending-count">확인 필요 {{ pendingCount }}건</span></h1>
      <IconBtn icon="plus" title="내역 추가" variant="primary" @click="openCreate" />
    </header>

    <!-- 카드 결제 알림 자동인식 권한 안내 (네이티브, 미허용 시) -->
    <div v-if="notifNative && !notifEnabled" class="notif-banner">
      <span>💳 카드 결제 알림을 자동으로 가계부에 등록하려면 <b>알림 접근 권한</b>이 필요합니다.</span>
      <button type="button" class="notif-btn" @click="openNotifSettings">설정 열기</button>
    </div>

    <div class="month-nav">
      <IconBtn icon="chevronLeft" title="이전 달" size="sm" @click="prevMonth" />
      <span class="period">{{ periodLabel }}</span>
      <IconBtn icon="chevronRight" title="다음 달" size="sm" @click="nextMonth" />
      <IconBtn
        icon="filter" title="검색·필터" class="filter-toggle"
        :variant="hasFilter ? 'primary' : 'default'" @click="filterOpen = !filterOpen"
      />
    </div>

    <div v-if="filterOpen" class="filter-bar">
      <input
        v-model="filters.keyword" type="text" class="f-keyword"
        placeholder="메모·분류 검색" @keyup.enter="applyFilters"
      />
      <select v-model="filters.type">
        <option value="">구분 전체</option>
        <option value="INCOME">수입</option>
        <option value="EXPENSE">지출</option>
        <option value="TRANSFER">이체</option>
      </select>
      <select v-model="filters.category">
        <option value="">분류 전체</option>
        <option v-for="c in allCategoryNames" :key="c" :value="c">{{ c }}</option>
      </select>
      <select v-model="filters.walletId">
        <option value="">계좌 전체</option>
        <option v-for="w in wallets" :key="w.id" :value="w.id">{{ w.name }}</option>
      </select>
      <select v-model="filters.tagId">
        <option value="">태그 전체</option>
        <option v-for="t in tagOptions" :key="t.id" :value="t.id">{{ t.name }}</option>
      </select>
      <IconBtn icon="search" title="적용" variant="primary" @click="applyFilters" />
      <IconBtn icon="refresh" title="초기화" @click="resetFilters" />
    </div>

    <div class="summary">
      <div class="card income">
        <span class="label">수입</span>
        <span class="value">+{{ won(summary.totalIncome) }}</span>
      </div>
      <div class="card expense">
        <span class="label">지출</span>
        <span class="value">-{{ won(summary.totalExpense) }}</span>
      </div>
      <div class="card balance">
        <span class="label">잔액</span>
        <span class="value">{{ won(summary.balance) }}</span>
      </div>
    </div>

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

    <div v-else-if="entries.length" class="day-list">
      <section v-for="g in entriesByDay" :key="g.date" class="day-group">
        <div class="day-head">
          <span class="day-date">{{ dayLabel(g.date) }}</span>
          <span class="day-sums">
            <span v-if="g.income" class="income">+{{ won(g.income) }}</span>
            <span v-if="g.expense" class="expense">-{{ won(g.expense) }}</span>
          </span>
        </div>
        <ul class="day-items">
          <li v-for="e in g.items" :key="e.id" class="entry-item" :class="{ 'is-pending': e.pending }">
            <div class="ei-line1">
              <span class="ei-cat">
                <span class="type-dot" :class="dotClass(e.type)"></span>
                <template v-if="e.type === 'TRANSFER'">이체</template>
                <template v-else>{{ e.category || (e.type === 'INCOME' ? '수입' : '지출') }}</template>
              </span>
              <span v-if="e.pending" class="ei-pending">확인필요</span>
              <span v-if="e.type === 'TRANSFER'" class="ei-wallet">{{ e.walletName }} → {{ e.toWalletName }}</span>
              <span v-else-if="e.walletName" class="ei-wallet">{{ e.walletName }}</span>
              <span class="ei-amount" :class="amountClass(e.type)">{{ amountSign(e.type) }}{{ won(e.amount) }}</span>
              <span class="ei-act">
                <button v-if="e.pending" type="button" class="confirm-btn" title="확인(확정)" @click="confirmRow(e)">확인</button>
                <IconBtn icon="edit" title="수정" size="sm" @click="openEdit(e)" />
                <IconBtn icon="trash" title="삭제" variant="danger" size="sm" @click="remove(e)" />
              </span>
            </div>
            <div v-if="e.memo || e.installmentMonths > 1 || (e.tags && e.tags.length)" class="ei-line2">
              <span v-if="e.installmentMonths > 1" class="ei-install">{{ e.installmentMonths }}개월 할부 · 월 {{ won(Math.round(e.amount / e.installmentMonths)) }}</span>
              <span v-if="e.memo" class="ei-memo">{{ e.memo }}</span>
              <span v-for="t in e.tags" :key="t" class="row-tag">{{ t }}</span>
            </div>
          </li>
        </ul>
      </section>
    </div>
    <p v-else-if="!loading" class="msg">{{ hasFilter ? '조건에 맞는 내역이 없습니다.' : '이 달의 내역이 없습니다.' }}</p>

    <!-- 추가/수정 모달 -->
    <Teleport to="body">
      <Transition name="fade">
        <div v-if="formOpen" class="modal-backdrop" @click.self="formOpen = false">
          <div class="modal" role="dialog" aria-modal="true">
            <button class="close" type="button" @click="formOpen = false">×</button>
            <h2>{{ editId ? '내역 수정' : '내역 추가' }}</h2>

            <form class="entry-form" @submit.prevent="submit">
              <!-- 영수증 OCR (온디바이스) -->
              <div v-if="!isRepayment" class="receipt-box">
                <input
                  ref="receiptInput" type="file" accept="image/*"
                  :capture="webCapture ? 'environment' : undefined"
                  class="receipt-input" @change="onReceiptFile"
                />
                <button
                  type="button" class="receipt-btn"
                  :disabled="submitting || ocrRunning" @click="pickReceipt"
                >📷 영수증 스캔</button>
                <span v-if="ocrRunning" class="receipt-status">영수증 인식 중…</span>
                <span v-else-if="ocrResult" class="receipt-status ok">
                  <template v-if="ocrResult.amount">{{ won(ocrResult.amount) }}원</template>
                  <template v-if="ocrResult.date"> · {{ ocrResult.date }}</template>
                  <template v-if="ocrResult.store"> · {{ ocrResult.store }}</template>
                  <template v-if="ocrResult.card"> · 💳{{ ocrResult.card }}</template>
                  자동 입력됨
                </span>
                <span v-else class="receipt-hint">사진에서 금액·날짜·상호를 자동 입력</span>
                <div v-if="ocrRunning" class="receipt-progress"><div class="bar"></div></div>
              </div>

              <label>거래일<input v-model="form.entryDate" type="date" :disabled="submitting" /></label>
              <label>구분
                <select v-model="form.type" :disabled="submitting">
                  <option value="EXPENSE">지출</option>
                  <option value="INCOME">수입</option>
                  <option value="TRANSFER">이체</option>
                  <option v-if="!editId" value="REPAYMENT">상환/납부</option>
                </select>
              </label>
              <!-- 계좌 종류 먼저 선택(라디오) → 해당 종류만 셀렉트에 -->
              <div class="field">
                <div class="field-row">
                  <span class="field-label">계좌 종류</span>
                  <div class="wallet-radios">
                    <label v-for="k in WALLET_KINDS" :key="k.value" class="radio">
                      <input type="radio" :value="k.value" v-model="form.walletKind" :disabled="submitting" @change="onWalletKindChange" />
                      {{ k.label }}
                    </label>
                    <label v-if="form.type !== 'TRANSFER'" class="radio">
                      <input type="radio" value="CASH" v-model="form.walletKind" :disabled="submitting" @change="onWalletKindChange" /> 현금
                    </label>
                  </div>
                </div>
                <select v-if="form.walletKind && form.walletKind !== 'CASH'" v-model="form.walletId" :disabled="submitting">
                  <option value="">{{ form.type === 'INCOME' || form.type === 'EXPENSE' ? '계좌/카드 선택' : '출금 계좌 선택' }}</option>
                  <option v-for="w in walletsOfKind" :key="w.id" :value="w.id">
                    {{ w.name }}{{ w.issuer ? ` (${w.issuer})` : '' }}
                  </option>
                </select>
              </div>
              <!-- 카드 지출: 할부 개월수 (일시불 또는 2~24개월) -->
              <label v-if="showInstallment">할부
                <select v-model="form.installmentMonths" :disabled="submitting">
                  <option value="">일시불</option>
                  <option v-for="m in 23" :key="m + 1" :value="m + 1">{{ m + 1 }}개월</option>
                </select>
                <small v-if="installmentMonthly" class="install-hint">월 약 {{ won(installmentMonthly) }}원</small>
              </label>
              <template v-if="form.type === 'TRANSFER'">
                <div class="field">
                  <div class="field-row">
                    <span class="field-label">입금 종류</span>
                    <div class="wallet-radios">
                      <label v-for="k in WALLET_KINDS" :key="k.value" class="radio">
                        <input type="radio" :value="k.value" v-model="form.toWalletKind" :disabled="submitting" @change="onToWalletKindChange" />
                        {{ k.label }}
                      </label>
                    </div>
                  </div>
                  <select v-if="form.toWalletKind" v-model="form.toWalletId" :disabled="submitting">
                    <option value="">입금 계좌 선택</option>
                    <option v-for="w in toWalletsOfKind" :key="w.id" :value="w.id">
                      {{ w.name }}{{ w.issuer ? ` (${w.issuer})` : '' }}
                    </option>
                  </select>
                </div>
              </template>

              <!-- 상환/납부: 대상(대출/카드) + 원금 + 이자 -->
              <template v-if="isRepayment">
                <label>대상(대출/카드)
                  <select v-model="form.toWalletId" :disabled="submitting">
                    <option value="">(선택)</option>
                    <option v-for="w in liabilityWallets" :key="w.id" :value="w.id">
                      {{ w.name }}{{ w.issuer ? ` (${w.issuer})` : '' }}
                    </option>
                  </select>
                </label>
                <label>원금<input v-model.number="form.principal" type="number" min="0" placeholder="원" :disabled="submitting" /></label>
                <label>이자<input v-model.number="form.interest" type="number" min="0" placeholder="원" :disabled="submitting" /></label>
              </template>

              <label v-if="form.type === 'INCOME' || form.type === 'EXPENSE'">분류
                <div v-if="!addingCategory" class="cat-input">
                  <select v-model="form.category" :disabled="submitting">
                    <option value="">(선택)</option>
                    <option v-for="c in categoryOptions" :key="c" :value="c">{{ c }}</option>
                  </select>
                  <button type="button" class="cat-add-btn" :disabled="submitting" @click="startAddCategory">+ 추가</button>
                </div>
                <div v-else class="cat-input">
                  <input
                    v-model="newCategoryName" type="text"
                    :placeholder="form.type === 'EXPENSE' ? '새 지출 분류' : '새 수입 분류'"
                    :disabled="catSubmitting" @keyup.enter.prevent="confirmAddCategory"
                  />
                  <button type="button" class="cat-add-btn primary" :disabled="catSubmitting" @click="confirmAddCategory">확인</button>
                  <button type="button" class="cat-add-btn" :disabled="catSubmitting" @click="cancelAddCategory">취소</button>
                </div>
              </label>
              <label v-if="!isRepayment">금액<input v-model.number="form.amount" type="number" min="0" placeholder="원" :disabled="submitting" /></label>
              <label>메모<input v-model="form.memo" type="text" placeholder="(선택)" :disabled="submitting" /></label>

              <div v-if="!isRepayment" class="tag-field">
                <span class="tag-label">태그</span>
                <div class="tag-badges">
                  <button
                    v-for="t in tagOptions"
                    :key="t.id"
                    type="button"
                    class="tag-badge"
                    :class="{ active: selectedTagIds.includes(t.id) }"
                    @click="toggleTag(t.id)"
                  >{{ t.name }}</button>
                  <span v-if="!tagOptions.length" class="tag-empty">등록된 태그가 없습니다 (태그 관리에서 추가)</span>
                </div>
              </div>

              <p v-if="formError" class="msg error">{{ formError }}</p>

              <div class="buttons">
                <IconBtn icon="close" title="취소" @click="formOpen = false" />
                <IconBtn icon="save" :title="editId ? '수정' : '등록'" variant="primary" type="submit" :disabled="submitting" />
              </div>
            </form>
          </div>
        </div>
      </Transition>
    </Teleport>

    <!-- 영수증 등록 방식 선택 (카메라/갤러리) -->
    <Teleport to="body">
      <Transition name="fade">
        <div v-if="receiptPickerOpen" class="modal-backdrop picker-backdrop" @click.self="receiptPickerOpen = false">
          <div class="modal picker-modal" role="dialog" aria-modal="true">
            <button class="close" type="button" @click="receiptPickerOpen = false">×</button>
            <h2>영수증 등록</h2>
            <div class="picker-options">
              <button type="button" class="picker-opt" @click="chooseCamera">
                <span class="po-icon">📷</span>
                <span class="po-label">카메라 촬영</span>
                <span class="po-desc">지금 영수증을 찍어요</span>
              </button>
              <button type="button" class="picker-opt" @click="chooseGallery">
                <span class="po-icon">🖼️</span>
                <span class="po-label">갤러리에서 선택</span>
                <span class="po-desc">저장된 사진을 골라요</span>
              </button>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </section>
</template>

<style scoped>
.account {
  max-width: 760px;
  margin: 0 auto;
}
.account-head {
  display: flex;
  align-items: center;
  justify-content: space-between;
  margin-bottom: 1rem;
}
h1 {
  font-size: 1.5rem;
}
button {
  padding: 0.45rem 0.9rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
  cursor: pointer;
}
button:disabled {
  opacity: 0.5;
  cursor: not-allowed;
}
button.danger {
  border-color: #c0392b;
  color: #c0392b;
}
button.primary {
  border-color: hsla(160, 100%, 37%, 1);
  color: hsla(160, 100%, 37%, 1);
}
.month-nav {
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 1rem;
  margin-bottom: 1rem;
}
.period {
  font-size: 1.1rem;
  font-weight: 600;
  min-width: 8rem;
  text-align: center;
}
.filter-toggle {
  font-size: 0.82rem;
  padding: 0.35rem 0.7rem;
}
.filter-toggle.on {
  border-color: hsla(160, 100%, 37%, 1);
  color: hsla(160, 100%, 37%, 1);
}
.filter-badge {
  margin-left: 0.25rem;
  font-size: 0.6rem;
  vertical-align: middle;
}
.filter-bar {
  display: flex;
  flex-wrap: wrap;
  gap: 0.5rem;
  margin-bottom: 1rem;
  padding: 0.75rem;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  background: var(--color-background-soft);
}
.filter-bar input,
.filter-bar select {
  padding: 0.4rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background);
  color: var(--color-text);
  font-size: 0.85rem;
}
.filter-bar .f-keyword {
  flex: 1;
  min-width: 8rem;
}
.summary {
  display: flex;
  gap: 0.75rem;
  margin-bottom: 1.25rem;
}
.card {
  flex: 1;
  border: 1px solid var(--color-border);
  border-radius: 6px;
  padding: 0.75rem 1rem;
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.card .label {
  font-size: 0.85rem;
  opacity: 0.7;
}
.card .value {
  font-size: 1.15rem;
  font-weight: 700;
}
.card.income .value {
  color: #2e7d32;
}
.card.expense .value {
  color: #c0392b;
}
/* 일별 그룹 목록 */
.day-group {
  margin-bottom: 1rem;
}
.day-head {
  display: flex;
  align-items: baseline;
  justify-content: space-between;
  padding: 0.3rem 0.1rem;
  border-bottom: 2px solid var(--color-border);
  margin-bottom: 0.2rem;
}
.day-date {
  font-size: 0.9rem;
  font-weight: 700;
}
.day-sums {
  display: flex;
  gap: 0.7rem;
  font-size: 0.85rem;
  font-weight: 600;
}
.day-sums .income {
  color: #2e7d32;
}
.day-sums .expense {
  color: #c0392b;
}
.day-items {
  list-style: none;
}
.entry-item {
  padding: 0.5rem 0.1rem;
  border-bottom: 1px solid var(--color-border);
}
.entry-item.is-pending {
  background: rgba(230, 126, 34, 0.07);
}
.ei-pending {
  font-size: 0.7rem;
  font-weight: 600;
  color: #e67e22;
  border: 1px solid #e67e22;
  border-radius: 3px;
  padding: 0.02rem 0.3rem;
  white-space: nowrap;
}
.confirm-btn {
  padding: 0.18rem 0.5rem;
  font-size: 0.78rem;
  font-weight: 600;
  border: 1px solid hsla(160, 100%, 37%, 1);
  border-radius: 4px;
  background: hsla(160, 100%, 37%, 1);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}
.notif-banner {
  display: flex;
  align-items: center;
  gap: 0.6rem;
  flex-wrap: wrap;
  padding: 0.6rem 0.8rem;
  margin-bottom: 1rem;
  border: 1px solid hsla(160, 100%, 37%, 0.4);
  border-radius: 8px;
  background: hsla(160, 100%, 37%, 0.08);
  font-size: 0.83rem;
}
.notif-btn {
  margin-left: auto;
  padding: 0.35rem 0.8rem;
  font-size: 0.82rem;
  border: 1px solid hsla(160, 100%, 37%, 1);
  border-radius: 4px;
  background: hsla(160, 100%, 37%, 1);
  color: #fff;
  cursor: pointer;
  white-space: nowrap;
}
.pending-count {
  margin-left: 0.5rem;
  font-size: 0.72rem;
  font-weight: 600;
  vertical-align: middle;
  color: #e67e22;
  border: 1px solid #e67e22;
  border-radius: 999px;
  padding: 0.1rem 0.5rem;
}
.ei-line1 {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.ei-cat {
  font-weight: 500;
  white-space: nowrap;
}
.ei-wallet {
  font-size: 0.74rem;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  opacity: 0.85;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
  max-width: 45%;
}
.ei-amount {
  margin-left: auto;
  font-weight: 700;
  white-space: nowrap;
}
.ei-amount.income {
  color: #2e7d32;
}
.ei-amount.expense {
  color: #c0392b;
}
.ei-amount.transfer {
  color: var(--color-text);
  opacity: 0.8;
}
.ei-act {
  display: flex;
  gap: 0.25rem;
  flex-shrink: 0;
}
.ei-line2 {
  margin-top: 0.25rem;
  padding-left: 1.1rem;
  font-size: 0.8rem;
  opacity: 0.85;
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  gap: 0.35rem;
}
.type-dot {
  display: inline-block;
  width: 8px;
  height: 8px;
  border-radius: 50%;
  margin-right: 0.35rem;
}
.type-dot.income {
  background: #2e7d32;
}
.type-dot.expense {
  background: #c0392b;
}
.type-dot.transfer {
  background: #888;
}
.msg {
  margin: 1rem 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: 360px;
  /* 폼이 길어도 모달 안에서 스크롤되도록 + 하단 소프트키/제스처바 안전영역 확보 */
  max-height: calc(100vh - 2rem);
  overflow-y: auto;
  padding: 1.75rem 1.5rem calc(1.5rem + env(safe-area-inset-bottom));
  background: var(--color-background);
  border: 1px solid var(--color-border);
  border-radius: 8px;
  box-shadow: 0 10px 30px rgba(0, 0, 0, 0.25);
}
.modal .close {
  position: absolute;
  top: 0.5rem;
  right: 0.6rem;
  width: 2rem;
  height: 2rem;
  border: 0;
  background: transparent;
  font-size: 1.5rem;
  line-height: 1;
}
.modal h2 {
  margin-bottom: 1rem;
  font-size: 1.2rem;
  text-align: center;
}
.entry-form {
  display: flex;
  flex-direction: column;
  gap: 0.6rem;
}
.receipt-box {
  display: flex;
  align-items: center;
  flex-wrap: wrap;
  gap: 0.5rem;
  padding: 0.5rem 0.6rem;
  border: 1px dashed var(--color-border);
  border-radius: 6px;
  background: var(--color-background-soft);
}
.receipt-input {
  display: none;
}
.receipt-btn {
  padding: 0.4rem 0.7rem;
  font-size: 0.85rem;
  border: 1px solid hsla(160, 100%, 37%, 0.6);
  border-radius: 4px;
  background: var(--color-background);
  color: hsla(160, 100%, 37%, 1);
  cursor: pointer;
  white-space: nowrap;
}
.receipt-hint {
  font-size: 0.76rem;
  opacity: 0.6;
}
.receipt-status {
  font-size: 0.78rem;
  opacity: 0.85;
}
.receipt-status.ok {
  color: hsla(160, 100%, 37%, 1);
}
/* 인식 중 무한 로딩 바 */
.receipt-progress {
  flex-basis: 100%;
  height: 4px;
  border-radius: 2px;
  background: var(--color-background-mute);
  overflow: hidden;
}
.receipt-progress .bar {
  width: 40%;
  height: 100%;
  border-radius: 2px;
  background: hsla(160, 100%, 37%, 1);
  animation: receipt-indeterminate 1.1s ease-in-out infinite;
}
@keyframes receipt-indeterminate {
  0% { margin-left: -40%; }
  100% { margin-left: 100%; }
}
/* 영수증 등록 방식 선택 팝업 */
.picker-backdrop {
  z-index: 1200; /* 내역 모달(1000) 위에 */
}
.picker-modal {
  max-width: 320px;
}
.picker-options {
  display: flex;
  gap: 0.7rem;
}
.picker-opt {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 0.3rem;
  padding: 1.1rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 10px;
  background: var(--color-background-soft);
  color: var(--color-text);
  cursor: pointer;
  transition: border-color 0.15s, transform 0.1s;
}
.picker-opt:hover {
  border-color: hsla(160, 100%, 37%, 0.6);
  transform: translateY(-1px);
}
.po-icon {
  font-size: 1.7rem;
  line-height: 1;
}
.po-label {
  font-weight: 600;
  font-size: 0.9rem;
}
.po-desc {
  font-size: 0.72rem;
  opacity: 0.6;
  text-align: center;
}
.entry-form label {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
}
.entry-form input,
.entry-form select {
  padding: 0.5rem 0.7rem;
  border: 1px solid var(--color-border);
  border-radius: 4px;
  background: var(--color-background-soft);
  color: var(--color-text);
}
/* 계좌 종류 라디오 (라벨+라디오 한 줄, 선택 종류는 셀렉트로 아래) */
.field {
  display: flex;
  flex-direction: column;
  gap: 0.3rem;
}
.field-row {
  display: flex;
  align-items: center;
  gap: 0.5rem;
}
.field-label {
  font-size: 0.82rem;
  flex-shrink: 0;
}
.wallet-radios {
  display: flex;
  flex: 1;
  min-width: 0;
  flex-wrap: wrap;
  gap: 0.3rem 0.5rem;
}
.wallet-radios .radio {
  display: flex;
  flex-direction: row;
  align-items: center;
  gap: 0.15rem;
  font-size: 0.82rem;
  cursor: pointer;
}
.wallet-radios .radio input {
  padding: 0;
  width: auto;
  margin: 0;
}
.cat-input {
  display: flex;
  gap: 0.4rem;
}
.cat-input select,
.cat-input input {
  flex: 1;
  min-width: 0;
}
.cat-add-btn {
  padding: 0.4rem 0.6rem;
  font-size: 0.8rem;
  white-space: nowrap;
  flex-shrink: 0;
}
.cat-add-btn.primary {
  border-color: hsla(160, 100%, 37%, 1);
  color: hsla(160, 100%, 37%, 1);
}
.tag-field {
  display: flex;
  flex-direction: column;
  gap: 0.25rem;
  font-size: 0.85rem;
}
.tag-badges {
  display: flex;
  flex-wrap: wrap;
  gap: 0.35rem;
}
.tag-badge {
  padding: 0.25rem 0.6rem;
  border: 1px solid var(--color-border);
  border-radius: 999px;
  background: var(--color-background);
  color: var(--color-text);
  font-size: 0.82rem;
  cursor: pointer;
}
.tag-badge.active {
  border-color: hsla(160, 100%, 37%, 1);
  background: hsla(160, 100%, 37%, 0.12);
  color: hsla(160, 100%, 37%, 1);
  font-weight: 600;
}
.tag-empty {
  font-size: 0.8rem;
  opacity: 0.6;
}
.row-tag {
  margin-left: 0.35rem;
  font-size: 0.75rem;
  color: hsla(160, 100%, 37%, 1);
}
.install-hint {
  font-size: 0.75rem;
  opacity: 0.65;
  margin-top: 0.1rem;
}
.ei-install {
  font-size: 0.74rem;
  padding: 0.05rem 0.4rem;
  border: 1px solid hsla(160, 100%, 37%, 0.5);
  border-radius: 3px;
  color: hsla(160, 100%, 37%, 1);
  white-space: nowrap;
}
.row-wallet {
  margin-right: 0.35rem;
  font-size: 0.75rem;
  padding: 0.05rem 0.35rem;
  border: 1px solid var(--color-border);
  border-radius: 3px;
  opacity: 0.85;
}
.buttons {
  display: flex;
  justify-content: flex-end;
  gap: 0.5rem;
  margin-top: 0.5rem;
  /* 폼이 길 때도 저장/취소 버튼이 항상 보이도록 하단 고정 */
  position: sticky;
  bottom: calc(-1.5rem - env(safe-area-inset-bottom));
  padding: 0.6rem 0 0.2rem;
  background: var(--color-background);
}
.fade-enter-active,
.fade-leave-active {
  transition: opacity 0.18s ease;
}
.fade-enter-from,
.fade-leave-to {
  opacity: 0;
}

/* ===== 모바일 ===== */
@media (max-width: 768px) {
  .account {
    max-width: 100%;
  }
  .summary {
    gap: 0.5rem;
  }
  .card {
    padding: 0.6rem 0.7rem;
  }
  .card .value {
    font-size: 1rem;
  }
  .ei-wallet {
    max-width: 38%;
  }
  .filter-bar .f-keyword {
    flex-basis: 100%;
  }
}
</style>
