import { createRequestError } from './request-client.js' import { normalizeBusinessFileAccess } from './business-file-contract.js' const lineageTreeError = (message) => createRequestError(message, 'LINEAGE_TREE_RESPONSE_INVALID') const normalizeLineagePersonId = (value) => { if (typeof value === 'string' && /^[1-9]\d*$/.test(value)) return value if (typeof value === 'number' && Number.isSafeInteger(value) && value > 0) return String(value) throw lineageTreeError('世系树包含无效人物标识') } const normalizeLineageText = (value, label, { required = false } = {}) => { if (value === undefined || value === null) { if (required) throw lineageTreeError(`世系树缺少${label}`) return '' } if (typeof value !== 'string') throw lineageTreeError(`世系树${label}无效`) const normalized = value.trim() if (required && !normalized) throw lineageTreeError(`世系树缺少${label}`) return normalized } const lineageDatePart = (value, label) => { const normalized = normalizeLineageText(value, label) if (!normalized) return '' if (!/^\d{4}-\d{2}-\d{2}/.test(normalized)) { throw lineageTreeError(`世系树${label}无效`) } if (normalized.length === 10) return normalized const instant = new Date(normalized) if (Number.isNaN(instant.getTime())) throw lineageTreeError(`世系树${label}无效`) const chinaTime = new Date(instant.getTime() + 8 * 60 * 60 * 1000) const year = chinaTime.getUTCFullYear() const month = String(chinaTime.getUTCMonth() + 1).padStart(2, '0') const day = String(chinaTime.getUTCDate()).padStart(2, '0') return `${year}-${month}-${day}` } const compactLineageYears = (birthDate, deathDate) => { const birthYear = birthDate ? birthDate.slice(0, 4) : '' const deathYear = deathDate ? deathDate.slice(0, 4) : '' return birthYear || deathYear ? `${birthYear}—${deathYear}` : '生卒待补' } export const normalizeLineageTree = (value) => { if (!Array.isArray(value)) throw lineageTreeError('世系树响应不是列表') const seen = new Set() const normalized = [] const normalizedById = new Map() const appendNode = (node, parentId, relationOverride = '', spouseOf = '') => { if (!node || typeof node !== 'object' || Array.isArray(node)) { throw lineageTreeError('世系树包含无效节点') } const id = normalizeLineagePersonId(node.personId) if (seen.has(id)) throw lineageTreeError('世系树包含重复人物标识') seen.add(id) if (seen.size > 5000) throw lineageTreeError('世系树节点数量超出客户端上限') if (!Number.isSafeInteger(node.generation) || node.generation < 1) { throw lineageTreeError('世系树人物世代无效') } const generationName = normalizeLineageText(node.generationName, '字辈') const rankName = normalizeLineageText(node.rankName, '排行名称') const relationName = normalizeLineageText(node.relationName, '人物关系') const birthDate = lineageDatePart(node.birthDate, '出生日期') const deathDate = lineageDatePart(node.deathDate, '逝世日期') const normalizedNode = { id, parentId, ...(spouseOf ? { spouseOf } : {}), name: normalizeLineageText(node.name, '人物姓名', { required: true }), relation: relationOverride || rankName || (parentId ? (relationName && relationName !== '配偶' ? relationName : '排行待补') : relationName || '始祖'), rankName, generation: node.generation, branch: generationName ? (generationName.endsWith('字辈') ? generationName : `${generationName}字辈`) : '字辈待补', years: birthDate || deathDate ? `${birthDate}—${deathDate}` : '生卒待补', treeYears: compactLineageYears(birthDate, deathDate), avatarFile: normalizeBusinessFileAccess( node.avatarFile, '世系人物头像', 'LINEAGE_TREE_RESPONSE_INVALID' ), sex: normalizeLineageText(node.sex, '性别'), personStatus: normalizeLineageText(node.personStatus, '人物状态') } normalized.push(normalizedNode) normalizedById.set(id, normalizedNode) return id } const walk = (node, parentId = null, depth = 0) => { if (depth > 64) throw lineageTreeError('世系树深度超出客户端上限') if (!node || typeof node !== 'object' || Array.isArray(node)) { throw lineageTreeError('世系树包含无效节点') } const spouses = node.spouses ?? [] const children = node.children ?? [] if (!Array.isArray(spouses) || !Array.isArray(children)) { throw lineageTreeError('世系树亲属集合无效') } const id = normalizeLineagePersonId(node.personId) const knownSpouseId = spouses .map((spouse) => normalizeLineagePersonId(spouse?.personId)) .find((spouseId) => normalizedById.has(spouseId)) || '' if (seen.has(id)) { if (knownSpouseId) return id throw lineageTreeError('世系树包含重复人物标识') } const knownSpouse = knownSpouseId ? normalizedById.get(knownSpouseId) : null const nodeParentId = knownSpouse ? knownSpouse.parentId : parentId const appendedId = appendNode(node, nodeParentId, knownSpouse ? '配偶' : '', knownSpouseId) spouses.forEach((spouse) => { const spouseId = normalizeLineagePersonId(spouse?.personId) if (seen.has(spouseId)) return appendNode(spouse, nodeParentId, '配偶', appendedId) }) children.forEach((child) => walk(child, knownSpouseId || appendedId, depth + 1)) } value.forEach((root) => walk(root)) return normalized }