/**
 * 教材模块纯逻辑工具函数。
 *
 * 从 data-access.ts 和组件中抽离的纯函数，便于单元测试。
 */

import type { Chapter, KnowledgePoint } from "./types"

/**
 * 章节排序比较器：先按 order 升序，order 相同按 title 字典序。
 */
export function sortChapters(a: Chapter, b: Chapter): number {
  const ao = a.order ?? 0
  const bo = b.order ?? 0
  if (ao !== bo) return ao - bo
  return a.title.localeCompare(b.title)
}

/**
 * 将扁平章节列表构建为树形结构。
 *
 * 算法：
 * 1. 第一遍遍历：所有章节放入 Map，并初始化 children: []
 * 2. 第二遍遍历：根据 parentId 挂载到父节点的 children；无 parentId 或父节点不存在则作为根
 * 3. 递归排序
 *
 * 时间复杂度 O(n log n)，空间复杂度 O(n)。
 *
 * @param rows 扁平章节列表（顺序无关）
 * @returns 树形根节点数组（已排序），每个节点的 children 均已初始化为非空数组
 */
export function buildChapterTree(rows: Chapter[]): ChapterTreeNode[] {
  type ChapterNode = ChapterTreeNode

  const byId = new Map<string, ChapterNode>()
  for (const ch of rows) {
    byId.set(ch.id, { ...ch, children: [] })
  }

  const roots: ChapterNode[] = []
  for (const ch of byId.values()) {
    const pid = ch.parentId
    if (pid) {
      const parent = byId.get(pid)
      if (parent) {
        parent.children.push(ch)
      } else {
        roots.push(ch)
      }
    } else {
      roots.push(ch)
    }
  }

  const sortRecursive = (nodes: ChapterNode[]) => {
    nodes.sort(sortChapters)
    for (const n of nodes) {
      sortRecursive(n.children)
    }
  }

  sortRecursive(roots)
  return roots
}

/**
 * 章节树节点：在 Chapter 基础上强制 children 为非空数组。
 */
export type ChapterTreeNode = Chapter & { children: ChapterTreeNode[] }

/**
 * 构建章节索引 Map（id → Chapter），用于快速查找。
 *
 * 递归遍历整棵树，包括所有子节点。
 */
export function buildChapterIndex(chapters: Chapter[]): Map<string, Chapter> {
  const index = new Map<string, Chapter>()

  const walk = (nodes: Chapter[]) => {
    for (const node of nodes) {
      index.set(node.id, node)
      if (node.children && node.children.length > 0) walk(node.children)
    }
  }

  walk(chapters)
  return index
}

/**
 * 在章节树中查找某个章节的父节点。
 *
 * @returns 父节点；如果是根节点或未找到，返回 null。
 */
export function findChapterParent(
  items: Chapter[],
  id: string
): Chapter | null {
  for (const item of items) {
    if (item.children?.some((c) => c.id === id)) return item
    if (item.children) {
      const found = findChapterParent(item.children, id)
      if (found) return found
    }
  }
  return null
}

/**
 * 在章节树中递归查找指定 ID 的章节。
 *
 * P1-6 修复：从 lesson-preparation 4 个页面文件中提取的共享工具，
 * 消除重复代码（DRY 原则）。
 *
 * @param chapters 章节树（含 children 递归结构）
 * @param id 目标章节 ID
 * @returns 匹配的章节；未找到返回 undefined
 */
export function findChapterById(
  chapters: Chapter[],
  id: string
): Chapter | undefined {
  for (const ch of chapters) {
    if (ch.id === id) return ch
    if (ch.children && ch.children.length > 0) {
      const found = findChapterById(ch.children, id)
      if (found) return found
    }
  }
  return undefined
}

/**
 * 过滤出指定章节的知识点。
 */
export function filterKnowledgePointsByChapter(
  knowledgePoints: KnowledgePoint[],
  chapterId: string | null
): KnowledgePoint[] {
  if (!chapterId) return []
  return knowledgePoints.filter((kp) => kp.chapterId === chapterId)
}

/**
 * 规范化可选字符串字段：trim 后若为空返回 null。
 */
export function normalizeOptional(
  v: string | null | undefined
): string | null {
  const trimmed = v?.trim()
  if (!trimmed) return null
  return trimmed
}

/**
 * 转义正则元字符，用于构建字面量匹配的正则。
 */
function escapeRegExp(s: string): string {
  return s.replace(/[.*+?^${}()|[\]\\]/g, "\\$&")
}

/**
 * P2-2 知识点高亮：单遍替换实现。
 *
 * 旧实现 O(n×m)：对每个知识点名做一次 `new RegExp(..., "gi")` 全局替换，
 * 内容被反复扫描 n 次，且长名替换可能破坏短名匹配。
 *
 * 新实现 O(m)：构建单个 alternation 正则 `(name1|name2|...)`，一次扫描完成所有替换。
 * 按名称长度降序排列保证最长匹配优先（避免 "物理" 抢占 "物理学"）。
 *
 * @param content 章节正文（Markdown）
 * @param kps 当前章节的知识点列表
 * @returns 替换后的 Markdown，知识点名被包装为 `[name](#kp-{id})` 链接
 */
export function highlightKnowledgePoints(
  content: string,
  kps: KnowledgePoint[]
): string {
  if (!content || kps.length === 0) return content

  // 按名称长度降序，保证最长匹配优先
  const sorted = [...kps].sort((a, b) => b.name.length - a.name.length)

  // 构建 id 查找表：name（小写）→ kpId（取第一个匹配，因已按长度降序）
  // 使用小写作为键以支持大小写不敏感匹配
  const nameToId = new Map<string, string>()
  for (const kp of sorted) {
    const key = kp.name.toLowerCase()
    if (!nameToId.has(key)) {
      nameToId.set(key, kp.id)
    }
  }

  // 构建单遍 alternation 正则
  const pattern = sorted
    .map((kp) => `(${escapeRegExp(kp.name)})`)
    .join("|")
  const regex = new RegExp(pattern, "gi")

  return content.replace(regex, (match: string) => {
    const id = nameToId.get(match.toLowerCase())
    if (id) return `[${match}](#kp-${id})`
    return match
  })
}

/**
 * 检测在添加新边 (from -> to) 后是否形成环。
 *
 * 算法：DFS 从 to 出发，若能到达 from 则有环。
 *
 * @param existingEdges 现有边列表，每项为 [from, to]
 * @param newFrom 新边的起点
 * @param newTo 新边的终点
 * @returns true 表示添加后会形成环
 */
export function hasCycleAfterAddingEdge(
  existingEdges: Array<[string, string]>,
  newFrom: string,
  newTo: string,
): boolean {
  // 构建邻接表
  const adj = new Map<string, string[]>()
  for (const [from, to] of existingEdges) {
    const arr = adj.get(from) ?? []
    arr.push(to)
    adj.set(from, arr)
  }
  // 添加新边
  const arr = adj.get(newFrom) ?? []
  arr.push(newTo)
  adj.set(newFrom, arr)

  // DFS 从 newTo 出发，若能到达 newFrom 则有环
  const visited = new Set<string>()
  const stack = [newTo]
  while (stack.length > 0) {
    const node = stack.pop()
    if (!node) continue
    if (node === newFrom) return true
    if (visited.has(node)) continue
    visited.add(node)
    const neighbors = adj.get(node) ?? []
    for (const n of neighbors) {
      if (!visited.has(n)) stack.push(n)
    }
  }
  return false
}
