<script setup>
import { ref, computed, onMounted } from 'vue'
import { useMessage } from 'naive-ui'
import {
  NButton, NIcon, NSpin, NTag
} from 'naive-ui'
import {
  AddOutline, RefreshOutline, GitNetworkOutline, CreateOutline,
  TrashOutline, FlashOutline, AlertCircleOutline, PlayOutline,
  ResizeOutline, GridOutline
} from '@vicons/ionicons5'
import GraphSourceForm from '../../components/knowledge/GraphSourceForm.vue'
import GraphViewer from '../../components/knowledge/GraphViewer.vue'
import NodeDetailPanel from '../../components/knowledge/NodeDetailPanel.vue'
import CodeEditor from '../../components/skill/CodeEditor.vue'
import {
  getGraphSources, deleteGraphSource, testExistingGraphSource,
  executeGraphQuery
} from '../../api/graphsource'

const message = useMessage()

// ============ 图谱源列表 ============
const loading = ref(false)
const graphSources = ref([])
const selectedId = ref(null)
const showForm = ref(false)
const editing = ref(null)

// ============ Cypher 查询 ============
const DEFAULT_CYPHER = 'MATCH (n)-[r]->(m)\nRETURN n, r, m\nLIMIT 100'
const cypher = ref(DEFAULT_CYPHER)
const querying = ref(false)
const graphData = ref(null)
const errorMsg = ref('')
const selectedElement = ref(null)
const viewerRef = ref(null)

const selectedGs = computed(() =>
  graphSources.value.find(g => g.id === selectedId.value) || null
)

// ============ 加载 ============
async function loadGraphSources() {
  loading.value = true
  try {
    const res = await getGraphSources()
    const list = res.data || []
    graphSources.value = list
    if (!selectedId.value && list.length) {
      selectedId.value = list[0].id
    }
  } catch (e) {
    message.error('加载图谱源失败：' + e.message)
  } finally {
    loading.value = false
  }
}

// ============ 操作 ============
function onAdd() {
  editing.value = null
  showForm.value = true
}
function onEdit(gs) {
  editing.value = gs
  showForm.value = true
}
async function onDelete(gs) {
  if (!confirm(`确定删除图谱源「${gs.name}」？关联的 driver 缓存将被清理。`)) return
  try {
    await deleteGraphSource(gs.id)
    if (selectedId.value === gs.id) {
      selectedId.value = graphSources.value.length > 1
        ? graphSources.value.find(g => g.id !== gs.id)?.id || null
        : null
    }
    await loadGraphSources()
    message.success('已删除')
  } catch (e) { /* 全局拦截器已处理 */ }
}
async function onTestExisting(gs) {
  try {
    const res = await testExistingGraphSource(gs.id)
    if (res.data?.success) message.success('连接成功')
    else message.error('连接失败：' + (res.data?.message || '未知错误'))
    await loadGraphSources()
  } catch (e) { /* 全局拦截器已处理 */ }
}
function onSaved() {
  loadGraphSources()
}
function selectGs(id) {
  selectedId.value = id
  // 切换图谱时清空旧数据
  graphData.value = null
  errorMsg.value = ''
  selectedElement.value = null
}

// ============ Cypher 查询 ============
async function runQuery() {
  if (!selectedId.value) {
    message.warning('请先选择图谱源')
    return
  }
  const c = cypher.value?.trim()
  if (!c) {
    message.warning('请输入 Cypher')
    return
  }
  querying.value = true
  errorMsg.value = ''
  graphData.value = null
  try {
    const res = await executeGraphQuery(selectedId.value, c)
    graphData.value = res.data
    if (res.data?.truncated) {
      message.warning('结果超过上限，已截断显示')
    } else if (res.data?.nodes?.length === 0) {
      message.info('查询返回 0 个节点')
    }
  } catch (e) {
    errorMsg.value = e.message || '查询失败'
  } finally {
    querying.value = false
  }
}

function onViewerSelect(el) {
  selectedElement.value = el
}
function fitView() {
  viewerRef.value?.fitView?.()
}
function relayout() {
  viewerRef.value?.relayout?.()
}

onMounted(() => {
  loadGraphSources()
})
</script>

<template>
  <div class="kg-page">

    <div class="kg-layout">
      <!-- 左侧：图谱源列表 -->
      <aside class="kg-sidebar">
        <div class="sidebar-header">
          <span class="sidebar-title">图谱源</span>
          <div class="sidebar-actions">
            <button class="icon-btn-sm" @click="onAdd" title="新增图谱源">
              <n-icon size="14">
                <AddOutline />
              </n-icon>
            </button>
            <button class="icon-btn-sm" @click="loadGraphSources" title="刷新">
              <n-icon size="14">
                <RefreshOutline />
              </n-icon>
            </button>
          </div>
        </div>

        <div class="sidebar-body">
          <div v-if="loading && !graphSources.length" class="loading">
            <n-spin size="small" />
          </div>
          <div v-else-if="!graphSources.length" class="empty-block">
            <n-icon size="32" color="var(--text-muted)">
              <GitNetworkOutline />
            </n-icon>
            <div class="empty-title">暂无图谱源</div>
            <n-button size="small" type="primary" @click="onAdd">
              <template #icon><n-icon>
                  <AddOutline />
                </n-icon></template>
              新增图谱源
            </n-button>
          </div>
          <div v-else class="gs-list">
            <div v-for="gs in graphSources" :key="gs.id" class="gs-card" :class="{ active: selectedId === gs.id }"
              @click="selectGs(gs.id)">
              <div class="gs-card-header">
                <n-icon size="14" color="var(--color-accent)">
                  <GitNetworkOutline />
                </n-icon>
                <span class="gs-name" :title="gs.name">{{ gs.name }}</span>
              </div>
              <div class="gs-uri" :title="gs.uri">{{ gs.uri }}</div>
              <div class="gs-status">
                <span v-if="gs.testStatus === 'SUCCESS'" class="status-ok">
                  <n-icon size="10">
                    <FlashOutline />
                  </n-icon> 已连通
                </span>
                <span v-else-if="gs.testStatus === 'FAILED'" class="status-fail">
                  <n-icon size="10">
                    <AlertCircleOutline />
                  </n-icon> 连接失败
                </span>
                <span v-else class="status-unknown">未测试</span>
                <span class="gs-user">{{ gs.username }}@{{ gs.database || 'default' }}</span>
              </div>
              <div class="gs-card-footer">
                <button class="action-btn" title="测试连接" @click.stop="onTestExisting(gs)">
                  <n-icon size="12">
                    <FlashOutline />
                  </n-icon>
                </button>
                <button class="action-btn" title="编辑" @click.stop="onEdit(gs)">
                  <n-icon size="12">
                    <CreateOutline />
                  </n-icon>
                </button>
                <button class="action-btn danger" title="删除" @click.stop="onDelete(gs)">
                  <n-icon size="12">
                    <TrashOutline />
                  </n-icon>
                </button>
              </div>
            </div>
          </div>
        </div>
      </aside>

      <!-- 右侧：图谱查看 -->
      <main class="kg-main">
        <div v-if="!selectedGs" class="main-empty">
          <n-icon size="48" color="var(--text-muted)">
            <GitNetworkOutline />
          </n-icon>
          <div class="empty-title">请选择左侧图谱源</div>
          <div class="empty-sub">选择图谱源后可执行 Cypher 查询并可视化结果</div>
        </div>
        <div v-else class="main-content">
          <!-- 工具栏 -->
          <div class="query-toolbar">
            <n-button size="small" type="primary" :loading="querying" @click="runQuery">
              <template #icon><n-icon>
                  <PlayOutline />
                </n-icon></template>
              查询
            </n-button>
            <div class="toolbar-hint">
              <span v-if="graphData">节点 {{ graphData.nodes.length }} · 边 {{ graphData.edges.length }} · {{
              graphData.durationMs }} ms</span>
              <span v-else>仅允许 MATCH/RETURN 等只读语句</span>
            </div>
            <div class="toolbar-right">
              <n-button size="tiny" quaternary @click="relayout" :disabled="!graphData">
                <template #icon><n-icon>
                    <GridOutline />
                  </n-icon></template>
                重新布局
              </n-button>
              <n-button size="tiny" quaternary @click="fitView" :disabled="!graphData">
                <template #icon><n-icon>
                    <ResizeOutline />
                  </n-icon></template>
                适配视图
              </n-button>
            </div>
          </div>

          <!-- Cypher 编辑器 -->
          <div class="cypher-editor">
            <CodeEditor v-model="cypher" language="cypher" :height="'120px'" :minimap="false" />
          </div>

          <!-- 错误提示 -->
          <div class="error-banner" v-if="errorMsg">
            <n-icon size="14" color="#ef4444">
              <AlertCircleOutline />
            </n-icon>
            <span>{{ errorMsg }}</span>
          </div>

          <!-- 图谱视图 + 详情面板 -->
          <div class="graph-area">
            <div class="viewer-wrap">
              <GraphViewer ref="viewerRef" :graph-data="graphData" :loading="querying" @select="onViewerSelect" />
            </div>
            <aside class="detail-wrap">
              <NodeDetailPanel :selected="selectedElement" />
            </aside>
          </div>
        </div>
      </main>
    </div>

    <GraphSourceForm v-model:show="showForm" :editing="editing" @saved="onSaved" />
  </div>
</template>

<style scoped>
.kg-page {
  display: flex;
  flex-direction: column;
  height: 100%;
  min-height: 0;
}

.driver-banner {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  background: rgba(245, 158, 11, 0.08);
  border-bottom: 1px solid rgba(245, 158, 11, 0.2);
  color: #f59e0b;
  font-size: 12px;
}

.banner-text {
  line-height: 1.5;
}

.banner-text code {
  font-family: 'JetBrains Mono', monospace;
  background: rgba(0, 0, 0, 0.2);
  padding: 1px 4px;
  border-radius: 3px;
}

.kg-layout {
  display: grid;
  grid-template-columns: 280px 1fr;
  flex: 1;
  min-height: 0;
}

/* 左侧 */
.kg-sidebar {
  display: flex;
  flex-direction: column;
  border-right: 1px solid var(--border-color);
  background: var(--bg-secondary);
  min-height: 0;
}

.sidebar-header {
  display: flex;
  align-items: center;
  justify-content: space-between;
  padding: 12px 14px;
  border-bottom: 1px solid var(--border-color);
}

.sidebar-title {
  font-size: 13px;
  font-weight: 600;
  color: var(--text-secondary);
}

.sidebar-actions {
  display: flex;
  gap: 4px;
}

.icon-btn-sm {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  display: flex;
  align-items: center;
}

.icon-btn-sm:hover {
  color: var(--color-accent);
  background: rgba(37, 99, 235, 0.1);
}

.sidebar-body {
  flex: 1;
  overflow-y: auto;
  padding: 8px;
}

.loading,
.empty-block {
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  padding: 40px 16px;
  gap: 12px;
  color: var(--text-muted);
  font-size: 12px;
}

.empty-title {
  font-size: 14px;
  font-weight: 500;
  color: var(--text-secondary);
}

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

.gs-list {
  display: flex;
  flex-direction: column;
  gap: 6px;
}

.gs-card {
  padding: 10px 12px;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: 6px;
  cursor: pointer;
  transition: all 0.15s;
}

.gs-card:hover {
  border-color: var(--color-accent);
}

.gs-card.active {
  border-color: var(--color-accent);
  background: rgba(37, 99, 235, 0.08);
}

.gs-card-header {
  display: flex;
  align-items: center;
  gap: 6px;
}

.gs-name {
  flex: 1;
  font-size: 13px;
  font-weight: 500;
  color: var(--text-primary);
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gs-uri {
  margin-top: 4px;
  font-size: 10px;
  color: var(--text-muted);
  font-family: 'JetBrains Mono', monospace;
  white-space: nowrap;
  overflow: hidden;
  text-overflow: ellipsis;
}

.gs-status {
  margin-top: 4px;
  display: flex;
  align-items: center;
  justify-content: space-between;
  font-size: 10px;
}

.status-ok {
  color: #10b981;
  display: flex;
  align-items: center;
  gap: 3px;
}

.status-fail {
  color: #ef4444;
  display: flex;
  align-items: center;
  gap: 3px;
}

.status-unknown {
  color: var(--text-muted);
}

.gs-user {
  color: var(--text-muted);
  font-family: 'JetBrains Mono', monospace;
}

.gs-card-footer {
  display: flex;
  gap: 4px;
  margin-top: 8px;
  justify-content: flex-end;
  border-top: 1px dashed var(--border-color);
  padding-top: 6px;
}

.action-btn {
  background: none;
  border: none;
  color: var(--text-muted);
  cursor: pointer;
  padding: 4px;
  border-radius: 4px;
  display: flex;
  align-items: center;
}

.action-btn:hover {
  color: var(--color-accent);
  background: rgba(37, 99, 235, 0.1);
}

.action-btn.danger:hover {
  color: #ef4444;
  background: rgba(239, 68, 68, 0.1);
}

/* 右侧 */
.kg-main {
  display: flex;
  flex-direction: column;
  min-height: 0;
  background: var(--bg-primary);
}

.main-empty {
  flex: 1;
  display: flex;
  flex-direction: column;
  align-items: center;
  justify-content: center;
  gap: 12px;
  color: var(--text-muted);
}

.main-content {
  flex: 1;
  display: flex;
  flex-direction: column;
  min-height: 0;
  padding: 8px;
  gap: 8px;
}

.query-toolbar {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 6px 10px;
  background: var(--bg-card);
  border: 1px solid var(--border-color);
  border-radius: 6px;
}

.toolbar-hint {
  flex: 1;
  font-size: 11px;
  color: var(--text-muted);
  font-family: 'JetBrains Mono', monospace;
}

.toolbar-right {
  display: flex;
  gap: 4px;
}

.cypher-editor {
  background: #1e1e1e;
  border: 1px solid var(--border-color);
  border-radius: 6px;
  overflow: hidden;
}

.error-banner {
  display: flex;
  align-items: center;
  gap: 6px;
  padding: 8px 12px;
  background: rgba(239, 68, 68, 0.08);
  border: 1px solid rgba(239, 68, 68, 0.2);
  border-radius: 6px;
  color: #ef4444;
  font-size: 12px;
  font-family: 'JetBrains Mono', monospace;
}

.graph-area {
  flex: 1;
  min-height: 0;
  display: grid;
  grid-template-columns: 1fr 280px;
  gap: 8px;
}

.viewer-wrap,
.detail-wrap {
  min-height: 0;
  border-radius: 6px;
  overflow: hidden;
  border: 1px solid var(--border-color);
}
</style>
