<template>
  <div class="category-page">
    <Breadcrumb :items="['知识管理', '公共分类']" />
    <div class="category-layout">
      <a-card class="tree-card" title="分类树" :bordered="false">
        <template #extra>
          <a-space>
            <a-button v-if="canManage" type="primary" size="small" :disabled="writeBusy" @click="openCreate(0)">新增一级分类</a-button>
            <a-button size="small" :loading="treeLoading" :disabled="writeBusy" @click="loadTree">刷新</a-button>
          </a-space>
        </template>
        <a-spin :loading="treeLoading" class="tree-loading">
          <a-tree
            v-if="treeData.length"
            v-model:selected-keys="selectedKeys"
            :data="treeData"
            block-node
            default-expand-all
            @select="handleSelect"
          >
            <template #title="nodeData">
              <span>{{ nodeData.title }}</span>
              <a-tag v-if="nodeData.category.status === 'inactive'" color="gray" size="small">停用</a-tag>
            </template>
          </a-tree>
          <a-empty v-else description="暂无公共分类" />
        </a-spin>
      </a-card>

      <a-card class="detail-card" :bordered="false">
        <template #title>{{ selectedCategory?.name || '分类详情' }}</template>
        <template #extra>
          <a-space v-if="selectedCategory && canManage">
            <a-button
              v-if="selectedCategory.level < 3"
              type="primary"
              size="small"
              :disabled="writeBusy"
              @click="openCreate(selectedCategory.id)"
            >
              新增子分类
            </a-button>
            <a-button size="small" :disabled="writeBusy" @click="openEdit">编辑</a-button>
            <a-button
              size="small"
              status="danger"
              :loading="deletingID === selectedCategory.id"
              :disabled="writeBusy"
              @click="confirmDelete"
            >
              删除
            </a-button>
          </a-space>
        </template>

        <a-spin :loading="detailLoading" class="detail-loading">
          <a-descriptions v-if="selectedCategory" :column="2" bordered>
            <a-descriptions-item label="分类名称">{{ selectedCategory.name }}</a-descriptions-item>
            <a-descriptions-item label="状态">
              <a-tag :color="selectedCategory.status === 'active' ? 'green' : 'gray'">
                {{ selectedCategory.status === 'active' ? '启用' : '停用' }}
              </a-tag>
            </a-descriptions-item>
            <a-descriptions-item label="层级">第 {{ selectedCategory.level }} 级</a-descriptions-item>
            <a-descriptions-item label="排序">{{ selectedCategory.sort_order }}</a-descriptions-item>
            <a-descriptions-item label="文档数">{{ selectedCategory.doc_count }}</a-descriptions-item>
            <a-descriptions-item label="FAQ 数">{{ selectedCategory.faq_count }}</a-descriptions-item>
            <a-descriptions-item label="创建人">{{ selectedCategory.creator_name || '-' }}</a-descriptions-item>
            <a-descriptions-item label="更新时间">{{ formatTime(selectedCategory.updated_at) }}</a-descriptions-item>
            <a-descriptions-item label="描述" :span="2">{{ selectedCategory.description || '-' }}</a-descriptions-item>
            <a-descriptions-item label="备注" :span="2">{{ selectedCategory.remarks || '-' }}</a-descriptions-item>
          </a-descriptions>
          <a-empty v-else description="请从左侧选择分类" />
        </a-spin>
      </a-card>
    </div>

    <a-modal
      :visible="formVisible"
      :title="editingCategory ? '编辑分类' : '新增分类'"
      :closable="!submitting"
      :mask-closable="!submitting"
      :esc-to-close="!submitting"
      :on-before-cancel="canCloseForm"
      width="640px"
      @cancel="closeForm"
      @update:visible="handleFormVisibleChange"
    >
      <a-form ref="formRef" :model="form" layout="vertical">
        <a-form-item label="分类名称" field="name" :rules="[{ required: true, message: '请输入分类名称' }]">
          <a-input v-model="form.name" :max-length="100" placeholder="请输入分类名称" />
        </a-form-item>
        <a-row :gutter="16">
          <a-col :span="12">
            <a-form-item label="父分类" field="parent_id">
              <a-select v-model="form.parent_id" placeholder="请选择父分类">
                <a-option :value="0">无（一级分类）</a-option>
                <a-option v-for="option in parentOptions" :key="option.id" :value="option.id">
                  {{ option.label }}{{ option.status === 'inactive' ? '（停用）' : '' }}
                </a-option>
              </a-select>
            </a-form-item>
          </a-col>
          <a-col :span="6">
            <a-form-item label="排序" field="sort_order">
              <a-input-number v-model="form.sort_order" :min="0" style="width: 100%" />
            </a-form-item>
          </a-col>
          <a-col :span="6">
            <a-form-item label="状态" field="status">
              <a-select v-model="form.status">
                <a-option value="active">启用</a-option>
                <a-option value="inactive">停用</a-option>
              </a-select>
            </a-form-item>
          </a-col>
        </a-row>
        <a-form-item label="描述" field="description">
          <a-textarea v-model="form.description" :max-length="500" :auto-size="{ minRows: 3, maxRows: 6 }" />
        </a-form-item>
        <a-form-item label="备注" field="remarks">
          <a-textarea v-model="form.remarks" :max-length="500" :auto-size="{ minRows: 2, maxRows: 4 }" />
        </a-form-item>
      </a-form>
      <template #footer>
        <a-space>
          <a-button :disabled="submitting" @click="closeForm">取消</a-button>
          <a-button type="primary" :loading="submitting" :disabled="submitting" @click="submitForm">保存</a-button>
        </a-space>
      </template>
    </a-modal>
  </div>
</template>

<script lang="ts" setup>
import { computed, onMounted, reactive, ref } from 'vue'
import axios from 'axios'
import dayjs from 'dayjs'
import { Message, Modal } from '@arco-design/web-vue'
import type { FormInstance } from '@arco-design/web-vue'
import {
  createCategory,
  deleteCategory,
  fetchCategoryDetail,
  fetchCategoryTree,
  updateCategory,
  type Category,
  type CategoryStatus,
  type CategoryTreeNode,
} from '@/api/kb/category'
import usePermissionCodes from '@/hooks/usePermissionCodes'

interface TreeViewNode {
  key: string
  title: string
  category: CategoryTreeNode
  children: TreeViewNode[]
}

interface ParentOption {
  id: number
  label: string
  level: number
  status: CategoryStatus
}

const { hasPermission } = usePermissionCodes()
const canManage = computed(() => hasPermission('kb:content:manage'))
const treeLoading = ref(false)
const detailLoading = ref(false)
const tree = ref<CategoryTreeNode[]>([])
const selectedKeys = ref<string[]>([])
const selectedCategory = ref<Category | null>(null)
const selectedTreeID = ref(0)
let treeSequence = 0
let detailSequence = 0

const treeData = computed<TreeViewNode[]>(() => tree.value.map((node) => toViewNode(node)))

const formVisible = ref(false)
const submitting = ref(false)
const deletingID = ref(0)
const deletePending = ref(false)
const writeBusy = computed(() => submitting.value || deletePending.value)
const formRef = ref<FormInstance>()
const editingCategory = ref<Category | null>(null)
const form = reactive({
  name: '',
  description: '',
  parent_id: 0,
  sort_order: 0,
  status: 'active' as CategoryStatus,
  remarks: '',
})

const parentOptions = computed<ParentOption[]>(() => {
  const excluded = editingCategory.value ? descendantIds(editingCategory.value.id) : new Set<number>()
  if (editingCategory.value) excluded.add(editingCategory.value.id)
  const subtreeHeight = editingCategory.value ? categorySubtreeHeight(editingCategory.value.id) : 1
  return flattenTree(tree.value).filter((item) => !excluded.has(item.id) && item.level + subtreeHeight <= 3)
})

function toViewNode(node: CategoryTreeNode): TreeViewNode {
  return {
    key: String(node.id),
    title: node.name,
    category: node,
    children: (node.children || []).map(toViewNode),
  }
}

function flattenTree(nodes: CategoryTreeNode[], prefix = ''): ParentOption[] {
  return nodes.flatMap((node) => {
    const label = prefix ? `${prefix} / ${node.name}` : node.name
    return [{ id: node.id, label, level: node.level, status: node.status }, ...flattenTree(node.children || [], label)]
  })
}

function findCategory(nodes: CategoryTreeNode[], id: number): CategoryTreeNode | undefined {
  for (const node of nodes) {
    if (node.id === id) return node
    const child = findCategory(node.children || [], id)
    if (child) return child
  }
  return undefined
}

function descendantIds(id: number): Set<number> {
  const result = new Set<number>()
  const root = findCategory(tree.value, id)
  const visit = (nodes: CategoryTreeNode[]) => {
    nodes.forEach((node) => {
      result.add(node.id)
      visit(node.children || [])
    })
  }
  if (root) visit(root.children || [])
  return result
}

function categorySubtreeHeight(id: number): number {
  const root = findCategory(tree.value, id)
  if (!root?.children?.length) return 1
  return 1 + Math.max(...root.children.map((child) => categorySubtreeHeight(child.id)))
}

/** 加载最新分类树，并保留仍存在的选中项。 */
async function loadTree() {
  const sequence = ++treeSequence
  ++detailSequence
  detailLoading.value = false
  selectedCategory.value = null
  treeLoading.value = true
  try {
    const reply = await fetchCategoryTree()
    if (sequence !== treeSequence) return
    if (reply.code !== 0) throw new Error(reply.message || '获取分类树失败')
    tree.value = reply.details || []
    const selectedID = selectedTreeID.value
    const selected = selectedID ? findCategory(tree.value, selectedID) : tree.value[0]
    selectedTreeID.value = selected?.id || 0
    selectedKeys.value = selected ? [String(selected.id)] : []
    if (selected) await loadCategoryDetail(selected.id)
  } catch (error) {
    if (sequence === treeSequence) {
      tree.value = []
      selectedCategory.value = null
      selectedTreeID.value = 0
      selectedKeys.value = []
      showRequestError(error, '获取分类树失败')
    }
  } finally {
    if (sequence === treeSequence) treeLoading.value = false
  }
}

function handleSelect(keys: Array<string | number>) {
  const id = Number(keys[0])
  if (!Number.isFinite(id) || !findCategory(tree.value, id)) {
    selectedTreeID.value = 0
    selectedCategory.value = null
    return
  }
  selectedTreeID.value = id
  loadCategoryDetail(id)
}

/** 分类树只负责导航，完整详情始终由详情接口读取。 */
async function loadCategoryDetail(id: number) {
  const sequence = ++detailSequence
  selectedCategory.value = null
  detailLoading.value = true
  try {
    const reply = await fetchCategoryDetail(id)
    if (sequence !== detailSequence || selectedTreeID.value !== id) return
    if (reply.code !== 0) throw new Error(reply.message || '获取分类详情失败')
    selectedCategory.value = reply.details
  } catch (error) {
    if (sequence === detailSequence && selectedTreeID.value === id) showRequestError(error, '获取分类详情失败')
  } finally {
    if (sequence === detailSequence) detailLoading.value = false
  }
}

function openCreate(parentID: number) {
  if (writeBusy.value) return
  editingCategory.value = null
  Object.assign(form, { name: '', description: '', parent_id: parentID, sort_order: 0, status: 'active', remarks: '' })
  formVisible.value = true
}

function openEdit() {
  if (!selectedCategory.value || writeBusy.value) return
  editingCategory.value = selectedCategory.value
  Object.assign(form, {
    name: selectedCategory.value.name,
    description: selectedCategory.value.description,
    parent_id: selectedCategory.value.parent_id,
    sort_order: selectedCategory.value.sort_order,
    status: selectedCategory.value.status,
    remarks: selectedCategory.value.remarks,
  })
  formVisible.value = true
}

function closeForm() {
  if (submitting.value) return
  formVisible.value = false
  editingCategory.value = null
  formRef.value?.clearValidate()
}

async function submitForm() {
  if (submitting.value) return
  submitting.value = true
  try {
    if (await formRef.value?.validate()) return
    const payload = {
      name: form.name.trim(),
      description: form.description.trim(),
      icon: editingCategory.value?.icon || '',
      color: editingCategory.value?.color || '',
      parent_id: form.parent_id,
      sort_order: form.sort_order,
      status: form.status,
      remarks: form.remarks.trim(),
    }
    const reply = editingCategory.value ? await updateCategory({ ...payload, id: editingCategory.value.id }) : await createCategory(payload)
    if (reply.code !== 0) throw new Error(reply.message || '保存分类失败')
    Message.success(editingCategory.value ? '分类已更新' : '分类已创建')
    formVisible.value = false
    editingCategory.value = null
    formRef.value?.clearValidate()
    await loadTree()
  } catch (error) {
    showRequestError(error, '保存分类失败')
  } finally {
    submitting.value = false
  }
}

function confirmDelete() {
  if (!selectedCategory.value || writeBusy.value) return
  const category = selectedCategory.value
  deletePending.value = true
  deletingID.value = category.id
  Modal.confirm({
    title: '确认删除分类',
    content: `确认删除「${category.name}」吗？存在子分类或内容时服务端将拒绝删除。`,
    onOk: async () => {
      try {
        const reply = await deleteCategory(category.id)
        if (reply.code !== 0) throw new Error(reply.message || '删除分类失败')
        Message.success('分类已删除')
        selectedTreeID.value = 0
        selectedCategory.value = null
        await loadTree()
      } catch (error) {
        showRequestError(error, '删除分类失败')
      } finally {
        deletePending.value = false
        deletingID.value = 0
      }
    },
    onCancel: finishDelete,
  })
}

function finishDelete() {
  deletePending.value = false
  deletingID.value = 0
}

function canCloseForm() {
  return !submitting.value
}

function handleFormVisibleChange(visible: boolean) {
  if (!visible) closeForm()
}

function showRequestError(error: unknown, fallback: string) {
  if (axios.isAxiosError(error)) {
    const status = error.response?.status
    const message = (error.response?.data as { message?: string } | undefined)?.message
    if (status === 403) return Message.error('无操作权限')
    if (status === 400 || status === 409) return Message.error(message || fallback)
  }
  Message.error(error instanceof Error && error.message ? error.message : fallback)
}

function formatTime(value: string) {
  return value ? dayjs(value).format('YYYY-MM-DD HH:mm') : '-'
}

onMounted(loadTree)
</script>

<style scoped lang="less">
.category-page {
  padding: 0 20px 20px;
}
.category-layout {
  display: grid;
  grid-template-columns: 360px minmax(0, 1fr);
  gap: 16px;
  margin-top: 16px;
}
.tree-card,
.detail-card {
  min-height: 560px;
}
.tree-loading,
.detail-loading {
  width: 100%;
}
:deep(.arco-tree-node-title) {
  display: inline-flex;
  align-items: center;
  gap: 8px;
}
@media (max-width: 960px) {
  .category-layout {
    grid-template-columns: 1fr;
  }
}
</style>
