<template>
  <div class="host-detail-page">
    <!-- 顶部返回栏 -->
    <div class="page-header">
      <div class="header-left">
        <el-button @click="goBack" link class="back-btn"><el-icon><ArrowLeft /></el-icon> 返回宿主机列表</el-button>
      </div>
      <div class="header-right">
        <el-button plain :icon="Refresh" @click="loadDetail" :loading="loading">刷新</el-button>
      </div>
    </div>

    <div class="main-content" v-loading="loading">
      <!-- 实例概览栏 -->
      <div class="instance-overview" v-if="detail">
        <div class="overview-left">
          <h2 class="instance-name">{{ detail.name || '-' }} <span class="instance-id">{{ detail.id }}</span></h2>
        </div>
        <div class="overview-actions">
          <!-- <el-button type="warning" plain @click="openTokenDialog"><el-icon><Key /></el-icon>创建注册令牌</el-button> -->
          <el-button type="primary" plain @click="handleEdit">编辑宿主机</el-button>
          <el-button type="danger" plain @click="handleDelete">删除</el-button>
        </div>
      </div>

      <!-- 状态概览条 -->
      <div class="status-bar" v-if="detail">
        <div class="status-item">
          <span class="status-label">状态</span>
          <span class="status-value">
            <span class="status-dot" :class="detail.is_active ? 'dot-running' : 'dot-other'"></span>
            {{ detail.is_active ? '启用' : '禁用' }}
          </span>
        </div>
        <div class="status-item">
          <span class="status-label">IP 地址</span>
          <span class="status-value">{{ detail.ip || '-' }}</span>
        </div>
        <div class="status-item">
          <span class="status-label">CPU</span>
          <span class="status-value">{{ detail.max_cpu || 0 }}核</span>
        </div>
        <div class="status-item">
          <span class="status-label">内存</span>
          <span class="status-value">{{ formatMemKB(detail.max_memory) }}</span>
        </div>
        <div class="status-item">
          <span class="status-label">磁盘</span>
          <span class="status-value">{{ formatDiskGB(detail.max_disk) }}</span>
        </div>
      </div>
      <div class="status-bar" v-if="detail">
        <div class="status-item">
          <span class="status-label">带宽</span>
          <span class="status-value">↓{{ detail.rx_bandwidth || 0 }} / ↑{{ detail.tx_bandwidth || 0 }} Mbps</span>
        </div>
        <div class="status-item">
          <span class="status-label">创建时间</span>
          <span class="status-value">{{ formatTimestamp(detail.created_at) }}</span>
        </div>
      </div>

      <!-- 标签页 -->
      <el-tabs v-model="activeTab" class="detail-tabs" v-if="detail">
        <el-tab-pane label="基本信息" name="info">
          <div class="section-block">
            <h3 class="section-title">配置信息</h3>
            <div class="config-grid">
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">服务地址</span>
                  <span class="config-value mono-text">{{ detail.base_url || '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">SSH 端口</span>
                  <span class="config-value">{{ detail.port || '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">SSH 用户</span>
                  <span class="config-value">{{ detail.user || '-' }}</span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">认证Token</span>
                  <span class="config-value secret-cell">
                    <template v-if="detail.token">
                      <code>{{ showToken ? detail.token : '••••••••••••' }}</code>
                      <el-button link type="primary" size="small" @click="showToken = !showToken">{{ showToken ? '隐藏' : '显示' }}</el-button>
                      <el-button v-if="showToken" link type="primary" size="small" @click="copyText(detail.token)">复制</el-button>
                    </template>
                    <span v-else class="text-muted">未设置</span>
                  </span>
                </div>
                <div class="config-cell">
                  <span class="config-label">SSH 密码</span>
                  <span class="config-value secret-cell">
                    <template v-if="detail.password">
                      <code>{{ showPassword ? detail.password : '••••••••' }}</code>
                      <el-button link type="primary" size="small" @click="showPassword = !showPassword">{{ showPassword ? '隐藏' : '显示' }}</el-button>
                      <el-button v-if="showPassword" link type="primary" size="small" @click="copyText(detail.password)">复制</el-button>
                    </template>
                    <span v-else class="text-muted">未设置</span>
                  </span>
                </div>
                <div class="config-cell">
                  <span class="config-label">私钥</span>
                  <span class="config-value secret-cell">
                    <template v-if="detail.private_key">
                      <code>{{ showPrivateKey ? detail.private_key.substring(0, 40) + '...' : '••••••••••••' }}</code>
                      <el-button link type="primary" size="small" @click="showPrivateKey = !showPrivateKey">{{ showPrivateKey ? '隐藏' : '显示' }}</el-button>
                      <el-button v-if="showPrivateKey" link type="primary" size="small" @click="copyText(detail.private_key)">复制</el-button>
                    </template>
                    <span v-else class="text-muted">未设置</span>
                  </span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell">
                  <span class="config-label">宿主机组</span>
                  <span class="config-value">{{ detail.host_group_id ? `#${detail.host_group_id}` : '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">介绍</span>
                  <span class="config-value">{{ detail.description || '-' }}</span>
                </div>
                <div class="config-cell">
                  <span class="config-label">更新时间</span>
                  <span class="config-value">{{ formatTimestamp(detail.updated_at) }}</span>
                </div>
              </div>
            </div>
          </div>
          <div class="section-block">
            <h3 class="section-title clickable" @click="showDetailDiskIo = !showDetailDiskIo">
              硬盘 IO 限制
              <el-icon class="section-arrow" :class="{ expanded: showDetailDiskIo }"><ArrowRight /></el-icon>
            </h3>
            <div v-show="showDetailDiskIo" class="config-grid">
              <div class="config-row">
                <div class="config-cell" v-for="f in diskIoBwFields" :key="f.key">
                  <span class="config-label">{{ f.label }}带宽</span>
                  <span class="config-value mono-text">{{ formatDiskIoVal(detail[f.key], true) }}</span>
                </div>
              </div>
              <div class="config-row">
                <div class="config-cell" v-for="f in diskIoIopsFields" :key="f.key">
                  <span class="config-label">{{ f.label }} IOPS</span>
                  <span class="config-value mono-text">{{ formatDiskIoVal(detail[f.key], false) }}</span>
                </div>
              </div>
            </div>
          </div>
        </el-tab-pane>

        <el-tab-pane label="额度统计" name="quotaStats">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">资源额度统计</h3>
              <el-button size="small" :icon="Refresh" @click="loadQuotaStats" :loading="quotaStatsLoading">刷新</el-button>
            </div>
            <template v-if="quotaStats">
              <el-descriptions :column="3" border size="small" style="margin-top:12px">
                <el-descriptions-item label="虚拟机数量">{{ quotaStats.vm_count ?? '-' }}</el-descriptions-item>
                <el-descriptions-item label="规划 CPU">{{ quotaStats.planned_cpu ?? '-' }} 核</el-descriptions-item>
                <el-descriptions-item label="已分配 CPU">{{ quotaStats.allocated_cpu ?? '-' }} 核</el-descriptions-item>
                <el-descriptions-item label="规划内存">{{ formatQuotaMem(quotaStats.planned_memory) }}</el-descriptions-item>
                <el-descriptions-item label="已分配内存">{{ formatQuotaMem(quotaStats.allocated_memory) }}</el-descriptions-item>
                <el-descriptions-item label="实时内存">{{ formatQuotaBytes(quotaStats.actual_memory_used) }} / {{ formatQuotaBytes(quotaStats.actual_memory_total) }}</el-descriptions-item>
                <el-descriptions-item label="规划磁盘">{{ quotaStats.planned_disk ?? '-' }} GB</el-descriptions-item>
                <el-descriptions-item label="已分配磁盘">{{ quotaStats.allocated_disk ?? '-' }} GB</el-descriptions-item>
                <el-descriptions-item label="实时 CPU">{{ quotaStats.actual_cpu_percent != null ? quotaStats.actual_cpu_percent.toFixed(1) + '%' : '-' }}</el-descriptions-item>
                <el-descriptions-item label="规划下行带宽">{{ quotaStats.planned_rx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
                <el-descriptions-item label="已分配下行带宽">{{ quotaStats.allocated_rx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
                <el-descriptions-item label="规划上行带宽">{{ quotaStats.planned_tx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
                <el-descriptions-item label="已分配上行带宽">{{ quotaStats.allocated_tx_bandwidth ?? '-' }} Mbps</el-descriptions-item>
              </el-descriptions>
              <template v-if="quotaStatsDisk.length">
                <h4 style="margin:16px 0 8px;font-size:13px;color:#606266">磁盘使用</h4>
                <el-table :data="quotaStatsDisk" size="small" stripe>
                  <el-table-column prop="path" label="路径" min-width="160" />
                  <el-table-column label="总量" width="100"><template #default="{row}">{{ formatQuotaBytes(row.total) }}</template></el-table-column>
                  <el-table-column label="已用" width="100"><template #default="{row}">{{ formatQuotaBytes(row.used) }}</template></el-table-column>
                  <el-table-column label="使用率" width="100"><template #default="{row}">{{ row.total ? ((row.used / row.total) * 100).toFixed(1) + '%' : '-' }}</template></el-table-column>
                </el-table>
              </template>
            </template>
            <el-empty v-else-if="!quotaStatsLoading" description="暂无额度统计数据" :image-size="60" />
          </div>
        </el-tab-pane>

        <el-tab-pane label="监控" name="monitor">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">监控指标</h3>
              <div style="display: flex; align-items: center; gap: 8px; flex-wrap: wrap">
                <el-date-picker
                  v-model="monitorDateRange"
                  type="datetimerange"
                  range-separator="至"
                  start-placeholder="开始时间"
                  end-placeholder="结束时间"
                  size="small"
                  style="width: 360px"
                  :shortcuts="monitorShortcuts"
                  @change="loadHistoricalMetrics"
                />
                <span style="font-size:12px;color:#909399;white-space:nowrap">粒度: {{ currentIntervalLabel }}</span>
                <el-button size="small" :icon="Refresh" @click="loadHistoricalMetrics" :loading="historicalMetricsLoading">刷新</el-button>
              </div>
            </div>
            <template v-if="latestMetrics">
              <div class="metric-summary-row">
                <div class="metric-summary-card">
                  <div class="metric-summary-label">CPU 使用率</div>
                  <div class="metric-summary-value">{{ latestMetrics.cpu_usage?.toFixed(1) }}%</div>
                  <div class="metric-summary-sub">{{ latestMetrics.cpu_count }} 核</div>
                </div>
                <div class="metric-summary-card">
                  <div class="metric-summary-label">内存使用率</div>
                  <div class="metric-summary-value">{{ latestMetrics.mem_percent?.toFixed(1) }}%</div>
                  <div class="metric-summary-sub">{{ formatBytesRaw(latestMetrics.mem_used) }} / {{ formatBytesRaw(latestMetrics.mem_total) }}</div>
                </div>
                <div class="metric-summary-card">
                  <div class="metric-summary-label">公网流量</div>
                  <div class="metric-summary-value">↓{{ formatNetLabel(latestMetrics.inet_rx) }}</div>
                  <div class="metric-summary-sub">↑{{ formatNetLabel(latestMetrics.inet_tx) }}</div>
                </div>
                <div class="metric-summary-card">
                  <div class="metric-summary-label">内网流量</div>
                  <div class="metric-summary-value">↓{{ formatBytesRaw(latestMetrics.net_rx) }}</div>
                  <div class="metric-summary-sub">↑{{ formatBytesRaw(latestMetrics.net_tx) }}</div>
                </div>
              </div>
            </template>
            <template v-if="historicalMetricsData">
              <el-row :gutter="16">
                <el-col :span="12">
                  <el-card shadow="hover" class="metrics-card">
                    <template #header><span class="metrics-title"><el-icon><Monitor /></el-icon> CPU 使用率</span></template>
                    <div ref="cpuChartRef" class="chart-container"></div>
                  </el-card>
                </el-col>
                <el-col :span="12">
                  <el-card shadow="hover" class="metrics-card">
                    <template #header><span class="metrics-title"><el-icon><Coin /></el-icon> 内存使用率</span></template>
                    <div ref="memChartRef" class="chart-container"></div>
                  </el-card>
                </el-col>
              </el-row>
              <el-row :gutter="16" style="margin-top: 16px">
                <el-col :span="12">
                  <el-card shadow="hover" class="metrics-card">
                    <template #header><span class="metrics-title"><el-icon><Connection /></el-icon> 公网流量</span></template>
                    <div ref="inetChartRef" class="chart-container"></div>
                  </el-card>
                </el-col>
                <el-col :span="12">
                  <el-card shadow="hover" class="metrics-card">
                    <template #header><span class="metrics-title"><el-icon><Connection /></el-icon> 内网流量</span></template>
                    <div ref="netChartRef" class="chart-container"></div>
                  </el-card>
                </el-col>
              </el-row>
            </template>
            <el-empty v-else-if="!historicalMetricsLoading" description="加载监控数据中..." :image-size="80" />
          </div>
        </el-tab-pane>

        <el-tab-pane label="镜像管理" name="image">
          <ImageManage v-if="hostTabLoaded['image']" ref="imageManageRef" />
        </el-tab-pane>
        <el-tab-pane label="网络管理" name="network">
          <NetworkManage v-if="hostTabLoaded['network']" ref="networkManageRef" />
        </el-tab-pane>
        <el-tab-pane label="数据卷管理" name="volume">
          <VolumeManage v-if="hostTabLoaded['volume']" ref="volumeManageRef" />
        </el-tab-pane>
        <el-tab-pane label="虚拟机管理" name="vm">
          <VmManage v-if="hostTabLoaded['vm']" ref="vmManageRef" />
        </el-tab-pane>
        <el-tab-pane label="快照管理" name="snapshot">
          <SnapshotManage v-if="hostTabLoaded['snapshot']" ref="snapshotManageRef" />
        </el-tab-pane>
        <el-tab-pane label="备份管理" name="backup">
          <BackupManage v-if="hostTabLoaded['backup']" ref="backupManageRef" />
        </el-tab-pane>

        <el-tab-pane label="组网管理" name="networking">
          <div class="section-block">
            <div class="section-header">
              <h3 class="section-title">用户组网列表</h3>
              <div style="display: flex; gap: 8px; align-items: center">
                <el-input v-model="nwFilterUserId" placeholder="按用户ID筛选" style="width: 140px" size="small" clearable @clear="loadNetworkingList" @keyup.enter="loadNetworkingList" />
                <el-input v-model="nwKeyword" placeholder="关键词搜索" style="width: 160px" size="small" clearable @clear="loadNetworkingList" @keyup.enter="loadNetworkingList" />
                <el-button size="small" :icon="Search" @click="loadNetworkingList">搜索</el-button>
                <el-button size="small" type="primary" @click="handleNwCreate">创建组网</el-button>
                <el-button size="small" :icon="Refresh" @click="loadNetworkingList" :loading="nwLoading">刷新</el-button>
              </div>
            </div>
            <el-table :data="nwList" v-loading="nwLoading" stripe size="small">
              <el-table-column prop="id" label="ID" width="60" />
              <el-table-column prop="name" label="名称" min-width="140" show-overflow-tooltip />
              <el-table-column prop="user_id" label="用户ID" width="80" />
              <el-table-column prop="host_id" label="宿主机ID" width="90" />
              <el-table-column label="网桥" width="120">
                <template #default="{ row }">{{ row.bridge_name || '-' }}</template>
              </el-table-column>
              <el-table-column label="网关" min-width="140">
                <template #default="{ row }"><span class="mono-text">{{ row.gateway || '-' }}</span></template>
              </el-table-column>
              <el-table-column label="创建时间" width="170">
                <template #default="{ row }">{{ formatTimestamp(row.created_at) }}</template>
              </el-table-column>
              <el-table-column label="操作" width="200" fixed="right">
                <template #default="{ row }">
                  <el-button link type="primary" size="small" @click="handleNwDetail(row)">详情</el-button>
                  <el-button link type="success" size="small" @click="handleNwAssign(row)">分配IP</el-button>
                  <el-button link type="danger" size="small" @click="handleNwDelete(row)">删除</el-button>
                </template>
              </el-table-column>
            </el-table>
            <el-empty v-if="!nwList.length && !nwLoading" description="暂无组网" :image-size="60" />
            <div class="pagination-wrapper" v-if="nwTotal > 0">
              <el-pagination v-model:current-page="nwPage" v-model:page-size="nwPageSize"
                :page-sizes="[10, 20, 50]" :total="nwTotal" layout="total, sizes, prev, pager, next" small
                @size-change="s => { nwPageSize = s; nwPage = 1; loadNetworkingList() }"
                @current-change="p => { nwPage = p; loadNetworkingList() }" />
            </div>
          </div>
        </el-tab-pane>
      </el-tabs>

      <!-- 组网详情弹窗 -->
      <el-dialog v-model="nwDetailVisible" title="组网详情" width="800px" destroy-on-close>
        <el-descriptions :column="2" border v-if="nwDetailData" style="margin-bottom: 16px">
          <el-descriptions-item label="ID">{{ nwDetailData.id }}</el-descriptions-item>
          <el-descriptions-item label="名称">{{ nwDetailData.name }}</el-descriptions-item>
          <el-descriptions-item label="用户ID">{{ nwDetailData.user_id }}</el-descriptions-item>
          <el-descriptions-item label="宿主机ID">{{ nwDetailData.host_id }}</el-descriptions-item>
          <el-descriptions-item label="网桥">{{ nwDetailData.bridge_name || '-' }}</el-descriptions-item>
          <el-descriptions-item label="网关">{{ nwDetailData.gateway || '-' }}</el-descriptions-item>
          <el-descriptions-item label="创建时间">{{ formatTimestamp(nwDetailData.created_at) }}</el-descriptions-item>
          <el-descriptions-item label="更新时间">{{ formatTimestamp(nwDetailData.updated_at) }}</el-descriptions-item>
        </el-descriptions>
        <h4 style="margin: 0 0 12px">组网下的网络 (已分配)</h4>
        <el-table :data="nwDetailNetworks" size="small" stripe>
          <el-table-column label="网络ID" width="70">
            <template #default="{ row }">{{ row.network?.id || '-' }}</template>
          </el-table-column>
          <el-table-column label="IP地址" min-width="150">
            <template #default="{ row }"><span class="mono-text">{{ row.network?.address || '-' }}</span></template>
          </el-table-column>
          <el-table-column label="MAC地址" min-width="160">
            <template #default="{ row }"><span class="mono-text">{{ row.network?.mac_address || '-' }}</span></template>
          </el-table-column>
          <el-table-column label="类型" width="80">
            <template #default="{ row }"><el-tag size="small">{{ row.network?.type || '-' }}</el-tag></template>
          </el-table-column>
          <el-table-column label="虚拟机ID" width="90">
            <template #default="{ row }">{{ row.network?.vm_id || '-' }}</template>
          </el-table-column>
          <el-table-column label="操作" width="80">
            <template #default="{ row }">
              <el-button link type="danger" size="small" @click="handleNwRemoveNet(row)">移除</el-button>
            </template>
          </el-table-column>
        </el-table>
        <el-empty v-if="!nwDetailNetworks.length" description="暂无分配的网络" :image-size="50" />
        <template #footer><el-button @click="nwDetailVisible = false">关闭</el-button></template>
      </el-dialog>

      <!-- 创建组网弹窗 -->
      <el-dialog v-model="nwCreateVisible" title="创建组网" width="480px" destroy-on-close class="tk-dialog">
        <el-form ref="nwCreateFormRef" :model="nwCreateForm" :rules="nwCreateRules" label-width="100px">
          <div class="tk-section">
            <div class="tk-section-title">组网信息</div>
            <el-form-item label="用户" prop="user_id">
              <div style="display: flex; gap: 8px; width: 100%">
                <el-input :model-value="nwCreateForm.user_id ? `${nwCreateUserName} (ID: ${nwCreateForm.user_id})` : '未选择'" disabled style="flex: 1" />
                <el-button type="primary" @click="showNwUserSelector = true">选择</el-button>
                <el-button v-if="nwCreateForm.user_id" @click="nwCreateForm.user_id = 0; nwCreateUserName = ''">清除</el-button>
              </div>
            </el-form-item>
            <el-form-item label="网桥名称">
              <el-input v-model="nwCreateForm.bridge_name" placeholder="可选" />
            </el-form-item>
            <el-form-item label="网关">
              <el-input v-model="nwCreateForm.gateway" placeholder="可选，如 10.0.0.1" />
            </el-form-item>
          </div>
        </el-form>
        <template #footer>
          <div class="tk-dialog-footer">
            <el-button @click="nwCreateVisible = false">取消</el-button>
            <el-button type="primary" :loading="nwSubmitLoading" @click="submitNwCreate">创建</el-button>
          </div>
        </template>
      </el-dialog>

      <!-- 分配IP弹窗 -->
      <el-dialog v-model="nwAssignVisible" title="为虚拟机分配组网IP" width="480px" destroy-on-close class="tk-dialog">
        <el-form label-width="100px">
          <div class="tk-section">
            <div class="tk-section-title">分配信息</div>
            <el-form-item label="组网">{{ nwAssignTarget?.name || '-' }} (ID: {{ nwAssignTarget?.id }})</el-form-item>
            <el-form-item label="虚拟机" required>
              <div style="display: flex; gap: 8px; width: 100%">
                <el-input :model-value="nwAssignVmId ? `${nwAssignVmName} (ID: ${nwAssignVmId})` : '未选择'" disabled style="flex: 1" />
                <el-button type="primary" @click="showNwVmSelector = true">选择</el-button>
              </div>
            </el-form-item>
            <el-form-item label="指定IP">
              <el-input v-model="nwAssignIp" placeholder="留空自动分配" />
            </el-form-item>
          </div>
        </el-form>
        <template #footer>
          <div class="tk-dialog-footer">
            <el-button @click="nwAssignVisible = false">取消</el-button>
            <el-button type="primary" :loading="nwSubmitLoading" @click="submitNwAssign" :disabled="!nwAssignVmId">分配</el-button>
          </div>
        </template>
      </el-dialog>

      <UserListSelector v-model="showNwUserSelector" @confirm="handleNwUserSelected" />
      <VmSelectorPopup v-model="showNwVmSelector" :service-id="serviceId" :host-id="hostId" @confirm="handleNwVmSelected" />
    </div>

    <!-- 编辑弹窗 -->
    <el-dialog v-model="editDialogVisible" title="编辑宿主机" width="890px" destroy-on-close class="tk-dialog">
      <el-form ref="formRef" :model="formData" :rules="formRules" label-width="100px">
        <div class="tk-section">
          <div class="tk-section-title">基本信息</div>
          <el-form-item label="名称" prop="name"><el-input v-model="formData.name" /></el-form-item>
          <el-form-item label="服务地址" prop="base_url"><el-input v-model="formData.base_url" /></el-form-item>
          <el-form-item label="IP 地址" prop="ip"><el-input v-model="formData.ip" /></el-form-item>
          <el-form-item label="认证Token"><el-input v-model="formData.token" show-password /></el-form-item>
        </div>
        <div class="tk-section">
          <div class="tk-section-title">SSH 配置</div>
          <el-form-item label="端口"><el-input-number v-model="formData.port" :min="0" :max="65535" controls-position="right" style="width: 100%" /></el-form-item>
          <el-form-item label="用户名"><el-input v-model="formData.user" /></el-form-item>
          <el-form-item label="密码"><el-input v-model="formData.password" show-password /></el-form-item>
          <el-form-item label="私钥"><el-input v-model="formData.private_key" type="textarea" :rows="4" placeholder="SSH 私钥内容" /></el-form-item>
        </div>
        <div class="tk-section">
          <div class="tk-section-title">资源限制</div>
          <div class="tk-resource-grid">
            <el-form-item label="CPU"><el-input-number v-model="formData.max_cpu" :min="0" controls-position="right" /><span class="tk-res-unit">核</span></el-form-item>
            <el-form-item label="内存">
              <el-input-number v-model="memoryDisplay" :min="0" controls-position="right" />
              <el-select v-model="memoryUnit" class="tk-unit-select">
                <el-option v-for="u in memoryUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </el-form-item>
            <el-form-item label="磁盘">
              <el-input-number v-model="diskDisplay" :min="0" controls-position="right" />
              <el-select v-model="diskUnit" class="tk-unit-select">
                <el-option v-for="u in diskUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </el-form-item>
            <el-form-item label="下行带宽"><el-input-number v-model="formData.rx_bandwidth" :min="0" controls-position="right" /><span class="tk-res-unit">Mbps</span></el-form-item>
            <el-form-item label="上行带宽"><el-input-number v-model="formData.tx_bandwidth" :min="0" controls-position="right" /><span class="tk-res-unit">Mbps</span></el-form-item>
          </div>
        </div>
        <div class="tk-section">
          <div class="tk-section-title clickable" @click="showDiskIoSection = !showDiskIoSection">
            硬盘 IO 限制
            <el-icon class="section-arrow" :class="{ expanded: showDiskIoSection }"><ArrowRight /></el-icon>
            <span class="section-hint">可选，不展开则使用默认值</span>
          </div>
          <div v-show="showDiskIoSection">
            <div class="io-sub-title">
              带宽限制
              <el-select v-model="ioBwUnit" class="tk-unit-select" style="width: 90px; margin-left: 8px">
                <el-option v-for="u in ioBwUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </div>
            <div class="tk-resource-grid">
              <el-form-item v-for="f in diskIoBwFields" :key="f.key" :label="f.label">
                <el-input-number :model-value="+(formData[f.key] / getIoBwFactor()).toFixed(2)" @update:model-value="v => formData[f.key] = Math.round((v || 0) * getIoBwFactor())" :min="0" controls-position="right" />
              </el-form-item>
            </div>
            <div class="io-sub-title">IOPS 限制</div>
            <div class="tk-resource-grid">
              <el-form-item v-for="f in diskIoIopsFields" :key="f.key" :label="f.label">
                <el-input-number v-model="formData[f.key]" :min="0" controls-position="right" />
              </el-form-item>
            </div>
          </div>
        </div>
        <div class="tk-section">
          <div class="tk-section-title">其他配置</div>
          <el-form-item label="宿主机组">
            <div style="display: flex; gap: 8px; width: 100%">
              <el-input :model-value="formData.host_group_id ? `宿主机组 #${formData.host_group_id}` : '未选择'" disabled style="flex: 1" />
              <el-button type="primary" @click="showGroupSelector = true">选择</el-button>
              <el-button v-if="formData.host_group_id" @click="formData.host_group_id = 0">清除</el-button>
            </div>
          </el-form-item>
          <el-form-item label="介绍"><el-input v-model="formData.description" type="textarea" :rows="3" /></el-form-item>
        </div>
      </el-form>
      <template #footer>
        <div class="tk-dialog-footer">
          <el-button @click="editDialogVisible = false">取消</el-button>
          <el-button type="primary" :loading="submitLoading" @click="handleSubmit">确定</el-button>
        </div>
      </template>
    </el-dialog>

    <HostGroupSelectorPopup v-model="showGroupSelector" :service-id="serviceId" :current-id="formData.host_group_id" @confirm="g => formData.host_group_id = g.id" />

    <!-- 创建注册令牌弹窗 -->
    <el-dialog v-model="tokenDialogVisible" title="创建宿主机注册令牌" width="700px" destroy-on-close class="token-dialog">
      <el-form ref="tokenFormRef" :model="tokenForm" :rules="tokenRules" label-width="120px">
        <div class="tk-section">
          <div class="tk-section-title">基本信息</div>
          <el-form-item label="宿主机名称" prop="name">
            <el-input v-model="tokenForm.name" placeholder="为该宿主机命名" />
          </el-form-item>
          <el-form-item label="所属宿主机组" prop="host_group_id">
            <div style="display: flex; gap: 8px; width: 100%">
              <el-input :model-value="tokenForm.host_group_id ? `宿主机组 #${tokenForm.host_group_id}` : ''" placeholder="请选择宿主机组" disabled style="flex: 1" />
              <el-button type="primary" @click="showTokenGroupSelector = true">选择</el-button>
              <el-button v-if="tokenForm.host_group_id" @click="tokenForm.host_group_id = 0">清除</el-button>
            </div>
          </el-form-item>
          <el-form-item label="宿主机描述">
            <el-input v-model="tokenForm.description" type="textarea" :rows="2" placeholder="宿主机描述（可选）" />
          </el-form-item>
        </div>
        <div class="tk-section">
          <div class="tk-section-title">资源配额</div>
          <div class="tk-resource-grid">
            <el-form-item label="CPU" prop="max_cpu" class="tk-res-item">
              <el-input-number v-model="tokenForm.max_cpu" :min="1" controls-position="right" /><span class="tk-res-unit">核</span>
            </el-form-item>
            <el-form-item label="内存" prop="max_memory" class="tk-res-item">
              <el-input-number v-model="tokenMemDisplay" :min="0" controls-position="right" />
              <el-select v-model="tokenMemUnit" class="tk-unit-select">
                <el-option v-for="u in memoryUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </el-form-item>
            <el-form-item label="磁盘" prop="max_disk" class="tk-res-item">
              <el-input-number v-model="tokenDiskDisplay" :min="0" controls-position="right" />
              <el-select v-model="tokenDiskUnit" class="tk-unit-select">
                <el-option v-for="u in diskUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </el-form-item>
            <el-form-item label="下行带宽" class="tk-res-item">
              <el-input-number v-model="tokenForm.rx_bandwidth" :min="0" controls-position="right" /><span class="tk-res-unit">Mbps</span>
            </el-form-item>
            <el-form-item label="上行带宽" class="tk-res-item">
              <el-input-number v-model="tokenForm.tx_bandwidth" :min="0" controls-position="right" /><span class="tk-res-unit">Mbps</span>
            </el-form-item>
          </div>
        </div>
        <div class="tk-section">
          <div class="tk-section-title clickable" @click="showTokenDiskIo = !showTokenDiskIo">
            硬盘 IO 限制
            <el-icon class="section-arrow" :class="{ expanded: showTokenDiskIo }"><ArrowRight /></el-icon>
            <span class="section-hint">可选，不展开则使用默认值</span>
          </div>
          <div v-show="showTokenDiskIo">
            <div class="io-sub-title">
              带宽限制
              <el-select v-model="tokenIoBwUnit" class="tk-unit-select" style="width: 90px; margin-left: 8px">
                <el-option v-for="u in ioBwUnitOptions" :key="u.label" :label="u.label" :value="u.label" />
              </el-select>
            </div>
            <div class="tk-resource-grid">
              <el-form-item v-for="f in diskIoBwFields" :key="f.key" :label="f.label" class="tk-res-item">
                <el-input-number :model-value="+(tokenForm[f.key] / getTokenIoBwFactor()).toFixed(2)" @update:model-value="v => tokenForm[f.key] = Math.round((v || 0) * getTokenIoBwFactor())" :min="0" controls-position="right" />
              </el-form-item>
            </div>
            <div class="io-sub-title">IOPS 限制</div>
            <div class="tk-resource-grid">
              <el-form-item v-for="f in diskIoIopsFields" :key="f.key" :label="f.label" class="tk-res-item">
                <el-input-number v-model="tokenForm[f.key]" :min="0" controls-position="right" />
              </el-form-item>
            </div>
          </div>
        </div>
        <div class="tk-section">
          <div class="tk-section-title">令牌有效期</div>
          <el-form-item label="有效期" prop="expire_hours">
            <el-input-number v-model="tokenForm.expire_hours" :min="1" :max="8760" controls-position="right" style="width: 100%" />
            <div class="form-hint">单位：小时。默认 24 小时，最大 8760 小时（365天）</div>
          </el-form-item>
        </div>
      </el-form>
      <template #footer>
        <div class="tk-dialog-footer">
          <el-button @click="tokenDialogVisible = false">取消</el-button>
          <el-button type="primary" :loading="tokenSubmitLoading" @click="handleTokenSubmit">
            <el-icon><Key /></el-icon>创建令牌
          </el-button>
        </div>
      </template>
    </el-dialog>

    <!-- 令牌结果弹窗 -->
    <el-dialog v-model="tokenResultVisible" title="注册令牌已生成" width="560px" :close-on-click-modal="false" class="token-result-dialog">
      <div class="tk-result-wrapper">
        <div class="tk-result-header">
          <el-icon class="tk-result-icon"><Key /></el-icon>
          <div>
            <div class="tk-result-name">{{ tokenResultInfo.name }}</div>
            <div class="tk-result-meta">有效期 {{ tokenResultInfo.expire_hours }} 小时</div>
          </div>
        </div>
        <el-alert type="warning" :closable="false" show-icon style="margin-bottom: 16px">
          <template #title>请立即复制并保存此令牌，关闭后将无法再次查看</template>
        </el-alert>
        <div class="tk-token-block">
          <div class="tk-token-label">后端地址</div>
          <div class="tk-token-value">{{ baseUrl }}</div>
        </div>
        <div class="tk-token-block">
          <div class="tk-token-label">service_id（主控服务ID）</div>
          <div class="tk-token-value">{{ tokenResultInfo.service_id }}</div>
        </div>
        <div class="tk-token-block">
          <div class="tk-token-label">注册令牌</div>
          <div class="tk-token-value">{{ tokenResultInfo.token }}</div>
        </div>
        <el-button type="primary" class="tk-copy-btn" @click="copyToken">
          <el-icon><CopyDocument /></el-icon>复制令牌到剪贴板
        </el-button>
      </div>
      <template #footer>
        <el-button @click="tokenResultVisible = false">关闭</el-button>
      </template>
    </el-dialog>

    <!-- 令牌用宿主机组选择器 -->
    <HostGroupSelectorPopup v-model="showTokenGroupSelector" :service-id="serviceId" :current-id="tokenForm.host_group_id" @confirm="handleTokenGroupSelected" />
  </div>
</template>

<script setup>
import { ref, reactive, computed, onMounted, onActivated, onDeactivated, onBeforeUnmount, watch, nextTick, provide } from 'vue'
import { useRoute, useRouter } from 'vue-router'
import { ElMessage, ElMessageBox } from 'element-plus'
import { ArrowLeft, ArrowRight, Refresh, Edit, Delete, Monitor, Coin, Connection, Search, Plus, Key, CopyDocument } from '@element-plus/icons-vue'
import {
  getRemoteHostDetail, updateRemoteHost, deleteRemoteHost,
  getUserNetworkingList, getUserNetworkingDetail, createUserNetworking, deleteUserNetworking,
  assignUserNetworking, removeUserNetworkingNetwork,
  createHostToken, getMetricsHistory, getHostQuotaStats
} from '@/api/admin/kvmService'
import { extractApiError } from '@/utils/kvmErrorUtil'
import { baseUrl } from '@/config/env'
import HostGroupSelectorPopup from '@/components/admin/HostGroupSelectorPopup.vue'
import ImageManage from '@/views/virtualization/ImageManage.vue'
import NetworkManage from '@/views/virtualization/NetworkManage.vue'
import VolumeManage from '@/views/virtualization/VolumeManage.vue'
import VmManage from '@/views/virtualization/VmManage.vue'
import SnapshotManage from '@/views/virtualization/SnapshotManage.vue'
import BackupManage from '@/views/virtualization/BackupManage.vue'
import { useTagsViewStore } from '@/store/tagsViewStore'
import UserListSelector from '@/components/admin/UserListSelector.vue'
import VmSelectorPopup from '@/components/admin/VmSelectorPopup.vue'
import * as echarts from 'echarts'

const route = useRoute()
const router = useRouter()
const tagsViewStore = useTagsViewStore()

const serviceId = computed(() => parseInt(route.query.service_id) || 0)
const serviceName = computed(() => route.query.service_name || '')
const hostId = computed(() => parseInt(route.query.id) || 0)

const activeTab = ref('info')
const hostTabLoaded = reactive({ image: false, network: false, volume: false, vm: false, snapshot: false, backup: false, networking: false })

const imageManageRef = ref(null)
const networkManageRef = ref(null)
const volumeManageRef = ref(null)
const vmManageRef = ref(null)
const snapshotManageRef = ref(null)
const backupManageRef = ref(null)
const tabRefMap = { image: imageManageRef, network: networkManageRef, volume: volumeManageRef, vm: vmManageRef, snapshot: snapshotManageRef, backup: backupManageRef }

watch(activeTab, (tab) => {
  if (!['info', 'monitor', 'networking'].includes(tab)) {
    if (!hostTabLoaded[tab]) {
      hostTabLoaded[tab] = true
    } else {
      nextTick(() => { tabRefMap[tab]?.value?.loadList?.() })
    }
  }
  if (tab === 'monitor' && detail.value) {
    if (!historicalMetricsData.value) {
      loadHistoricalMetrics()
    }
  }
  if (tab === 'networking') loadNetworkingList()
  if (tab === 'quotaStats' && !quotaStats.value) loadQuotaStats()
})

const loading = ref(false)
const submitLoading = ref(false)
const detail = ref(null)

provide('embedded', true)
provide('serviceId', serviceId)
provide('serviceName', serviceName)
provide('hostId', hostId)
provide('hostDetail', detail)
const showToken = ref(false)
const showPassword = ref(false)
const showPrivateKey = ref(false)
const copyText = (text) => {
  if (!text) return
  if (navigator.clipboard?.writeText) {
    navigator.clipboard.writeText(text).then(() => ElMessage.success('已复制到剪贴板')).catch(() => fallbackCopy(text))
  } else {
    fallbackCopy(text)
  }
}
const fallbackCopy = (text) => {
  const ta = document.createElement('textarea')
  ta.value = text
  ta.style.cssText = 'position:fixed;left:-9999px;top:-9999px'
  document.body.appendChild(ta)
  ta.select()
  try {
    document.execCommand('copy')
    ElMessage.success('已复制到剪贴板')
  } catch { ElMessage.error('复制失败') }
  document.body.removeChild(ta)
}
const historicalMetricsData = ref(null)
const historicalMetricsLoading = ref(false)
const editDialogVisible = ref(false)
const showGroupSelector = ref(false)
const formRef = ref(null)

const diskIoDefaults = {
  read_bytes_sec: 314572800, write_bytes_sec: 314572800,
  read_iops_sec: 1000, write_iops_sec: 1000,
  read_bytes_sec_max: 314572800, write_bytes_sec_max: 314572800,
  read_iops_sec_max: 1000, write_iops_sec_max: 1000
}
const diskIoBwFields = [
  { key: 'read_bytes_sec', label: '读取' },
  { key: 'write_bytes_sec', label: '写入' },
  { key: 'read_bytes_sec_max', label: '突发读取' },
  { key: 'write_bytes_sec_max', label: '突发写入' }
]
const diskIoIopsFields = [
  { key: 'read_iops_sec', label: '读取' },
  { key: 'write_iops_sec', label: '写入' },
  { key: 'read_iops_sec_max', label: '突发读取' },
  { key: 'write_iops_sec_max', label: '突发写入' }
]
const diskIoFields = [
  ...diskIoBwFields.map(f => ({ ...f, isBandwidth: true })),
  ...diskIoIopsFields.map(f => ({ ...f, isBandwidth: false }))
]
const formatDiskIoVal = (val, isBandwidth) => {
  if (!val && val !== 0) return '-'
  val = Number(val)
  if (!isBandwidth) return val.toLocaleString() + ' IOPS'
  if (val >= 1073741824) return (val / 1073741824).toFixed(1) + ' GB/s'
  if (val >= 1048576) return (val / 1048576).toFixed(0) + ' MB/s'
  if (val >= 1024) return (val / 1024).toFixed(0) + ' KB/s'
  return val + ' B/s'
}
const ioBwUnitOptions = [
  { label: 'B/s', factor: 1 },
  { label: 'KB/s', factor: 1024 },
  { label: 'MB/s', factor: 1048576 },
  { label: 'GB/s', factor: 1073741824 }
]
const ioBwUnit = ref('MB/s')
const tokenIoBwUnit = ref('MB/s')
const getIoBwFactor = () => ioBwUnitOptions.find(u => u.label === ioBwUnit.value)?.factor || 1048576
const getTokenIoBwFactor = () => ioBwUnitOptions.find(u => u.label === tokenIoBwUnit.value)?.factor || 1048576

const showDiskIoSection = ref(false)
const showTokenDiskIo = ref(false)
const showDetailDiskIo = ref(false)

const formData = reactive({
  name: '', base_url: '', ip: '', token: '', port: 22, user: '', password: '', private_key: '',
  max_cpu: 0, max_memory: 0, max_disk: 0, rx_bandwidth: 0, tx_bandwidth: 0, host_group_id: 0, description: '',
  ...diskIoDefaults
})
const formRules = {
  name: [{ required: true, message: '请输入名称', trigger: 'blur' }],
  base_url: [{ required: true, message: '请输入服务地址', trigger: 'blur' }],
  ip: [{ required: true, message: '请输入IP', trigger: 'blur' }]
}

const formatTimestamp = (ts) => {
  if (!ts) return '-'
  if (typeof ts === 'object' && ts.seconds) return new Date(Number(ts.seconds) * 1000).toLocaleString('zh-CN')
  if (typeof ts === 'string' || typeof ts === 'number') { const d = new Date(ts); return isNaN(d.getTime()) ? String(ts) : d.toLocaleString('zh-CN') }
  return '-'
}
const formatMemKB = (v) => {
  if (!v) return '-'; v = Number(v)
  if (v >= 1073741824) return (v / 1073741824).toFixed(1) + ' TB'
  if (v >= 1048576) return (v / 1048576).toFixed(1) + ' GB'
  if (v >= 1024) return (v / 1024).toFixed(1) + ' MB'
  return v + ' KB'
}
const formatDiskGB = (v) => {
  if (!v) return '-'; v = Number(v)
  if (v >= 1024) return (v / 1024).toFixed(1) + ' TB'
  return v.toFixed(1) + ' GB'
}

const memoryUnit = ref('GB')
const diskUnit = ref('GB')
const memoryUnitOptions = [
  { label: 'KB', factor: 1 },
  { label: 'MB', factor: 1024 },
  { label: 'GB', factor: 1048576 },
  { label: 'TB', factor: 1073741824 }
]
const diskUnitOptions = [
  { label: 'GB', factor: 1 },
  { label: 'TB', factor: 1024 }
]
const getMemFactor = () => memoryUnitOptions.find(u => u.label === memoryUnit.value)?.factor || 1048576
const getDiskFactor = () => diskUnitOptions.find(u => u.label === diskUnit.value)?.factor || 1

const memoryDisplay = computed({
  get: () => formData.max_memory ? +(formData.max_memory / getMemFactor()).toFixed(2) : 0,
  set: (v) => { formData.max_memory = Math.round((v || 0) * getMemFactor()) }
})
const diskDisplay = computed({
  get: () => formData.max_disk ? +(formData.max_disk / getDiskFactor()).toFixed(2) : 0,
  set: (v) => { formData.max_disk = Math.round((v || 0) * getDiskFactor()) }
})
const formatBytesRaw = (val) => {
  if (!val && val !== 0) return '-'; val = Number(val)
  if (val >= 1099511627776) return (val / 1099511627776).toFixed(2) + ' TB'
  if (val >= 1073741824) return (val / 1073741824).toFixed(2) + ' GB'
  if (val >= 1048576) return (val / 1048576).toFixed(2) + ' MB'
  if (val >= 1024) return (val / 1024).toFixed(1) + ' KB'
  return val + ' B'
}

const loadDetail = async () => {
  if (!serviceId.value || !hostId.value) return
  loading.value = true
  try {
    const res = await getRemoteHostDetail({ service_id: serviceId.value, id: hostId.value })
    const body = res?.data
    if (body?.code === 200 && body?.data) {
      detail.value = body.data.host ?? body.data.data ?? body.data
    } else { ElMessage.error(extractApiError(body, '加载失败')) }
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '加载失败')) } finally { loading.value = false }
}

// ---- 额度统计 ----
const quotaStats = ref(null)
const quotaStatsLoading = ref(false)
const quotaStatsDisk = computed(() => {
  if (!quotaStats.value?.actual_disk_json) return []
  try { return JSON.parse(quotaStats.value.actual_disk_json) } catch { return [] }
})

const loadQuotaStats = async () => {
  if (!serviceId.value || !hostId.value) return
  quotaStatsLoading.value = true
  try {
    const res = await getHostQuotaStats({ service_id: serviceId.value, host_id: hostId.value })
    if (res?.data?.code === 200) quotaStats.value = res.data.data
    else quotaStats.value = null
  } catch { quotaStats.value = null } finally { quotaStatsLoading.value = false }
}

const formatQuotaMem = (mb) => {
  if (!mb && mb !== 0) return '-'
  if (mb >= 1024) return (mb / 1024).toFixed(1) + ' GB'
  return mb + ' MB'
}

const formatQuotaBytes = (bytes) => {
  if (!bytes && bytes !== 0) return '-'
  const n = Number(bytes)
  if (n >= 1073741824) return (n / 1073741824).toFixed(2) + ' GB'
  if (n >= 1048576) return (n / 1048576).toFixed(1) + ' MB'
  if (n >= 1024) return (n / 1024).toFixed(0) + ' KB'
  return n + ' B'
}

const cpuChartRef = ref(null)
const memChartRef = ref(null)
const netChartRef = ref(null)
const inetChartRef = ref(null)
let cpuChart = null
let memChart = null
let netChart = null
let inetChart = null
let isPageActive = false

const latestMetrics = computed(() => {
  const arr = historicalMetricsData.value
  if (!Array.isArray(arr) || !arr.length) return null
  return arr[arr.length - 1]
})

const makeDefaultRange = () => {
  const now = new Date()
  return [new Date(now.getTime() - 10 * 60 * 1000), now]
}
const monitorDateRange = ref(makeDefaultRange())

const monitorShortcuts = [
  { text: '最近10分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 10 * 60000), n] } },
  { text: '最近30分钟', value: () => { const n = new Date(); return [new Date(n.getTime() - 30 * 60000), n] } },
  { text: '最近1小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 3600000), n] } },
  { text: '最近6小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 6 * 3600000), n] } },
  { text: '最近12小时', value: () => { const n = new Date(); return [new Date(n.getTime() - 12 * 3600000), n] } },
  { text: '最近1天', value: () => { const n = new Date(); return [new Date(n.getTime() - 86400000), n] } },
  { text: '最近7天', value: () => { const n = new Date(); return [new Date(n.getTime() - 7 * 86400000), n] } },
]

function calcInterval(startTime, endTime) {
  const spanMin = (endTime.getTime() - startTime.getTime()) / 60000
  if (spanMin < 30) return '1m'
  if (spanMin < 60) return '3m'
  if (spanMin < 360) return '5m'
  if (spanMin < 720) return '15m'
  if (spanMin < 1440) return '30m'
  if (spanMin < 4320) return '1h'
  if (spanMin < 10080) return '2h'
  if (spanMin < 43200) return '6h'
  if (spanMin < 129600) return '12h'
  return '1d'
}

const intervalLabelMap = { '1m': '1分钟', '3m': '3分钟', '5m': '5分钟', '15m': '15分钟', '30m': '30分钟', '1h': '1小时', '2h': '2小时', '6h': '6小时', '12h': '12小时', '1d': '1天' }
const currentIntervalLabel = computed(() => {
  if (!monitorDateRange.value || monitorDateRange.value.length < 2) return '-'
  const iv = calcInterval(new Date(monitorDateRange.value[0]), new Date(monitorDateRange.value[1]))
  return intervalLabelMap[iv] || iv
})

const loadHistoricalMetrics = async () => {
  if (!serviceId.value || !hostId.value) return
  if (!monitorDateRange.value || monitorDateRange.value.length < 2) return
  historicalMetricsLoading.value = true
  try {
    const startTime = new Date(monitorDateRange.value[0])
    const endTime = new Date(monitorDateRange.value[1])
    const interval = calcInterval(startTime, endTime)
    const params = {
      service_id: serviceId.value,
      host_id: hostId.value,
      start: startTime.toISOString(),
      end_time: endTime.toISOString(),
      interval
    }
    
    const res = await getMetricsHistory(params)
    const body = res?.data
    if (body?.code === 200 && body?.data) {
      historicalMetricsData.value = Array.isArray(body.data) ? body.data : (body.data.data || [])
      await nextTick()
      renderHistoricalCharts()
    } else {
      ElMessage.error(extractApiError(body, '加载历史指标失败'))
    }
  } catch (e) {
    ElMessage.error(extractApiError(e?.response?.data, '加载历史指标失败'))
  } finally {
    historicalMetricsLoading.value = false
  }
}


const formatNetLabel = (v) => {
  if (!v) return '0 B/s'
  if (v >= 1073741824) return (v / 1073741824).toFixed(1) + ' GB/s'
  if (v >= 1048576) return (v / 1048576).toFixed(1) + ' MB/s'
  if (v >= 1024) return (v / 1024).toFixed(1) + ' KB/s'
  return v + ' B/s'
}

// 渲染历史指标图表
const renderHistoricalCharts = () => {
  const metrics = historicalMetricsData.value
  if (!Array.isArray(metrics) || !metrics.length) return

  const spanMs = monitorDateRange.value ? (new Date(monitorDateRange.value[1]).getTime() - new Date(monitorDateRange.value[0]).getTime()) : 0
  const showDate = spanMs >= 12 * 3600 * 1000
  const symbolType = spanMs >= 7 * 86400 * 1000 ? 'circle' : 'none'
  const labelRotate = showDate ? 45 : 0

  const times = metrics.map(m => {
    const date = new Date(m.bucket)
    if (showDate) return date.toLocaleDateString('zh-CN', { month: '2-digit', day: '2-digit', hour: '2-digit', minute: '2-digit' })
    return date.toLocaleTimeString('zh-CN', { hour12: false, hour: '2-digit', minute: '2-digit' })
  })

  const cpuData = metrics.map(m => m.cpu_usage ?? 0)
  const memData = metrics.map(m => m.mem_percent ?? 0)
  const inetRxData = metrics.map(m => m.inet_rx ?? 0)
  const inetTxData = metrics.map(m => m.inet_tx ?? 0)

  const netRxRate = []
  const netTxRate = []
  for (let i = 0; i < metrics.length; i++) {
    if (i === 0) { netRxRate.push(0); netTxRate.push(0); continue }
    const dt = (new Date(metrics[i].bucket) - new Date(metrics[i - 1].bucket)) / 1000
    if (dt > 0) {
      netRxRate.push(Math.max(0, ((metrics[i].net_rx ?? 0) - (metrics[i - 1].net_rx ?? 0)) / dt))
      netTxRate.push(Math.max(0, ((metrics[i].net_tx ?? 0) - (metrics[i - 1].net_tx ?? 0)) / dt))
    } else {
      netRxRate.push(0); netTxRate.push(0)
    }
  }

  const baseGrid = { top: 10, right: 16, bottom: 24, left: 50 }
  const makeXAxis = () => ({ type: 'category', data: times, boundaryGap: false, axisLabel: { fontSize: 10, rotate: labelRotate } })
  const makeSeries = (name, data, color) => ({ name, type: 'line', smooth: true, symbol: symbolType, areaStyle: { opacity: 0.15 }, lineStyle: { width: 2, color }, itemStyle: { color }, data })

  if (cpuChartRef.value) {
    if (!cpuChart) cpuChart = echarts.init(cpuChartRef.value)
    cpuChart.setOption({
      tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} CPU: ${p[0].value.toFixed(1)}%` },
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, max: 100, axisLabel: { fontSize: 10, formatter: v => v + '%' } },
      series: [makeSeries('CPU', cpuData, '#409eff')]
    }, true)
  }

  if (memChartRef.value) {
    if (!memChart) memChart = echarts.init(memChartRef.value)
    memChart.setOption({
      tooltip: { trigger: 'axis', formatter: (p) => `${p[0].axisValue}<br/>${p[0].marker} 内存: ${p[0].value.toFixed(1)}%` },
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, max: 100, axisLabel: { fontSize: 10, formatter: v => v + '%' } },
      series: [makeSeries('内存', memData, '#67c23a')]
    }, true)
  }

  if (inetChartRef.value) {
    if (!inetChart) inetChart = echarts.init(inetChartRef.value)
    inetChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => {
        let s = params[0].axisValue
        params.forEach(p => { s += `<br/>${p.marker} ${p.seriesName}: ${formatNetLabel(p.value)}` })
        return s
      }},
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: formatNetLabel } },
      series: [makeSeries('接收', inetRxData, '#409eff'), makeSeries('发送', inetTxData, '#e6a23c')]
    }, true)
  }

  if (netChartRef.value) {
    if (!netChart) netChart = echarts.init(netChartRef.value)
    netChart.setOption({
      tooltip: { trigger: 'axis', formatter: (params) => {
        let s = params[0].axisValue
        params.forEach(p => { s += `<br/>${p.marker} ${p.seriesName}: ${formatNetLabel(p.value)}` })
        return s
      }},
      grid: baseGrid, xAxis: makeXAxis(),
      yAxis: { type: 'value', min: 0, axisLabel: { fontSize: 10, formatter: formatNetLabel } },
      series: [makeSeries('接收', netRxRate, '#409eff'), makeSeries('发送', netTxRate, '#e6a23c')]
    }, true)
  }
}

const disposeCharts = () => {
  cpuChart?.dispose(); cpuChart = null
  memChart?.dispose(); memChart = null
  netChart?.dispose(); netChart = null
  inetChart?.dispose(); inetChart = null
}

const handleEdit = () => {
  if (!detail.value) return
  const d = detail.value
  Object.assign(formData, {
    name: d.name || '', base_url: d.base_url || '', ip: d.ip || '', token: d.token || '',
    port: d.port || 22, user: d.user || '', password: d.password || '', private_key: d.private_key || '',
    max_cpu: d.max_cpu || 0, max_memory: d.max_memory || 0, max_disk: d.max_disk || 0,
    rx_bandwidth: d.rx_bandwidth || 0, tx_bandwidth: d.tx_bandwidth || 0,
    host_group_id: d.host_group_id || 0, description: d.description || '',
    ...Object.fromEntries(diskIoFields.map(f => [f.key, d[f.key] ?? diskIoDefaults[f.key]]))
  })
  showDiskIoSection.value = diskIoFields.some(f => d[f.key] && d[f.key] !== diskIoDefaults[f.key])
  editDialogVisible.value = true
}

const handleSubmit = () => {
  formRef.value?.validate(async (valid) => {
    if (!valid) return
    submitLoading.value = true
    try {
      const payload = { ...formData, service_id: serviceId.value, id: hostId.value }
      if (!payload.token) delete payload.token
      if (!payload.password) delete payload.password
      if (!payload.private_key) delete payload.private_key
      if (!payload.description) delete payload.description
      if (!payload.host_group_id) delete payload.host_group_id
      const res = await updateRemoteHost(payload)
      if (res?.data?.code === 200) { ElMessage.success('修改成功'); editDialogVisible.value = false; loadDetail() }
      else ElMessage.error(extractApiError(res?.data, '修改失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '修改失败')) } finally { submitLoading.value = false }
  })
}

const handleDelete = () => {
  ElMessageBox.confirm(`确定要删除宿主机「${detail.value?.name}」吗？`, '删除确认', {
    confirmButtonText: '确定删除', cancelButtonText: '取消', type: 'warning'
  }).then(async () => {
    try {
      const res = await deleteRemoteHost({ service_id: serviceId.value, id: hostId.value })
      if (res?.data?.code === 200) { ElMessage.success('删除成功'); goBack() }
      else ElMessage.error(extractApiError(res?.data, '删除失败'))
    } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
  }).catch(() => {})
}

// ========== 创建注册令牌 ==========
const tokenDialogVisible = ref(false)
const tokenSubmitLoading = ref(false)
const tokenResultVisible = ref(false)
const showTokenGroupSelector = ref(false)
const tokenFormRef = ref(null)
const tokenMemUnit = ref('GB')
const tokenDiskUnit = ref('GB')

const tokenForm = reactive({
  name: '', host_group_id: 0, max_cpu: 4,
  max_memory: 4194304, max_disk: 100,
  rx_bandwidth: 100, tx_bandwidth: 100,
  description: '', expire_hours: 24,
  ...diskIoDefaults
})
const tokenResultInfo = reactive({ name: '', expire_hours: 24, token: '', service_id: 0 })
const tokenRules = {
  name: [{ required: true, message: '请输入宿主机名称', trigger: 'blur' }],
  host_group_id: [{ required: true, type: 'number', min: 1, message: '请选择宿主机组', trigger: 'change' }],
  max_cpu: [{ required: true, type: 'number', min: 1, message: '请设置最大CPU核数', trigger: 'change' }],
  max_memory: [{ required: true, type: 'number', min: 1, message: '请设置最大内存', trigger: 'change' }],
  max_disk: [{ required: true, type: 'number', min: 1, message: '请设置最大磁盘', trigger: 'change' }],
  expire_hours: [{ required: true, type: 'number', min: 1, message: '请设置有效期', trigger: 'change' }]
}

const getTokenMemFactor = () => memoryUnitOptions.find(u => u.label === tokenMemUnit.value)?.factor || 1048576
const getTokenDiskFactor = () => diskUnitOptions.find(u => u.label === tokenDiskUnit.value)?.factor || 1
const tokenMemDisplay = computed({
  get: () => tokenForm.max_memory ? +(tokenForm.max_memory / getTokenMemFactor()).toFixed(2) : 0,
  set: (v) => { tokenForm.max_memory = Math.round((v || 0) * getTokenMemFactor()) }
})
const tokenDiskDisplay = computed({
  get: () => tokenForm.max_disk ? +(tokenForm.max_disk / getTokenDiskFactor()).toFixed(2) : 0,
  set: (v) => { tokenForm.max_disk = Math.round((v || 0) * getTokenDiskFactor()) }
})

const openTokenDialog = () => {
  const d = detail.value
  Object.assign(tokenForm, {
    name: '', host_group_id: d?.host_group_id || 0,
    max_cpu: d?.max_cpu || 4,
    max_memory: d?.max_memory || 4194304,
    max_disk: d?.max_disk || 100,
    rx_bandwidth: d?.rx_bandwidth || 100,
    tx_bandwidth: d?.tx_bandwidth || 100,
    description: '', expire_hours: 24,
    ...Object.fromEntries(diskIoFields.map(f => [f.key, d?.[f.key] ?? diskIoDefaults[f.key]]))
  })
  tokenMemUnit.value = 'GB'
  tokenDiskUnit.value = 'GB'
  showTokenDiskIo.value = false
  tokenIoBwUnit.value = 'MB/s'
  tokenDialogVisible.value = true
}

const handleTokenGroupSelected = (group) => {
  tokenForm.host_group_id = group.id
}

const handleTokenSubmit = () => {
  tokenFormRef.value?.validate(async (valid) => {
    if (!valid) return
    tokenSubmitLoading.value = true
    try {
      const fd = new FormData()
      fd.append('service_id', serviceId.value)
      fd.append('name', tokenForm.name)
      fd.append('host_group_id', tokenForm.host_group_id)
      fd.append('max_cpu', tokenForm.max_cpu)
      fd.append('max_memory', tokenForm.max_memory)
      fd.append('max_disk', tokenForm.max_disk)
      fd.append('rx_bandwidth', tokenForm.rx_bandwidth)
      fd.append('tx_bandwidth', tokenForm.tx_bandwidth)
      diskIoFields.forEach(f => { if (tokenForm[f.key] !== undefined) fd.append(f.key, tokenForm[f.key]) })
      fd.append('description', tokenForm.description || '')
      fd.append('expire_hours', tokenForm.expire_hours)
      const res = await createHostToken(fd)
      const body = res?.data
      if (body?.code === 200 && body?.data) {
        tokenResultInfo.name = tokenForm.name
        tokenResultInfo.expire_hours = tokenForm.expire_hours
        tokenResultInfo.token = body.data.token || body.data.Token || JSON.stringify(body.data)
        tokenResultInfo.service_id = serviceId.value
        tokenDialogVisible.value = false
        tokenResultVisible.value = true
        ElMessage.success('注册令牌创建成功')
      } else {
        ElMessage.error(extractApiError(body, '创建令牌失败'))
      }
    } catch (e) {
      ElMessage.error(extractApiError(e?.response?.data, '创建令牌失败'))
    } finally {
      tokenSubmitLoading.value = false
    }
  })
}

const copyToken = async () => {
  const text = `后端地址：${baseUrl}\nservice_id：${tokenResultInfo.service_id}\n注册令牌：${tokenResultInfo.token}`
  try {
    await navigator.clipboard.writeText(text)
    ElMessage.success('令牌信息已复制到剪贴板')
  } catch {
    const ta = document.createElement('textarea')
    ta.value = text
    document.body.appendChild(ta)
    ta.select()
    document.execCommand('copy')
    document.body.removeChild(ta)
    ElMessage.success('令牌信息已复制到剪贴板')
  }
}

const goBack = () => {
  tagsViewStore.delVisitedView(route)
  router.push({ path: '/virtualization/kvm-service-detail', query: { service_id: serviceId.value, service_name: serviceName.value } })
}

// ---- 组网管理 ----
const nwLoading = ref(false)
const nwSubmitLoading = ref(false)
const nwList = ref([])
const nwTotal = ref(0)
const nwPage = ref(1)
const nwPageSize = ref(10)
const nwFilterUserId = ref('')
const nwKeyword = ref('')

const nwDetailVisible = ref(false)
const nwDetailData = ref(null)
const nwDetailNetworks = ref([])
const nwDetailLoading = ref(false)

const nwCreateVisible = ref(false)
const nwCreateFormRef = ref(null)
const nwCreateForm = reactive({ user_id: 0, bridge_name: '', gateway: '' })
const nwCreateUserName = ref('')
const showNwUserSelector = ref(false)
const nwCreateRules = {
  user_id: [{ required: true, message: '请选择用户', trigger: 'change', type: 'number', min: 1 }]
}

const nwAssignVisible = ref(false)
const nwAssignTarget = ref(null)
const nwAssignVmId = ref(0)
const nwAssignVmName = ref('')
const nwAssignIp = ref('')
const showNwVmSelector = ref(false)

const loadNetworkingList = async () => {
  nwLoading.value = true
  try {
    const params = {
      service_id: serviceId.value,
      page: nwPage.value,
      count: nwPageSize.value,
      host_id: hostId.value
    }
    if (nwFilterUserId.value) params.user_id = parseInt(nwFilterUserId.value)
    if (nwKeyword.value) params.keyword = nwKeyword.value
    const res = await getUserNetworkingList(params)
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      nwList.value = Array.isArray(inner) ? inner : (inner.data || [])
      nwTotal.value = inner.meta?.count ?? inner.total ?? nwList.value.length
    }
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取组网列表失败')) }
  finally { nwLoading.value = false }
}

const handleNwDetail = async (row) => {
  nwDetailVisible.value = true
  nwDetailData.value = row
  nwDetailNetworks.value = []
  nwDetailLoading.value = true
  try {
    const res = await getUserNetworkingDetail({ service_id: serviceId.value, networking_id: row.id })
    if (res?.data?.code === 200 && res?.data?.data) {
      const inner = res.data.data
      nwDetailData.value = inner.data ?? inner
      nwDetailNetworks.value = inner.networks || []
    }
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '获取详情失败')) }
  finally { nwDetailLoading.value = false }
}

const handleNwCreate = () => {
  Object.assign(nwCreateForm, { user_id: 0, bridge_name: '', gateway: '' })
  nwCreateUserName.value = ''
  nwCreateVisible.value = true
}

const handleNwUserSelected = (user) => {
  nwCreateForm.user_id = user.user_id || user.id
  nwCreateUserName.value = user.user_name || user.name || ''
}

const submitNwCreate = async () => {
  if (!nwCreateForm.user_id) { ElMessage.warning('请选择用户'); return }
  nwSubmitLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('host_id', hostId.value)
    fd.append('user_id', nwCreateForm.user_id)
    if (nwCreateForm.bridge_name) fd.append('bridge_name', nwCreateForm.bridge_name)
    if (nwCreateForm.gateway) fd.append('gateway', nwCreateForm.gateway)
    const res = await createUserNetworking(fd)
    if (res?.data?.code === 200) {
      ElMessage.success('创建成功')
      nwCreateVisible.value = false
      loadNetworkingList()
    } else ElMessage.error(extractApiError(res?.data, '创建失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '创建失败')) }
  finally { nwSubmitLoading.value = false }
}

const handleNwDelete = (row) => {
  ElMessageBox.confirm(`确定删除组网「${row.name || row.id}」？该操作不可撤销。`, '删除确认', { type: 'warning' })
    .then(async () => {
      try {
        const res = await deleteUserNetworking({ service_id: serviceId.value, networking_id: row.id })
        if (res?.data?.code === 200) { ElMessage.success('已删除'); loadNetworkingList() }
        else ElMessage.error(extractApiError(res?.data, '删除失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '删除失败')) }
    }).catch(() => {})
}

const handleNwAssign = (row) => {
  nwAssignTarget.value = row
  nwAssignVmId.value = 0
  nwAssignVmName.value = ''
  nwAssignIp.value = ''
  nwAssignVisible.value = true
}

const handleNwVmSelected = (vm) => {
  nwAssignVmId.value = vm.id
  nwAssignVmName.value = vm.name || ''
}

const submitNwAssign = async () => {
  if (!nwAssignVmId.value || !nwAssignTarget.value) { ElMessage.warning('请选择虚拟机'); return }
  nwSubmitLoading.value = true
  try {
    const fd = new FormData()
    fd.append('service_id', serviceId.value)
    fd.append('networking_id', nwAssignTarget.value.id)
    fd.append('vm_id', nwAssignVmId.value)
    if (nwAssignIp.value.trim()) fd.append('ip', nwAssignIp.value.trim())
    const res = await assignUserNetworking(fd)
    if (res?.data?.code === 200) {
      ElMessage.success('分配成功')
      nwAssignVisible.value = false
      if (nwDetailVisible.value && nwDetailData.value?.id === nwAssignTarget.value.id) {
        handleNwDetail(nwAssignTarget.value)
      }
    } else ElMessage.error(extractApiError(res?.data, '分配失败'))
  } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '分配失败')) }
  finally { nwSubmitLoading.value = false }
}

const handleNwRemoveNet = (netItem) => {
  const net = netItem.network || netItem
  if (!net.id || !nwDetailData.value?.id) { ElMessage.warning('缺少网络信息'); return }
  ElMessageBox.confirm(`确定移除网络 (ID: ${net.id}, VM: ${net.vm_id || '-'}) ？`, '移除确认', { type: 'warning' })
    .then(async () => {
      try {
        const fd = new FormData()
        fd.append('service_id', serviceId.value)
        fd.append('networking_id', nwDetailData.value.id)
        fd.append('network_id', net.id)
        fd.append('vm_id', net.vm_id || 0)
        const res = await removeUserNetworkingNetwork(fd)
        if (res?.data?.code === 200) {
          ElMessage.success('已移除')
          handleNwDetail(nwDetailData.value)
        } else ElMessage.error(extractApiError(res?.data, '移除失败'))
      } catch (e) { ElMessage.error(extractApiError(e?.response?.data, '移除失败')) }
    }).catch(() => {})
}

let loadedHostId = null

const initPage = () => {
  if (!hostId.value || loadedHostId === hostId.value) return
  loadedHostId = hostId.value
  activeTab.value = 'info'
  Object.keys(hostTabLoaded).forEach(k => hostTabLoaded[k] = false)
  detail.value = null
  showToken.value = false
  showPassword.value = false
  showPrivateKey.value = false
  historicalMetricsData.value = null
  disposeCharts()
  loadDetail()
  if (activeTab.value === 'monitor') loadHistoricalMetrics()
}

watch(hostId, () => { if (isPageActive) initPage() })
onActivated(() => {
  isPageActive = true
  if (loadedHostId !== hostId.value) initPage()
})
onMounted(() => { isPageActive = true; initPage() })
onDeactivated(() => { isPageActive = false })
onBeforeUnmount(() => { isPageActive = false; disposeCharts() })
</script>

<style scoped>
.host-detail-page { padding: 0; }
.page-header { display: flex; justify-content: space-between; align-items: center; padding: 12px 24px; background: #fff; border-bottom: 1px solid #e8e8e8; }
.header-left { display: flex; align-items: center; }
.back-btn { font-size: 14px; color: #606266; }
.back-btn:hover { color: #409eff; }
.header-right { display: flex; gap: 8px; }

.main-content { padding: 20px 24px; }

.instance-overview { display: flex; justify-content: space-between; align-items: center; background: #fff; padding: 20px 24px; border-radius: 4px 4px 0 0; border: 1px solid #e8e8e8; border-bottom: none; }
.instance-name { margin: 0; font-size: 18px; font-weight: 600; color: #1d2129; }
.instance-id { font-size: 14px; font-weight: 400; color: #86909c; margin-left: 8px; }
.overview-actions { display: flex; gap: 8px; }

.status-bar { display: flex; background: #fff; padding: 16px 24px; border: 1px solid #e8e8e8; border-top: 1px solid #f0f0f0; border-radius: 0 0 4px 4px; margin-bottom: 16px; }
.status-item { flex: 1; display: flex; flex-direction: column; gap: 4px; }
.status-item + .status-item { border-left: 1px solid #e8e8e8; padding-left: 24px; }
.status-label { font-size: 12px; color: #86909c; }
.status-value { font-size: 14px; color: #1d2129; font-weight: 500; display: flex; align-items: center; gap: 6px; }
.status-dot { width: 8px; height: 8px; border-radius: 50%; display: inline-block; }
.dot-running { background: #00b42a; }
.dot-other { background: #c9cdd4; }

.detail-tabs { background: #fff; border-radius: 4px; border: 1px solid #e8e8e8; padding: 0 24px; }
:deep(.detail-tabs > .el-tabs__header) { margin-bottom: 0; }
:deep(.detail-tabs > .el-tabs__content) { padding: 0 0 24px; }

.section-block { margin-top: 20px; }
.section-title { font-size: 15px; font-weight: 600; color: #1d2129; margin: 0 0 16px; }
.section-header { display: flex; justify-content: space-between; align-items: center; margin-bottom: 16px; }
.section-header .section-title { margin-bottom: 0; }

.config-grid { border: 1px solid #e8e8e8; border-radius: 4px; overflow: hidden; }
.config-row { display: flex; border-bottom: 1px solid #e8e8e8; }
.config-row:last-child { border-bottom: none; }
.config-cell { flex: 1; padding: 12px 16px; border-right: 1px solid #e8e8e8; }
.config-cell:last-child { border-right: none; }
.config-label { display: block; font-size: 12px; color: #86909c; margin-bottom: 4px; }
.config-value { display: block; font-size: 14px; color: #1d2129; word-break: break-all; }
.secret-cell { display: flex; align-items: center; gap: 4px; flex-wrap: wrap; }
.secret-cell code { max-width: 200px; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; }
.mono-text { font-family: 'Consolas', 'Monaco', monospace; }

.metrics-card { margin-bottom: 0; }
.metrics-title { font-weight: 600; font-size: 13px; display: inline-flex; align-items: center; gap: 6px; }
.metrics-title .el-icon { font-size: 16px; color: #409eff; }
.chart-container { width: 100%; height: 220px; }

.metric-summary-row { display: flex; gap: 16px; margin-bottom: 16px; }
.metric-summary-card { flex: 1; min-width: 0; background: #f7f8fa; border-radius: 6px; padding: 14px 16px; border: 1px solid #e8e8e8; display: flex; flex-direction: column; }
.metric-summary-label { font-size: 12px; color: #86909c; margin-bottom: 8px; }
.metric-summary-value { font-size: 22px; font-weight: 600; color: #1d2129; line-height: 1.2; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }
.metric-summary-sub { font-size: 12px; color: #86909c; margin-top: 4px; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; }

.clickable { cursor: pointer; user-select: none; display: flex; align-items: center; gap: 6px; }
.clickable:hover { color: #409eff; }
.section-arrow { transition: transform 0.2s; font-size: 14px; }
.section-arrow.expanded { transform: rotate(90deg); }
.section-hint { font-size: 12px; color: #909399; font-weight: 400; }
.tk-section-title.clickable { cursor: pointer; user-select: none; display: flex; align-items: center; gap: 6px; }
.tk-section-title.clickable:hover { color: #409eff; }
.io-sub-title { font-size: 13px; font-weight: 500; color: #606266; margin: 12px 0 8px; display: flex; align-items: center; }

</style>
