<template>
  <div class="nodes fade-in">
    <div class="page-header">
      <div>
        <h2 class="page-title">节点管理</h2>
        <p class="page-subtitle">注册、监控和管理网络节点</p>
      </div>
      <button class="btn-primary" @click="showRegister = true">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2.5"><line x1="12" y1="5" x2="12" y2="19"/><line x1="5" y1="12" x2="19" y2="12"/></svg>
        注册节点
      </button>
    </div>

    <!-- Search -->
    <div class="search-bar">
      <svg class="search-icon" width="15" height="15" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="1.5"><circle cx="11" cy="11" r="8"/><line x1="21" y1="21" x2="16.65" y2="16.65"/></svg>
      <input v-model="search" placeholder="搜索节点 ID 或名称..." />
      <span v-if="search" class="search-clear" @click="search = ''">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
      </span>
      <span class="search-count">{{ filtered.length }} / {{ nodes.length }}</span>
    </div>

    <!-- Error Banner -->
    <div v-if="errorMsg" class="error-banner">
      <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"/><line x1="15" y1="9" x2="9" y2="15"/><line x1="9" y1="9" x2="15" y2="15"/></svg>
      <span>{{ errorMsg }}</span>
      <button class="error-dismiss" @click="errorMsg = ''">
        <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><line x1="18" y1="6" x2="6" y2="18"/><line x1="6" y1="6" x2="18" y2="18"/></svg>
      </button>
    </div>

    <!-- Loading -->
    <div v-if="loading" class="loading-state">
      <div class="loading-ring"></div>
      <span>加载节点列表...</span>
    </div>

    <!-- Empty -->
    <div v-else-if="filtered.length === 0" class="empty-state">
      <svg width="48" height="48" viewBox="0 0 24 24" fill="none" stroke="var(--text-dim)" stroke-width="1" opacity="0.4"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
      <h3 v-if="search">未找到匹配节点</h3>
      <h3 v-else>暂无注册节点</h3>
      <p>{{ search ? '尝试其他关键词' : '点击右上角注册第一个节点' }}</p>
    </div>

    <!-- Table -->
    <div v-else class="table-container">
      <table>
        <thead>
          <tr>
            <th>节点 ID</th>
            <th>名称</th>
            <th>IP 地址</th>
            <th>公钥</th>
            <th>状态</th>
            <th>最后在线</th>
          </tr>
        </thead>
        <tbody>
          <tr v-for="node in filtered" :key="node.id" class="table-row">
            <td><code class="code-id">{{ node.node_id }}</code></td>
            <td><span class="node-name">{{ node.name || '-' }}</span></td>
            <td><span class="ip-badge">{{ node.ip_address || '-' }}</span></td>
            <td><code class="key-trunc">{{ node.public_key ? node.public_key.slice(0,16)+'...' : '-' }}</code></td>
            <td>
              <span :class="node.online ? 'status-pill online' : 'status-pill offline'">
                <span class="dot"></span>
                {{ node.online ? '在线' : '离线' }}
              </span>
            </td>
            <td class="time-cell">{{ node.last_seen ? formatTime(node.last_seen) : '-' }}</td>
          </tr>
        </tbody>
      </table>
    </div>

    <!-- Register Modal -->
    <div v-if="showRegister" class="modal-overlay" @click.self="showRegister = false">
      <div class="modal" @click.stop>
        <div class="modal-shine"></div>
        <div class="modal-header">
          <h2>
            <svg width="18" height="18" viewBox="0 0 24 24" fill="none" stroke="var(--primary-light)" stroke-width="2"><rect x="2" y="2" width="20" height="8" rx="2" ry="2"/><rect x="2" y="14" width="20" height="8" rx="2" ry="2"/><line x1="6" y1="6" x2="6.01" y2="6"/><line x1="6" y1="18" x2="6.01" y2="18"/></svg>
            注册节点
          </h2>
        </div>
        <div class="modal-body">
          <div class="field">
            <label>节点 ID</label>
            <input v-model="form.nodeId" placeholder="zeromesh-cli 生成的 40-bit 地址" />
          </div>
          <div class="field">
            <label>公钥 (Hex)</label>
            <input v-model="form.publicKey" placeholder="64 位十六进制公钥" />
          </div>
          <div class="field">
            <label>名称</label>
            <input v-model="form.name" placeholder="例如: office-server-01" />
          </div>
          <div class="field-row">
            <div class="field">
              <label>IP 地址</label>
              <input v-model="form.ipAddress" placeholder="可选" />
            </div>
            <div class="field" style="width: 100px;">
              <label>端口</label>
              <input v-model.number="form.port" type="number" placeholder="9993" />
            </div>
          </div>
        </div>
        <div class="modal-footer">
          <button class="btn-ghost" @click="showRegister = false">取消</button>
          <button class="btn-primary" @click="registerNode">注册节点</button>
        </div>
      </div>
    </div>
  </div>
</template>

<script setup>
import { ref, computed, onMounted } from 'vue'
import api from '../api'

const loading = ref(true)
const errorMsg = ref('')
const nodes = ref([])

function showError(msg) {
  errorMsg.value = msg
  setTimeout(() => { if (errorMsg.value === msg) errorMsg.value = '' }, 5000)
}
const search = ref('')
const showRegister = ref(false)
const form = ref({ nodeId: '', publicKey: '', name: '', ipAddress: '', port: 9993 })

const filtered = computed(() => {
  if (!search.value) return nodes.value
  const q = search.value.toLowerCase()
  return nodes.value.filter(n =>
    (n.node_id && n.node_id.toLowerCase().includes(q)) ||
    (n.name && n.name.toLowerCase().includes(q))
  )
})

onMounted(fetchNodes)

async function fetchNodes() {
  loading.value = true
  try {
    const res = await api.nodeList()
    nodes.value = res.data.nodes || []
  } catch { nodes.value = [] }
  finally { loading.value = false }
}

async function registerNode() {
  try {
    await api.nodeRegister({
      node_id: form.value.nodeId,
      public_key: form.value.publicKey,
      name: form.value.name,
      ip_address: form.value.ipAddress,
      port: form.value.port,
      version: '1.0.0',
    })
    showRegister.value = false
    form.value = { nodeId: '', publicKey: '', name: '', ipAddress: '', port: 9993 }
    await fetchNodes()
  } catch (e) { showError(e.response?.data?.error || '注册失败') }
}

function formatTime(ts) {
  const d = new Date(ts)
  const now = new Date()
  const diff = now - d
  if (diff < 60000) return '刚刚'
  if (diff < 3600000) return `${Math.floor(diff/60000)} 分钟前`
  if (diff < 86400000) return `${Math.floor(diff/3600000)} 小时前`
  return d.toLocaleDateString('zh-CN')
}
</script>

<style scoped>
.nodes { animation: fadeIn 0.4s ease-out; }

.page-header { display: flex; align-items: flex-start; justify-content: space-between; margin-bottom: 24px; }
.page-title { font-size: 22px; font-weight: 700; }
.page-subtitle { font-size: 13px; color: var(--text-muted); margin-top: 2px; }

/* Search */
.search-bar {
  display: flex; align-items: center; gap: 8px;
  padding: 8px 14px; border-radius: 12px;
  border: 1px solid var(--border); background: var(--bg-card);
  margin-bottom: 20px; position: relative;
  transition: border-color 0.2s;
}
.search-bar:focus-within { border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.05); }
.search-icon { flex-shrink: 0; color: var(--text-dim); }
.search-bar input { flex: 1; border: none; background: transparent; color: var(--text); font-size: 13px; outline: none; }
.search-bar input::placeholder { color: var(--text-dim); }
.search-clear { cursor: pointer; color: var(--text-dim); padding: 2px; border-radius: 4px; }
.search-clear:hover { color: var(--text); background: var(--bg-input); }
.search-count { font-size: 11px; color: var(--text-dim); font-variant-numeric: tabular-nums; }

.loading-state { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 60px; color: var(--text-muted); }
.loading-ring { width: 28px; height: 28px; border: 2px solid var(--border); border-top-color: var(--primary); border-radius: 50%; animation: spin 0.8s linear infinite; }
@keyframes spin { to { transform: rotate(360deg); } }

.empty-state { display: flex; flex-direction: column; align-items: center; gap: 12px; padding: 60px; text-align: center; }
.empty-state h3 { font-size: 16px; font-weight: 600; margin-top: 8px; }
.empty-state p { font-size: 13px; color: var(--text-muted); }

/* Table */
.table-container { background: var(--bg-card); border-radius: 14px; border: 1px solid var(--border); overflow: hidden; backdrop-filter: blur(12px); }
table { width: 100%; border-collapse: collapse; }
th { padding: 12px 16px; font-size: 11px; text-transform: uppercase; letter-spacing: 0.5px; color: var(--text-dim); font-weight: 600; text-align: left; border-bottom: 1px solid var(--border); background: rgba(0,0,0,0.15); }
td { padding: 12px 16px; border-bottom: 1px solid var(--border); font-size: 13px; }
tr:last-child td { border-bottom: none; }
.table-row { transition: background 0.15s; }
.table-row:hover { background: rgba(99, 102, 241, 0.04); }

.code-id { background: var(--bg-input); padding: 2px 8px; border-radius: 5px; font-size: 12px; color: var(--primary-light); font-family: monospace; }
.node-name { font-weight: 500; }
.ip-badge { font-family: monospace; font-size: 12px; color: var(--text-muted); background: rgba(6, 182, 212, 0.08); padding: 2px 8px; border-radius: 5px; }
.key-trunc { background: var(--bg-input); padding: 2px 6px; border-radius: 4px; font-size: 11px; color: var(--text-dim); max-width: 100px; display: inline-block; overflow: hidden; text-overflow: ellipsis; white-space: nowrap; vertical-align: bottom; }

.status-pill { display: inline-flex; align-items: center; gap: 5px; padding: 3px 10px; border-radius: 20px; font-size: 11px; font-weight: 500; }
.status-pill.online { background: rgba(34, 197, 94, 0.08); color: var(--success); border: 1px solid rgba(34, 197, 94, 0.15); }
.status-pill.offline { background: rgba(100, 116, 139, 0.08); color: var(--text-muted); border: 1px solid rgba(100, 116, 139, 0.15); }
.status-pill .dot { width: 5px; height: 5px; border-radius: 50%; }
.status-pill.online .dot { background: var(--success); box-shadow: 0 0 5px var(--success-glow); }
.status-pill.offline .dot { background: var(--text-dim); }

.time-cell { font-size: 12px; color: var(--text-muted); }

/* Buttons */
.btn-primary {
  display: inline-flex; align-items: center; gap: 6px;
  padding: 9px 18px; border: none; border-radius: 10px;
  background: linear-gradient(135deg, var(--primary), var(--accent));
  color: #fff; font-size: 13px; font-weight: 600; cursor: pointer;
  transition: all 0.25s;
}
.btn-primary:hover { transform: translateY(-1px); box-shadow: 0 4px 16px rgba(99, 102, 241, 0.35); }

.btn-ghost {
  padding: 8px 18px; border-radius: 10px;
  background: transparent; border: 1px solid var(--border);
  color: var(--text-muted); font-size: 13px; cursor: pointer; transition: all 0.15s;
}
.btn-ghost:hover { background: var(--bg-input); color: var(--text); }

/* Modal */
.modal-overlay {
  position: fixed; inset: 0; background: rgba(2, 6, 23, 0.7);
  backdrop-filter: blur(8px);
  display: flex; align-items: center; justify-content: center; z-index: 100;
  animation: fadeIn 0.15s ease-out;
}
.modal {
  background: var(--bg); border-radius: 16px; padding: 0;
  border: 1px solid var(--border); width: 480px; max-width: 90vw;
  box-shadow: 0 24px 80px rgba(0,0,0,0.5);
  overflow: hidden; position: relative;
}
.modal-shine { position: absolute; top: 0; left: 0; right: 0; height: 2px; background: linear-gradient(90deg, transparent, var(--primary-light), var(--accent), transparent); }
.modal-header { padding: 24px 24px 0; }
.modal-header h2 { display: flex; align-items: center; gap: 8px; font-size: 17px; font-weight: 700; }
.modal-body { padding: 20px 24px; }
.modal-footer { display: flex; gap: 8px; justify-content: flex-end; padding: 16px 24px; border-top: 1px solid var(--border); background: rgba(0,0,0,0.15); }

.field { margin-bottom: 16px; }
.field label { display: block; font-size: 12px; color: var(--text-muted); margin-bottom: 6px; font-weight: 500; }
.field input {
  width: 100%; padding: 9px 12px; border-radius: 9px;
  border: 1px solid var(--border); background: var(--bg-input);
  color: var(--text); font-size: 14px; transition: all 0.2s;
}
.field input:focus { outline: none; border-color: var(--primary); box-shadow: 0 0 0 3px rgba(99, 102, 241, 0.1); }
.field-row { display: flex; gap: 12px; }
.field-row .field { flex: 1; }

.error-banner {
  display: flex; align-items: center; gap: 8px;
  padding: 10px 14px; margin-bottom: 16px;
  border-radius: 10px; border: 1px solid rgba(239, 68, 68, 0.2);
  background: rgba(239, 68, 68, 0.06); color: var(--danger);
  font-size: 13px; animation: fadeIn 0.2s ease-out;
}
.error-dismiss { margin-left: auto; background: none; border: none; color: var(--danger); cursor: pointer; padding: 2px; border-radius: 4px; opacity: 0.6; }
.error-dismiss:hover { opacity: 1; }
</style>
