<template>
	<aside class="knowledge-sidebar">
		<div class="sidebar-header">
			<div>
				<div class="sidebar-title">知识库列表</div>
			</div>
			<div class="sidebar-actions">
				<el-button type="primary" @click="openCreateDrawer">
					<el-icon><Plus /></el-icon>
					新建
				</el-button>
			</div>
		</div>

		<div class="sidebar-toolbar">
			<el-input
				v-model="keyword"
				clearable
				placeholder="搜索知识库名称"
				@keyup.enter="refreshKnowledgeBases"
				@clear="refreshKnowledgeBases"
			>
				<template #append>
					<el-button @click="refreshKnowledgeBases">
						<el-icon><Search /></el-icon>
					</el-button>
				</template>
			</el-input>
			<el-radio-group v-model="typeFilter" size="small" @change="refreshKnowledgeBases">
				<el-radio-button label="">全部</el-radio-button>
				<el-radio-button label="document">知识</el-radio-button>
				<el-radio-button label="faq">问答</el-radio-button>
			</el-radio-group>
		</div>

		<el-scrollbar
			ref="scrollbarRef"
			class="sidebar-body"
			v-loading="loading"
			@scroll="handleScroll"
		>
			<div v-if="knowledgeBases.length" class="base-list">
				<div
					v-for="item in knowledgeBases"
					:key="item.id"
					class="base-card"
					:class="{ 'base-card--active': item.id === currentBaseId }"
					role="button"
					tabindex="0"
					@click="selectBase(item)"
					@keydown.enter="selectBase(item)"
					@keydown.space.prevent="selectBase(item)"
				>
					<div class="base-card__top">
						<div class="base-card__title">{{ item.name }}</div>
						<el-tag :type="item.type === 'faq' ? 'warning' : 'success'" size="small">
							{{ item.type === 'faq' ? '问答' : '文档' }}
						</el-tag>
					</div>
					<div class="base-card__desc">{{ item.description || '暂无描述' }}</div>

					<div class="base-card__footer">
						<span>{{ item.updateTime || item.creationTime || '-' }}</span>
						<div class="base-card__actions" @click.stop>
							<el-button link type="primary" @click="openEditDrawer(item.id)">编辑</el-button>
							<el-button link type="danger" @click="removeKnowledgeBase(item.id)">删除</el-button>
						</div>
					</div>
				</div>
				<div v-if="loadingMore" class="list-status">加载中...</div>
				<div v-else-if="!hasNextPage" class="list-status">没有更多了</div>
			</div>
			<el-empty v-else description="暂无知识库" />
		</el-scrollbar>
		<EditDrawer ref="editDrawerRef" @refresh="refreshKnowledgeBases" />
	</aside>
</template>

<script setup lang="ts">
import { nextTick, onMounted, ref, watch } from 'vue'
import { ElMessage, ElMessageBox } from 'element-plus'
import { Plus, Search } from '@element-plus/icons-vue'
import { knowledge } from '@repo/api-service'
import type { KnowledgeBaseItem } from './types'
import EditDrawer from './components/KnowledgeBaseEditDrawer.vue'

const props = defineProps<{ currentBaseId: string }>()
const emit = defineEmits<{ (e: 'select-base', value?: KnowledgeBaseItem): void }>()

const PAGE_SIZE = 20

const loading = ref(false)
const loadingMore = ref(false)
const keyword = ref('')
const typeFilter = ref<'' | 'document' | 'faq'>('')
const knowledgeBases = ref<KnowledgeBaseItem[]>([])
const currentPage = ref(1)
const hasNextPage = ref(false)
const scrollbarRef = ref()
const editDrawerRef = ref<InstanceType<typeof EditDrawer>>()

function selectBase(record: KnowledgeBaseItem) {
	emit('select-base', record)
}

function getScrollWrap() {
	return scrollbarRef.value?.wrapRef as HTMLElement | undefined
}

function resetPagination() {
	currentPage.value = 1
	hasNextPage.value = false
	knowledgeBases.value = []
}

function ensureCurrentSelection() {
	if (!knowledgeBases.value.length) {
		emit('select-base', undefined)
		return
	}
	const exists = knowledgeBases.value.some((item) => item.id === props.currentBaseId)
	if (!props.currentBaseId || !exists) {
		selectBase(knowledgeBases.value[0]!)
	}
}

async function fetchKnowledgeBases(reset = true) {
	if (loading.value || loadingMore.value) return

	if (reset) {
		resetPagination()
		loading.value = true
	} else {
		if (!hasNextPage.value) return
		loadingMore.value = true
	}

	try {
		const pageIndex = reset ? 1 : currentPage.value + 1
		const res = await knowledge.postKnowledgeBasePageList({
			keyword: keyword.value,
			type: typeFilter.value,
			pageIndex,
			pageSize: PAGE_SIZE
		})
		if (!res?.isSuccess) return

		const list = (res.result?.model || []) as KnowledgeBaseItem[]
		knowledgeBases.value = reset ? list : [...knowledgeBases.value, ...list]
		currentPage.value = res.result?.currentPage || pageIndex
		hasNextPage.value = !!res.result?.hasNextPage
		ensureCurrentSelection()

		if (reset) {
			await nextTick()
			getScrollWrap()?.scrollTo({ top: 0 })
		}
	} finally {
		if (reset) {
			loading.value = false
			await nextTick()
			tryLoadMoreWhenNotFilled()
		} else {
			loadingMore.value = false
		}
	}
}

function tryLoadMoreWhenNotFilled() {
	const wrap = getScrollWrap()
	if (!wrap || loading.value || loadingMore.value || !hasNextPage.value) return
	if (wrap.scrollHeight <= wrap.clientHeight + 100) {
		void fetchKnowledgeBases(false)
	}
}

function handleScroll() {
	const wrap = getScrollWrap()
	if (!wrap || loading.value || loadingMore.value || !hasNextPage.value) return
	const distanceToBottom = wrap.scrollHeight - wrap.scrollTop - wrap.clientHeight
	if (distanceToBottom <= 100) {
		void fetchKnowledgeBases(false)
	}
}

async function refreshKnowledgeBases() {
	await fetchKnowledgeBases(true)
}

function openCreateDrawer() {
	editDrawerRef.value?.openCreateDrawer()
}

function openEditDrawer(id: string) {
	editDrawerRef.value?.openEditDrawer(id)
}

async function removeKnowledgeBase(id: string) {
	const confirmed = await ElMessageBox.confirm('确定删除该知识库吗？删除后不可恢复。', '删除确认', {
		type: 'warning'
	})
		.then(() => true)
		.catch(() => false)
	if (!confirmed) return

	await knowledge.postKnowledgeBaseOpenApiDelete({ id })
	ElMessage.success('知识库已删除')
	await refreshKnowledgeBases()
}

watch(
	() => props.currentBaseId,
	(value) => {
		if (!value && knowledgeBases.value.length) {
			selectBase(knowledgeBases.value[0]!)
		}
	}
)

onMounted(async () => {
	await refreshKnowledgeBases()
})
</script>

<style scoped lang="less">
.knowledge-sidebar {
	width: 360px;
	display: flex;
	flex-direction: column;
	gap: 16px;
	padding: 16px;
	border-radius: 12px;
	background: #fff;
	box-shadow: 0 8px 24px rgba(15, 23, 42, 0.06);
}

.sidebar-header {
	display: flex;
	align-items: flex-start;
	justify-content: space-between;
	gap: 12px;
}

.sidebar-actions {
	display: flex;
	align-items: center;
	gap: 8px;
	flex-wrap: wrap;
}

.sidebar-title {
	font-size: 18px;
	font-weight: 700;
	color: #111827;
}

.sidebar-subtitle {
	margin-top: 6px;
	font-size: 12px;
	line-height: 1.6;
	color: #6b7280;
}

.sidebar-toolbar {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.sidebar-body {
	flex: 1;
	min-height: 0;
}

.base-list {
	display: flex;
	flex-direction: column;
	gap: 12px;
}

.list-status {
	padding: 4px 0 8px;
	text-align: center;
	font-size: 12px;
	color: #9ca3af;
}

.base-card {
	padding: 14px;
	text-align: left;
	border: 1px solid #e5e7eb;
	border-radius: 14px;
	background: #f8fafc;
	cursor: pointer;
	transition: all 0.2s ease;
}

.base-card:hover,
.base-card--active {
	border-color: #3b82f6;
	background: #eff6ff;
	box-shadow: 0 8px 20px rgba(59, 130, 246, 0.12);
}

.base-card__top,
.base-card__footer {
	display: flex;
	align-items: center;
	justify-content: space-between;
	gap: 12px;
}

.base-card__title {
	font-size: 15px;
	font-weight: 700;
	color: #111827;
}

.base-card__desc {
	margin: 10px 0;
	display: -webkit-box;
	overflow: hidden;
	font-size: 13px;
	line-height: 1.6;
	color: #4b5563;
	-webkit-line-clamp: 2;
	-webkit-box-orient: vertical;
}

.base-card__meta,
.base-card__footer {
	font-size: 12px;
	color: #6b7280;
}

.base-card__meta {
	display: flex;
	gap: 12px;
	margin-bottom: 10px;
}

.base-card__actions {
	display: flex;
	align-items: center;
	gap: 8px;
	display: none;
}

.base-card:hover .base-card__actions {
	display: flex;
}

.sidebar-footer {
	display: flex;
	justify-content: flex-end;
}

@media (max-width: 960px) {
	.knowledge-sidebar {
		width: 100%;
	}

	.form-grid,
	.switch-row,
	.switch-row--4 {
		grid-template-columns: minmax(0, 1fr);
	}
}
</style>
