<template>
  <v-app :style="appChromeVars">
    <!-- ── App Bar (隐藏于登录页) ── -->
    <v-app-bar v-if="auth.isLoggedIn" color="primary" elevation="0" flat class="ps-4">
      <v-app-bar-nav-icon class="mr-3" @click="drawer = !drawer" />

      <v-avatar color="surface" size="40" class="pa-1">
        <v-icon icon="mdi-server-network" size="24" />
      </v-avatar>

      <v-app-bar-title>Nexus</v-app-bar-title>

      <template #append>
        <v-btn class="text-none me-1 px-3" height="48" slim>
          <template #prepend>
            <v-avatar color="surface-light" size="32" start>
              <v-icon icon="mdi-account" size="18" />
            </v-avatar>
          </template>

          <span class="hidden-sm-and-down">{{ auth.admin?.username || '管理员' }}</span>

          <v-menu activator="parent">
            <v-list density="compact" nav>
              <v-list-item
                prepend-icon="mdi-theme-light-dark"
                title="切换主题"
                link
                @click="toggleTheme"
              />
              <v-list-item
                prepend-icon="mdi-cog-outline"
                title="设置"
                link
                @click="router.push('/settings')"
              />
              <v-list-item
                prepend-icon="mdi-logout"
                title="退出登录"
                link
                @click="doLogout"
              />
            </v-list>
          </v-menu>

          <template #append>
            <v-icon icon="mdi-chevron-down" />
          </template>
        </v-btn>
      </template>
    </v-app-bar>

    <!-- ── Navigation Drawer (隐藏于登录页) ── -->
    <v-navigation-drawer
      v-if="auth.isLoggedIn"
      v-model="drawer"
      color="surface"
      width="250"
      class="nexus-nav-drawer"
      :temporary="mobile"
    >
      <v-list nav slim>
        <v-list-subheader>运维</v-list-subheader>

        <v-list-item
          v-for="item in opsItems"
          :key="item.to"
          :prepend-icon="item.icon"
          :title="item.title"
          :to="item.to"
          :active="route.path === item.to"
          active-class="text-primary"
          link
          nav
          slim
          @click="navigateTo(item.to)"
          @mouseenter="scheduleRoutePrefetch(item.to)"
          @mouseleave="cancelRoutePrefetch(item.to)"
        />
      </v-list>

      <v-divider class="mx-3 my-1" />

      <v-list nav slim>
        <v-list-subheader>系统</v-list-subheader>

        <v-list-item
          v-for="item in sysItems"
          :key="item.to"
          :prepend-icon="item.icon"
          :title="item.title"
          :to="item.to"
          :active="route.path === item.to"
          active-class="text-primary"
          link
          nav
          slim
          @click="navigateTo(item.to)"
          @mouseenter="scheduleRoutePrefetch(item.to)"
          @mouseleave="cancelRoutePrefetch(item.to)"
        />
      </v-list>

      <template #append>
        <v-list-item
          :active="route.path === '/settings'"
          prepend-icon="mdi-cog-outline"
          title="设置"
          active-class="text-primary"
          class="ma-2"
          link
          nav
          slim
          @click="router.push('/settings')"
          @mouseenter="scheduleRoutePrefetch('/settings')"
          @mouseleave="cancelRoutePrefetch('/settings')"
        />
      </template>
    </v-navigation-drawer>

    <!-- ── 脚本执行全局进度 ── -->
    <div
      v-if="auth.isLoggedIn && runningItems.length"
      class="nexus-script-exec-bar"
    >
      <v-sheet
        v-for="item in runningItems"
        :key="item.executionId"
        class="nexus-script-exec-bar__item px-4 py-2"
        :class="{ 'nexus-script-exec-bar__item--clickable': item.taskKind === 'server_batch' }"
        color="surface"
        border
        @click="item.taskKind === 'server_batch' ? openRunningBatchJob(item) : undefined"
      >
        <div class="d-flex align-center flex-wrap ga-2 mb-1">
          <span class="text-body-2 font-weight-medium text-truncate">{{ item.label }}</span>
          <v-chip size="x-small" variant="tonal" color="primary" label>#{{ item.executionId }}</v-chip>
          <v-spacer />
          <span class="text-caption text-medium-emphasis">
            已进行 {{ item.done }} 台，剩余 {{ item.remaining }} 台
            <span class="ml-2">({{ item.progress }})</span>
          </span>
        </div>
        <v-progress-linear
          :model-value="item.total ? Math.round((item.done / item.total) * 100) : 0"
          color="primary"
          height="6"
          rounded
          striped
          stream
        />
      </v-sheet>
    </div>

    <!-- ── 脚本提交 / 执行结束：顶栏下方居中 ── -->
    <div
      v-if="auth.isLoggedIn && (scriptSubmitToast.visible || completeAlerts.length)"
      class="nexus-top-center-script-notices"
    >
      <Transition name="nexus-script-submit-toast-fade">
        <v-alert
          v-if="scriptSubmitToast.visible"
          key="script-submit"
          :color="scriptSubmitToast.color"
          variant="tonal"
          density="compact"
          class="nexus-top-center-script-notices__item"
          role="status"
          @click="dismissScriptSubmitToast()"
        >
          {{ scriptSubmitToast.text }}
        </v-alert>
      </Transition>
      <v-alert
        v-for="alert in completeAlerts"
        :key="alert.executionId"
        :class="[
          'nexus-top-center-script-notices__item',
          { 'nexus-script-exec-alert--fading': alert.fading },
        ]"
        :type="completeAlertType(alert.status)"
        variant="tonal"
        closable
        density="compact"
        @click="dismissCompleteAlert(alert.executionId)"
        @click:close="dismissCompleteAlert(alert.executionId)"
      >
        <div class="d-flex align-center flex-wrap ga-2 justify-center">
          <span>
            「{{ alert.label }}」执行结束：
            成功 {{ alert.success }} / 失败 {{ alert.failed }} / 共 {{ alert.total }} 台
          </span>
          <v-btn
            size="small"
            variant="text"
            color="primary"
            @click.stop="goToTaskDetail(alert)"
          >
            查看详情
          </v-btn>
        </div>
      </v-alert>
    </div>

    <GlobalBrowserPanel v-if="auth.isLoggedIn" />

    <!-- ── Main Content ── -->
    <v-main :class="mainLayoutClass" :style="mainContentOffset">
      <router-view v-slot="{ Component, route: activeRoute }">
        <keep-alive :include="cachedPageNames" :max="12">
          <component :is="Component" :key="activeRoute.name ?? activeRoute.path" />
        </keep-alive>
      </router-view>
    </v-main>

    <!-- ── Snackbar ── -->
    <v-snackbar
      v-model="snackbar.show"
      :color="snackbar.color"
      :timeout="3000"
      location="top"
      class="nexus-top-center-snackbar"
      multi-line
    >
      {{ snackbar.text }}
    </v-snackbar>

  </v-app>
</template>

<script setup lang="ts">
import { ref, reactive, watch, onMounted, provide, computed } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { useTheme, useDisplay } from 'vuetify'
import { useAuthStore } from '@/stores/auth'
import { useWebSocket } from '@/composables/useWebSocket'
import {
  useScriptExecutionQueue,
  onScriptExecutionComplete,
  type ScriptExecCompleteAlert,
} from '@/composables/useScriptExecutionQueue'
import { openServerBatchJobResult } from '@/composables/useServerBatchJobViewer'
import { executionRecordPath } from '@/utils/executionRecordRoute'
import { useScriptCompleteSound } from '@/composables/useScriptCompleteSound'
import { usePushCompleteSound } from '@/composables/usePushCompleteSound'
import {
  scriptSubmitToastState as scriptSubmitToast,
  dismissScriptSubmitToast,
} from '@/composables/useScriptSubmitToast'
import { clearCachedQueries } from '@/composables/useCachedQuery'
import { scheduleRoutePrefetch, cancelRoutePrefetch } from '@/composables/useRoutePrefetch'
import { CACHED_PAGE_NAMES } from '@/constants/cachedPages'
import { api } from '@/api'
import GlobalBrowserPanel from '@/components/GlobalBrowserPanel.vue'

const cachedPageNames = [...CACHED_PAGE_NAMES]

const route = useRoute()
const router = useRouter()
const theme = useTheme()
const auth = useAuthStore()
const ws = useWebSocket()
const { runningItems, completeAlerts, dismissCompleteAlert } = useScriptExecutionQueue()
const { playCompleteSound, syncFromServer: syncScriptSoundFromServer } = useScriptCompleteSound()
const { syncFromServer: syncPushSoundFromServer } = usePushCompleteSound()

function completeAlertType(status: string): 'success' | 'warning' | 'error' | 'info' {
  if (status === 'completed') return 'success'
  if (status === 'failed') return 'error'
  if (status === 'partial') return 'warning'
  return 'info'
}

function goToTaskDetail(alert: ScriptExecCompleteAlert) {
  dismissCompleteAlert(alert.executionId)
  if (alert.taskKind === 'server_batch') {
    void openServerBatchJobResult(alert.executionId, alert.label)
    return
  }
  router.push(executionRecordPath(alert.executionId, 'script')).catch(() => {})
}

function openRunningBatchJob(item: { executionId: number; label: string; taskKind?: string }) {
  if (item.taskKind !== 'server_batch') return
  void openServerBatchJobResult(item.executionId, item.label)
}

onMounted(() => {
  onScriptExecutionComplete(() => playCompleteSound())
})

// Auto-connect WebSocket when logged in
watch(() => auth.isLoggedIn, (loggedIn) => {
  if (loggedIn) {
    ws.connect()
    syncThemeFromServer()
    syncScriptSoundFromServer()
    syncPushSoundFromServer()
  }
  else ws.disconnect()
}, { immediate: true })

// ── Theme: localStorage + MySQL ──
async function syncThemeFromServer() {
  if (!auth.isLoggedIn) return
  try {
    const data = await api<{ value: string }>('/settings/theme')
    if (data.value === 'dark' || data.value === 'light') {
      theme.global.name.value = data.value
      localStorage.setItem('nexus_theme', data.value)
    }
  } catch { /* non-critical — keep localStorage value */ }
}

async function toggleTheme() {
  const newTheme = theme.global.current.value.dark ? 'light' : 'dark'
  theme.global.name.value = newTheme
  localStorage.setItem('nexus_theme', newTheme)
  // Persist to MySQL (fire-and-forget)
  try {
    await api('/settings/theme', { method: 'PUT', body: JSON.stringify({ value: newTheme }) })
  } catch { /* keep localStorage as fallback */ }
}

const drawer = ref(true)
const { mobile } = useDisplay()

/** 终端页监听菜单开合，在动画结束后 refit xterm */
provide('nexusDrawer', drawer)

const mainLayoutClass = computed(() => ({
  'nexus-terminal-main': route.path === '/terminal',
  'nexus-page-main': route.path !== '/terminal' && route.path !== '/login',
}))

const BROWSER_PANEL_HEIGHT = 420

const appChromeVars = computed(() => {
  const dock = auth.isLoggedIn ? `${BROWSER_PANEL_HEIGHT}px` : '0px'
  return {
    '--nexus-browser-dock-height': dock,
    '--nexus-browser-dock-offset': dock,
  }
})

const mainContentOffset = computed(() => {
  if (!auth.isLoggedIn) return undefined
  const execRows = runningItems.value.length
  const execExtra = execRows ? execRows * 52 : 0
  const browserExtra = BROWSER_PANEL_HEIGHT
  const total = execExtra + browserExtra
  return {
    paddingTop: `calc(var(--v-layout-top, 64px) + ${total}px)`,
  }
})

const opsItems = [
  { to: '/',          title: '仪表盘',   icon: 'mdi-view-dashboard-outline' },
  { to: '/servers',   title: '服务器',   icon: 'mdi-server' },
  { to: '/watch-metrics', title: '监测历史', icon: 'mdi-chart-line' },
  { to: '/terminal',  title: '终端',       icon: 'mdi-console' },
  { to: '/files',     title: '文件管理',   icon: 'mdi-folder-outline' },
  { to: '/push',      title: '推送',     icon: 'mdi-upload-outline' },
  { to: '/scripts',   title: '脚本库',   icon: 'mdi-code-braces' },
  { to: '/executions', title: '执行记录', icon: 'mdi-clipboard-play-outline' },
  { to: '/schedules', title: '调度',     icon: 'mdi-clock-outline' },
  { to: '/retries',   title: '重试队列', icon: 'mdi-refresh' },
]

const sysItems = [
  { to: '/commands', title: '命令日志', icon: 'mdi-console' },
  { to: '/alerts',   title: '告警中心', icon: 'mdi-bell-outline' },
  { to: '/audit',    title: '审计日志', icon: 'mdi-clipboard-text-outline' },
]

const snackbar = reactive({ show: false, text: '', color: 'success' })

function navigateTo(to: string) {
  if (route.path !== to) {
    router.push(to).catch(() => {})
  }
  if (mobile.value) {
    drawer.value = false
  }
}

// ── Theme ──
// See toggleTheme() above — persists to both localStorage and MySQL


function doLogout() {
  clearCachedQueries()
  auth.logout()
  router.push('/login')
}

// Expose snackbar globally
window.$snackbar = (text: string, color = 'success') => {
  snackbar.text = text
  snackbar.color = color
  snackbar.show = true
}
</script>

<style>
/*
 * 终端页：占满视口剩余高度；保留 --v-layout-top 顶栏留白（禁止 padding-top:0，否则标签栏被 App Bar 遮挡）
 * 宽度随 --v-layout-left 随侧栏开合自动缩进
 */
.v-application .v-main.nexus-terminal-main {
  display: flex;
  flex-direction: column;
  box-sizing: border-box;
  height: 100dvh;
  max-height: 100dvh;
  min-height: 0;
  max-width: 100%;
  overflow: hidden;
  padding-top: var(--v-layout-top, 64px) !important;
  padding-bottom: 0 !important;
  transition:
    padding 0.2s cubic-bezier(0.4, 0, 0.2, 1),
    height 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.v-application .v-main.nexus-terminal-main > .v-container {
  flex: 1 1 auto;
  min-height: 0;
  min-width: 0;
  max-width: 100%;
  width: 100%;
  padding: 0;
}

/* 仪表盘等业务页：随侧栏 --v-layout-left 缩进，表格可横向滚动 */
.v-application .v-main.nexus-page-main {
  box-sizing: border-box;
  min-height: calc(100dvh - var(--v-layout-top, 64px));
  min-width: 0;
  max-width: 100%;
  overflow-x: hidden;
  transition: padding 0.2s cubic-bezier(0.4, 0, 0.2, 1);
}
.v-application .v-main.nexus-page-main > * {
  min-width: 0;
  max-width: 100%;
}

/* 侧栏菜单项过多时可滚动，避免「调度/重试队列」在视口外无法点击 */
.nexus-nav-drawer :deep(.v-navigation-drawer__content) {
  overflow-y: auto;
}

.nexus-script-exec-bar {
  position: fixed;
  top: calc(var(--v-layout-top, 64px) + var(--nexus-browser-dock-offset, 0px));
  left: var(--v-layout-left, 0);
  right: 0;
  z-index: 1005;
  display: flex;
  flex-direction: column;
  gap: 0;
  pointer-events: none;
}
.nexus-script-exec-bar__item {
  pointer-events: auto;
  border-bottom: 1px solid rgba(var(--v-border-color), var(--v-border-opacity));
}
.nexus-script-exec-bar__item--clickable {
  cursor: pointer;
}
.nexus-script-exec-bar__item--clickable:hover {
  background: rgba(var(--v-theme-primary), 0.04);
}

.nexus-top-center-script-notices {
  position: fixed;
  top: calc(var(--v-layout-top, 64px) + 8px);
  left: 50%;
  transform: translateX(-50%);
  z-index: 2501;
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  width: min(520px, calc(100vw - 32px));
  pointer-events: none;
}
.nexus-top-center-script-notices__item {
  pointer-events: auto;
  width: 100%;
  cursor: pointer;
  text-align: center;
}
.nexus-script-exec-alert--fading {
  opacity: 0;
  transition: opacity 0.35s ease;
}
.nexus-script-submit-toast-fade-enter-active,
.nexus-script-submit-toast-fade-leave-active {
  transition: opacity 0.2s ease;
}
.nexus-script-submit-toast-fade-enter-from,
.nexus-script-submit-toast-fade-leave-to {
  opacity: 0;
}

/* 全局 Snackbar：顶栏下方水平居中（校验/警告/保存成功等） */
.v-application .v-snackbar.nexus-top-center-snackbar {
  z-index: 10000 !important;
  left: 50% !important;
  right: auto !important;
  transform: translateX(-50%);
  top: calc(var(--v-layout-top, 64px) + 8px) !important;
  bottom: auto !important;
  justify-content: center;
  max-width: min(520px, calc(100vw - 32px));
}
.v-application .v-snackbar.nexus-top-center-snackbar .v-snackbar__wrapper {
  min-width: min(280px, calc(100vw - 48px));
}
</style>
