<script setup lang="ts">
import { ref, computed, onMounted, watch } from 'vue'
import { useI18n } from 'vue-i18n'
import { useRoute } from 'vue-router'
import { useSystemStore } from '@/stores/system'
import { useConfigStore } from '@/stores/config'
import { useAuthStore } from '@/stores/auth'
import {
  authApi,
  configApi,
  hidApi,
  streamApi,
  atxConfigApi,
  extensionsApi,
  systemApi,
  updateApi,
  type EncoderBackendInfo,
  type AuthConfig,
  type RustDeskConfigResponse,
  type RustDeskStatusResponse,
  type RustDeskPasswordResponse,
  type RtspStatusResponse,
  type RtspConfigUpdate,
  type WebConfig,
  type UpdateOverviewResponse,
  type UpdateStatusResponse,
  type UpdateChannel,
  type VideoEncoderSelfCheckResponse,
} from '@/api'
import type {
  ExtensionsStatus,
  ExtensionStatus,
  AtxDriverType,
  ActiveLevel,
  AtxDevices,
  OtgEndpointBudget,
  OtgHidProfile,
  OtgHidFunctions,
} from '@/types/generated'
import { setLanguage } from '@/i18n'
import { useClipboard } from '@/composables/useClipboard'
import { getVideoFormatState } from '@/lib/video-format-support'
import AppLayout from '@/components/AppLayout.vue'
import { Button } from '@/components/ui/button'
import { Card, CardContent, CardDescription, CardHeader, CardTitle } from '@/components/ui/card'
import { Input } from '@/components/ui/input'
import { Label } from '@/components/ui/label'
import { Switch } from '@/components/ui/switch'
import { Separator } from '@/components/ui/separator'
import { Badge } from '@/components/ui/badge'
import { Sheet, SheetContent, SheetTrigger } from '@/components/ui/sheet'
import {
  Dialog,
  DialogContent,
  DialogFooter,
  DialogHeader,
  DialogTitle,
} from '@/components/ui/dialog'
import {
  Monitor,
  Keyboard,
  Info,
  Sun,
  Moon,
  Eye,
  EyeOff,
  Save,
  Check,
  HardDrive,
  Power,
  Server,
  Menu,
  Lock,
  User,
  RefreshCw,
  Terminal,
  Play,
  Square,
  ChevronRight,
  Plus,
  Trash2,
  ExternalLink,
  Copy,
  ScreenShare,
  Radio,
} from 'lucide-vue-next'

const { t, te, locale } = useI18n()
const route = useRoute()
const systemStore = useSystemStore()
const configStore = useConfigStore()
const authStore = useAuthStore()

// Settings state
const activeSection = ref('appearance')
const mobileMenuOpen = ref(false)
const loading = ref(false)
const saved = ref(false)
const SETTINGS_SECTION_IDS = new Set([
  'appearance',
  'account',
  'access',
  'video',
  'hid',
  'msd',
  'atx',
  'environment',
  'ext-ttyd',
  'ext-rustdesk',
  'ext-rtsp',
  'ext-remote-access',
  'about',
])

// Navigation structure
const navGroups = computed(() => [
  {
    title: t('settings.system'),
    items: [
      { id: 'appearance', label: t('settings.appearance'), icon: Sun },
      { id: 'account', label: t('settings.account'), icon: User },
      { id: 'access', label: t('settings.access'), icon: Lock },
    ]
  },
  {
    title: t('settings.hardware'),
    items: [
      { id: 'video', label: t('settings.video'), icon: Monitor, status: config.value.video_device ? t('settings.configured') : null },
      { id: 'hid', label: t('settings.hid'), icon: Keyboard, status: config.value.hid_backend.toUpperCase() },
      ...(config.value.msd_enabled ? [{ id: 'msd', label: t('settings.msd'), icon: HardDrive }] : []),
      { id: 'atx', label: t('settings.atx'), icon: Power },
      { id: 'environment', label: t('settings.environment'), icon: Server },
    ]
  },
  {
    title: t('settings.extensions'),
    items: [
      { id: 'ext-ttyd', label: t('extensions.ttyd.title'), icon: Terminal },
      { id: 'ext-rustdesk', label: t('extensions.rustdesk.title'), icon: ScreenShare },
      { id: 'ext-rtsp', label: t('extensions.rtsp.title'), icon: Radio },
      { id: 'ext-remote-access', label: t('extensions.remoteAccess.title'), icon: ExternalLink },
    ]
  },
  {
    title: t('settings.other'),
    items: [
      { id: 'about', label: t('settings.about'), icon: Info },
    ]
  }
])

function selectSection(id: string) {
  activeSection.value = id
  mobileMenuOpen.value = false
}

function normalizeSettingsSection(value: unknown): string | null {
  return typeof value === 'string' && SETTINGS_SECTION_IDS.has(value) ? value : null
}

// Theme
const theme = ref<'light' | 'dark' | 'system'>('system')

// Account settings
const usernameInput = ref('')
const usernamePassword = ref('')
const usernameSaving = ref(false)
const usernameSaved = ref(false)
const usernameError = ref('')
const currentPassword = ref('')
const newPassword = ref('')
const confirmPassword = ref('')
const passwordSaving = ref(false)
const passwordSaved = ref(false)
const passwordError = ref('')
const showPasswords = ref(false)

// Auth config state
const authConfig = ref<AuthConfig>({
  session_timeout_secs: 3600 * 24,
  single_user_allow_multiple_sessions: false,
  totp_enabled: false,
  totp_secret: undefined,
})
const authConfigLoading = ref(false)

// Extensions management
const extensions = ref<ExtensionsStatus | null>(null)
const extensionsLoading = ref(false)
const extensionLogs = ref<Record<string, string[]>>({
  ttyd: [],
  gostc: [],
  easytier: [],
})
const showLogs = ref<Record<string, boolean>>({
  ttyd: false,
  gostc: false,
  easytier: false,
})

// Terminal dialog
const showTerminalDialog = ref(false)

// Extension config (local edit state)
const extConfig = ref({
  ttyd: { enabled: false, shell: '/bin/bash' },
  gostc: { enabled: false, addr: 'gostc.mofeng.run', key: '', tls: true },
  easytier: { enabled: false, network_name: '', network_secret: '', peer_urls: [] as string[], virtual_ip: '' },
})

// RustDesk config state
const rustdeskConfig = ref<RustDeskConfigResponse | null>(null)
const rustdeskStatus = ref<RustDeskStatusResponse | null>(null)
const rustdeskPassword = ref<RustDeskPasswordResponse | null>(null)
const rustdeskLoading = ref(false)
const rustdeskCopied = ref<'id' | 'password' | null>(null)
const { copy: clipboardCopy } = useClipboard()
const rustdeskLocalConfig = ref({
  enabled: false,
  rendezvous_server: '',
  relay_server: '',
  relay_key: '',
})

// RTSP config state
const rtspStatus = ref<RtspStatusResponse | null>(null)
const rtspLoading = ref(false)
const rtspLocalConfig = ref<RtspConfigUpdate & { password?: string }>({
  enabled: false,
  bind: '0.0.0.0',
  port: 8554,
  path: 'live',
  allow_one_client: true,
  codec: 'h264',
  username: '',
  password: '',
})
const rtspStreamUrl = computed(() => {
  const host = window.location.hostname || '127.0.0.1'
  const path = (rtspLocalConfig.value.path || 'live').trim().replace(/^\/+|\/+$/g, '') || 'live'
  const port = Number(rtspLocalConfig.value.port) || 8554
  return `rtsp://${host}:${port}/${path}`
})

// Web server config state
const webServerConfig = ref<WebConfig>({
  http_port: 8080,
  https_port: 8443,
  bind_address: '0.0.0.0',
  bind_addresses: ['0.0.0.0'],
  https_enabled: false,
})
const webServerLoading = ref(false)
const showRestartDialog = ref(false)
const restarting = ref(false)
const updateChannel = ref<UpdateChannel>('stable')
const updateOverview = ref<UpdateOverviewResponse | null>(null)
const updateStatus = ref<UpdateStatusResponse | null>(null)
const updateLoading = ref(false)
const updateSawRestarting = ref(false)
const updateSawRequestFailure = ref(false)
const updateAutoReloadTriggered = ref(false)
const updateRunning = computed(() => {
  const phase = updateStatus.value?.phase
  return phase === 'checking'
    || phase === 'downloading'
    || phase === 'verifying'
    || phase === 'installing'
    || phase === 'restarting'
})
let updateStatusTimer: number | null = null
type BindMode = 'all' | 'loopback' | 'custom'
const bindMode = ref<BindMode>('all')
const bindAllIpv6 = ref(false)
const bindLocalIpv6 = ref(false)
const bindAddressList = ref<string[]>([])
const bindAddressError = computed(() => {
  if (bindMode.value !== 'custom') return ''
  return normalizeBindAddresses(bindAddressList.value).length
    ? ''
    : t('settings.bindAddressListEmpty')
})
const effectiveBindAddresses = computed(() => {
  if (bindMode.value === 'all') {
    return bindAllIpv6.value ? ['0.0.0.0', '::'] : ['0.0.0.0']
  }
  if (bindMode.value === 'loopback') {
    return bindLocalIpv6.value ? ['127.0.0.1', '::1'] : ['127.0.0.1']
  }
  return normalizeBindAddresses(bindAddressList.value)
})

// Config
interface DeviceConfig {
  video: Array<{
    path: string
    name: string
    driver: string
    formats: Array<{
      format: string
      description: string
      resolutions: Array<{
        width: number
        height: number
        fps: number[]
      }>
    }>
  }>
  serial: Array<{ path: string; name: string }>
  audio: Array<{ name: string; description: string }>
  udc: Array<{ name: string }>
}

const devices = ref<DeviceConfig>({
  video: [],
  serial: [],
  audio: [],
  udc: [],
})

const config = ref({
  video_device: '',
  video_format: '',
  video_width: 1920,
  video_height: 1080,
  video_fps: 30,
  hid_backend: 'ch9329',
  hid_serial_device: '',
  hid_serial_baudrate: 9600,
  hid_otg_udc: '',
  hid_otg_profile: 'custom' as OtgHidProfile,
  hid_otg_endpoint_budget: 'six' as OtgEndpointBudget,
  hid_otg_functions: {
    keyboard: true,
    mouse_relative: true,
    mouse_absolute: true,
    consumer: true,
  } as OtgHidFunctions,
  hid_otg_keyboard_leds: false,
  msd_enabled: false,
  msd_dir: '',
  encoder_backend: 'auto',
  // STUN/TURN settings
  stun_server: '',
  turn_server: '',
  turn_username: '',
  turn_password: '',
})

// Tracks whether TURN password is configured on the server
const hasTurnPassword = ref(false)

type OtgSelfCheckLevel = 'info' | 'warn' | 'error'
type OtgCheckGroupStatus = 'ok' | 'warn' | 'error' | 'skipped'

interface OtgSelfCheckItem {
  id: string
  ok: boolean
  level: OtgSelfCheckLevel
  message: string
  hint?: string
  path?: string
}

interface OtgSelfCheckResult {
  overall_ok: boolean
  error_count: number
  warning_count: number
  hid_backend: string
  selected_udc: string | null
  bound_udc: string | null
  udc_state: string | null
  udc_speed: string | null
  available_udcs: string[]
  other_gadgets: string[]
  checks: OtgSelfCheckItem[]
}

interface OtgCheckGroupDef {
  id: string
  titleKey: string
  checkIds: string[]
}

interface OtgCheckGroup {
  id: string
  titleKey: string
  status: OtgCheckGroupStatus
  okCount: number
  warningCount: number
  errorCount: number
  items: OtgSelfCheckItem[]
}

const otgSelfCheckLoading = ref(false)
const otgSelfCheckResult = ref<OtgSelfCheckResult | null>(null)
const otgSelfCheckError = ref('')
const otgRunButtonPressed = ref(false)

const otgCheckGroupDefs: OtgCheckGroupDef[] = [
  {
    id: 'udc',
    titleKey: 'settings.otgSelfCheck.groups.udc',
    checkIds: ['udc_dir_exists', 'udc_has_entries', 'configured_udc_valid'],
  },
  {
    id: 'gadget_config',
    titleKey: 'settings.otgSelfCheck.groups.gadgetConfig',
    checkIds: ['configfs_mounted', 'usb_gadget_dir_exists', 'libcomposite_loaded'],
  },
  {
    id: 'one_kvm',
    titleKey: 'settings.otgSelfCheck.groups.oneKvm',
    checkIds: ['one_kvm_gadget_exists', 'one_kvm_bound_udc', 'other_gadgets', 'udc_conflict'],
  },
  {
    id: 'functions',
    titleKey: 'settings.otgSelfCheck.groups.functions',
    checkIds: ['hid_functions_present', 'config_c1_exists', 'function_links_ok', 'hid_device_nodes'],
  },
  {
    id: 'link',
    titleKey: 'settings.otgSelfCheck.groups.link',
    checkIds: ['udc_state', 'udc_speed'],
  },
]

const otgCheckGroups = computed<OtgCheckGroup[]>(() => {
  const items = otgSelfCheckResult.value?.checks || []
  return otgCheckGroupDefs.map((group) => {
    const groupItems = items.filter(item => group.checkIds.includes(item.id))
    const errorCount = groupItems.filter(item => item.level === 'error').length
    const warningCount = groupItems.filter(item => item.level === 'warn').length
    const okCount = Math.max(0, groupItems.length - errorCount - warningCount)
    let status: OtgCheckGroupStatus = 'skipped'
    if (groupItems.length > 0) {
      if (errorCount > 0) status = 'error'
      else if (warningCount > 0) status = 'warn'
      else status = 'ok'
    }

    return {
      id: group.id,
      titleKey: group.titleKey,
      status,
      okCount,
      warningCount,
      errorCount,
      items: groupItems,
    }
  })
})

function otgCheckLevelClass(level: OtgSelfCheckLevel): string {
  if (level === 'error') return 'bg-red-500'
  if (level === 'warn') return 'bg-amber-500'
  return 'bg-blue-500'
}

function otgCheckStatusText(level: OtgSelfCheckLevel): string {
  if (level === 'error') return t('common.error')
  if (level === 'warn') return t('common.warning')
  return t('common.info')
}

function otgGroupStatusClass(status: OtgCheckGroupStatus): string {
  if (status === 'error') return 'bg-red-500'
  if (status === 'warn') return 'bg-amber-500'
  if (status === 'ok') return 'bg-emerald-500'
  return 'bg-muted-foreground/40'
}

function otgGroupStatusText(status: OtgCheckGroupStatus): string {
  return t(`settings.otgSelfCheck.status.${status}`)
}

function otgGroupSummary(group: OtgCheckGroup): string {
  if (group.items.length === 0) {
    return t('settings.otgSelfCheck.notRun')
  }
  return t('settings.otgSelfCheck.groupCounts', {
    ok: group.okCount,
    warnings: group.warningCount,
    errors: group.errorCount,
  })
}

function otgCheckMessage(item: OtgSelfCheckItem): string {
  const key = `settings.otgSelfCheck.messages.${item.id}`
  const label = te(key) ? t(key) : item.message
  const result = otgSelfCheckResult.value
  if (!result) return label

  const value = (name: string) => t(`settings.otgSelfCheck.values.${name}`)

  switch (item.id) {
    case 'udc_has_entries':
      return `${label}：${result.available_udcs.length ? result.available_udcs.join(', ') : value('missing')}`
    case 'configured_udc_valid':
      if (!result.selected_udc) return `${label}：${value('notConfigured')}`
      return `${label}：${item.ok ? result.selected_udc : `${value('missing')}/${result.selected_udc}`}`
    case 'configfs_mounted':
      return `${label}：${item.ok ? value('mounted') : value('unmounted')}`
    case 'usb_gadget_dir_exists':
      return `${label}：${item.ok ? value('available') : value('unavailable')}`
    case 'libcomposite_loaded':
      return `${label}：${item.ok ? value('available') : value('unavailable')}`
    case 'one_kvm_gadget_exists':
      return `${label}：${item.ok ? value('exists') : value('missing')}`
    case 'other_gadgets':
      return `${label}：${result.other_gadgets.length ? result.other_gadgets.join(', ') : value('none')}`
    case 'one_kvm_bound_udc':
      return `${label}：${result.bound_udc || value('unbound')}`
    case 'udc_conflict':
      return `${label}：${item.ok ? value('noConflict') : value('conflict')}`
    case 'udc_state':
      return `${label}：${result.udc_state || value('unknown')}`
    case 'udc_speed':
      return `${label}：${result.udc_speed || value('unknown')}`
    default:
      return `${label}：${item.ok ? value('normal') : value('abnormal')}`
  }
}

function otgCheckHint(item: OtgSelfCheckItem): string {
  if (!item.hint) return ''
  const key = `settings.otgSelfCheck.hints.${item.id}`
  return te(key) ? t(key) : item.hint
}

async function runOtgSelfCheck() {
  otgSelfCheckLoading.value = true
  otgSelfCheckError.value = ''
  try {
    otgSelfCheckResult.value = await hidApi.otgSelfCheck()
  } catch (e) {
    console.error('Failed to run OTG self-check:', e)
    otgSelfCheckError.value = t('settings.otgSelfCheck.failed')
  } finally {
    otgSelfCheckLoading.value = false
  }
}

async function onRunOtgSelfCheckClick() {
  if (!otgSelfCheckLoading.value) {
    otgRunButtonPressed.value = true
    window.setTimeout(() => {
      otgRunButtonPressed.value = false
    }, 160)
  }
  await runOtgSelfCheck()
}

type VideoEncoderSelfCheckCell = VideoEncoderSelfCheckResponse['rows'][number]['cells'][number]
type VideoEncoderSelfCheckRow = VideoEncoderSelfCheckResponse['rows'][number]

const videoEncoderSelfCheckLoading = ref(false)
const videoEncoderSelfCheckResult = ref<VideoEncoderSelfCheckResponse | null>(null)
const videoEncoderSelfCheckError = ref('')
const videoEncoderRunButtonPressed = ref(false)

function videoEncoderCell(row: VideoEncoderSelfCheckRow, codecId: string): VideoEncoderSelfCheckCell | undefined {
  return row.cells.find(cell => cell.codec_id === codecId)
}

const currentHardwareEncoderText = computed(() =>
  videoEncoderSelfCheckResult.value?.current_hardware_encoder === 'None'
    ? t('settings.encoderSelfCheck.none')
    : (videoEncoderSelfCheckResult.value?.current_hardware_encoder || t('settings.encoderSelfCheck.none'))
)

function videoEncoderCodecLabel(codecId: string, codecName: string): string {
  return codecId === 'h265' ? 'H.265' : codecName
}

function videoEncoderCellClass(ok: boolean | undefined): string {
  return ok ? 'text-emerald-600 dark:text-emerald-400' : 'text-red-600 dark:text-red-400'
}

function videoEncoderCellSymbol(ok: boolean | undefined): string {
  return ok ? '✓' : '✗'
}

function videoEncoderCellTime(cell: VideoEncoderSelfCheckCell | undefined): string {
  if (!cell || typeof cell.elapsed_ms !== 'number') return '-'
  return `${cell.elapsed_ms}ms`
}

async function runVideoEncoderSelfCheck() {
  videoEncoderSelfCheckLoading.value = true
  videoEncoderSelfCheckError.value = ''
  try {
    videoEncoderSelfCheckResult.value = await streamApi.encoderSelfCheck()
  } catch (e) {
    console.error('Failed to run encoder self-check:', e)
    videoEncoderSelfCheckError.value = t('settings.encoderSelfCheck.failed')
  } finally {
    videoEncoderSelfCheckLoading.value = false
  }
}

async function onRunVideoEncoderSelfCheckClick() {
  if (!videoEncoderSelfCheckLoading.value) {
    videoEncoderRunButtonPressed.value = true
    window.setTimeout(() => {
      videoEncoderRunButtonPressed.value = false
    }, 160)
  }
  await runVideoEncoderSelfCheck()
}

function defaultOtgEndpointBudgetForUdc(udc?: string): OtgEndpointBudget {
  return /musb/i.test(udc || '') ? 'five' as OtgEndpointBudget : 'six' as OtgEndpointBudget
}

function normalizeOtgEndpointBudget(budget: OtgEndpointBudget | undefined, udc?: string): OtgEndpointBudget {
  if (!budget || budget === 'auto') {
    return defaultOtgEndpointBudgetForUdc(udc)
  }
  return budget
}

function endpointLimitForBudget(budget: OtgEndpointBudget): number | null {
  if (budget === 'unlimited') return null
  return budget === 'five' ? 5 : 6
}

const effectiveOtgFunctions = computed(() => ({ ...config.value.hid_otg_functions }))

const otgEndpointLimit = computed(() =>
  endpointLimitForBudget(config.value.hid_otg_endpoint_budget)
)

const otgRequiredEndpoints = computed(() => {
  if (config.value.hid_backend !== 'otg') return 0
  const functions = effectiveOtgFunctions.value
  let endpoints = 0
  if (functions.keyboard) {
    endpoints += 1
    if (config.value.hid_otg_keyboard_leds) endpoints += 1
  }
  if (functions.mouse_relative) endpoints += 1
  if (functions.mouse_absolute) endpoints += 1
  if (functions.consumer) endpoints += 1
  if (config.value.msd_enabled) endpoints += 2
  return endpoints
})

const isOtgEndpointBudgetValid = computed(() => {
  if (config.value.hid_backend !== 'otg') return true
  const limit = otgEndpointLimit.value
  return limit === null || otgRequiredEndpoints.value <= limit
})

const otgEndpointUsageText = computed(() => {
  const limit = otgEndpointLimit.value
  if (limit === null) {
    return t('settings.otgEndpointUsageUnlimited', { used: otgRequiredEndpoints.value })
  }
  return t('settings.otgEndpointUsage', { used: otgRequiredEndpoints.value, limit })
})

const showOtgEndpointBudgetHint = computed(() =>
  config.value.hid_backend === 'otg'
)

const isKeyboardLedToggleDisabled = computed(() =>
  config.value.hid_backend !== 'otg' || !effectiveOtgFunctions.value.keyboard
)

function describeEndpointBudget(budget: OtgEndpointBudget): string {
  switch (budget) {
    case 'five':
      return '5'
    case 'six':
      return '6'
    case 'unlimited':
      return t('settings.otgEndpointBudgetUnlimited')
    default:
      return '6'
  }
}

const isHidFunctionSelectionValid = computed(() => {
  if (config.value.hid_backend !== 'otg') return true
  const f = config.value.hid_otg_functions
  return !!(f.keyboard || f.mouse_relative || f.mouse_absolute || f.consumer)
})

// OTG Descriptor settings
const otgVendorIdHex = ref('1d6b')
const otgProductIdHex = ref('0104')
const otgManufacturer = ref('One-KVM')
const otgProduct = ref('One-KVM USB Device')
const otgSerialNumber = ref('')

// Validate hex input
const validateHex = (event: Event, _field: string) => {
  const input = event.target as HTMLInputElement
  input.value = input.value.replace(/[^0-9a-fA-F]/g, '').toLowerCase()
}

watch(() => config.value.msd_enabled, (enabled) => {
  if (!enabled && activeSection.value === 'msd') {
    activeSection.value = 'hid'
  }
})

watch(bindMode, (mode) => {
  if (mode === 'custom' && bindAddressList.value.length === 0) {
    bindAddressList.value = ['']
  }
})

// ATX config state
const atxConfig = ref({
  enabled: false,
  power: {
    driver: 'none' as AtxDriverType,
    device: '',
    pin: 0,
    active_level: 'high' as ActiveLevel,
    baud_rate: 9600,
  },
  reset: {
    driver: 'none' as AtxDriverType,
    device: '',
    pin: 0,
    active_level: 'high' as ActiveLevel,
    baud_rate: 9600,
  },
  led: {
    enabled: false,
    gpio_chip: '',
    gpio_pin: 0,
    inverted: false,
  },
  wol_interface: '',
})

// ATX devices for discovery
const atxDevices = ref<AtxDevices>({
  gpio_chips: [],
  usb_relays: [],
  serial_ports: [],
})

const ch9329ReservedSerialDevice = computed(() => {
  if (config.value.hid_backend !== 'ch9329') return ''
  return config.value.hid_serial_device.trim()
})

const isSharedAtxSerialRelay = computed(() => {
  const power = atxConfig.value.power
  const reset = atxConfig.value.reset
  return (
    power.driver === 'serial'
    && reset.driver === 'serial'
    && !!power.device.trim()
    && power.device === reset.device
  )
})

// Encoder backend
const availableBackends = ref<EncoderBackendInfo[]>([])

const selectedBackendFormats = computed(() => {
  if (config.value.encoder_backend === 'auto') return []
  const backend = availableBackends.value.find(b => b.id === config.value.encoder_backend)
  return backend?.supported_formats || []
})

const isCh9329Backend = computed(() => config.value.hid_backend === 'ch9329')

const selectedDevice = computed(() => {
  return devices.value.video.find(d => d.path === config.value.video_device)
})

const availableFormats = computed(() => {
  if (!selectedDevice.value) return []
  return selectedDevice.value.formats
})

const availableFormatOptions = computed(() => {
  return availableFormats.value.map(format => {
    const state = getVideoFormatState(format.format, 'config', config.value.encoder_backend)
    return {
      ...format,
      state,
      disabled: state === 'unsupported',
    }
  })
})

const selectableFormats = computed(() => {
  return availableFormatOptions.value.filter(format => !format.disabled)
})

const selectedFormat = computed(() => {
  if (!selectedDevice.value || !config.value.video_format) return null
  return selectedDevice.value.formats.find(f => f.format === config.value.video_format)
})

const availableResolutions = computed(() => {
  if (!selectedFormat.value) return []
  const resMap = new Map<string, { width: number; height: number; fps: number[] }>()

  selectedFormat.value.resolutions.forEach(res => {
    const key = `${res.width}x${res.height}`
    if (!resMap.has(key)) {
      resMap.set(key, { ...res })
    } else {
      const existing = resMap.get(key)!
      const allFps = [...new Set([...existing.fps, ...res.fps])].sort((a, b) => b - a)
      existing.fps = allFps
    }
  })
  
  return Array.from(resMap.values()).sort((a, b) => (b.width * b.height) - (a.width * a.height))
})

const availableFps = computed(() => {
  const currentRes = availableResolutions.value.find(
    r => r.width === config.value.video_width && r.height === config.value.video_height
  )
  return currentRes ? currentRes.fps : []
})

// Keep the selected format aligned with currently selectable formats.
watch(
  selectableFormats,
  () => {
    if (selectableFormats.value.length === 0) {
      config.value.video_format = ''
      return
    }

    const isValid = selectableFormats.value.some(f => f.format === config.value.video_format)
    if (!isValid) {
      config.value.video_format = selectableFormats.value[0]?.format || ''
    }
  },
  { deep: true },
)

// Watch for format change to set default resolution
watch(() => config.value.video_format, () => {
  if (availableResolutions.value.length > 0) {
    const isValid = availableResolutions.value.some(
      r => r.width === config.value.video_width && r.height === config.value.video_height
    )
    if (!isValid) {
      const best = availableResolutions.value[0]
      if (best) {
        config.value.video_width = best.width
        config.value.video_height = best.height
        if (best.fps?.[0]) config.value.video_fps = best.fps[0]
      }
    }
  }
})

// Watch for resolution change to set default FPS
watch(() => [config.value.video_width, config.value.video_height], () => {
  const fpsList = availableFps.value
  if (fpsList.length > 0) {
    if (!fpsList.includes(config.value.video_fps)) {
      const firstFps = fpsList[0]
      if (typeof firstFps === 'number') {
        config.value.video_fps = firstFps
      }
    }
  }
})

watch(() => authStore.user, (value) => {
  if (value) {
    usernameInput.value = value
  }
})


// Format bytes to human readable string
function formatBytes(bytes: number): string {
  if (bytes === 0) return '0 B'
  const k = 1024
  const sizes = ['B', 'KB', 'MB', 'GB', 'TB']
  const i = Math.floor(Math.log(bytes) / Math.log(k))
  return `${(bytes / Math.pow(k, i)).toFixed(1)} ${sizes[i]}`
}

// Theme handling
function setTheme(newTheme: 'light' | 'dark' | 'system') {
  theme.value = newTheme
  localStorage.setItem('theme', newTheme)

  if (newTheme === 'system') {
    const prefersDark = window.matchMedia('(prefers-color-scheme: dark)').matches
    document.documentElement.classList.toggle('dark', prefersDark)
  } else {
    document.documentElement.classList.toggle('dark', newTheme === 'dark')
  }
}

// Language handling
function handleLanguageChange(lang: string) {
  if (lang === 'zh-CN' || lang === 'en-US') {
    setLanguage(lang)
  }
}

// Account updates
async function changeUsername() {
  usernameError.value = ''
  usernameSaved.value = false

  if (usernameInput.value.length < 2) {
    usernameError.value = t('auth.enterUsername')
    return
  }
  if (!usernamePassword.value) {
    usernameError.value = t('auth.enterPassword')
    return
  }

  usernameSaving.value = true
  try {
    await authApi.changeUsername(usernameInput.value, usernamePassword.value)
    usernameSaved.value = true
    usernamePassword.value = ''
    await authStore.checkAuth()
    usernameInput.value = authStore.user || usernameInput.value
    setTimeout(() => {
      usernameSaved.value = false
    }, 2000)
  } catch (e) {
    usernameError.value = t('auth.invalidPassword')
  } finally {
    usernameSaving.value = false
  }
}

async function changePassword() {
  passwordError.value = ''
  passwordSaved.value = false

  if (!currentPassword.value) {
    passwordError.value = t('auth.enterPassword')
    return
  }
  if (newPassword.value.length < 4) {
    passwordError.value = t('setup.passwordHint')
    return
  }
  if (newPassword.value !== confirmPassword.value) {
    passwordError.value = t('setup.passwordMismatch')
    return
  }

  passwordSaving.value = true
  try {
    await authApi.changePassword(currentPassword.value, newPassword.value)
    currentPassword.value = ''
    newPassword.value = ''
    confirmPassword.value = ''
    passwordSaved.value = true
    setTimeout(() => {
      passwordSaved.value = false
    }, 2000)
  } catch (e) {
    passwordError.value = t('auth.invalidPassword')
  } finally {
    passwordSaving.value = false
  }
}

// Save config using domain-separated APIs
async function saveConfig() {
  loading.value = true
  saved.value = false

  try {
    // Save only config related to the active section
    const savePromises: Promise<unknown>[] = []

    // Video config (including encoder and WebRTC/STUN/TURN settings)
    if (activeSection.value === 'video') {
      savePromises.push(
        configStore.updateVideo({
          device: config.value.video_device || undefined,
          format: config.value.video_format || undefined,
          width: config.value.video_width,
          height: config.value.video_height,
          fps: config.value.video_fps,
        })
      )
      // Save Stream/Encoder and STUN/TURN config together
      savePromises.push(
        configStore.updateStream({
          encoder: config.value.encoder_backend as any,
          stun_server: config.value.stun_server || undefined,
          turn_server: config.value.turn_server || undefined,
          turn_username: config.value.turn_username || undefined,
          turn_password: config.value.turn_password || undefined,
        })
      )
    }

    // HID config
    if (activeSection.value === 'hid') {
      if (!isHidFunctionSelectionValid.value || !isOtgEndpointBudgetValid.value) {
        return
      }
      const hidUpdate: any = {
        backend: config.value.hid_backend as any,
        ch9329_port: config.value.hid_serial_device || undefined,
        ch9329_baudrate: config.value.hid_serial_baudrate,
      }
      // Add descriptor config for OTG backend
      if (config.value.hid_backend === 'otg') {
        hidUpdate.otg_descriptor = {
          vendor_id: parseInt(otgVendorIdHex.value, 16) || 0x1d6b,
          product_id: parseInt(otgProductIdHex.value, 16) || 0x0104,
          manufacturer: otgManufacturer.value || 'One-KVM',
          product: otgProduct.value || 'One-KVM USB Device',
          serial_number: otgSerialNumber.value || undefined,
        }
        hidUpdate.otg_profile = 'custom'
        hidUpdate.otg_endpoint_budget = config.value.hid_otg_endpoint_budget
        hidUpdate.otg_functions = { ...config.value.hid_otg_functions }
        hidUpdate.otg_keyboard_leds = config.value.hid_otg_keyboard_leds
      }
      savePromises.push(configStore.updateHid(hidUpdate))
      savePromises.push(
        configStore.updateMsd({
          enabled: config.value.msd_enabled,
        })
      )
    }

    // MSD config
    if (activeSection.value === 'msd') {
      savePromises.push(
        configStore.updateMsd({
          msd_dir: config.value.msd_dir || undefined,
        })
      )
    }

    await Promise.all(savePromises)
    saved.value = true
    setTimeout(() => (saved.value = false), 2000)
  } catch (e) {
    console.error('Failed to save config:', e)
  } finally {
    loading.value = false
  }
}

// Load config using domain-separated APIs
async function loadConfig() {
  try {
    // Load all domain configs in parallel
    const [video, stream, hid, msd] = await Promise.all([
      configStore.refreshVideo(),
      configStore.refreshStream(),
      configStore.refreshHid(),
      configStore.refreshMsd(),
    ])

    config.value = {
      video_device: video.device || '',
      video_format: video.format || '',
      video_width: video.width || 1920,
      video_height: video.height || 1080,
      video_fps: video.fps || 30,
      hid_backend: hid.backend || 'none',
      hid_serial_device: hid.ch9329_port || '',
      hid_serial_baudrate: hid.ch9329_baudrate || 9600,
      hid_otg_udc: hid.otg_udc || '',
      hid_otg_profile: 'custom' as OtgHidProfile,
      hid_otg_endpoint_budget: normalizeOtgEndpointBudget(hid.otg_endpoint_budget, hid.otg_udc || ''),
      hid_otg_functions: {
        keyboard: hid.otg_functions?.keyboard ?? true,
        mouse_relative: hid.otg_functions?.mouse_relative ?? true,
        mouse_absolute: hid.otg_functions?.mouse_absolute ?? true,
        consumer: hid.otg_functions?.consumer ?? true,
      } as OtgHidFunctions,
      hid_otg_keyboard_leds: hid.otg_keyboard_leds ?? false,
      msd_enabled: msd.enabled || false,
      msd_dir: msd.msd_dir || '',
      encoder_backend: stream.encoder || 'auto',
      // STUN/TURN settings
      stun_server: stream.stun_server || '',
      turn_server: stream.turn_server || '',
      turn_username: stream.turn_username || '',
      turn_password: '', // Password is never returned from server; set-only field
    }

    // Track whether TURN password is configured
    hasTurnPassword.value = stream.has_turn_password || false

    // Load OTG descriptor config
    if (hid.otg_descriptor) {
      otgVendorIdHex.value = hid.otg_descriptor.vendor_id?.toString(16).padStart(4, '0') || '1d6b'
      otgProductIdHex.value = hid.otg_descriptor.product_id?.toString(16).padStart(4, '0') || '0104'
      otgManufacturer.value = hid.otg_descriptor.manufacturer || 'One-KVM'
      otgProduct.value = hid.otg_descriptor.product || 'One-KVM USB Device'
      otgSerialNumber.value = hid.otg_descriptor.serial_number || ''
    }

  } catch (e) {
    console.error('Failed to load config:', e)
  }
}

async function loadDevices() {
  try {
    devices.value = await configApi.listDevices()
  } catch (e) {
    console.error('Failed to load devices:', e)
  }
}

async function loadBackends() {
  try {
    const result = await streamApi.getCodecs()
    availableBackends.value = result.backends || []
  } catch (e) {
    console.error('Failed to load encoder backends:', e)
  }
}

// Auth config functions
async function loadAuthConfig() {
  authConfigLoading.value = true
  try {
    authConfig.value = await configStore.refreshAuth()
  } catch (e) {
    console.error('Failed to load auth config:', e)
  } finally {
    authConfigLoading.value = false
  }
}

async function saveAuthConfig() {
  authConfigLoading.value = true
  try {
    authConfig.value = await configStore.updateAuth({
      single_user_allow_multiple_sessions: authConfig.value.single_user_allow_multiple_sessions,
    })
  } catch (e) {
    console.error('Failed to save auth config:', e)
  } finally {
    authConfigLoading.value = false
  }
}

// Extension management functions
async function loadExtensions() {
  extensionsLoading.value = true
  try {
    extensions.value = await extensionsApi.getAll()
    // Sync config from server
    if (extensions.value) {
      const ttyd = extensions.value.ttyd.config
      extConfig.value.ttyd = {
        enabled: ttyd.enabled,
        shell: ttyd.shell,
      }
      extConfig.value.gostc = { ...extensions.value.gostc.config }
      const easytier = extensions.value.easytier.config
      extConfig.value.easytier = {
        enabled: easytier.enabled,
        network_name: easytier.network_name,
        network_secret: easytier.network_secret,
        peer_urls: easytier.peer_urls || [],
        virtual_ip: easytier.virtual_ip || '',
      }
    }
  } catch (e) {
    console.error('Failed to load extensions:', e)
  } finally {
    extensionsLoading.value = false
  }
}

async function startExtension(id: 'ttyd' | 'gostc' | 'easytier') {
  try {
    await extensionsApi.start(id)
    await loadExtensions()
  } catch (e) {
    console.error(`Failed to start ${id}:`, e)
  }
}

async function stopExtension(id: 'ttyd' | 'gostc' | 'easytier') {
  try {
    await extensionsApi.stop(id)
    await loadExtensions()
  } catch (e) {
    console.error(`Failed to stop ${id}:`, e)
  }
}

async function refreshExtensionLogs(id: 'ttyd' | 'gostc' | 'easytier') {
  try {
    const result = await extensionsApi.logs(id, 100)
    extensionLogs.value[id] = result.logs
  } catch (e) {
    console.error(`Failed to load ${id} logs:`, e)
  }
}

async function saveExtensionConfig(id: 'ttyd' | 'gostc' | 'easytier') {
  loading.value = true
  try {
    if (id === 'ttyd') {
      await extensionsApi.updateTtyd(extConfig.value.ttyd)
    } else if (id === 'gostc') {
      await extensionsApi.updateGostc(extConfig.value.gostc)
    } else if (id === 'easytier') {
      await extensionsApi.updateEasytier(extConfig.value.easytier)
    }
    await loadExtensions()
    saved.value = true
    setTimeout(() => (saved.value = false), 2000)
  } catch (e) {
    console.error(`Failed to save ${id} config:`, e)
  } finally {
    loading.value = false
  }
}

function isExtRunning(status: ExtensionStatus | undefined): boolean {
  return status?.state === 'running'
}

function openTerminal() {
  showTerminalDialog.value = true
}

function openTerminalInNewTab() {
  window.open('/api/terminal/', '_blank')
}

function getExtStatusText(status: ExtensionStatus | undefined): string {
  if (!status) return t('extensions.stopped')
  switch (status.state) {
    case 'unavailable': return t('extensions.unavailable')
    case 'stopped': return t('extensions.stopped')
    case 'running': return t('extensions.running')
    case 'failed': return t('extensions.failed')
    default: return t('extensions.stopped')
  }
}

function getExtStatusClass(status: ExtensionStatus | undefined): string {
  if (!status) return 'bg-gray-400'
  switch (status.state) {
    case 'unavailable': return 'bg-gray-400'
    case 'stopped': return 'bg-gray-400'
    case 'running': return 'bg-green-500'
    case 'failed': return 'bg-red-500'
    default: return 'bg-gray-400'
  }
}

function addEasytierPeer() {
  if (!extConfig.value.easytier.peer_urls) {
    extConfig.value.easytier.peer_urls = []
  }
  extConfig.value.easytier.peer_urls.push('')
}

function removeEasytierPeer(index: number) {
  if (extConfig.value.easytier.peer_urls) {
    extConfig.value.easytier.peer_urls.splice(index, 1)
  }
}

// ATX management functions
async function loadAtxConfig() {
  try {
    const config = await configStore.refreshAtx()
    atxConfig.value = {
      enabled: config.enabled,
      power: { ...config.power },
      reset: { ...config.reset },
      led: { ...config.led },
      wol_interface: config.wol_interface || '',
    }
    clearAtxSerialDeviceConflicts()
    syncSharedAtxSerialBaudRate()
  } catch (e) {
    console.error('Failed to load ATX config:', e)
  }
}

async function loadAtxDevices() {
  try {
    atxDevices.value = await atxConfigApi.listDevices()
  } catch (e) {
    console.error('Failed to load ATX devices:', e)
  }
}

async function saveAtxConfig() {
  loading.value = true
  saved.value = false
  try {
    syncSharedAtxSerialBaudRate()
    await configStore.updateAtx({
      enabled: atxConfig.value.enabled,
      power: {
        driver: atxConfig.value.power.driver,
        device: atxConfig.value.power.device || undefined,
        pin: atxConfig.value.power.pin,
        active_level: atxConfig.value.power.active_level,
        baud_rate: atxConfig.value.power.baud_rate,
      },
      reset: {
        driver: atxConfig.value.reset.driver,
        device: atxConfig.value.reset.device || undefined,
        pin: atxConfig.value.reset.pin,
        active_level: atxConfig.value.reset.active_level,
        baud_rate: isSharedAtxSerialRelay.value
          ? atxConfig.value.power.baud_rate
          : atxConfig.value.reset.baud_rate,
      },
      led: {
        enabled: atxConfig.value.led.enabled,
        gpio_chip: atxConfig.value.led.gpio_chip || undefined,
        gpio_pin: atxConfig.value.led.gpio_pin,
        inverted: atxConfig.value.led.inverted,
      },
      wol_interface: atxConfig.value.wol_interface || undefined,
    })
    saved.value = true
    setTimeout(() => (saved.value = false), 2000)
  } catch (e) {
    console.error('Failed to save ATX config:', e)
  } finally {
    loading.value = false
  }
}

function getAtxDevicesForDriver(driver: string): string[] {
  if (driver === 'gpio') {
    return atxDevices.value.gpio_chips
  } else if (driver === 'serial') {
    return atxDevices.value.serial_ports
  } else if (driver === 'usbrelay') {
    return atxDevices.value.usb_relays
  }
  return []
}

function isAtxSerialDeviceReserved(device: string): boolean {
  const reserved = ch9329ReservedSerialDevice.value
  return !!reserved && device === reserved
}

function formatAtxDeviceLabel(driver: string, device: string): string {
  if (driver === 'serial' && isAtxSerialDeviceReserved(device)) {
    return `${device} (CH9329 in use)`
  }
  return device
}

function clearAtxSerialDeviceConflicts() {
  const reserved = ch9329ReservedSerialDevice.value
  if (!reserved) return

  if (atxConfig.value.power.driver === 'serial' && atxConfig.value.power.device === reserved) {
    atxConfig.value.power.device = ''
  }
  if (atxConfig.value.reset.driver === 'serial' && atxConfig.value.reset.device === reserved) {
    atxConfig.value.reset.device = ''
  }
}

function syncSharedAtxSerialBaudRate() {
  if (!isSharedAtxSerialRelay.value) return
  atxConfig.value.reset.baud_rate = atxConfig.value.power.baud_rate
}

watch(
  () => [config.value.hid_backend, config.value.hid_serial_device],
  () => {
    clearAtxSerialDeviceConflicts()
  },
)

watch(
  () => [
    atxConfig.value.power.driver,
    atxConfig.value.power.device,
    atxConfig.value.power.baud_rate,
    atxConfig.value.reset.driver,
    atxConfig.value.reset.device,
  ],
  () => {
    syncSharedAtxSerialBaudRate()
  },
)

// RustDesk management functions
async function loadRustdeskConfig() {
  rustdeskLoading.value = true
  try {
    const status = await configStore.refreshRustdeskStatus()
    const config = status.config
    rustdeskConfig.value = config
    rustdeskStatus.value = status
    rustdeskLocalConfig.value = {
      enabled: config.enabled,
      rendezvous_server: config.rendezvous_server,
      relay_server: config.relay_server || '',
      relay_key: '',
    }
  } catch (e) {
    console.error('Failed to load RustDesk config:', e)
  } finally {
    rustdeskLoading.value = false
  }
}

async function loadRustdeskPassword() {
  try {
    rustdeskPassword.value = await configStore.refreshRustdeskPassword()
  } catch (e) {
    console.error('Failed to load RustDesk password:', e)
  }
}

function normalizeRustdeskServer(value: string, defaultPort: number): string | undefined {
  const trimmed = value.trim()
  if (!trimmed) return undefined
  if (trimmed.includes(':')) return trimmed
  return `${trimmed}:${defaultPort}`
}

function normalizeRtspPath(path: string): string {
  return path.trim().replace(/^\/+|\/+$/g, '') || 'live'
}

function normalizeBindAddresses(addresses: string[]): string[] {
  return addresses.map(addr => addr.trim()).filter(Boolean)
}

function applyBindStateFromConfig(config: WebConfig) {
  const rawAddrs =
    config.bind_addresses && config.bind_addresses.length > 0
      ? config.bind_addresses
      : config.bind_address
        ? [config.bind_address]
        : []
  const addrs = normalizeBindAddresses(rawAddrs)
  const isAll = addrs.length > 0 && addrs.every(addr => addr === '0.0.0.0' || addr === '::') && addrs.includes('0.0.0.0')
  const isLoopback =
    addrs.length > 0 &&
    addrs.every(addr => addr === '127.0.0.1' || addr === '::1') &&
    addrs.includes('127.0.0.1')
  if (isAll) {
    bindMode.value = 'all'
    bindAllIpv6.value = addrs.includes('::')
    return
  }
  if (isLoopback) {
    bindMode.value = 'loopback'
    bindLocalIpv6.value = addrs.includes('::1')
    return
  }
  bindMode.value = 'custom'
  bindAddressList.value = addrs.length ? [...addrs] : ['']
}

function addBindAddress() {
  bindAddressList.value.push('')
}

function removeBindAddress(index: number) {
  bindAddressList.value.splice(index, 1)
  if (bindAddressList.value.length === 0) {
    bindAddressList.value.push('')
  }
}

// Web server config functions
async function loadWebServerConfig() {
  try {
    const config = await configStore.refreshWeb()
    webServerConfig.value = config
    applyBindStateFromConfig(config)
  } catch (e) {
    console.error('Failed to load web server config:', e)
  }
}

async function saveWebServerConfig() {
  if (bindAddressError.value) return
  webServerLoading.value = true
  try {
    const update = {
      http_port: webServerConfig.value.http_port,
      https_port: webServerConfig.value.https_port,
      https_enabled: webServerConfig.value.https_enabled,
      bind_addresses: effectiveBindAddresses.value,
    }
    const updated = await configStore.updateWeb(update)
    webServerConfig.value = updated
    applyBindStateFromConfig(updated)
    showRestartDialog.value = true
  } catch (e) {
    console.error('Failed to save web server config:', e)
  } finally {
    webServerLoading.value = false
  }
}

async function restartServer() {
  restarting.value = true
  try {
    await systemApi.restart()
    // Wait for server to restart, then reload page
    setTimeout(() => {
      const protocol = webServerConfig.value.https_enabled ? 'https' : 'http'
      const port = webServerConfig.value.https_enabled
        ? webServerConfig.value.https_port
        : webServerConfig.value.http_port
      const newUrl = `${protocol}://${window.location.hostname}:${port}`
      window.location.href = newUrl
    }, 3000)
  } catch (e) {
    console.error('Failed to restart server:', e)
    restarting.value = false
  }
}

async function loadUpdateOverview() {
  updateLoading.value = true
  try {
    updateOverview.value = await updateApi.overview(updateChannel.value)
  } catch (e) {
    console.error('Failed to load update overview:', e)
  } finally {
    updateLoading.value = false
  }
}

async function refreshUpdateStatus() {
  try {
    updateStatus.value = await updateApi.status()

    if (updateSawRestarting.value && !updateAutoReloadTriggered.value) {
      if (updateSawRequestFailure.value || updateStatus.value.phase === 'idle') {
        updateAutoReloadTriggered.value = true
        window.location.reload()
      }
    }
  } catch (e) {
    console.error('Failed to refresh update status:', e)
    if (updateSawRestarting.value) {
      updateSawRequestFailure.value = true
    }
  }
}

function stopUpdatePolling() {
  if (updateStatusTimer !== null) {
    window.clearInterval(updateStatusTimer)
    updateStatusTimer = null
  }
}

function startUpdatePolling() {
  if (updateStatusTimer !== null) return
  updateStatusTimer = window.setInterval(async () => {
    await refreshUpdateStatus()
    if (updateStatus.value?.phase === 'restarting') {
      updateSawRestarting.value = true
    }
    if (!updateRunning.value) {
      stopUpdatePolling()
      await loadUpdateOverview()
    }
  }, 1000)
}

async function startOnlineUpgrade() {
  try {
    updateSawRestarting.value = false
    updateSawRequestFailure.value = false
    updateAutoReloadTriggered.value = false
    await updateApi.upgrade({ channel: updateChannel.value })
    await refreshUpdateStatus()
    startUpdatePolling()
  } catch (e) {
    console.error('Failed to start upgrade:', e)
  }
}

function updatePhaseText(phase?: string): string {
  switch (phase) {
    case 'idle': return t('settings.updatePhaseIdle')
    case 'checking': return t('settings.updatePhaseChecking')
    case 'downloading': return t('settings.updatePhaseDownloading')
    case 'verifying': return t('settings.updatePhaseVerifying')
    case 'installing': return t('settings.updatePhaseInstalling')
    case 'restarting': return t('settings.updatePhaseRestarting')
    case 'success': return t('settings.updatePhaseSuccess')
    case 'failed': return t('settings.updatePhaseFailed')
    default: return t('common.unknown')
  }
}

function localizeUpdateMessage(message?: string): string | null {
  if (!message) return null

  if (message === 'Checking for updates') return t('settings.updateMsgChecking')
  if (message.startsWith('Downloading binary')) {
    return message.replace('Downloading binary', t('settings.updateMsgDownloading'))
  }
  if (message === 'Verifying sha256') return t('settings.updateMsgVerifying')
  if (message === 'Replacing binary') return t('settings.updateMsgInstalling')
  if (message === 'Restarting service') return t('settings.updateMsgRestarting')

  return message
}

function updateStatusBadgeText(): string {
  return localizeUpdateMessage(updateStatus.value?.message)
    || updatePhaseText(updateStatus.value?.phase)
}

async function saveRustdeskConfig() {
  loading.value = true
  saved.value = false
  try {
    const rendezvousServer = normalizeRustdeskServer(
      rustdeskLocalConfig.value.rendezvous_server,
      21116,
    )
    const relayServer = normalizeRustdeskServer(rustdeskLocalConfig.value.relay_server, 21117)
    await configStore.updateRustdesk({
      enabled: rustdeskLocalConfig.value.enabled,
      rendezvous_server: rendezvousServer,
      relay_server: relayServer,
      relay_key: rustdeskLocalConfig.value.relay_key || undefined,
    })
    await loadRustdeskConfig()
    // Clear relay_key input after save (it's a password field)
    rustdeskLocalConfig.value.relay_key = ''
    saved.value = true
    setTimeout(() => (saved.value = false), 2000)
  } catch (e) {
    console.error('Failed to save RustDesk config:', e)
  } finally {
    loading.value = false
  }
}

async function regenerateRustdeskId() {
  if (!confirm(t('extensions.rustdesk.confirmRegenerateId'))) return
  rustdeskLoading.value = true
  try {
    await configStore.regenerateRustdeskId()
    await loadRustdeskConfig()
    await loadRustdeskPassword()
  } catch (e) {
    console.error('Failed to regenerate RustDesk ID:', e)
  } finally {
    rustdeskLoading.value = false
  }
}

async function regenerateRustdeskPassword() {
  if (!confirm(t('extensions.rustdesk.confirmRegeneratePassword'))) return
  rustdeskLoading.value = true
  try {
    await configStore.regenerateRustdeskPassword()
    await loadRustdeskConfig()
    await loadRustdeskPassword()
  } catch (e) {
    console.error('Failed to regenerate RustDesk password:', e)
  } finally {
    rustdeskLoading.value = false
  }
}

async function startRustdesk() {
  rustdeskLoading.value = true
  try {
    // Enable and save config to start the service
    await configStore.updateRustdesk({ enabled: true })
    rustdeskLocalConfig.value.enabled = true
    await loadRustdeskConfig()
  } catch (e) {
    console.error('Failed to start RustDesk:', e)
  } finally {
    rustdeskLoading.value = false
  }
}

async function stopRustdesk() {
  rustdeskLoading.value = true
  try {
    // Disable and save config to stop the service
    await configStore.updateRustdesk({ enabled: false })
    rustdeskLocalConfig.value.enabled = false
    await loadRustdeskConfig()
  } catch (e) {
    console.error('Failed to stop RustDesk:', e)
  } finally {
    rustdeskLoading.value = false
  }
}

async function copyToClipboard(text: string, type: 'id' | 'password') {
  const success = await clipboardCopy(text)
  if (success) {
    rustdeskCopied.value = type
    setTimeout(() => (rustdeskCopied.value = null), 2000)
  }
}

function getRustdeskServiceStatusText(status: string | undefined): string {
  if (!status) return t('extensions.rustdesk.notConfigured')
  switch (status) {
    case 'running': return t('extensions.running')
    case 'starting': return t('extensions.starting')
    case 'stopped': return t('extensions.stopped')
    case 'not_initialized': return t('extensions.rustdesk.notInitialized')
    default:
      // Handle "error: xxx" format
      if (status.startsWith('error:')) return t('extensions.failed')
      return status
  }
}

function getRustdeskRendezvousStatusText(status: string | null | undefined): string {
  if (!status) return '-'
  switch (status) {
    case 'registered': return t('extensions.rustdesk.registered')
    case 'connected': return t('extensions.rustdesk.connected')
    case 'connecting': return t('extensions.rustdesk.connecting')
    case 'disconnected': return t('extensions.rustdesk.disconnected')
    default:
      // Handle "error: xxx" format
      if (status.startsWith('error:')) return t('extensions.failed')
      return status
  }
}

function getRustdeskStatusClass(status: string | null | undefined): string {
  switch (status) {
    case 'running':
    case 'registered':
    case 'connected': return 'bg-green-500'
    case 'starting':
    case 'connecting': return 'bg-yellow-500'
    case 'stopped':
    case 'not_initialized':
    case 'disconnected': return 'bg-gray-400'
    default:
      // Handle "error: xxx" format
      if (status?.startsWith('error:')) return 'bg-red-500'
      return 'bg-gray-400'
  }
}

async function loadRtspConfig() {
  rtspLoading.value = true
  try {
    const status = await configStore.refreshRtspStatus()
    rtspStatus.value = status
    rtspLocalConfig.value = {
      enabled: status.config.enabled,
      bind: status.config.bind,
      port: status.config.port,
      path: status.config.path,
      allow_one_client: status.config.allow_one_client,
      codec: status.config.codec,
      username: status.config.username || '',
      password: '',
    }
  } catch (e) {
    console.error('Failed to load RTSP config:', e)
  } finally {
    rtspLoading.value = false
  }
}

async function saveRtspConfig() {
  loading.value = true
  saved.value = false
  try {
    const update: RtspConfigUpdate = {
      enabled: !!rtspLocalConfig.value.enabled,
      bind: rtspLocalConfig.value.bind?.trim() || '0.0.0.0',
      port: Number(rtspLocalConfig.value.port) || 8554,
      path: normalizeRtspPath(rtspLocalConfig.value.path || 'live'),
      allow_one_client: !!rtspLocalConfig.value.allow_one_client,
      codec: rtspLocalConfig.value.codec || 'h264',
      username: (rtspLocalConfig.value.username || '').trim(),
    }

    const nextPassword = (rtspLocalConfig.value.password || '').trim()
    if (nextPassword) {
      update.password = nextPassword
    }

    await configStore.updateRtsp(update)
    await loadRtspConfig()
    rtspLocalConfig.value.password = ''
    saved.value = true
    setTimeout(() => (saved.value = false), 2000)
  } catch (e) {
    console.error('Failed to save RTSP config:', e)
  } finally {
    loading.value = false
  }
}

async function startRtsp() {
  rtspLoading.value = true
  try {
    await configStore.updateRtsp({ enabled: true })
    rtspLocalConfig.value.enabled = true
    await loadRtspConfig()
  } catch (e) {
    console.error('Failed to start RTSP:', e)
  } finally {
    rtspLoading.value = false
  }
}

async function stopRtsp() {
  rtspLoading.value = true
  try {
    await configStore.updateRtsp({ enabled: false })
    rtspLocalConfig.value.enabled = false
    await loadRtspConfig()
  } catch (e) {
    console.error('Failed to stop RTSP:', e)
  } finally {
    rtspLoading.value = false
  }
}

function getRtspServiceStatusText(status: string | undefined): string {
  if (!status) return t('extensions.stopped')
  switch (status) {
    case 'running': return t('extensions.running')
    case 'starting': return t('extensions.starting')
    case 'stopped': return t('extensions.stopped')
    default:
      if (status.startsWith('error:')) return t('extensions.failed')
      return status
  }
}

function getRtspStatusClass(status: string | undefined): string {
  switch (status) {
    case 'running': return 'bg-green-500'
    case 'starting': return 'bg-yellow-500'
    case 'stopped': return 'bg-gray-400'
    default:
      if (status?.startsWith('error:')) return 'bg-red-500'
      return 'bg-gray-400'
  }
}

// Lifecycle
onMounted(async () => {
  // Load theme preference
  const storedTheme = localStorage.getItem('theme') as 'light' | 'dark' | 'system' | null
  if (storedTheme) {
    theme.value = storedTheme
  }

  await Promise.all([
    systemStore.fetchSystemInfo(),
    loadConfig(),
    loadDevices(),
    loadBackends(),
    loadAuthConfig(),
    loadExtensions(),
    loadAtxConfig(),
    loadAtxDevices(),
    loadRustdeskConfig(),
    loadRustdeskPassword(),
    loadRtspConfig(),
    loadWebServerConfig(),
    loadUpdateOverview(),
    refreshUpdateStatus(),
  ])
  usernameInput.value = authStore.user || ''

  if (updateRunning.value) {
    startUpdatePolling()
  }
})

watch(updateChannel, async () => {
  await loadUpdateOverview()
})

watch(() => config.value.hid_backend, () => {
  otgSelfCheckResult.value = null
  otgSelfCheckError.value = ''
})

watch(() => route.query.tab, (tab) => {
  const section = normalizeSettingsSection(tab)
  if (section && activeSection.value !== section) {
    selectSection(section)
  }
}, { immediate: true })
</script>

<template>
  <AppLayout>
    <div class="flex h-full overflow-hidden">
      <!-- Mobile Header -->
      <div class="lg:hidden fixed top-16 left-0 right-0 z-20 flex items-center px-4 py-3 border-b bg-background">
        <Sheet v-model:open="mobileMenuOpen">
          <SheetTrigger as-child>
            <Button variant="ghost" size="icon" class="mr-2 h-9 w-9">
              <Menu class="h-4 w-4" />
              <span class="sr-only">{{ t('common.menu') }}</span>
            </Button>
          </SheetTrigger>
          <SheetContent side="left" class="w-72 p-0">
            <div class="p-6">
              <h2 class="text-lg font-semibold mb-4">{{ t('settings.title') }}</h2>
              <nav class="space-y-6">
                <div v-for="group in navGroups" :key="group.title" class="space-y-1">
                  <h3 class="px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">{{ group.title }}</h3>
                  <button
                    type="button"
                    v-for="item in group.items"
                    :key="item.id"
                    @click="selectSection(item.id)"
                    :class="[
                      'w-full flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors',
                      activeSection === item.id
                        ? 'bg-primary text-primary-foreground'
                        : 'hover:bg-muted'
                    ]"
                  >
                    <component :is="item.icon" class="h-4 w-4" />
                    <span>{{ item.label }}</span>
                    <Badge v-if="item.status" variant="outline" :class="['ml-auto text-xs', activeSection === item.id ? 'border-primary-foreground/50 text-primary-foreground' : '']">{{ item.status }}</Badge>
                  </button>
                </div>
              </nav>
            </div>
          </SheetContent>
        </Sheet>
        <h1 class="text-lg font-semibold">{{ t('settings.title') }}</h1>
      </div>

      <!-- Desktop Sidebar -->
      <aside class="hidden lg:block w-64 shrink-0 border-r bg-muted/30">
        <div class="sticky top-0 p-6 space-y-6">
          <h1 class="text-xl font-semibold">{{ t('settings.title') }}</h1>
          <nav class="space-y-6">
            <div v-for="group in navGroups" :key="group.title" class="space-y-1">
              <h3 class="px-3 text-xs font-medium text-muted-foreground uppercase tracking-wider mb-2">{{ group.title }}</h3>
              <button
                type="button"
                v-for="item in group.items"
                :key="item.id"
                @click="activeSection = item.id"
                :class="[
                  'w-full flex items-center gap-3 px-3 py-2 text-sm rounded-md transition-colors',
                  activeSection === item.id
                    ? 'bg-primary text-primary-foreground'
                    : 'hover:bg-muted'
                ]"
              >
                <component :is="item.icon" class="h-4 w-4" />
                <span>{{ item.label }}</span>
                <Badge v-if="item.status" variant="outline" :class="['ml-auto text-xs', activeSection === item.id ? 'border-primary-foreground/50 text-primary-foreground' : '']">{{ item.status }}</Badge>
              </button>
            </div>
          </nav>
        </div>
      </aside>

      <!-- Main Content -->
      <main class="flex-1 overflow-y-auto">
        <div class="max-w-2xl mx-auto p-6 lg:p-8 pt-20 lg:pt-8 space-y-6">

          <!-- Appearance Section -->
          <div v-show="activeSection === 'appearance'" class="space-y-6">
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.theme') }}</CardTitle>
                <CardDescription>{{ t('settings.themeDesc') }}</CardDescription>
              </CardHeader>
              <CardContent>
                <div class="flex gap-2">
                  <Button :variant="theme === 'light' ? 'default' : 'outline'" size="sm" @click="setTheme('light')">
                    <Sun class="h-4 w-4 mr-2" />{{ t('settings.lightMode') }}
                  </Button>
                  <Button :variant="theme === 'dark' ? 'default' : 'outline'" size="sm" @click="setTheme('dark')">
                    <Moon class="h-4 w-4 mr-2" />{{ t('settings.darkMode') }}
                  </Button>
                  <Button :variant="theme === 'system' ? 'default' : 'outline'" size="sm" @click="setTheme('system')">
                    <Monitor class="h-4 w-4 mr-2" />{{ t('settings.systemMode') }}
                  </Button>
                </div>
              </CardContent>
            </Card>

            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.language') }}</CardTitle>
                <CardDescription>{{ t('settings.languageDesc') }}</CardDescription>
              </CardHeader>
              <CardContent>
                <div class="flex gap-2">
                  <Button :variant="locale === 'zh-CN' ? 'default' : 'outline'" size="sm" @click="handleLanguageChange('zh-CN')">中文</Button>
                  <Button :variant="locale === 'en-US' ? 'default' : 'outline'" size="sm" @click="handleLanguageChange('en-US')">English</Button>
                </div>
              </CardContent>
            </Card>
          </div>

          <!-- Account Section -->
          <div v-show="activeSection === 'account'" class="space-y-6">
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.username') }}</CardTitle>
                <CardDescription>{{ t('settings.usernameDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="account-username">{{ t('settings.username') }}</Label>
                  <Input id="account-username" v-model="usernameInput" />
                </div>
                <div class="space-y-2">
                  <Label for="account-username-password">{{ t('settings.currentPassword') }}</Label>
                  <Input id="account-username-password" v-model="usernamePassword" type="password" />
                </div>
                <p v-if="usernameError" class="text-xs text-destructive">{{ usernameError }}</p>
                <p v-else-if="usernameSaved" class="text-xs text-emerald-600">{{ t('common.success') }}</p>
                <div class="flex justify-end">
                  <Button @click="changeUsername" :disabled="usernameSaving">
                    <Save class="h-4 w-4 mr-2" />
                    {{ t('common.save') }}
                  </Button>
                </div>
              </CardContent>
            </Card>

            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.changePassword') }}</CardTitle>
                <CardDescription>{{ t('settings.passwordDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="account-current-password">{{ t('settings.currentPassword') }}</Label>
                  <Input id="account-current-password" v-model="currentPassword" type="password" />
                </div>
                <div class="space-y-2">
                  <Label for="account-new-password">{{ t('settings.newPassword') }}</Label>
                  <Input id="account-new-password" v-model="newPassword" type="password" />
                </div>
                <div class="space-y-2">
                  <Label for="account-confirm-password">{{ t('auth.confirmPassword') }}</Label>
                  <Input id="account-confirm-password" v-model="confirmPassword" type="password" />
                </div>
                <p v-if="passwordError" class="text-xs text-destructive">{{ passwordError }}</p>
                <p v-else-if="passwordSaved" class="text-xs text-emerald-600">{{ t('common.success') }}</p>
                <div class="flex justify-end">
                  <Button @click="changePassword" :disabled="passwordSaving">
                    <Save class="h-4 w-4 mr-2" />
                    {{ t('common.save') }}
                  </Button>
                </div>
              </CardContent>
            </Card>
          </div>

          <!-- Video Section -->
          <div v-show="activeSection === 'video'" class="space-y-6">
            <!-- Video Device Settings -->
            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.videoSettings') }}</CardTitle>
                  <CardDescription>{{ t('settings.videoSettingsDesc') }}</CardDescription>
                </div>
                <Button variant="ghost" size="icon" class="h-8 w-8" :aria-label="t('common.refresh')" @click="loadDevices">
                  <RefreshCw class="h-4 w-4" />
                </Button>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="video-device">{{ t('settings.videoDevice') }}</Label>
                  <select id="video-device" v-model="config.video_device" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option value="">{{ t('settings.selectDevice') }}</option>
                    <option v-for="dev in devices.video" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</option>
                  </select>
                </div>
                <div class="space-y-2">
                  <Label for="video-format">{{ t('settings.videoFormat') }}</Label>
                  <select id="video-format" v-model="config.video_format" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="!config.video_device">
                    <option value="">{{ t('settings.selectFormat') }}</option>
                    <option
                      v-for="fmt in availableFormatOptions"
                      :key="fmt.format"
                      :value="fmt.format"
                      :disabled="fmt.disabled"
                    >
                      {{ fmt.format }} - {{ fmt.description }}{{ fmt.disabled ? t('common.notSupportedYet') : '' }}
                    </option>
                  </select>
                </div>
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="video-resolution">{{ t('settings.resolution') }}</Label>
                    <select id="video-resolution" :value="`${config.video_width}x${config.video_height}`" @change="e => { const parts = (e.target as HTMLSelectElement).value.split('x').map(Number); if (parts[0] && parts[1]) { config.video_width = parts[0]; config.video_height = parts[1]; } }" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="!config.video_format">
                      <option v-for="res in availableResolutions" :key="`${res.width}x${res.height}`" :value="`${res.width}x${res.height}`">{{ res.width }}x{{ res.height }}</option>
                    </select>
                  </div>
                  <div class="space-y-2">
                    <Label for="video-fps">{{ t('settings.frameRate') }}</Label>
                    <select id="video-fps" v-model.number="config.video_fps" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="!config.video_format">
                      <option v-for="fps in availableFps" :key="fps" :value="fps">{{ fps }} FPS</option>
                      <option v-if="!availableFps.includes(config.video_fps)" :value="config.video_fps">{{ config.video_fps }} FPS</option>
                    </select>
                  </div>
                </div>
              </CardContent>
            </Card>

            <!-- Encoder Settings -->
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.encoderBackend') }}</CardTitle>
                <CardDescription>{{ t('settings.encoderBackendDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="encoder-backend">{{ t('settings.backend') }}</Label>
                  <select id="encoder-backend" v-model="config.encoder_backend" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option value="auto">{{ t('settings.autoRecommended') }}</option>
                    <option v-for="backend in availableBackends" :key="backend.id" :value="backend.id">{{ backend.name }} {{ backend.is_hardware ? `(${t('settings.hardware')})` : `(${t('settings.software')})` }}</option>
                  </select>
                </div>
                <div v-if="config.encoder_backend !== 'auto' && selectedBackendFormats.length > 0" class="space-y-2">
                  <Label>{{ t('settings.supportedFormats') }}</Label>
                  <div class="flex flex-wrap gap-2">
                    <Badge v-for="format in selectedBackendFormats" :key="format" variant="outline">{{ format.toUpperCase() }}</Badge>
                  </div>
                </div>
                <p class="text-xs text-muted-foreground">{{ t('settings.encoderHint') }}</p>
              </CardContent>
            </Card>

            <!-- WebRTC/STUN/TURN Settings -->
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.webrtcSettings') }}</CardTitle>
                <CardDescription>{{ t('settings.webrtcSettingsDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="stun-server">{{ t('settings.stunServer') }}</Label>
                  <Input
                    id="stun-server"
                    v-model="config.stun_server"
                    :placeholder="t('settings.stunServerPlaceholder')"
                  />
                  <p class="text-xs text-muted-foreground">{{ t('settings.stunServerHint') }}</p>
                </div>
                <Separator />
                <div class="space-y-2">
                  <Label for="turn-server">{{ t('settings.turnServer') }}</Label>
                  <Input
                    id="turn-server"
                    v-model="config.turn_server"
                    :placeholder="t('settings.turnServerPlaceholder')"
                  />
                  <p class="text-xs text-muted-foreground">{{ t('settings.turnServerHint') }}</p>
                </div>
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="turn-username">{{ t('settings.turnUsername') }}</Label>
                    <Input
                      id="turn-username"
                      v-model="config.turn_username"
                      :disabled="!config.turn_server"
                    />
                  </div>
                  <div class="space-y-2">
                    <Label for="turn-password">{{ t('settings.turnPassword') }}</Label>
                    <div class="relative">
                      <Input
                        id="turn-password"
                        v-model="config.turn_password"
                        :type="showPasswords ? 'text' : 'password'"
                        :disabled="!config.turn_server"
                        :placeholder="hasTurnPassword ? '••••••••' : ''"
                      />
                      <button
                        type="button"
                        class="absolute right-3 top-1/2 -translate-y-1/2 text-muted-foreground"
                        :aria-label="showPasswords ? t('extensions.rustdesk.hidePassword') : t('extensions.rustdesk.showPassword')"
                        @click="showPasswords = !showPasswords"
                      >
                        <Eye v-if="!showPasswords" class="h-4 w-4" />
                        <EyeOff v-else class="h-4 w-4" />
                      </button>
                    </div>
                    <p v-if="hasTurnPassword && !config.turn_password" class="text-xs text-muted-foreground">{{ t('settings.turnPasswordConfigured') }}</p>
                  </div>
                </div>
                <p class="text-xs text-muted-foreground">{{ t('settings.turnCredentialsHint') }}</p>
                <Separator />
                <p class="text-xs text-muted-foreground">{{ t('settings.iceConfigNote') }}</p>
              </CardContent>
            </Card>
          </div>

          <!-- HID Section -->
          <div v-show="activeSection === 'hid'" class="space-y-6">
            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.hidSettings') }}</CardTitle>
                  <CardDescription>{{ t('settings.hidSettingsDesc') }}</CardDescription>
                </div>
                <Button variant="ghost" size="icon" class="h-8 w-8" :aria-label="t('common.refresh')" @click="loadDevices">
                  <RefreshCw class="h-4 w-4" />
                </Button>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="hid-backend">{{ t('settings.hidBackend') }}</Label>
                  <select id="hid-backend" v-model="config.hid_backend" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option value="ch9329">CH9329 (Serial)</option>
                    <option value="otg">USB OTG</option>
                    <option value="none">{{ t('common.disabled') }}</option>
                  </select>
                </div>
                <div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
                  <Label for="serial-device">{{ t('settings.serialDevice') }}</Label>
                  <select id="serial-device" v-model="config.hid_serial_device" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option value="">{{ t('settings.selectDevice') }}</option>
                    <option v-for="dev in devices.serial" :key="dev.path" :value="dev.path">{{ dev.name }} ({{ dev.path }})</option>
                  </select>
                </div>
                <div v-if="config.hid_backend === 'ch9329'" class="space-y-2">
                  <Label for="serial-baudrate">{{ t('settings.baudRate') }}</Label>
                  <select id="serial-baudrate" v-model.number="config.hid_serial_baudrate" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option :value="9600">9600</option>
                    <option :value="19200">19200</option>
                    <option :value="38400">38400</option>
                    <option :value="57600">57600</option>
                    <option :value="115200">115200</option>
                  </select>
                </div>

                <!-- OTG Descriptor Settings -->
                <template v-if="config.hid_backend === 'otg'">
                  <Separator class="my-4" />
                  <div class="space-y-4">
                    <div>
                      <h4 class="text-sm font-medium">{{ t('settings.otgHidProfile') }}</h4>
                      <p class="text-sm text-muted-foreground">{{ t('settings.otgHidProfileDesc') }}</p>
                    </div>
                    <div class="space-y-2">
                      <Label for="otg-endpoint-budget">{{ t('settings.otgEndpointBudget') }}</Label>
                      <select id="otg-endpoint-budget" v-model="config.hid_otg_endpoint_budget" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                        <option value="five">5</option>
                        <option value="six">6</option>
                        <option value="unlimited">{{ t('settings.otgEndpointBudgetUnlimited') }}</option>
                      </select>
                      <p class="text-xs text-muted-foreground">{{ otgEndpointUsageText }}</p>
                    </div>
                    <div class="space-y-3">
                      <div class="space-y-3 rounded-md border border-border/60 p-3">
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgFunctionMouseRelative') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseRelativeDesc') }}</p>
                          </div>
                          <Switch v-model="config.hid_otg_functions.mouse_relative" />
                        </div>
                        <Separator />
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgFunctionMouseAbsolute') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMouseAbsoluteDesc') }}</p>
                          </div>
                          <Switch v-model="config.hid_otg_functions.mouse_absolute" />
                        </div>
                      </div>
                      <div class="space-y-3 rounded-md border border-border/60 p-3">
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgFunctionKeyboard') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionKeyboardDesc') }}</p>
                          </div>
                          <Switch v-model="config.hid_otg_functions.keyboard" />
                        </div>
                        <Separator />
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgFunctionConsumer') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionConsumerDesc') }}</p>
                          </div>
                          <Switch v-model="config.hid_otg_functions.consumer" />
                        </div>
                        <Separator />
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgKeyboardLeds') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgKeyboardLedsDesc') }}</p>
                          </div>
                          <Switch v-model="config.hid_otg_keyboard_leds" :disabled="isKeyboardLedToggleDisabled" />
                        </div>
                      </div>
                      <div class="space-y-3 rounded-md border border-border/60 p-3">
                        <div class="flex items-center justify-between">
                          <div>
                            <Label>{{ t('settings.otgFunctionMsd') }}</Label>
                            <p class="text-xs text-muted-foreground">{{ t('settings.otgFunctionMsdDesc') }}</p>
                          </div>
                          <Switch v-model="config.msd_enabled" />
                        </div>
                      </div>
                    </div>
                    <p class="text-xs text-amber-600 dark:text-amber-400">
                      {{ t('settings.otgProfileWarning') }}
                    </p>
                    <p v-if="showOtgEndpointBudgetHint" class="text-xs text-muted-foreground">
                      {{ t('settings.otgEndpointBudgetHint') }}
                    </p>
                    <p v-if="!isOtgEndpointBudgetValid" class="text-xs text-amber-600 dark:text-amber-400">
                      {{ t('settings.otgEndpointExceeded', { used: otgRequiredEndpoints, limit: describeEndpointBudget(config.hid_otg_endpoint_budget) }) }}
                    </p>
                  </div>
                  <Separator class="my-4" />
                  <div class="space-y-4">
                    <div>
                      <h4 class="text-sm font-medium">{{ t('settings.otgDescriptor') }}</h4>
                      <p class="text-sm text-muted-foreground">{{ t('settings.otgDescriptorDesc') }}</p>
                    </div>
                    <div class="grid gap-4 sm:grid-cols-2">
                      <div class="space-y-2">
                        <Label for="otg-vid">{{ t('settings.vendorId') }}</Label>
                        <Input
                          id="otg-vid"
                          v-model="otgVendorIdHex"
                          placeholder="1d6b"
                          maxlength="4"
                          @input="validateHex($event, 'vid')"
                        />
                      </div>
                      <div class="space-y-2">
                        <Label for="otg-pid">{{ t('settings.productId') }}</Label>
                        <Input
                          id="otg-pid"
                          v-model="otgProductIdHex"
                          placeholder="0104"
                          maxlength="4"
                          @input="validateHex($event, 'pid')"
                        />
                      </div>
                    </div>
                    <div class="space-y-2">
                      <Label for="otg-manufacturer">{{ t('settings.manufacturer') }}</Label>
                      <Input
                        id="otg-manufacturer"
                        v-model="otgManufacturer"
                        placeholder="One-KVM"
                        maxlength="126"
                      />
                    </div>
                    <div class="space-y-2">
                      <Label for="otg-product">{{ t('settings.productName') }}</Label>
                      <Input
                        id="otg-product"
                        v-model="otgProduct"
                        placeholder="One-KVM USB Device"
                        maxlength="126"
                      />
                    </div>
                    <div class="space-y-2">
                      <Label for="otg-serial">{{ t('settings.serialNumber') }}</Label>
                      <Input
                        id="otg-serial"
                        v-model="otgSerialNumber"
                        :placeholder="t('settings.serialNumberAuto')"
                        maxlength="126"
                      />
                    </div>
                    <p class="text-sm text-amber-600 dark:text-amber-400">
                      {{ t('settings.descriptorWarning') }}
                    </p>
                  </div>
                </template>
              </CardContent>
            </Card>

          </div>

          <!-- Environment Section -->
          <div v-show="activeSection === 'environment'" class="space-y-4 max-w-3xl">
            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.otgSelfCheck.title') }}</CardTitle>
                  <CardDescription>{{ t('settings.otgSelfCheck.desc') }}</CardDescription>
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  :disabled="otgSelfCheckLoading"
                  :class="[
                    'transition-all duration-150 active:scale-95 active:brightness-95',
                    otgRunButtonPressed ? 'scale-95 brightness-95' : ''
                  ]"
                  @click="onRunOtgSelfCheckClick"
                >
                  <RefreshCw class="h-4 w-4 mr-2" :class="{ 'animate-spin': otgSelfCheckLoading }" />
                  {{ t('settings.otgSelfCheck.run') }}
                </Button>
              </CardHeader>
              <CardContent class="space-y-3">
                <p v-if="otgSelfCheckError" class="text-xs text-red-600 dark:text-red-400">
                  {{ otgSelfCheckError }}
                </p>

                <template v-if="otgSelfCheckResult">
                  <div class="flex flex-wrap gap-2 text-xs">
                    <Badge
                      :variant="otgSelfCheckResult.overall_ok ? 'default' : 'destructive'"
                      class="font-medium"
                    >
                      {{ t('settings.otgSelfCheck.overall') }}：{{ otgSelfCheckResult.overall_ok ? t('settings.otgSelfCheck.ok') : t('settings.otgSelfCheck.hasIssues') }}
                    </Badge>
                    <Badge variant="outline" class="font-normal">
                      {{ t('settings.otgSelfCheck.counts', { errors: otgSelfCheckResult.error_count, warnings: otgSelfCheckResult.warning_count }) }}
                    </Badge>
                    <Badge variant="secondary" class="font-normal">
                      {{ t('settings.otgSelfCheck.selectedUdc') }}：{{ otgSelfCheckResult.selected_udc || '-' }}
                    </Badge>
                    <Badge variant="secondary" class="font-normal">
                      {{ t('settings.otgSelfCheck.boundUdc') }}：{{ otgSelfCheckResult.bound_udc || '-' }}
                    </Badge>
                  </div>

                  <div class="rounded-md border divide-y">
                    <details
                      v-for="group in otgCheckGroups"
                      :key="group.id"
                      :open="group.status === 'error' || group.status === 'warn'"
                      class="group"
                    >
                      <summary class="list-none cursor-pointer px-4 py-3 flex items-center justify-between gap-3 hover:bg-muted/40">
                        <div class="flex items-center gap-2 min-w-0">
                          <span class="inline-block h-2 w-2 rounded-full shrink-0" :class="otgGroupStatusClass(group.status)" />
                          <span class="text-sm font-medium truncate leading-6">{{ t(group.titleKey) }}</span>
                        </div>
                        <div class="flex items-center gap-2 shrink-0">
                          <span class="text-xs text-muted-foreground">{{ otgGroupSummary(group) }}</span>
                          <Badge variant="outline" class="text-[10px] h-5 px-1.5">{{ otgGroupStatusText(group.status) }}</Badge>
                        </div>
                      </summary>

                      <div v-if="group.items.length > 0" class="border-t bg-muted/20">
                        <div
                          v-for="item in group.items"
                          :key="item.id"
                          class="px-4 py-3 border-b last:border-b-0"
                        >
                          <div class="flex items-start gap-2">
                            <span class="inline-block h-2 w-2 rounded-full mt-1.5 shrink-0" :class="otgCheckLevelClass(item.level)" />
                            <div class="min-w-0 space-y-1">
                              <div class="flex items-center gap-2">
                                <p class="text-sm leading-5">{{ otgCheckMessage(item) }}</p>
                                <span class="text-[11px] text-muted-foreground shrink-0">{{ otgCheckStatusText(item.level) }}</span>
                              </div>
                              <div class="flex flex-wrap gap-x-2 gap-y-1 text-[11px] text-muted-foreground">
                                <span v-if="item.hint">{{ otgCheckHint(item) }}</span>
                                <code v-if="item.path" class="font-mono break-all">{{ item.path }}</code>
                              </div>
                            </div>
                          </div>
                        </div>
                      </div>
                      <div v-else class="border-t bg-muted/20 px-4 py-3 text-xs text-muted-foreground">
                        {{ t('settings.otgSelfCheck.notRun') }}
                      </div>
                    </details>
                  </div>
                </template>
                <p v-else-if="otgSelfCheckLoading" class="text-xs text-muted-foreground">
                  {{ t('common.loading') }}
                </p>
              </CardContent>
            </Card>

            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.encoderSelfCheck.title') }}</CardTitle>
                  <CardDescription>{{ t('settings.encoderSelfCheck.desc') }}</CardDescription>
                </div>
                <Button
                  variant="outline"
                  size="sm"
                  :disabled="videoEncoderSelfCheckLoading"
                  :class="[
                    'transition-all duration-150 active:scale-95 active:brightness-95',
                    videoEncoderRunButtonPressed ? 'scale-95 brightness-95' : ''
                  ]"
                  @click="onRunVideoEncoderSelfCheckClick"
                >
                  <RefreshCw class="h-4 w-4 mr-2" :class="{ 'animate-spin': videoEncoderSelfCheckLoading }" />
                  {{ t('settings.encoderSelfCheck.run') }}
                </Button>
              </CardHeader>
              <CardContent class="space-y-3">
                <p v-if="videoEncoderSelfCheckError" class="text-xs text-red-600 dark:text-red-400">
                  {{ videoEncoderSelfCheckError }}
                </p>

                <template v-if="videoEncoderSelfCheckResult">
                  <div class="text-sm">
                    {{ t('settings.encoderSelfCheck.currentHardwareEncoder') }}：{{ currentHardwareEncoderText }}
                  </div>

                  <div class="rounded-md border bg-card">
                    <table class="w-full table-fixed text-sm">
                      <thead>
                        <tr>
                          <th class="px-2 py-3 text-left font-medium w-[18%]">{{ t('settings.encoderSelfCheck.resolution') }}</th>
                          <th
                            v-for="codec in videoEncoderSelfCheckResult.codecs"
                            :key="codec.id"
                            class="px-2 py-3 text-center font-medium w-[20.5%]"
                          >
                            {{ videoEncoderCodecLabel(codec.id, codec.name) }}
                          </th>
                        </tr>
                      </thead>
                      <tbody>
                        <tr
                          v-for="row in videoEncoderSelfCheckResult.rows"
                          :key="row.resolution_id"
                        >
                          <td class="px-2 py-3 align-middle">
                            <div class="font-medium">{{ row.resolution_label }}</div>
                          </td>
                          <td
                            v-for="codec in videoEncoderSelfCheckResult.codecs"
                            :key="`${row.resolution_id}-${codec.id}`"
                            class="px-2 py-3 align-middle"
                          >
                            <div
                              class="flex flex-col items-center justify-center gap-1"
                              :class="videoEncoderCellClass(videoEncoderCell(row, codec.id)?.ok)"
                            >
                              <div class="text-lg leading-none font-semibold">
                                {{ videoEncoderCellSymbol(videoEncoderCell(row, codec.id)?.ok) }}
                              </div>
                              <div class="text-[11px] leading-4 text-foreground/70">
                                {{ videoEncoderCellTime(videoEncoderCell(row, codec.id)) }}
                              </div>
                            </div>
                          </td>
                        </tr>
                      </tbody>
                    </table>
                  </div>
                </template>
                <p v-else-if="videoEncoderSelfCheckLoading" class="text-xs text-muted-foreground">
                  {{ t('common.loading') }}
                </p>
              </CardContent>
            </Card>
          </div>

          <!-- Access Section -->
          <div v-show="activeSection === 'access'" class="space-y-6">
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.webServer') }}</CardTitle>
                <CardDescription>{{ t('settings.webServerDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label>{{ t('settings.httpsEnabled') }}</Label>
                    <p class="text-sm text-muted-foreground">{{ t('settings.httpsEnabledDesc') }}</p>
                  </div>
                  <Switch v-model="webServerConfig.https_enabled" />
                </div>

                <Separator />

                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label>{{ t('settings.httpPort') }}</Label>
                    <Input v-model.number="webServerConfig.http_port" type="number" min="1" max="65535" />
                  </div>
                  <div class="space-y-2">
                    <Label>{{ t('settings.httpsPort') }}</Label>
                    <Input v-model.number="webServerConfig.https_port" type="number" min="1" max="65535" />
                  </div>
                </div>

                <div class="space-y-2">
                  <Label>{{ t('settings.bindMode') }}</Label>
                  <select v-model="bindMode" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                    <option value="all">{{ t('settings.bindModeAll') }}</option>
                    <option value="loopback">{{ t('settings.bindModeLocal') }}</option>
                    <option value="custom">{{ t('settings.bindModeCustom') }}</option>
                  </select>
                  <p class="text-sm text-muted-foreground">{{ t('settings.bindModeDesc') }}</p>
                </div>

                <div v-if="bindMode === 'all'" class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label>{{ t('settings.bindIpv6') }}</Label>
                    <p class="text-xs text-muted-foreground">{{ t('settings.bindAllDesc') }}</p>
                  </div>
                  <Switch v-model="bindAllIpv6" />
                </div>

                <div v-if="bindMode === 'loopback'" class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label>{{ t('settings.bindIpv6') }}</Label>
                    <p class="text-xs text-muted-foreground">{{ t('settings.bindLocalDesc') }}</p>
                  </div>
                  <Switch v-model="bindLocalIpv6" />
                </div>

                <div v-if="bindMode === 'custom'" class="space-y-2">
                  <Label>{{ t('settings.bindAddressList') }}</Label>
                  <div class="space-y-2">
                    <div v-for="(_, i) in bindAddressList" :key="`bind-${i}`" class="flex gap-2">
                      <Input v-model="bindAddressList[i]" placeholder="192.168.1.10" />
                      <Button variant="ghost" size="icon" :aria-label="t('common.delete')" @click="removeBindAddress(i)">
                        <Trash2 class="h-4 w-4" />
                      </Button>
                    </div>
                    <Button variant="outline" size="sm" @click="addBindAddress">
                      <Plus class="h-4 w-4 mr-1" />
                      {{ t('settings.addBindAddress') }}
                    </Button>
                  </div>
                  <p class="text-xs text-muted-foreground">{{ t('settings.bindAddressListDesc') }}</p>
                  <p v-if="bindAddressError" class="text-xs text-destructive">{{ bindAddressError }}</p>
                </div>

                <div class="flex justify-end pt-4">
                  <Button @click="saveWebServerConfig" :disabled="webServerLoading || !!bindAddressError">
                    <Save class="h-4 w-4 mr-2" />
                    {{ t('common.save') }}
                  </Button>
                </div>
              </CardContent>
            </Card>
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.authSettings') }}</CardTitle>
                <CardDescription>{{ t('settings.authSettingsDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label>{{ t('settings.allowMultipleSessions') }}</Label>
                    <p class="text-xs text-muted-foreground">{{ t('settings.allowMultipleSessionsDesc') }}</p>
                  </div>
                  <Switch
                    v-model="authConfig.single_user_allow_multiple_sessions"
                    :disabled="authConfigLoading"
                  />
                </div>
                <Separator />
                <p class="text-xs text-muted-foreground">{{ t('settings.singleUserSessionNote') }}</p>
                <div class="flex justify-end pt-2">
                  <Button @click="saveAuthConfig" :disabled="authConfigLoading">
                    <Save class="h-4 w-4 mr-2" />
                    {{ t('common.save') }}
                  </Button>
                </div>
              </CardContent>
            </Card>
          </div>

          <!-- MSD Section -->
          <div v-show="activeSection === 'msd' && config.msd_enabled" class="space-y-6">
            <Card>
              <CardHeader>
                <CardTitle>{{ t('settings.msdSettings') }}</CardTitle>
                <CardDescription>{{ t('settings.msdDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div v-if="isCh9329Backend" class="rounded-md border border-amber-200 bg-amber-50 px-3 py-2 text-sm text-amber-900">
                  <p class="font-medium">{{ t('settings.msdCh9329Warning') }}</p>
                  <p class="text-xs text-amber-900/80">{{ t('settings.msdCh9329WarningDesc') }}</p>
                </div>
                <div class="space-y-4">
                  <div class="space-y-2">
                    <Label for="msd-dir">{{ t('settings.msdDir') }}</Label>
                    <Input id="msd-dir" v-model="config.msd_dir" placeholder="/etc/one-kvm/msd" :disabled="isCh9329Backend" />
                    <p class="text-xs text-muted-foreground">{{ t('settings.msdDirDesc') }}</p>
                  </div>
                  <p class="text-xs text-muted-foreground">{{ t('settings.msdDirHint') }}</p>
                </div>
                <Separator />
                <div class="flex items-center justify-between">
                  <div>
                    <p class="text-sm font-medium">{{ t('settings.msdStatus') }}</p>
                    <p class="text-xs text-muted-foreground">
                      {{ config.msd_enabled ? t('settings.willBeEnabledAfterSave') : t('settings.disabled') }}
                    </p>
                  </div>
                  <Badge :variant="config.msd_enabled ? 'default' : 'secondary'">
                    {{ config.msd_enabled ? t('common.enabled') : t('common.disabled') }}
                  </Badge>
                </div>
              </CardContent>
            </Card>
          </div>

          <!-- ATX Section -->
          <div v-show="activeSection === 'atx'" class="space-y-6">
            <!-- Enable ATX -->
            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.atxSettings') }}</CardTitle>
                  <CardDescription>{{ t('settings.atxSettingsDesc') }}</CardDescription>
                </div>
                <Button variant="ghost" size="icon" class="h-8 w-8" :aria-label="t('common.refresh')" @click="loadAtxDevices">
                  <RefreshCw class="h-4 w-4" />
                </Button>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label for="atx-enabled">{{ t('settings.atxEnable') }}</Label>
                    <p class="text-xs text-muted-foreground">{{ t('settings.atxEnableDesc') }}</p>
                  </div>
                  <Switch
                    id="atx-enabled"
                    v-model="atxConfig.enabled"
                  />
                </div>
              </CardContent>
            </Card>

            <!-- Power Button Config -->
            <Card v-if="atxConfig.enabled">
              <CardHeader>
                <CardTitle>{{ t('settings.atxPowerButton') }}</CardTitle>
                <CardDescription>{{ t('settings.atxPowerButtonDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="power-driver">{{ t('settings.atxDriver') }}</Label>
                    <select id="power-driver" v-model="atxConfig.power.driver" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                      <option value="none">{{ t('settings.atxDriverNone') }}</option>
                      <option value="gpio">{{ t('settings.atxDriverGpio') }}</option>
                      <option value="usbrelay">{{ t('settings.atxDriverUsbRelay') }}</option>
                      <option value="serial">{{ t('settings.atxDriverSerial') }}</option>
                    </select>
                  </div>
                  <div class="space-y-2">
                    <Label for="power-device">{{ t('settings.atxDevice') }}</Label>
                    <select id="power-device" v-model="atxConfig.power.device" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="atxConfig.power.driver === 'none'">
                      <option value="">{{ t('settings.selectDevice') }}</option>
                      <option
                        v-for="dev in getAtxDevicesForDriver(atxConfig.power.driver)"
                        :key="dev"
                        :value="dev"
                        :disabled="atxConfig.power.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
                      >
                        {{ formatAtxDeviceLabel(atxConfig.power.driver, dev) }}
                      </option>
                    </select>
                  </div>
                </div>
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="power-pin">{{ ['usbrelay', 'serial'].includes(atxConfig.power.driver) ? t('settings.atxChannel') : t('settings.atxPin') }}</Label>
                    <Input
                      id="power-pin"
                      type="number"
                      v-model.number="atxConfig.power.pin"
                      :min="atxConfig.power.driver === 'serial' ? 1 : 0"
                      :disabled="atxConfig.power.driver === 'none'"
                    />
                  </div>
                  <div v-if="atxConfig.power.driver === 'gpio'" class="space-y-2">
                    <Label for="power-level">{{ t('settings.atxActiveLevel') }}</Label>
                    <select id="power-level" v-model="atxConfig.power.active_level" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                      <option value="high">{{ t('settings.atxLevelHigh') }}</option>
                      <option value="low">{{ t('settings.atxLevelLow') }}</option>
                    </select>
                  </div>
                  <div v-if="atxConfig.power.driver === 'serial'" class="space-y-2">
                    <Label for="power-baudrate">{{ t('settings.baudRate') }}</Label>
                    <select id="power-baudrate" v-model.number="atxConfig.power.baud_rate" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                      <option :value="9600">9600</option>
                      <option :value="19200">19200</option>
                      <option :value="38400">38400</option>
                      <option :value="57600">57600</option>
                      <option :value="115200">115200</option>
                    </select>
                  </div>
                </div>
              </CardContent>
            </Card>

            <!-- Reset Button Config -->
            <Card v-if="atxConfig.enabled">
              <CardHeader>
                <CardTitle>{{ t('settings.atxResetButton') }}</CardTitle>
                <CardDescription>{{ t('settings.atxResetButtonDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="reset-driver">{{ t('settings.atxDriver') }}</Label>
                    <select id="reset-driver" v-model="atxConfig.reset.driver" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                      <option value="none">{{ t('settings.atxDriverNone') }}</option>
                      <option value="gpio">{{ t('settings.atxDriverGpio') }}</option>
                      <option value="usbrelay">{{ t('settings.atxDriverUsbRelay') }}</option>
                      <option value="serial">{{ t('settings.atxDriverSerial') }}</option>
                    </select>
                  </div>
                  <div class="space-y-2">
                    <Label for="reset-device">{{ t('settings.atxDevice') }}</Label>
                    <select id="reset-device" v-model="atxConfig.reset.device" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="atxConfig.reset.driver === 'none'">
                      <option value="">{{ t('settings.selectDevice') }}</option>
                      <option
                        v-for="dev in getAtxDevicesForDriver(atxConfig.reset.driver)"
                        :key="dev"
                        :value="dev"
                        :disabled="atxConfig.reset.driver === 'serial' && isAtxSerialDeviceReserved(dev)"
                      >
                        {{ formatAtxDeviceLabel(atxConfig.reset.driver, dev) }}
                      </option>
                    </select>
                  </div>
                </div>
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label for="reset-pin">{{ ['usbrelay', 'serial'].includes(atxConfig.reset.driver) ? t('settings.atxChannel') : t('settings.atxPin') }}</Label>
                    <Input
                      id="reset-pin"
                      type="number"
                      v-model.number="atxConfig.reset.pin"
                      :min="atxConfig.reset.driver === 'serial' ? 1 : 0"
                      :disabled="atxConfig.reset.driver === 'none'"
                    />
                  </div>
                  <div v-if="atxConfig.reset.driver === 'gpio'" class="space-y-2">
                    <Label for="reset-level">{{ t('settings.atxActiveLevel') }}</Label>
                    <select id="reset-level" v-model="atxConfig.reset.active_level" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                      <option value="high">{{ t('settings.atxLevelHigh') }}</option>
                      <option value="low">{{ t('settings.atxLevelLow') }}</option>
                    </select>
                  </div>
                  <div v-if="atxConfig.reset.driver === 'serial'" class="space-y-2">
                    <Label for="reset-baudrate">{{ t('settings.baudRate') }}</Label>
                    <select
                      id="reset-baudrate"
                      v-model.number="atxConfig.reset.baud_rate"
                      class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm"
                      :disabled="isSharedAtxSerialRelay"
                    >
                      <option :value="9600">9600</option>
                      <option :value="19200">19200</option>
                      <option :value="38400">38400</option>
                      <option :value="57600">57600</option>
                      <option :value="115200">115200</option>
                    </select>
                    <p v-if="isSharedAtxSerialRelay" class="text-xs text-muted-foreground">
                      {{ t('settings.atxSharedSerialBaudHint') }}
                    </p>
                  </div>
                </div>
              </CardContent>
            </Card>

            <!-- LED Sensing Config -->
            <Card v-if="atxConfig.enabled">
              <CardHeader>
                <CardTitle>{{ t('settings.atxLedSensing') }}</CardTitle>
                <CardDescription>{{ t('settings.atxLedSensingDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="space-y-0.5">
                    <Label for="led-enabled">{{ t('settings.atxLedEnable') }}</Label>
                    <p class="text-xs text-muted-foreground">{{ t('settings.atxLedEnableDesc') }}</p>
                  </div>
                  <Switch
                    id="led-enabled"
                    v-model="atxConfig.led.enabled"
                  />
                </div>
                <template v-if="atxConfig.led.enabled">
                  <Separator />
                  <div class="grid gap-4 sm:grid-cols-2">
                    <div class="space-y-2">
                      <Label for="led-chip">{{ t('settings.atxLedChip') }}</Label>
                      <select id="led-chip" v-model="atxConfig.led.gpio_chip" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                        <option value="">{{ t('settings.selectDevice') }}</option>
                        <option v-for="dev in atxDevices.gpio_chips" :key="dev" :value="dev">{{ dev }}</option>
                      </select>
                    </div>
                    <div class="space-y-2">
                      <Label for="led-pin">{{ t('settings.atxLedPin') }}</Label>
                      <Input id="led-pin" type="number" v-model.number="atxConfig.led.gpio_pin" min="0" />
                    </div>
                  </div>
                  <div class="flex items-center justify-between">
                    <div class="space-y-0.5">
                      <Label for="led-inverted">{{ t('settings.atxLedInverted') }}</Label>
                      <p class="text-xs text-muted-foreground">{{ t('settings.atxLedInvertedDesc') }}</p>
                    </div>
                    <Switch
                      id="led-inverted"
                      v-model="atxConfig.led.inverted"
                    />
                  </div>
                </template>
              </CardContent>
            </Card>

            <!-- WOL Config -->
            <Card v-if="atxConfig.enabled">
              <CardHeader>
                <CardTitle>{{ t('settings.atxWolSettings') }}</CardTitle>
                <CardDescription>{{ t('settings.atxWolSettingsDesc') }}</CardDescription>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="space-y-2">
                  <Label for="wol-interface">{{ t('settings.atxWolInterface') }}</Label>
                  <Input
                    id="wol-interface"
                    v-model="atxConfig.wol_interface"
                    :placeholder="t('settings.atxWolInterfacePlaceholder')"
                  />
                  <p class="text-xs text-muted-foreground">{{ t('settings.atxWolInterfaceHint') }}</p>
                </div>
              </CardContent>
            </Card>

            <!-- Save Button -->
            <div class="flex justify-end">
              <Button :disabled="loading" @click="saveAtxConfig">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>
          </div>

          <!-- ttyd Section -->
          <div v-show="activeSection === 'ext-ttyd'" class="space-y-6">
            <Card>
              <CardHeader>
                <div class="flex items-center justify-between">
                  <div class="space-y-1.5">
                    <CardTitle>{{ t('extensions.ttyd.title') }}</CardTitle>
                    <CardDescription>{{ t('extensions.ttyd.desc') }}</CardDescription>
                  </div>
                  <Badge :variant="extensions?.ttyd?.available ? 'default' : 'destructive'">
                    {{ extensions?.ttyd?.available ? t('extensions.available') : t('extensions.unavailable') }}
                  </Badge>
                </div>
              </CardHeader>
              <CardContent class="space-y-4">
                <div v-if="!extensions?.ttyd?.available" class="text-sm text-muted-foreground bg-muted p-3 rounded-md">
                  {{ t('extensions.binaryNotFound', { path: '/usr/bin/ttyd' }) }}
                </div>
                <template v-else>
                  <!-- Status and controls -->
                  <div class="flex items-center justify-between">
                    <div class="flex items-center gap-2">
                      <div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.ttyd?.status)]" />
                      <span class="text-sm">{{ getExtStatusText(extensions?.ttyd?.status) }}</span>
                    </div>
                    <div class="flex gap-2">
                      <Button
                        v-if="isExtRunning(extensions?.ttyd?.status)"
                        size="sm"
                        variant="default"
                        @click="openTerminal"
                      >
                        <Terminal class="h-4 w-4 mr-1" />
                        {{ t('extensions.ttyd.open') }}
                      </Button>
                      <Button
                        v-if="!isExtRunning(extensions?.ttyd?.status)"
                        size="sm"
                        @click="startExtension('ttyd')"
                        :disabled="extensionsLoading"
                      >
                        <Play class="h-4 w-4 mr-1" />
                        {{ t('extensions.start') }}
                      </Button>
                      <Button
                        v-else
                        size="sm"
                        variant="outline"
                        @click="stopExtension('ttyd')"
                        :disabled="extensionsLoading"
                      >
                        <Square class="h-4 w-4 mr-1" />
                        {{ t('extensions.stop') }}
                      </Button>
                    </div>
                  </div>
                  <Separator />
                  <!-- Config -->
                  <div class="grid gap-4">
                    <div class="flex items-center justify-between">
                      <Label>{{ t('extensions.autoStart') }}</Label>
                      <Switch v-model="extConfig.ttyd.enabled" :disabled="isExtRunning(extensions?.ttyd?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.ttyd.shell') }}</Label>
                      <Input v-model="extConfig.ttyd.shell" class="sm:col-span-3" placeholder="/bin/bash" :disabled="isExtRunning(extensions?.ttyd?.status)" />
                    </div>
                  </div>
                  <!-- Logs -->
                  <div class="space-y-2">
                    <button type="button" @click="showLogs.ttyd = !showLogs.ttyd; if (showLogs.ttyd) refreshExtensionLogs('ttyd')" class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground">
                      <ChevronRight :class="['h-4 w-4 transition-transform', showLogs.ttyd ? 'rotate-90' : '']" />
                      {{ t('extensions.viewLogs') }}
                    </button>
                    <div v-if="showLogs.ttyd" class="space-y-2">
                      <pre class="p-3 bg-muted rounded-md text-xs max-h-48 overflow-auto font-mono">{{ (extensionLogs.ttyd || []).join('\n') || t('extensions.noLogs') }}</pre>
                      <Button variant="ghost" size="sm" @click="refreshExtensionLogs('ttyd')">
                        <RefreshCw class="h-3 w-3 mr-1" />
                        {{ t('common.refresh') }}
                      </Button>
                    </div>
                  </div>
                </template>
              </CardContent>
            </Card>
            <!-- Save button -->
            <div v-if="extensions?.ttyd?.available" class="flex justify-end">
              <Button :disabled="loading || isExtRunning(extensions?.ttyd?.status)" @click="saveExtensionConfig('ttyd')">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>
          </div>

          <!-- Remote Access Section -->
          <div v-show="activeSection === 'ext-remote-access'" class="space-y-6">
            <Card>
              <CardHeader>
                <div class="flex items-center justify-between">
                  <div class="space-y-1.5">
                    <CardTitle>{{ t('extensions.gostc.title') }}</CardTitle>
                    <CardDescription>{{ t('extensions.gostc.desc') }}</CardDescription>
                  </div>
                  <Badge :variant="extensions?.gostc?.available ? 'default' : 'destructive'">
                    {{ extensions?.gostc?.available ? t('extensions.available') : t('extensions.unavailable') }}
                  </Badge>
                </div>
              </CardHeader>
              <CardContent class="space-y-4">
                <div v-if="!extensions?.gostc?.available" class="text-sm text-muted-foreground bg-muted p-3 rounded-md">
                  {{ t('extensions.binaryNotFound', { path: '/usr/bin/gostc' }) }}
                </div>
                <template v-else>
                  <!-- Status and controls -->
                  <div class="flex items-center justify-between">
                    <div class="flex items-center gap-2">
                      <div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.gostc?.status)]" />
                      <span class="text-sm">{{ getExtStatusText(extensions?.gostc?.status) }}</span>
                    </div>
                    <div class="flex gap-2">
                      <Button
                        v-if="!isExtRunning(extensions?.gostc?.status)"
                        size="sm"
                        @click="startExtension('gostc')"
                        :disabled="extensionsLoading || !extConfig.gostc.key"
                      >
                        <Play class="h-4 w-4 mr-1" />
                        {{ t('extensions.start') }}
                      </Button>
                      <Button
                        v-else
                        size="sm"
                        variant="outline"
                        @click="stopExtension('gostc')"
                        :disabled="extensionsLoading"
                      >
                        <Square class="h-4 w-4 mr-1" />
                        {{ t('extensions.stop') }}
                      </Button>
                    </div>
                  </div>
                  <Separator />
                  <!-- Config -->
                  <div class="grid gap-4">
                    <div class="flex items-center justify-between">
                      <Label>{{ t('extensions.autoStart') }}</Label>
                      <Switch v-model="extConfig.gostc.enabled" :disabled="isExtRunning(extensions?.gostc?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.gostc.addr') }}</Label>
                      <Input v-model="extConfig.gostc.addr" class="sm:col-span-3" placeholder="gostc.mofeng.run" :disabled="isExtRunning(extensions?.gostc?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.gostc.key') }}</Label>
                      <Input v-model="extConfig.gostc.key" type="password" class="sm:col-span-3" :disabled="isExtRunning(extensions?.gostc?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.gostc.tls') }}</Label>
                      <div class="sm:col-span-3">
                        <Switch v-model="extConfig.gostc.tls" :disabled="isExtRunning(extensions?.gostc?.status)" />
                      </div>
                    </div>
                  </div>
                  <!-- Logs -->
                  <div class="space-y-2">
                    <button type="button" @click="showLogs.gostc = !showLogs.gostc; if (showLogs.gostc) refreshExtensionLogs('gostc')" class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground">
                      <ChevronRight :class="['h-4 w-4 transition-transform', showLogs.gostc ? 'rotate-90' : '']" />
                      {{ t('extensions.viewLogs') }}
                    </button>
                    <div v-if="showLogs.gostc" class="space-y-2">
                      <pre class="p-3 bg-muted rounded-md text-xs max-h-48 overflow-auto font-mono">{{ (extensionLogs.gostc || []).join('\n') || t('extensions.noLogs') }}</pre>
                      <Button variant="ghost" size="sm" @click="refreshExtensionLogs('gostc')">
                        <RefreshCw class="h-3 w-3 mr-1" />
                        {{ t('common.refresh') }}
                      </Button>
                    </div>
                  </div>
                </template>
              </CardContent>
            </Card>
            <!-- Save button -->
            <div v-if="extensions?.gostc?.available" class="flex justify-end">
              <Button :disabled="loading || isExtRunning(extensions?.gostc?.status)" @click="saveExtensionConfig('gostc')">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>

            <Card>
              <CardHeader>
                <div class="flex items-center justify-between">
                  <div class="space-y-1.5">
                    <CardTitle>{{ t('extensions.easytier.title') }}</CardTitle>
                    <CardDescription>{{ t('extensions.easytier.desc') }}</CardDescription>
                  </div>
                  <Badge :variant="extensions?.easytier?.available ? 'default' : 'destructive'">
                    {{ extensions?.easytier?.available ? t('extensions.available') : t('extensions.unavailable') }}
                  </Badge>
                </div>
              </CardHeader>
              <CardContent class="space-y-4">
                <div v-if="!extensions?.easytier?.available" class="text-sm text-muted-foreground bg-muted p-3 rounded-md">
                  {{ t('extensions.binaryNotFound', { path: '/usr/bin/easytier-core' }) }}
                </div>
                <template v-else>
                  <!-- Status and controls -->
                  <div class="flex items-center justify-between">
                    <div class="flex items-center gap-2">
                      <div :class="['w-2 h-2 rounded-full', getExtStatusClass(extensions?.easytier?.status)]" />
                      <span class="text-sm">{{ getExtStatusText(extensions?.easytier?.status) }}</span>
                    </div>
                    <div class="flex gap-2">
                      <Button
                        v-if="!isExtRunning(extensions?.easytier?.status)"
                        size="sm"
                        @click="startExtension('easytier')"
                        :disabled="extensionsLoading || !extConfig.easytier.network_name"
                      >
                        <Play class="h-4 w-4 mr-1" />
                        {{ t('extensions.start') }}
                      </Button>
                      <Button
                        v-else
                        size="sm"
                        variant="outline"
                        @click="stopExtension('easytier')"
                        :disabled="extensionsLoading"
                      >
                        <Square class="h-4 w-4 mr-1" />
                        {{ t('extensions.stop') }}
                      </Button>
                    </div>
                  </div>
                  <Separator />
                  <!-- Config -->
                  <div class="grid gap-4">
                    <div class="flex items-center justify-between">
                      <Label>{{ t('extensions.autoStart') }}</Label>
                      <Switch v-model="extConfig.easytier.enabled" :disabled="isExtRunning(extensions?.easytier?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.easytier.networkName') }}</Label>
                      <Input v-model="extConfig.easytier.network_name" class="sm:col-span-3" :disabled="isExtRunning(extensions?.easytier?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.easytier.networkSecret') }}</Label>
                      <Input v-model="extConfig.easytier.network_secret" type="password" class="sm:col-span-3" :disabled="isExtRunning(extensions?.easytier?.status)" />
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.easytier.peers') }}</Label>
                      <div class="sm:col-span-3 space-y-2">
                        <div v-for="(_, i) in extConfig.easytier.peer_urls" :key="i" class="flex gap-2">
                          <Input v-model="extConfig.easytier.peer_urls[i]" placeholder="tcp://1.2.3.4:11010" :disabled="isExtRunning(extensions?.easytier?.status)" />
                          <Button variant="ghost" size="icon" :aria-label="t('common.delete')" @click="removeEasytierPeer(i)" :disabled="isExtRunning(extensions?.easytier?.status)">
                            <Trash2 class="h-4 w-4" />
                          </Button>
                        </div>
                        <Button variant="outline" size="sm" @click="addEasytierPeer" :disabled="isExtRunning(extensions?.easytier?.status)">
                          <Plus class="h-4 w-4 mr-1" />
                          {{ t('extensions.easytier.addPeer') }}
                        </Button>
                      </div>
                    </div>
                    <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                      <Label class="sm:text-right">{{ t('extensions.easytier.virtualIp') }}</Label>
                      <div class="sm:col-span-3 space-y-1">
                        <Input v-model="extConfig.easytier.virtual_ip" placeholder="10.0.0.1/24" :disabled="isExtRunning(extensions?.easytier?.status)" />
                        <p class="text-xs text-muted-foreground">{{ t('extensions.easytier.virtualIpHint') }}</p>
                      </div>
                    </div>
                  </div>
                  <!-- Logs -->
                  <div class="space-y-2">
                    <button type="button" @click="showLogs.easytier = !showLogs.easytier; if (showLogs.easytier) refreshExtensionLogs('easytier')" class="flex items-center gap-2 text-sm text-muted-foreground hover:text-foreground">
                      <ChevronRight :class="['h-4 w-4 transition-transform', showLogs.easytier ? 'rotate-90' : '']" />
                      {{ t('extensions.viewLogs') }}
                    </button>
                    <div v-if="showLogs.easytier" class="space-y-2">
                      <pre class="p-3 bg-muted rounded-md text-xs max-h-48 overflow-auto font-mono">{{ (extensionLogs.easytier || []).join('\n') || t('extensions.noLogs') }}</pre>
                      <Button variant="ghost" size="sm" @click="refreshExtensionLogs('easytier')">
                        <RefreshCw class="h-3 w-3 mr-1" />
                        {{ t('common.refresh') }}
                      </Button>
                    </div>
                  </div>
                </template>
              </CardContent>
            </Card>
            <!-- Save button -->
            <div v-if="extensions?.easytier?.available" class="flex justify-end">
              <Button :disabled="loading || isExtRunning(extensions?.easytier?.status)" @click="saveExtensionConfig('easytier')">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>
          </div>

          <!-- RTSP Section -->
          <div v-show="activeSection === 'ext-rtsp'" class="space-y-6">
            <Card>
              <CardHeader>
                <div class="flex items-center justify-between">
                  <div class="space-y-1.5">
                    <CardTitle>{{ t('extensions.rtsp.title') }}</CardTitle>
                    <CardDescription>{{ t('extensions.rtsp.desc') }}</CardDescription>
                  </div>
                  <div class="flex items-center gap-2">
                    <Badge :variant="rtspStatus?.service_status === 'running' ? 'default' : 'secondary'">
                      {{ getRtspServiceStatusText(rtspStatus?.service_status) }}
                    </Badge>
                    <Button variant="ghost" size="icon" class="h-8 w-8" :aria-label="t('common.refresh')" @click="loadRtspConfig" :disabled="rtspLoading">
                      <RefreshCw :class="['h-4 w-4', rtspLoading ? 'animate-spin' : '']" />
                    </Button>
                  </div>
                </div>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="flex items-center justify-between">
                  <div class="flex items-center gap-2">
                    <div :class="['w-2 h-2 rounded-full', getRtspStatusClass(rtspStatus?.service_status)]" />
                    <span class="text-sm">{{ getRtspServiceStatusText(rtspStatus?.service_status) }}</span>
                  </div>
                  <div class="flex items-center gap-2">
                    <Button
                      v-if="rtspStatus?.service_status !== 'running'"
                      size="sm"
                      @click="startRtsp"
                      :disabled="rtspLoading"
                    >
                      <Play class="h-4 w-4 mr-1" />
                      {{ t('extensions.start') }}
                    </Button>
                    <Button
                      v-else
                      size="sm"
                      variant="outline"
                      @click="stopRtsp"
                      :disabled="rtspLoading"
                    >
                      <Square class="h-4 w-4 mr-1" />
                      {{ t('extensions.stop') }}
                    </Button>
                  </div>
                </div>
                <Separator />

                <div class="grid gap-4">
                  <div class="flex items-center justify-between">
                    <Label>{{ t('extensions.autoStart') }}</Label>
                    <Switch v-model="rtspLocalConfig.enabled" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.bind') }}</Label>
                    <Input v-model="rtspLocalConfig.bind" class="sm:col-span-3" placeholder="0.0.0.0" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.port') }}</Label>
                    <Input v-model.number="rtspLocalConfig.port" class="sm:col-span-3" type="number" min="1" max="65535" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.path') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <Input v-model="rtspLocalConfig.path" :placeholder="t('extensions.rtsp.pathPlaceholder')" />
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.pathHint') }}</p>
                    </div>
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.codec') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <select v-model="rtspLocalConfig.codec" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm">
                        <option value="h264">H.264</option>
                        <option value="h265">H.265</option>
                      </select>
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.codecHint') }}</p>
                    </div>
                  </div>
                  <div class="flex items-center justify-between">
                    <Label>{{ t('extensions.rtsp.allowOneClient') }}</Label>
                    <Switch v-model="rtspLocalConfig.allow_one_client" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.username') }}</Label>
                    <Input v-model="rtspLocalConfig.username" class="sm:col-span-3" :placeholder="t('extensions.rtsp.usernamePlaceholder')" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rtsp.password') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <Input
                        v-model="rtspLocalConfig.password"
                        type="password"
                        :placeholder="rtspStatus?.config?.has_password ? t('extensions.rtsp.passwordSet') : t('extensions.rtsp.passwordPlaceholder')"
                      />
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rtsp.passwordHint') }}</p>
                    </div>
                  </div>
                </div>

                <Separator />

                <div class="rounded-md border p-3 bg-muted/20 space-y-1">
                  <p class="text-sm font-medium">{{ t('extensions.rtsp.urlPreview') }}</p>
                  <code class="font-mono text-sm break-all">{{ rtspStreamUrl }}</code>
                </div>
              </CardContent>
            </Card>
            <div class="flex justify-end">
              <Button :disabled="loading || rtspLoading" @click="saveRtspConfig">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>
          </div>

          <!-- RustDesk Section -->
          <div v-show="activeSection === 'ext-rustdesk'" class="space-y-6">
            <Card>
              <CardHeader>
                <div class="flex items-center justify-between">
                  <div class="space-y-1.5">
                    <CardTitle>{{ t('extensions.rustdesk.title') }}</CardTitle>
                    <CardDescription>{{ t('extensions.rustdesk.desc') }}</CardDescription>
                  </div>
                  <div class="flex items-center gap-2">
                    <Badge :variant="rustdeskStatus?.service_status === 'running' ? 'default' : 'secondary'">
                      {{ getRustdeskServiceStatusText(rustdeskStatus?.service_status) }}
                    </Badge>
                    <Button variant="ghost" size="icon" class="h-8 w-8" :aria-label="t('common.refresh')" @click="loadRustdeskConfig" :disabled="rustdeskLoading">
                      <RefreshCw :class="['h-4 w-4', rustdeskLoading ? 'animate-spin' : '']" />
                    </Button>
                  </div>
                </div>
              </CardHeader>
              <CardContent class="space-y-4">
                <!-- Status and controls -->
                <div class="flex items-center justify-between">
                  <div class="flex items-center gap-2">
                    <div :class="['w-2 h-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.service_status)]" />
                    <span class="text-sm">{{ getRustdeskServiceStatusText(rustdeskStatus?.service_status) }}</span>
                    <template v-if="rustdeskStatus?.rendezvous_status">
                      <span class="text-muted-foreground">|</span>
                      <div :class="['w-2 h-2 rounded-full', getRustdeskStatusClass(rustdeskStatus?.rendezvous_status)]" />
                      <span class="text-sm text-muted-foreground">{{ getRustdeskRendezvousStatusText(rustdeskStatus?.rendezvous_status) }}</span>
                    </template>
                  </div>
                  <div class="flex items-center gap-2">
                    <Button
                      v-if="rustdeskStatus?.service_status !== 'running'"
                      size="sm"
                      @click="startRustdesk"
                      :disabled="rustdeskLoading"
                    >
                      <Play class="h-4 w-4 mr-1" />
                      {{ t('extensions.start') }}
                    </Button>
                    <Button
                      v-else
                      size="sm"
                      variant="outline"
                      @click="stopRustdesk"
                      :disabled="rustdeskLoading"
                    >
                      <Square class="h-4 w-4 mr-1" />
                      {{ t('extensions.stop') }}
                    </Button>
                  </div>
                </div>
                <Separator />

                <!-- Config -->
                <div class="grid gap-4">
                  <div class="flex items-center justify-between">
                    <Label>{{ t('extensions.autoStart') }}</Label>
                    <Switch v-model="rustdeskLocalConfig.enabled" />
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.rendezvousServer') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <Input
                        v-model="rustdeskLocalConfig.rendezvous_server"
                        :placeholder="t('extensions.rustdesk.rendezvousServerPlaceholder')"
                      />
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.rendezvousServerHint') }}</p>
                    </div>
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.relayServer') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <Input
                        v-model="rustdeskLocalConfig.relay_server"
                        :placeholder="t('extensions.rustdesk.relayServerPlaceholder')"
                      />
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.relayServerHint') }}</p>
                    </div>
                  </div>
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.relayKey') }}</Label>
                    <div class="sm:col-span-3 space-y-1">
                      <Input
                        v-model="rustdeskLocalConfig.relay_key"
                        type="password"
                        :placeholder="rustdeskStatus?.config?.has_relay_key ? t('extensions.rustdesk.relayKeySet') : t('extensions.rustdesk.relayKeyPlaceholder')"
                      />
                      <p class="text-xs text-muted-foreground">{{ t('extensions.rustdesk.relayKeyHint') }}</p>
                    </div>
                  </div>
                </div>
                <Separator />

                <!-- Device Info -->
                <div class="space-y-3">
                  <h4 class="text-sm font-medium">{{ t('extensions.rustdesk.deviceInfo') }}</h4>

                  <!-- Device ID -->
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.deviceId') }}</Label>
                    <div class="sm:col-span-3 flex items-center gap-2">
                      <code class="font-mono text-lg bg-muted px-3 py-1 rounded">{{ rustdeskConfig?.device_id || '-' }}</code>
                      <Button
                        variant="ghost"
                        size="icon"
                        class="h-8 w-8"
                        :aria-label="t('extensions.rustdesk.copyId')"
                        @click="copyToClipboard(rustdeskConfig?.device_id || '', 'id')"
                        :disabled="!rustdeskConfig?.device_id"
                      >
                        <Check v-if="rustdeskCopied === 'id'" class="h-4 w-4 text-green-500" />
                        <Copy v-else class="h-4 w-4" />
                      </Button>
                      <Button variant="outline" size="sm" @click="regenerateRustdeskId" :disabled="rustdeskLoading">
                        <RefreshCw class="h-4 w-4 mr-1" />
                        {{ t('extensions.rustdesk.regenerateId') }}
                      </Button>
                    </div>
                  </div>

                  <!-- Device Password (shown directly) -->
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.devicePassword') }}</Label>
                    <div class="sm:col-span-3 flex items-center gap-2">
                      <code class="font-mono text-lg bg-muted px-3 py-1 rounded">{{ rustdeskPassword?.device_password || '-' }}</code>
                      <Button
                        variant="ghost"
                        size="icon"
                        class="h-8 w-8"
                        :aria-label="t('extensions.rustdesk.copyPassword')"
                        @click="copyToClipboard(rustdeskPassword?.device_password || '', 'password')"
                        :disabled="!rustdeskPassword?.device_password"
                      >
                        <Check v-if="rustdeskCopied === 'password'" class="h-4 w-4 text-green-500" />
                        <Copy v-else class="h-4 w-4" />
                      </Button>
                      <Button variant="outline" size="sm" @click="regenerateRustdeskPassword" :disabled="rustdeskLoading">
                        <RefreshCw class="h-4 w-4 mr-1" />
                        {{ t('extensions.rustdesk.regeneratePassword') }}
                      </Button>
                    </div>
                  </div>

                  <!-- Keypair Status -->
                  <div class="grid gap-2 sm:grid-cols-4 sm:items-center">
                    <Label class="sm:text-right">{{ t('extensions.rustdesk.keypairGenerated') }}</Label>
                    <div class="sm:col-span-3">
                      <Badge :variant="rustdeskConfig?.has_keypair ? 'default' : 'secondary'">
                        {{ rustdeskConfig?.has_keypair ? t('common.yes') : t('common.no') }}
                      </Badge>
                    </div>
                  </div>
                </div>
              </CardContent>
            </Card>
            <!-- Save button -->
            <div class="flex justify-end">
              <Button :disabled="loading" @click="saveRustdeskConfig">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
              </Button>
            </div>
          </div>

          <!-- About Section -->
          <div v-show="activeSection === 'about'" class="space-y-6">
            <Card>
              <CardHeader class="flex flex-row items-start justify-between space-y-0">
                <div class="space-y-1.5">
                  <CardTitle>{{ t('settings.onlineUpgrade') }}</CardTitle>
                  <CardDescription>{{ t('settings.onlineUpgradeDesc') }}</CardDescription>
                </div>
                <Button
                  variant="ghost"
                  size="icon"
                  class="h-8 w-8"
                  :aria-label="t('common.refresh')"
                  :disabled="updateRunning || updateLoading"
                  @click="loadUpdateOverview"
                >
                  <RefreshCw :class="['h-4 w-4', (updateLoading || updateRunning) ? 'animate-spin' : '']" />
                </Button>
              </CardHeader>
              <CardContent class="space-y-4">
                <div class="grid gap-4 sm:grid-cols-2">
                  <div class="space-y-2">
                    <Label>{{ t('settings.currentVersion') }}</Label>
                    <Badge variant="outline">
                      {{ updateOverview?.current_version || systemStore.version || t('common.unknown') }}
                      ({{ systemStore.buildDate || t('common.unknown') }})
                    </Badge>
                  </div>
                  <div class="space-y-2">
                    <Label>{{ t('settings.latestVersion') }}</Label>
                    <Badge variant="outline">{{ updateOverview?.latest_version || t('common.unknown') }}</Badge>
                  </div>
                </div>

                <div class="space-y-2">
                  <Label>{{ t('settings.updateChannel') }}</Label>
                  <select v-model="updateChannel" class="w-full h-9 px-3 rounded-md border border-input bg-background text-sm" :disabled="updateRunning">
                    <option value="stable">Stable</option>
                    <option value="beta">Beta</option>
                  </select>
                </div>

                <div class="space-y-2">
                  <div class="flex items-center justify-between">
                    <Label>{{ t('settings.updateStatus') }}</Label>
                    <Badge
                      variant="outline"
                      class="max-w-[60%] truncate"
                      :title="updateStatusBadgeText()"
                    >
                      {{ updateStatusBadgeText() }}
                    </Badge>
                  </div>
                  <div v-if="updateRunning || updateStatus?.phase === 'failed' || updateStatus?.phase === 'success'" class="w-full h-2 bg-muted rounded overflow-hidden">
                    <div class="h-full bg-primary transition-all" :style="{ width: `${Math.max(0, Math.min(100, updateStatus?.progress || 0))}%` }" />
                  </div>
                  <p v-if="updateStatus?.last_error" class="text-xs text-destructive">{{ updateStatus.last_error }}</p>
                </div>

                <div class="space-y-2">
                  <Label>{{ t('settings.releaseNotes') }}</Label>
                  <div v-if="updateLoading" class="text-sm text-muted-foreground">{{ t('common.loading') }}</div>
                  <div v-else-if="!updateOverview?.notes_between?.length" class="text-sm text-muted-foreground">{{ t('settings.noUpdates') }}</div>
                  <div v-else class="space-y-3 max-h-56 overflow-y-auto pr-1">
                    <div v-for="item in updateOverview.notes_between" :key="item.version" class="rounded border p-3 space-y-2">
                      <div class="flex items-center justify-between">
                        <span class="font-medium">v{{ item.version }}</span>
                        <span class="text-xs text-muted-foreground">{{ item.published_at }}</span>
                      </div>
                      <ul class="list-disc pl-5 text-sm space-y-1">
                        <li v-for="(note, idx) in item.notes" :key="`${item.version}-${idx}`">{{ note }}</li>
                      </ul>
                    </div>
                  </div>
                </div>

                <div class="flex justify-end gap-2">
                  <Button
                    :disabled="updateRunning || !updateOverview?.upgrade_available"
                    @click="startOnlineUpgrade"
                  >
                    <RefreshCw class="h-4 w-4 mr-2" :class="updateRunning ? 'animate-spin' : ''" />
                    {{ t('settings.startUpgrade') }}
                  </Button>
                </div>
              </CardContent>
            </Card>

            <!-- Device Info Card -->
            <Card v-if="systemStore.deviceInfo">
              <CardHeader>
                <CardTitle>{{ t('settings.deviceInfo') }}</CardTitle>
                <CardDescription>{{ t('settings.deviceInfoDesc') }}</CardDescription>
              </CardHeader>
              <CardContent>
                <div class="space-y-3">
                  <div class="flex justify-between items-center py-2 border-b">
                    <span class="text-sm text-muted-foreground">{{ t('settings.hostname') }}</span>
                    <span class="text-sm font-medium">{{ systemStore.deviceInfo.hostname }}</span>
                  </div>
                  <div class="flex justify-between items-center py-2 border-b">
                    <span class="text-sm text-muted-foreground">{{ t('settings.cpuModel') }}</span>
                    <span class="text-sm font-medium truncate max-w-[60%] text-right">{{ systemStore.deviceInfo.cpu_model }}</span>
                  </div>
                  <div class="flex justify-between items-center py-2 border-b">
                    <span class="text-sm text-muted-foreground">{{ t('settings.cpuUsage') }}</span>
                    <span class="text-sm font-medium">{{ systemStore.deviceInfo.cpu_usage.toFixed(1) }}%</span>
                  </div>
                  <div class="flex justify-between items-center py-2 border-b">
                    <span class="text-sm text-muted-foreground">{{ t('settings.memoryUsage') }}</span>
                    <span class="text-sm font-medium">{{ formatBytes(systemStore.deviceInfo.memory_used) }} / {{ formatBytes(systemStore.deviceInfo.memory_total) }}</span>
                  </div>
                  <div class="py-2">
                    <span class="text-sm text-muted-foreground">{{ t('settings.networkAddresses') }}</span>
                    <div class="mt-2 space-y-1">
                      <div v-for="addr in systemStore.deviceInfo.network_addresses" :key="addr.interface" class="flex justify-between items-center text-sm">
                        <span class="text-muted-foreground">{{ addr.interface }}</span>
                        <code class="font-mono bg-muted px-2 py-0.5 rounded">{{ addr.ip }}</code>
                      </div>
                      <div v-if="systemStore.deviceInfo.network_addresses.length === 0" class="text-sm text-muted-foreground">
                        {{ t('common.unknown') }}
                      </div>
                    </div>
                  </div>
                </div>
              </CardContent>
            </Card>

            <p class="text-xs text-muted-foreground text-center">{{ t('settings.builtWith') }}</p>
          </div>

          <!-- Save Button (sticky) -->
          <div v-if="['video', 'hid', 'msd'].includes(activeSection)" class="sticky bottom-0 pt-4 pb-2 bg-background border-t -mx-6 px-6 lg:-mx-8 lg:px-8">
            <div class="flex justify-end">
              <div class="flex items-center gap-3">
                <p v-if="activeSection === 'hid' && !isHidFunctionSelectionValid" class="text-xs text-amber-600 dark:text-amber-400">
                  {{ t('settings.otgFunctionMinWarning') }}
                </p>
                <Button :disabled="loading || (activeSection === 'hid' && !isHidFunctionSelectionValid)" @click="saveConfig">
                <Check v-if="saved" class="h-4 w-4 mr-2" /><Save v-else class="h-4 w-4 mr-2" />{{ saved ? t('common.success') : t('common.save') }}
                </Button>
              </div>
            </div>
          </div>

        </div>
      </main>
    </div>

    <!-- Terminal Dialog -->
    <Dialog v-model:open="showTerminalDialog">
      <DialogContent class="w-[95vw] max-w-5xl h-[85dvh] max-h-[720px] p-0 flex flex-col overflow-hidden">
        <DialogHeader class="px-4 py-3 border-b shrink-0">
          <DialogTitle class="flex items-center justify-between w-full">
            <div class="flex items-center gap-2">
              <Terminal class="h-5 w-5" />
              {{ t('extensions.ttyd.title') }}
            </div>
            <Button
              variant="ghost"
              size="icon"
              class="h-8 w-8 mr-8"
              @click="openTerminalInNewTab"
              :aria-label="t('extensions.ttyd.openInNewTab')"
              :title="t('extensions.ttyd.openInNewTab')"
            >
              <ExternalLink class="h-4 w-4" />
            </Button>
          </DialogTitle>
        </DialogHeader>
        <div class="flex-1 min-h-0">
          <iframe
            v-if="showTerminalDialog"
            src="/api/terminal/"
            class="w-full h-full border-0"
            allow="clipboard-read; clipboard-write"
            scrolling="no"
          />
        </div>
      </DialogContent>
    </Dialog>

    <!-- Restart Confirmation Dialog -->
    <Dialog v-model:open="showRestartDialog">
      <DialogContent>
        <DialogHeader>
          <DialogTitle>{{ t('settings.restartRequired') }}</DialogTitle>
        </DialogHeader>
        <p class="text-sm text-muted-foreground py-4">
          {{ t('settings.restartMessage') }}
        </p>
        <DialogFooter>
          <Button variant="outline" @click="showRestartDialog = false" :disabled="restarting">
            {{ t('common.later') }}
          </Button>
          <Button @click="restartServer" :disabled="restarting">
            <RefreshCw v-if="restarting" class="h-4 w-4 mr-2 animate-spin" />
            {{ restarting ? t('settings.restarting') : t('common.restartNow') }}
          </Button>
        </DialogFooter>
      </DialogContent>
    </Dialog>
  </AppLayout>
</template>
