/**
 * V5-19 T3：目标-评价一致性校验（教学评一致性）。
 *
 * 校验规则：
 * 1. 每个教学目标（objective 节点）应至少被一个评价（exercise 节点）覆盖。
 * 2. 每个 exercise 节点应至少关联一个知识点。
 * 3. 课案应至少包含一个 objective 与一个 exercise（仅 warning，不阻断保存）。
 *
 * 输出为纯数据结构（ConsistencyResult），UI 层负责渲染。
 * 该模块为纯函数，无副作用，便于单测。
 */
import type {
  ExerciseBlockData,
  LessonPlanDocument,
  LessonPlanNode,
} from "../types";

/** 校验级别 */
export type ConsistencySeverity = "warning" | "info";

/** 单条校验结果 */
export interface ConsistencyIssue {
  /** i18n 键后缀（consistency.* 命名空间下） */
  code:
    | "objectiveNotAssessed"
    | "exerciseWithoutObjective"
    | "noObjective"
    | "noExercise"
    | "exerciseNoKnowledgePoint"
    | "objectiveNoKnowledgePoint";
  severity: ConsistencySeverity;
  /** 关联节点 ID（如适用） */
  nodeId?: string;
  /** 关联节点标题（用于 UI 展示） */
  nodeTitle?: string;
  /** i18n 插值参数 */
  params?: Record<string, string | number>;
}

/** 校验结果汇总 */
export interface ConsistencyResult {
  issues: ConsistencyIssue[];
  /** 统计：目标数 */
  objectiveCount: number;
  /** 统计：评价数 */
  exerciseCount: number;
  /** 统计：被覆盖的目标数 */
  coveredObjectiveCount: number;
  /** 一致性分数（0-100，100 表示完全一致） */
  score: number;
}

/** 仅取教学节点（排除正文节点） */
function getTeachingNodes(doc: LessonPlanDocument): LessonPlanNode[] {
  return doc.nodes.filter((n): n is LessonPlanNode => n.type !== "textbook_content");
}

/** 安全读取 exercise 节点的知识点 ID 集合 */
function getExerciseKpIds(node: LessonPlanNode): Set<string> {
  if (node.type !== "exercise") return new Set();
  const data = node.data as ExerciseBlockData;
  const ids = new Set<string>();
  for (const item of data.items ?? []) {
    if (item.source === "inline" && item.inlineContent?.knowledgePointIds) {
      for (const kp of item.inlineContent.knowledgePointIds) ids.add(kp);
    }
  }
  for (const kp of data.knowledgePointIds ?? []) ids.add(kp);
  return ids;
}

/**
 * 执行目标-评价一致性校验。纯函数，无副作用。
 *
 * 当前以"节点"为粒度：objective 节点视为目标单元，exercise 节点视为评价单元。
 * 只要课案中存在至少一个 exercise，即视为目标已被覆盖（保守判定）。
 */
export function checkConsistency(doc: LessonPlanDocument): ConsistencyResult {
  const teachingNodes = getTeachingNodes(doc);
  const objectiveNodes = teachingNodes.filter((n) => n.type === "objective");
  const exerciseNodes = teachingNodes.filter((n) => n.type === "exercise");

  const issues: ConsistencyIssue[] = [];

  if (objectiveNodes.length === 0) {
    issues.push({ code: "noObjective", severity: "warning" });
  }

  if (exerciseNodes.length === 0) {
    issues.push({ code: "noExercise", severity: "warning" });
  }

  for (const ex of exerciseNodes) {
    const kpIds = getExerciseKpIds(ex);
    if (kpIds.size === 0) {
      issues.push({
        code: "exerciseNoKnowledgePoint",
        severity: "warning",
        nodeId: ex.id,
        nodeTitle: ex.title,
      });
    }
  }

  let coveredCount = 0;
  for (const obj of objectiveNodes) {
    const isCovered = exerciseNodes.length > 0;
    if (isCovered) {
      coveredCount++;
    } else {
      issues.push({
        code: "objectiveNotAssessed",
        severity: "warning",
        nodeId: obj.id,
        nodeTitle: obj.title,
        params: { title: obj.title || obj.type },
      });
    }
  }

  const totalChecks = objectiveNodes.length + exerciseNodes.length;
  const failedChecks = issues.filter((i) => i.severity === "warning").length;
  const score = totalChecks === 0 ? 100 : Math.max(0, Math.round(100 - (failedChecks / totalChecks) * 100));

  return {
    issues,
    objectiveCount: objectiveNodes.length,
    exerciseCount: exerciseNodes.length,
    coveredObjectiveCount: coveredCount,
    score,
  };
}

/** 便捷函数：是否存在 warning 级别问题 */
export function hasConsistencyWarnings(result: ConsistencyResult): boolean {
  return result.issues.some((i) => i.severity === "warning");
}
