<script setup>
import { computed, onErrorCaptured, onMounted, onUnmounted, ref, watch } from 'vue'
import { RouterView, useRoute, useRouter } from 'vue-router'
import AppHeader from '@/components/layout/AppHeader.vue'
import AppSidebar from '@/components/layout/AppSidebar.vue'
import AppBottomNav from '@/components/layout/AppBottomNav.vue'
import AdBanner from '@/components/AdBanner.vue'
import LoginModal from '@/components/LoginModal.vue'
import SignupModal from '@/components/SignupModal.vue'
import WebOnlyNotice from '@/components/WebOnlyNotice.vue'
import AppDialog from '@/components/ui/AppDialog.vue'
import AppLockScreen from '@/components/AppLockScreen.vue'
import { appLock } from '@/composables/appLock'
import { appError, reportFatal } from '@/composables/appError'
import { Capacitor } from '@capacitor/core'
import { reminders } from '@/native/reminders'
import { useAuthStore } from '@/stores/auth'
import { useUiStore } from '@/stores/ui'
import { demo, exitDemo } from '@/demo'
import { ADS_ENABLED } from '@/config/features'

const auth = useAuthStore()
const ui = useUiStore()
const route = useRoute()
const router = useRouter()

// 로그인 또는 둘러보기(데모) 상태면 사이드바/전체 셸 표시
const authed = computed(() => auth.isAuthenticated || demo.on)
// 광고: 광고 기능이 켜져 있고 유료(PREMIUM) 회원이 아닐 때만 표시 (레이아웃 하단 여백도 함께 제어)
const showAds = computed(() => ADS_ENABLED && !auth.isPremium)
// 공개 법적 고지(개인정보처리방침·약관)는 웹 브라우저에서도 접근 허용 (앱 마켓 정책 URL)
const isPublicLegal = computed(() => !!route.meta.public)

function leaveDemo() {
  exitDemo()
  router.push('/')
}

// 앱(Capacitor 네이티브) 또는 데스크톱(Electron) 클라이언트에서 전체 이용.
// 웹(브라우저)은 안내 페이지만 노출. 개발 모드(npm run dev)는 예외로 전체 앱 표시.
const isDesktop = /electron/i.test(navigator.userAgent)
const isApp = Capacitor.isNativePlatform() || import.meta.env.DEV || isDesktop

// http.js 의 401 처리에서 발생시키는 이벤트 → 세션 정리 후 로그인 팝업
function onUnauthorized() {
  auth.clear()
  ui.openLogin()
}
// http.js 의 403 PREMIUM_REQUIRED 처리 → 업그레이드 안내 화면으로
function onPremiumRequired() {
  if (route.name !== 'upgrade') router.push({ name: 'upgrade' })
}

// ===== 네트워크 상태 배너 =====
// 오프라인이거나(서버 연결 끊김) 서버 응답이 없을 때 상단에 안내 배너를 띄운다.
const online = ref(typeof navigator !== 'undefined' ? navigator.onLine : true)
const netTrouble = ref(false) // 온라인인데 서버 응답이 없는 경우(타임아웃/서버다운) 잠깐 표시
let netTimer = null
function onOnline() { online.value = true }
function onOffline() { online.value = false }
function onNetError() {
  if (typeof navigator !== 'undefined' && !navigator.onLine) { online.value = false; return }
  netTrouble.value = true
  clearTimeout(netTimer)
  netTimer = setTimeout(() => { netTrouble.value = false }, 5000)
}
const showNetBanner = computed(() => !online.value || netTrouble.value)
const netBannerText = computed(() =>
  !online.value
    ? '📡 인터넷 연결이 끊겼어요. 연결 상태를 확인해 주세요.'
    : '⚠️ 서버에 연결할 수 없어요. 잠시 후 다시 시도해 주세요.',
)
function retryReload() {
  window.location.reload()
}

// ===== 앱 잠금(PIN) =====
// 잠깐 다른 앱 다녀온 정도로는 잠그지 않고, 일정 시간 이상 백그라운드였을 때만 잠근다.
// (완전 종료/재시작은 onMounted 에서 잠금) — 카메라/갤러리/공유 등 인앱 동작은 suppress 로 제외.
const LOCK_GRACE_MS = 60 * 60 * 1000 // 1시간 미만 이탈은 잠그지 않음(잦은 PIN 입력 방지)
let backgroundedAt = 0
function onVisibility() {
  if (document.hidden) {
    backgroundedAt = Date.now()
  } else {
    const away = backgroundedAt ? Date.now() - backgroundedAt : 0
    backgroundedAt = 0
    if (away >= LOCK_GRACE_MS) appLock.lock() // lock() 은 suppress 창이면 건너뜀
    appLock.clearSuppress() // 복귀 — 일시중지 창 해제
  }
}
// 파일 선택(input[type=file]) 클릭 시 다음 백그라운드 전환을 잠금에서 제외
function onFileInputClick(e) {
  const t = e.target
  if (t && t.tagName === 'INPUT' && t.type === 'file') appLock.suppressLock()
}

// ===== 토스트 (뒤로가기 종료 안내 등) =====
const toastMsg = ref('')
let toastTimer = null
function onToast(e) {
  toastMsg.value = e.detail || ''
  clearTimeout(toastTimer)
  toastTimer = setTimeout(() => { toastMsg.value = '' }, 1800)
}

onMounted(() => {
  if (!isApp) return // 웹은 안내만 — 앱 전용 초기화 생략
  window.addEventListener('auth:unauthorized', onUnauthorized)
  window.addEventListener('premium:required', onPremiumRequired)
  window.addEventListener('online', onOnline)
  window.addEventListener('offline', onOffline)
  window.addEventListener('net:error', onNetError)
  window.addEventListener('app:toast', onToast)
  // 앱 잠금: 저장된 세션으로 재진입하는 경우 시작 시 잠금(새 로그인 직후는 잠그지 않음)
  if (appLock.state.enabled && appLock.hasSession()) appLock.lock()
  document.addEventListener('visibilitychange', onVisibility)
  document.addEventListener('click', onFileInputClick, true)
  ui.loadSignupEnabled() // 회원가입 허용 여부 선로딩(랜딩/로그인 가입 버튼에 반영)
  // 로그인 상태면 전체 프로필(아바타·포인트) 최신화 — 재로그인 없이 헤더 아바타 반영
  if (auth.isAuthenticated) {
    auth.refreshProfile().then(() => {
      if (reminders.isNative()) {
        reminders.scheduleExpiryReminder({
          premium: auth.isPremium,
          autoRenew: !!auth.user?.planAutoRenew,
          expiresAt: auth.user?.planExpiresAt,
        })
      }
    })
    if (reminders.isNative()) reminders.scheduleEntryReminder(false) // 오늘 기록 여부는 홈/내역에서 보정
  }
})
onUnmounted(() => {
  window.removeEventListener('auth:unauthorized', onUnauthorized)
  window.removeEventListener('premium:required', onPremiumRequired)
  window.removeEventListener('online', onOnline)
  window.removeEventListener('offline', onOffline)
  window.removeEventListener('net:error', onNetError)
  window.removeEventListener('app:toast', onToast)
  document.removeEventListener('visibilitychange', onVisibility)
  document.removeEventListener('click', onFileInputClick, true)
  clearTimeout(netTimer)
  clearTimeout(toastTimer)
})

// 자식 컴포넌트의 렌더/라이프사이클 오류를 잡아 흰 화면 대신 폴백 표시
onErrorCaptured((err, _instance, info) => {
  reportFatal({ err, info })
  return false // 상위로 전파 중단
})

// 페이지 이동 시 모바일 사이드바 자동 닫힘
watch(() => route.fullPath, () => ui.closeSidebar())
</script>

<template>
  <!-- 치명적 오류 폴백 (흰 화면 방지) -->
  <div v-if="appError.hasError" class="fatal-screen">
    <div class="fatal-inner">
      <div class="fatal-emoji">😵</div>
      <h1 class="fatal-title">문제가 발생했어요</h1>
      <p class="fatal-sub">일시적인 오류일 수 있어요. 새로고침 후 다시 시도해 주세요.</p>
      <button type="button" class="fatal-btn" @click="retryReload">새로고침</button>
    </div>
  </div>

  <template v-else>
  <!-- 웹(브라우저): 공개 법적 고지는 노출, 그 외엔 안내 페이지 -->
  <template v-if="!isApp">
    <RouterView v-if="isPublicLegal" />
    <WebOnlyNotice v-else />
  </template>

  <!-- 앱(Capacitor): 전체 기능 -->
  <template v-else>
    <!-- 앱 잠금 화면 (로그인 상태 + 잠금 활성 시) -->
    <AppLockScreen v-if="appLock.state.locked && auth.isAuthenticated" />

    <!-- 네트워크 오류/오프라인 안내 배너 -->
    <Transition name="netbar">
      <div v-if="showNetBanner" class="net-banner" role="alert">
        <span class="net-text">{{ netBannerText }}</span>
        <button type="button" class="net-retry" @click="retryReload">새로고침</button>
      </div>
    </Transition>

    <div class="layout" :class="{ 'sidebar-open': ui.sidebarOpen, 'no-sidebar': !authed, 'has-ad': showAds }">
      <AppHeader class="layout-top" />
      <!-- 로그인/둘러보기 전에는 메뉴가 없어 사이드바를 숨김(빈 칸 제거·중앙 정렬) -->
      <template v-if="authed">
        <AppSidebar class="layout-left" />
        <div class="sidebar-backdrop" @click="ui.closeSidebar()"></div>
      </template>
      <main class="layout-body">
        <div v-if="demo.on" class="demo-bar">
          <span>👀 <b>둘러보기 모드</b> · 샘플 데이터입니다.</span>
          <span class="demo-actions">
            <button type="button" class="db-login" @click="ui.openLogin('/account')">로그인</button>
            <button type="button" class="db-exit" @click="leaveDemo">나가기</button>
          </span>
        </div>
        <RouterView />
      </main>
      <!-- 하단 내비 위 광고 (유료 회원 제외). 앱은 320x50 모바일 배너(임시) -->
      <AdBanner v-if="showAds" class="app-ad" unit="DAN-qd8As3nqJjXTQKa6" :width="320" :height="50" />
      <AppBottomNav class="layout-bottom" />
    </div>

    <LoginModal />
    <SignupModal />

    <!-- 토스트 (뒤로가기 종료 안내 등) -->
    <Transition name="toast">
      <div v-if="toastMsg" class="app-toast" role="status">{{ toastMsg }}</div>
    </Transition>
  </template>

  <!-- 공용 인앱 다이얼로그 (네이티브 alert/confirm/prompt 대체) -->
  <AppDialog />
  </template>
</template>

<style scoped>
.layout {
  display: grid;
  grid-template-areas:
    'left   top'
    'left   body'
    'bottom bottom';
  grid-template-columns: 220px 1fr;
  grid-template-rows: auto 1fr auto;
  /* 앱 셸: 뷰포트 높이에 고정하고 본문(layout-body)만 스크롤 → 상단 헤더·하단 내비 고정, 페이지 전체 스크롤 방지 */
  height: 100vh; /* 폴백 */
  height: 100dvh; /* 동적 뷰포트(주소창·안전영역 반영) */
  overflow: hidden;
}

.layout-top {
  grid-area: top;
}

.layout-left {
  grid-area: left;
}

.demo-bar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 0.75rem;
  flex-wrap: wrap;
  margin-bottom: 1rem;
  padding: 0.55rem 0.9rem;
  border: 1px solid hsla(160, 100%, 37%, 0.4);
  border-radius: 8px;
  background: hsla(160, 100%, 37%, 0.08);
  font-size: 0.88rem;
}

/* 네트워크 오류/오프라인 배너 — 화면 최상단 고정 */
.net-banner {
  position: fixed;
  top: 0;
  left: 0;
  right: 0;
  z-index: 2000;
  display: flex;
  align-items: center;
  justify-content: center;
  gap: 0.75rem;
  padding: calc(env(safe-area-inset-top, 0) + 0.5rem) 0.9rem 0.5rem;
  background: #c0392b;
  color: #fff;
  font-size: 0.85rem;
  font-weight: 600;
  box-shadow: 0 1px 6px rgba(0, 0, 0, 0.25);
}
.net-text {
  word-break: keep-all;
}
.net-retry {
  flex: none;
  padding: 0.25rem 0.7rem;
  border: 1px solid rgba(255, 255, 255, 0.7);
  border-radius: 6px;
  background: transparent;
  color: #fff;
  font-size: 0.8rem;
  font-weight: 700;
  cursor: pointer;
}
.net-retry:hover {
  background: rgba(255, 255, 255, 0.15);
}
.netbar-enter-active,
.netbar-leave-active {
  transition: transform 0.2s ease, opacity 0.2s ease;
}
.netbar-enter-from,
.netbar-leave-to {
  transform: translateY(-100%);
  opacity: 0;
}

/* 토스트 */
.app-toast {
  position: fixed;
  left: 50%;
  bottom: calc(72px + env(safe-area-inset-bottom, 0));
  transform: translateX(-50%);
  z-index: 2600;
  max-width: 80vw;
  padding: 0.6rem 1.1rem;
  border-radius: 999px;
  background: rgba(0, 0, 0, 0.82);
  color: #fff;
  font-size: 0.85rem;
  white-space: nowrap;
  box-shadow: 0 2px 10px rgba(0, 0, 0, 0.3);
}
.toast-enter-active,
.toast-leave-active {
  transition: opacity 0.2s ease, transform 0.2s ease;
}
.toast-enter-from,
.toast-leave-to {
  opacity: 0;
  transform: translateX(-50%) translateY(8px);
}

/* 치명적 오류 폴백 화면 */
.fatal-screen {
  position: fixed;
  inset: 0;
  z-index: 4000;
  display: flex;
  align-items: center;
  justify-content: center;
  padding: 1.5rem;
  background: var(--color-background);
}
.fatal-inner {
  text-align: center;
  max-width: 360px;
}
.fatal-emoji {
  font-size: 2.8rem;
}
.fatal-title {
  margin-top: 0.6rem;
  font-size: 1.2rem;
  color: var(--color-heading);
}
.fatal-sub {
  margin-top: 0.5rem;
  font-size: 0.9rem;
  opacity: 0.7;
  line-height: 1.6;
  word-break: keep-all;
}
.fatal-btn {
  margin-top: 1.4rem;
  padding: 0.7rem 1.8rem;
  border: 0;
  border-radius: 10px;
  background: hsla(160, 100%, 37%, 1);
  color: #fff;
  font-size: 0.95rem;
  font-weight: 700;
  cursor: pointer;
}
.fatal-btn:hover {
  background: hsla(160, 100%, 32%, 1);
}
.demo-actions {
  display: flex;
  gap: 0.4rem;
}
.demo-bar button {
  padding: 0.35rem 0.8rem;
  border-radius: 6px;
  font-weight: 600;
  font-size: 0.85rem;
  cursor: pointer;
}
.db-login {
  border: 1px solid hsla(160, 100%, 37%, 1);
  background: hsla(160, 100%, 37%, 1);
  color: #fff;
}
.db-exit {
  border: 1px solid var(--color-border);
  background: var(--color-background);
  color: var(--color-text);
}
.layout-body {
  grid-area: body;
  padding: 1.5rem 2rem;
  /* 하단 고정 내비(56px + 안전영역) 뒤로 마지막 내용이 가려지지 않도록 항상 여백 확보 */
  padding-bottom: calc(56px + env(safe-area-inset-bottom, 0) + 1rem);
  /* 그리드 1fr 트랙 안에서만 스크롤(콘텐츠로 인해 트랙이 늘어나 페이지 전체가 스크롤되지 않도록 min-height:0) */
  min-height: 0;
  overflow-y: auto;
  overflow-x: hidden; /* 페이지 가로 스크롤 방지 — 넓은 표/차트는 각자 컨테이너에서 스크롤 */
  -webkit-overflow-scrolling: touch;
}

.layout-bottom {
  grid-area: bottom;
}

/* 하단 내비(56px) 바로 위 고정 광고 */
.app-ad {
  position: fixed;
  left: 0;
  right: 0;
  bottom: calc(56px + env(safe-area-inset-bottom, 0));
  z-index: 999;
  background: var(--color-background);
  border-top: 1px solid var(--color-border);
}
/* 광고가 있을 땐 본문 하단 여백을 광고 높이(320x50 배너)만큼 더 확보 */
.layout.has-ad .layout-body {
  padding-bottom: calc(56px + 62px + env(safe-area-inset-bottom, 0) + 1rem);
}

/* 로그인 전: 사이드바 없이 단일 컬럼 — 콘텐츠가 창 중앙에 오도록 */
.layout.no-sidebar {
  grid-template-columns: 1fr;
  grid-template-areas:
    'top'
    'body'
    'bottom';
}

.sidebar-backdrop {
  display: none;
}

/* 모바일: 사이드바를 오프캔버스 드로어로 */
@media (max-width: 768px) {
  .layout {
    grid-template-areas:
      'top'
      'body'
      'bottom';
    grid-template-columns: 1fr;
    grid-template-rows: auto 1fr auto;
  }
  .layout-left {
    position: fixed;
    top: 0;
    left: 0;
    bottom: 0;
    z-index: 1200;
    width: 78%;
    max-width: 280px;
    overflow-y: auto;
    transform: translateX(-100%);
    transition: transform 0.22s ease;
    box-shadow: 2px 0 16px rgba(0, 0, 0, 0.25);
  }
  .layout.sidebar-open .layout-left {
    transform: translateX(0);
  }
  .layout.sidebar-open .sidebar-backdrop {
    display: block;
    position: fixed;
    inset: 0;
    z-index: 1150;
    background: rgba(0, 0, 0, 0.45);
  }
  .layout-body {
    padding: 1rem;
    padding-bottom: calc(56px + env(safe-area-inset-bottom, 0) + 1rem);
  }
}
</style>
