<template>
	<div class="wiki-manage">
		<div class="wiki-layout" :class="{ 'wiki-layout--graph-only': isGraphMode }">
			<aside v-if="showWikiDetails" class="wiki-sidebar">
				<div class="wiki-sidebar__header">
					<div class="wiki-sidebar__heading">
						<div class="wiki-sidebar__title">页面列表</div>
						<div class="wiki-sidebar__subtitle">按页面筛选并快速定位图谱节点</div>
					</div>
				</div>

				<div class="wiki-sidebar__body">
					<div class="wiki-sidebar__toolbar">
						<el-input
							v-model="filters.keyword"
							clearable
							placeholder="搜索 Wiki 页面"
							@keyup.enter="refreshPages"
							@clear="refreshPages"
						>
							<template #append>
								<el-button @click="refreshPages">
									<el-icon>
										<Search />
									</el-icon>
								</el-button>
							</template>
						</el-input>
						<el-select
							v-model="filters.page_type"
							clearable
							placeholder="页面类型"
							@change="refreshPages"
						>
							<el-option label="全部类型" value="" />
							<el-option label="摘要" value="summary" />
							<el-option label="实体" value="entity" />
							<el-option label="概念" value="concept" />
							<el-option label="索引" value="index" />
							<el-option label="日志" value="log" />
						</el-select>
						<el-select
							v-model="filters.status"
							clearable
							placeholder="页面状态"
							@change="refreshPages"
						>
							<el-option label="全部状态" value="" />
							<el-option label="激活" value="active" />
							<el-option label="草稿" value="draft" />
							<el-option label="归档" value="archived" />
						</el-select>
					</div>

					<el-scrollbar class="wiki-sidebar__scroll">
						<div class="wiki-sidebar__list" v-loading="pageListLoading">
							<el-menu
								class="wiki-page-menu"
								:default-active="activePageSlug"
								@select="handleSelectPage"
							>
								<el-menu-item
									v-for="item in pageList"
									:key="item.id"
									:index="item.slug"
									class="wiki-page-menu__item"
								>
									<div class="wiki-menu-item">
										<div class="wiki-menu-item__main">
											<span class="wiki-menu-item__title" :title="item.title">{{
												item.title
											}}</span>
											<span class="wiki-menu-item__slug" :title="item.slug">{{ item.slug }}</span>
										</div>
										<div class="wiki-menu-item__extra">
											<el-tag size="small" :type="getPageTypeTagType(item.page_type)">
												{{ getPageTypeLabel(item.page_type) }}
											</el-tag>
											<span class="wiki-menu-item__version">{{
												item.version ? `v${item.version}` : '-'
											}}</span>
										</div>
									</div>
								</el-menu-item>
							</el-menu>
							<el-empty
								v-if="!pageListLoading && !pageList.length"
								description="暂无 Wiki 页面"
								class="page-empty page-empty--compact"
							/>
						</div>
					</el-scrollbar>

					<div class="wiki-sidebar__footer">
						<el-pagination
							v-if="pagination.total > 0"
							v-model:current-page="pagination.page"
							v-model:page-size="pagination.pageSize"
							small
							background
							layout="total, prev, pager, next"
							:total="pagination.total"
							:page-sizes="[20, 50, 100]"
							@current-change="loadPages"
							@size-change="handlePageSizeChange"
						/>
					</div>
				</div>
			</aside>

			<el-scrollbar class="wiki-main-scroll">
				<div v-if="showGraphSection" class="graph-card">
					<div class="graph-card__header">
						<div>
							<div class="graph-card__title">知识图谱</div>
							<div class="graph-card__desc">
								基于 Wiki 页面与关系边构建的图谱视图，可拖拽、缩放。
							</div>
							<div class="graph-card__summary">
								<div v-for="item in graphSummary" :key="item.label" class="graph-stat">
									<span class="graph-stat__label">{{ item.label }}</span>
									<span class="graph-stat__value">{{ item.value }}</span>
								</div>
							</div>
						</div>
						<div class="graph-card__legend">
							<div v-for="item in legendItems" :key="item.type" class="legend-item">
								<span class="legend-item__dot" :style="{ backgroundColor: item.color }"></span>
								<span>{{ item.label }}</span>
							</div>
						</div>
					</div>
					<div ref="graphContainerRef" class="graph-canvas" v-loading="graphLoading"></div>
				</div>

				<div v-if="showWikiDetails" class="detail-card" v-loading="detailLoading">
					<div class="detail-card__header">
						<div class="detail-card__title">页面信息</div>
						<div class="detail-card__actions">
							<el-button size="small" text @click="loadIndexPage">首页</el-button>
							<el-button size="small" text @click="openLogDialog">日志</el-button>
						</div>
					</div>

					<template v-if="selectedPage">
						<div class="page-summary">
							<div class="page-summary__row">
								<span class="page-summary__label">标题</span>
								<span class="page-summary__value">{{ selectedPage.title }}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">Slug</span>
								<span class="page-summary__value">{{ selectedPage.slug }}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">类型</span>
								<span class="page-summary__value">{{
									getPageTypeLabel(selectedPage.page_type)
								}}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">状态</span>
								<span class="page-summary__value">{{ selectedPage.status || '-' }}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">别名</span>
								<span class="page-summary__value">{{ formatList(selectedPage.aliases) }}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">入链</span>
								<span class="page-summary__value">{{ formatList(selectedPage.in_links) }}</span>
							</div>
							<div class="page-summary__row">
								<span class="page-summary__label">出链</span>
								<span class="page-summary__value">{{ formatList(selectedPage.out_links) }}</span>
							</div>
						</div>

						<div class="content-section">
							<div class="content-section__title">摘要</div>
							<div class="content-section__body">
								<XMarkdown :markdown="selectedPage.summary || '暂无摘要'" />
							</div>
						</div>

						<div class="content-section">
							<div class="content-section__title">正文</div>
							<div class="content-section__body content-section__body--code">
								<XMarkdown :markdown="selectedPage.content || '暂无内容'" />
							</div>
						</div>

						<div class="content-section">
							<div class="content-section__title">元数据</div>
							<div v-if="selectedPageMetadataEntries.length" class="metadata-grid">
								<div
									v-for="item in selectedPageMetadataEntries"
									:key="item.key"
									class="metadata-grid__item"
								>
									<div class="metadata-grid__label">{{ item.key }}</div>
									<div class="metadata-grid__value">{{ item.value }}</div>
								</div>
							</div>
							<el-empty v-else description="暂无元数据" class="page-empty page-empty--compact" />
						</div>
					</template>
					<el-empty v-else description="请选择 Wiki 页面" class="page-empty" />
				</div>
			</el-scrollbar>
		</div>

		<el-dialog
			v-if="showWikiDetails"
			v-model="logDialogVisible"
			title="日志信息"
			width="760px"
			destroy-on-close
			class="wiki-log-dialog"
		>
			<el-scrollbar class="wiki-log-dialog__scroll" v-loading="logLoading">
				<div v-if="logPage" class="log-summary">
					<div class="page-summary page-summary--compact">
						<div class="page-summary__row">
							<span class="page-summary__label">日志标题</span>
							<span class="page-summary__value">{{ logPage.title }}</span>
						</div>
						<div class="page-summary__row">
							<span class="page-summary__label">Slug</span>
							<span class="page-summary__value">{{ logPage.slug }}</span>
						</div>
						<div class="page-summary__row">
							<span class="page-summary__label">版本</span>
							<span class="page-summary__value">v{{ logPage.version }}</span>
						</div>
					</div>

					<div class="content-section">
						<div class="content-section__title">日志摘要</div>
						<div class="content-section__body">
							<XMarkdown :markdown="logPage.summary || '暂无日志摘要'" />
						</div>
					</div>

					<div class="content-section">
						<div class="content-section__title">日志正文</div>
						<div class="content-section__body content-section__body--code">
							<XMarkdown :markdown="logPage.content || '暂无日志内容'" />
						</div>
					</div>
				</div>
				<el-empty v-else description="暂无日志信息" class="page-empty page-empty--compact" />
			</el-scrollbar>
		</el-dialog>
	</div>
</template>

<script setup lang="ts">
import { computed, nextTick, onBeforeUnmount, onMounted, ref, watch } from 'vue'
import { Search } from '@element-plus/icons-vue'
import { XMarkdown } from 'vue-element-plus-x'
import * as d3 from 'd3'
import { wiki } from '@repo/api-service'
import type { KnowledgeBaseItem } from './types'

type WikiPageType = 'summary' | 'entity' | 'concept' | 'index' | 'log' | string

interface WikiPageItem {
	id: string
	title: string
	slug: string
	page_type: WikiPageType
	status: string
	summary: string
	content: string
	version: number
	aliases: string[]
	in_links: string[]
	out_links: string[]
	page_metadata: Record<string, any>
}

interface WikiGraphNode {
	id: string
	title: string
	slug: string
	page_type: WikiPageType
	linkCount: number
}

interface WikiGraphEdge {
	id: string
	source: string
	target: string
	weight: number
}

interface WikiStats {
	is_active: boolean
	orphan_count: number
	pages_by_type: {
		concept: number
		entity: number
		index: number
		log: number
		summary: number
	}
	pending_issues: number
	pending_tasks: number
	recent_updates: Array<{
		id: string
		slug: string
		title: string
		page_type: WikiPageType
		version: number
	}>
	total_links: number
	total_pages: number
}

type ForceNode = d3.SimulationNodeDatum & WikiGraphNode
type ForceLink = d3.SimulationLinkDatum<ForceNode> & WikiGraphEdge

const props = withDefaults(
	defineProps<{
		currentBase: KnowledgeBaseItem
		mode?: 'wiki' | 'graph'
	}>(),
	{
		mode: 'wiki'
	}
)

const isGraphMode = computed(() => props.mode === 'graph')
const showWikiDetails = computed(() => !isGraphMode.value)
const showGraphSection = computed(() => isGraphMode.value)

const graphContainerRef = ref<HTMLDivElement>()
const pageListLoading = ref(false)
const graphLoading = ref(false)
const detailLoading = ref(false)
const logLoading = ref(false)
const logDialogVisible = ref(false)

const filters = ref({
	keyword: '',
	page_type: '',
	status: ''
})

const pagination = ref({
	page: 1,
	pageSize: 20,
	total: 0
})

const pageList = ref<WikiPageItem[]>([])
const selectedPage = ref<WikiPageItem | null>(null)
const logPage = ref<WikiPageItem | null>(null)
const stats = ref<WikiStats>({
	is_active: false,
	orphan_count: 0,
	pages_by_type: {
		concept: 0,
		entity: 0,
		index: 0,
		log: 0,
		summary: 0
	},
	pending_issues: 0,
	pending_tasks: 0,
	recent_updates: [],
	total_links: 0,
	total_pages: 0
})

const graphData = ref<{ nodes: WikiGraphNode[]; edges: WikiGraphEdge[] }>({
	nodes: [],
	edges: []
})

let simulation: d3.Simulation<ForceNode, ForceLink> | null = null

const legendItems = computed(() => [
	{ type: 'summary', label: '摘要', color: '#1d6ff2' },
	{ type: 'entity', label: '实体', color: '#2cb67d' },
	{ type: 'concept', label: '概念', color: '#f08c24' },
	{ type: 'index', label: '综合', color: '#3fa7ff' },
	{ type: 'log', label: '日志', color: '#eb5757' }
])

const selectedPageMetadataEntries = computed(() =>
	Object.entries(selectedPage.value?.page_metadata || {}).map(([key, value]) => ({
		key,
		value: typeof value === 'string' ? value : JSON.stringify(value)
	}))
)

const activePageSlug = computed(() => selectedPage.value?.slug || '')

const graphSummary = computed(() => {
	const duplicateCount =
		stats.value.total_links > graphData.value.edges.length
			? stats.value.total_links - graphData.value.edges.length
			: 0

	return [
		{ label: '节点数', value: graphData.value.nodes.length },
		{ label: '关系数', value: graphData.value.edges.length },
		{ label: '合并重复边', value: duplicateCount }
	]
})

function getNodeColor(type: WikiPageType) {
	switch (type) {
		case 'summary':
			return '#1d6ff2'
		case 'entity':
			return '#2cb67d'
		case 'concept':
			return '#f08c24'
		case 'index':
			return '#3fa7ff'
		case 'log':
			return '#eb5757'
		default:
			return '#9aa4b2'
	}
}

function getPageTypeLabel(type: WikiPageType) {
	switch (type) {
		case 'summary':
			return '摘要'
		case 'entity':
			return '实体'
		case 'concept':
			return '概念'
		case 'index':
			return '综合'
		case 'log':
			return '日志'
		default:
			return type || '未知'
	}
}

function getPageTypeTagType(type: WikiPageType) {
	switch (type) {
		case 'summary':
			return 'primary'
		case 'entity':
			return 'success'
		case 'concept':
			return 'warning'
		case 'index':
			return 'info'
		case 'log':
			return 'danger'
		default:
			return 'info'
	}
}

function truncateLabel(label: string, max = 14) {
	return label.length > max ? `${label.slice(0, max)}...` : label
}

function formatList(list?: string[]) {
	return list?.length ? list.join(' / ') : '-'
}

async function loadStats() {
	if (!props.currentBase.id) return
	const res = await wiki.postWikiStats({ knowledge_base_id: props.currentBase.id })
	if (res?.isSuccess) {
		stats.value = res.result as WikiStats
	}
}

async function loadGraph() {
	if (!props.currentBase.id) return
	graphLoading.value = true
	try {
		const res = await wiki.postWikiGraph({ knowledge_base_id: props.currentBase.id })
		if (res?.isSuccess) {
			const nodes = res.result?.nodes || []
			const nodeSlugSet = new Set(nodes.map((item) => item.slug).filter(Boolean))
			const edgeMap = new Map<string, WikiGraphEdge>()

			for (const item of res.result?.edges || []) {
				if (!item.source || !item.target) continue
				if (item.source === item.target) continue
				if (!nodeSlugSet.has(item.source) || !nodeSlugSet.has(item.target)) continue

				const edgeKey = `${item.source}__${item.target}`
				const current = edgeMap.get(edgeKey)

				if (current) {
					current.weight += 1
					continue
				}

				edgeMap.set(edgeKey, {
					id: item.id,
					source: item.source,
					target: item.target,
					weight: 1
				})
			}

			graphData.value = {
				nodes: nodes.map((item) => ({
					id: item.slug,
					title: item.title,
					slug: item.slug,
					page_type: item.page_type,
					linkCount: item.linkCount
				})),
				edges: Array.from(edgeMap.values())
			}
			await nextTick()
			renderGraph()
		}
	} finally {
		graphLoading.value = false
	}
}

async function loadPages() {
	if (!props.currentBase.id) return
	pageListLoading.value = true
	try {
		const res = await wiki.postWikiPageList({
			knowledge_base_id: props.currentBase.id,
			pageIndex: pagination.value.page,
			pageSize: pagination.value.pageSize,
			keyword: filters.value.keyword,
			page_type: filters.value.page_type,
			slug: '',
			status: filters.value.status
		})
		if (res?.isSuccess) {
			pageList.value = (res.result?.model || []) as WikiPageItem[]
			pagination.value.total = res.result?.totalCount || 0
			const hasSelected = pageList.value.some((item) => item.slug === selectedPage.value?.slug)
			if (!hasSelected) {
				selectedPage.value = null
			}
			if (!selectedPage.value && pageList.value[0]) {
				await handleSelectPage(pageList.value[0].slug)
			} else if (!pageList.value.length) {
				highlightSelectedNode()
			}
		}
	} finally {
		pageListLoading.value = false
	}
}

async function refreshPages() {
	pagination.value.page = 1
	await loadPages()
}

async function handlePageSizeChange() {
	pagination.value.page = 1
	await loadPages()
}

async function handleSelectPage(slug: string) {
	if (!props.currentBase.id || !slug) return
	detailLoading.value = true
	try {
		const res = await wiki.postWikiPage({
			knowledge_base_id: props.currentBase.id,
			slug
		})
		if (res?.isSuccess) {
			selectedPage.value = res.result as WikiPageItem
			highlightSelectedNode(slug)
		}
	} finally {
		detailLoading.value = false
	}
}

async function loadIndexPage() {
	if (!props.currentBase.id) return
	detailLoading.value = true
	try {
		const res = await wiki.postWikiIndex({ knowledge_base_id: props.currentBase.id })
		if (res?.isSuccess) {
			selectedPage.value = res.result as WikiPageItem
			highlightSelectedNode(selectedPage.value.slug)
		}
	} finally {
		detailLoading.value = false
	}
}

async function loadLogPage() {
	if (!props.currentBase.id) return
	logLoading.value = true
	try {
		const res = await wiki.postWikiLog({ knowledge_base_id: props.currentBase.id })
		if (res?.isSuccess) {
			logPage.value = res.result as WikiPageItem
		}
	} catch {
		logPage.value = null
	} finally {
		logLoading.value = false
	}
}

async function openLogDialog() {
	logDialogVisible.value = true
	await loadLogPage()
}

function highlightSelectedNode(slug?: string) {
	const root = graphContainerRef.value
	if (!root) return
	const relationSet = new Set<string>()

	if (slug) {
		for (const edge of graphData.value.edges) {
			if (edge.source === slug) relationSet.add(edge.target)
			if (edge.target === slug) relationSet.add(edge.source)
		}
	}

	root.querySelectorAll<SVGCircleElement>('.wiki-node-circle').forEach((node) => {
		const isActive = node.dataset.slug === slug
		const isRelated = relationSet.has(node.dataset.slug || '')

		node.setAttribute(
			'stroke',
			isActive ? '#ffffff' : isRelated ? 'rgba(255,255,255,0.92)' : 'rgba(255,255,255,0.78)'
		)
		node.setAttribute('stroke-width', isActive ? '3.5' : isRelated ? '2.2' : '1.5')
		node.setAttribute('opacity', slug && !isActive && !isRelated ? '0.35' : '1')
	})

	root.querySelectorAll<SVGLineElement>('.wiki-link-line').forEach((line) => {
		const source = line.dataset.source || ''
		const target = line.dataset.target || ''
		const isActive = !!slug && (source === slug || target === slug)

		line.setAttribute('stroke', isActive ? 'rgba(255,255,255,0.68)' : 'rgba(255,255,255,0.22)')
		line.setAttribute('opacity', slug && !isActive ? '0.18' : '1')
	})

	root.querySelectorAll<SVGTextElement>('.wiki-node-label').forEach((label) => {
		const isActive = label.dataset.slug === slug
		const isRelated = relationSet.has(label.dataset.slug || '')

		label.setAttribute('opacity', slug && !isActive && !isRelated ? '0.38' : '1')
		label.setAttribute('font-weight', isActive ? '700' : isRelated ? '600' : '500')
	})
}

function renderGraph() {
	const container = graphContainerRef.value
	if (!container) return

	simulation?.stop()
	container.innerHTML = ''

	const width = Math.max(container.clientWidth, 720)
	const height = Math.max(container.clientHeight, 460)

	const nodes: ForceNode[] = graphData.value.nodes.map((item) => ({ ...item }))
	const links: ForceLink[] = graphData.value.edges.map((item) => ({ ...item }))

	const svg = d3
		.select(container)
		.append('svg')
		.attr('width', width)
		.attr('height', height)
		.attr('viewBox', `0 0 ${width} ${height}`)

	const defs = svg.append('defs')

	defs
		.append('marker')
		.attr('id', 'wiki-arrow')
		.attr('viewBox', '0 -5 10 10')
		.attr('refX', 15)
		.attr('refY', 0)
		.attr('markerWidth', 4.5)
		.attr('markerHeight', 4.5)
		.attr('orient', 'auto')
		.append('path')
		.attr('d', 'M0,-5L10,0L0,5')
		.attr('fill', 'rgba(255,255,255,0.65)')

	const root = svg.append('g')

	svg.call(
		d3
			.zoom<SVGSVGElement, unknown>()
			.scaleExtent([0.4, 2.5])
			.on('zoom', (event) => {
				root.attr('transform', event.transform)
			})
	)

	const link = root
		.append('g')
		.selectAll('line')
		.data(links)
		.enter()
		.append('line')
		.attr('class', 'wiki-link-line')
		.attr('data-source', (d) => d.source)
		.attr('data-target', (d) => d.target)
		.attr('stroke', 'rgba(255,255,255,0.22)')
		.attr('stroke-width', (d) => 0.75 + Math.min(1.8, d.weight * 0.24))
		.attr('marker-end', 'url(#wiki-arrow)')

	const nodeGroup = root
		.append('g')
		.selectAll('g')
		.data(nodes)
		.enter()
		.append('g')
		.style('cursor', 'pointer')
		.on('click', (_event, datum) => {
			void handleSelectPage(datum.slug)
		})

	nodeGroup
		.append('circle')
		.attr('class', 'wiki-node-circle')
		.attr('data-slug', (d) => d.slug)
		.attr('r', (d) => Math.max(8, Math.min(18, 8 + d.linkCount * 0.8)))
		.attr('fill', (d) => getNodeColor(d.page_type))
		.attr('stroke', 'rgba(255,255,255,0.9)')
		.attr('stroke-width', 1.5)

	nodeGroup
		.append('text')
		.attr('class', 'wiki-node-label')
		.attr('data-slug', (d) => d.slug)
		.text((d) => truncateLabel(d.title))
		.attr('fill', '#e5e7eb')
		.attr('font-size', 11)
		.attr('text-anchor', 'middle')
		.attr('font-weight', 500)
		.attr('dy', 24)

	simulation = d3
		.forceSimulation(nodes)
		.force(
			'link',
			d3
				.forceLink<ForceNode, ForceLink>(links)
				.id((d) => d.id)
				.distance((d) => {
					const sourceCount = (d.source as ForceNode).linkCount || 0
					const targetCount = (d.target as ForceNode).linkCount || 0
					const density = Math.max(sourceCount, targetCount)
					return 72 + Math.min(48, density * 2) - Math.min(18, d.weight * 2)
				})
				.strength((d) => Math.min(0.95, 0.22 + d.weight * 0.12))
		)
		.force('charge', d3.forceManyBody().strength(-320))
		.force('center', d3.forceCenter(width / 2, height / 2))
		.force(
			'collision',
			d3.forceCollide<ForceNode>().radius((d) => Math.max(24, 16 + d.linkCount * 1.2))
		)
		.on('tick', () => {
			link
				.attr('x1', (d) => (d.source as ForceNode).x || 0)
				.attr('y1', (d) => (d.source as ForceNode).y || 0)
				.attr('x2', (d) => (d.target as ForceNode).x || 0)
				.attr('y2', (d) => (d.target as ForceNode).y || 0)

			nodeGroup.attr('transform', (d) => `translate(${d.x || 0},${d.y || 0})`)
		})

	nodeGroup.call(
		d3
			.drag<SVGGElement, ForceNode>()
			.on('start', (event, d) => {
				if (!event.active) simulation?.alphaTarget(0.3).restart()
				d.fx = d.x
				d.fy = d.y
			})
			.on('drag', (event, d) => {
				d.fx = event.x
				d.fy = event.y
			})
			.on('end', (event, d) => {
				if (!event.active) simulation?.alphaTarget(0)
				d.fx = null
				d.fy = null
			})
	)

	highlightSelectedNode(selectedPage.value?.slug)
}

async function loadAll() {
	if (!props.currentBase.id) return

	const tasks = [loadStats()]
	if (isGraphMode.value) {
		tasks.push(loadGraph())
	} else {
		tasks.push(loadPages())
	}
	await Promise.all(tasks)
}

watch(
	() => props.currentBase.id,
	() => {
		filters.value.keyword = ''
		filters.value.page_type = ''
		filters.value.status = ''
		pagination.value.page = 1
		pagination.value.total = 0
		selectedPage.value = null
		logPage.value = null
		pageList.value = []
		graphData.value = { nodes: [], edges: [] }
		if (props.currentBase.id) {
			loadAll()
		}
	},
	{ immediate: true }
)

onMounted(() => {
	window.addEventListener('resize', renderGraph)
})

onBeforeUnmount(() => {
	window.removeEventListener('resize', renderGraph)
	simulation?.stop()
})
</script>

<style scoped lang="less">
.wiki-manage {
	display: flex;
	flex-direction: column;
	height: calc(100vh - 250px);
	min-height: 520px;
	min-width: 0;
}

.wiki-layout {
	display: grid;
	grid-template-columns: 320px minmax(0, 1fr);
	gap: 12px;
	flex: 1;
	min-height: 0;
}

.wiki-layout--graph-only {
	display: block;
}

.wiki-sidebar {
	width: 320px;
	display: flex;
	flex-direction: column;
	border: 1px solid var(--border-light);
	border-radius: 10px;
	background: var(--bg-base);
	overflow: hidden;
	min-height: 0;
}

.wiki-sidebar__header {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 10px;
	padding: 10px 12px;
	border-bottom: 1px solid var(--border-light);
}

.wiki-sidebar__title {
	font-size: 14px;
	font-weight: 700;
	color: var(--text-primary);
}

.wiki-sidebar__subtitle {
	margin-top: 2px;
	font-size: 12px;
	line-height: 1.4;
	color: var(--text-secondary);
}

.wiki-sidebar__body {
	display: flex;
	flex: 1;
	flex-direction: column;
	min-height: 0;
	padding: 10px;
	gap: 10px;
}

.wiki-sidebar__toolbar {
	display: flex;
	flex-direction: column;
	gap: 8px;
	flex-shrink: 0;
}

.wiki-sidebar__scroll {
	flex: 1;
	min-height: 0;
}

.wiki-sidebar__list {
	min-height: 0;
}

.wiki-sidebar__footer {
	display: flex;
	justify-content: center;
	padding-top: 2px;
	flex-shrink: 0;
}

.wiki-page-menu {
	border-right: 0;
	background: transparent;
}

.wiki-page-menu__item {
	height: auto;
	min-height: 58px;
	margin-bottom: 4px;
	padding: 7px 8px !important;
	border-radius: 8px;
	line-height: 1.3;
}

.wiki-page-menu__item.is-active {
	background: rgba(129, 0, 66, 0.08);
}

.wiki-menu-item {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 8px;
	width: 100%;
	min-width: 0;
}

.wiki-menu-item__main {
	display: flex;
	flex: 1;
	flex-direction: column;
	gap: 3px;
	min-width: 0;
}

.wiki-menu-item__title,
.wiki-menu-item__slug {
	overflow: hidden;
	text-overflow: ellipsis;
	white-space: nowrap;
}

.wiki-menu-item__title {
	font-size: 13px;
	font-weight: 600;
	color: var(--text-primary);
}

.wiki-menu-item__slug {
	font-size: 12px;
	color: var(--text-secondary);
}

.wiki-menu-item__extra {
	display: flex;
	flex-shrink: 0;
	flex-direction: column;
	align-items: flex-end;
	gap: 4px;
}

.wiki-menu-item__version {
	font-size: 12px;
	line-height: 1;
	color: var(--text-secondary);
}

.wiki-main-scroll {
	min-width: 0;
	min-height: 0;
	border: 1px solid var(--border-light);
	border-radius: 10px;
	background: var(--bg-base);
}

.wiki-main-scroll :deep(.el-scrollbar__view) {
	min-height: 100%;
	padding: 12px;
	box-sizing: border-box;
}

.graph-card,
.detail-card {
	background: var(--bg-base);
}

.detail-card {
	min-height: 100%;
}

.page-empty {
	min-height: 260px;
	display: flex;
	align-items: center;
	justify-content: center;
}

.page-empty--compact {
	min-height: 140px;
}

.graph-card__header,
.detail-card__header {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
	margin-bottom: 10px;
}

.graph-card__title,
.detail-card__title {
	font-size: 15px;
	font-weight: 700;
	color: var(--text-primary);
}

.graph-card__desc {
	margin-top: 4px;
	font-size: 12px;
	color: var(--text-secondary);
}

.graph-card__summary {
	display: flex;
	flex-wrap: wrap;
	gap: 10px;
	margin-top: 12px;
}

.graph-stat {
	display: inline-flex;
	align-items: center;
	gap: 8px;
	padding: 6px 10px;
	border: 1px solid var(--border-light);
	border-radius: 999px;
	background: var(--bg-container);
}

.graph-stat__label {
	font-size: 12px;
	color: var(--text-secondary);
}

.graph-stat__value {
	font-size: 12px;
	font-weight: 700;
	color: var(--text-primary);
}

.graph-card__legend {
	display: flex;
	flex-wrap: wrap;
	gap: 12px;
}

.legend-item {
	display: inline-flex;
	align-items: center;
	gap: 6px;
	font-size: 12px;
	color: var(--text-secondary);
}

.legend-item__dot {
	width: 10px;
	height: 10px;
	border-radius: 999px;
}

.graph-canvas {
	height: 520px;
	border-radius: 12px;
	background:
		radial-gradient(circle at top, rgba(255, 255, 255, 0.08), transparent 42%),
		linear-gradient(180deg, #272727 0%, #202020 100%);
	overflow: hidden;
}

.detail-card__actions {
	display: flex;
	align-items: center;
	gap: 6px;
}

.page-summary {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 6px 14px;
	margin-bottom: 12px;
	padding: 10px;
	border-radius: 8px;
	background: var(--bg-container);
}

.page-summary--compact {
	grid-template-columns: minmax(0, 1fr);
}

.page-summary__row {
	display: grid;
	grid-template-columns: 64px minmax(0, 1fr);
	gap: 8px;
	min-width: 0;
}

.page-summary__label {
	font-size: 12px;
	color: var(--text-secondary);
}

.page-summary__value {
	font-size: 13px;
	color: var(--text-primary);
	word-break: break-word;
}

.content-section {
	margin-bottom: 12px;
}

.content-section__title {
	margin-bottom: 6px;
	font-size: 13px;
	font-weight: 700;
	color: var(--text-primary);
}

.content-section__body {
	padding: 10px;
	border-radius: 8px;
	background: var(--bg-container);
	color: var(--text-primary);
	line-height: 1.6;
	white-space: pre-wrap;
	word-break: break-word;
}

.content-section__body--code {
	overflow: auto;
	font-family: Consolas, 'Courier New', monospace;
	font-size: 12px;
}

.metadata-grid {
	display: grid;
	grid-template-columns: repeat(2, minmax(0, 1fr));
	gap: 8px;
}

.metadata-grid__item {
	padding: 10px;
	border: 1px solid var(--border-light);
	border-radius: 8px;
	background: var(--bg-container);
}

.metadata-grid__label {
	font-size: 12px;
	color: var(--text-secondary);
}

.metadata-grid__value {
	margin-top: 6px;
	color: var(--text-primary);
	word-break: break-word;
	white-space: pre-wrap;
}

.wiki-log-dialog__scroll {
	max-height: min(68vh, 680px);
}

@media (max-width: 1200px) {
	.metadata-grid {
		grid-template-columns: minmax(0, 1fr);
	}
}

@media (max-width: 960px) {
	.wiki-manage {
		height: auto;
		min-height: 0;
	}

	.wiki-layout {
		grid-template-columns: minmax(0, 1fr);
	}

	.wiki-sidebar {
		width: 100%;
		max-height: 420px;
	}

	.wiki-main-scroll {
		min-height: 520px;
	}

	.graph-card__header,
	.detail-card__header {
		flex-direction: column;
	}

	.graph-canvas {
		height: 420px;
	}

	.page-summary {
		grid-template-columns: minmax(0, 1fr);
	}
}
</style>
