import { createHash } from "node:crypto";
import { readdir, readFile, stat } from "node:fs/promises";
import path from "node:path";

import { FACT_KINDS, InitializationStore } from "./initialization-store.ts";
import type { ProjectContext } from "./project-context.ts";

export interface LowTierDocumentOrganizer {
  role: string;
  model: string;
  organize(input: { document: string; project: string; knownFacts: string[]; unknowns: string[] }): Promise<string>;
}

export type DocumentGenerationMode = "low-tier-model" | "template";
export type DocumentGenerationResult = { targetPath: string; status: "complete" | "conflict" | "failed"; outputPath?: string; error?: string };

type Fact = { kind: string; repository: string; objectKey: string; value: Record<string, unknown>; evidenceLevel: string };
type SourceDocument = { id: string; path: string; contentHash: string; title: string; headings: string[]; excerpt: string };
type DocumentClassification = { sourceDocumentId: string; scope: "project" | "requirement" | "unknown"; documentType: KnowledgeDocumentType | "unknown"; requirementId?: string; requirementTitle?: string; confidence: "exact" | "inferred" | "heuristic"; reasons: string[] };
type DocumentKnowledge = { sources: SourceDocument[]; classifications: DocumentClassification[] };
type KnowledgePageType = "source-summary" | "entity" | "concept" | "requirement" | "synthesis" | "bug-report" | "bug-fix" | "incident" | "lesson";
type KnowledgePage = {
  id: string;
  path: string;
  type: KnowledgePageType;
  title: string;
  status: "draft" | "inbox";
  sourceRefs: string[];
  relatedRequirements: string[];
  relatedCodeFacts: string[];
  confidence: "high" | "medium" | "low";
};
type IndexedDocumentScope = "project" | "requirement" | "knowledge";
type InterfaceContract = {
  id: string;
  name: string;
  kind: "tool" | "skill" | "cli" | "artifact";
  path: string;
  purpose: string;
  approval: "read" | "exec" | "none";
  actions: Array<{
    name: string;
    purpose: string;
    request: Record<string, unknown>;
    response: Record<string, unknown>;
    writes: string[];
    reads: string[];
  }>;
};
const TEMPLATE_VERSION = "project-context-v2";
const KNOWLEDGE_DOCUMENT_TYPES = [
  "requirements",
  "product-design",
  "architecture",
  "frontend-tech",
  "backend-tech",
  "test-cases",
  "api",
] as const;
type KnowledgeDocumentType = typeof KNOWLEDGE_DOCUMENT_TYPES[number];

function markdownCell(value: unknown): string {
  return String(value ?? "未确认").replaceAll("|", "\\|").replace(/[\r\n]+/g, " ");
}
function sha(value: string): string { return `sha256:${createHash("sha256").update(value).digest("hex")}`; }
async function currentHash(target: string): Promise<string | null> {
  try { return sha(await readFile(target, "utf8")); } catch { return null; }
}
function safeNarrative(value: string): string {
  const text = value.trim();
  if (!text) return "现有数据不足以确认更多项目用途。";
  if (text.includes("```") || /\b(?:schema_version|snapshotId|runId|sha256:|CREATE TABLE)\b/i.test(text)) throw new Error("Model output contains internal structure or unsupported machine details");
  return text;
}
function table(headers: string[], rows: unknown[][]): string {
  return [`| ${headers.join(" | ")} |`, `|${headers.map(() => "---").join("|")}|`, ...rows.map(row => `| ${row.map(markdownCell).join(" | ")} |`)].join("\n");
}
function yamlString(value: string): string { return JSON.stringify(value); }
function frontmatter(fields: Record<string, string | boolean | string[] | null | undefined>): string {
  const lines = ["---"];
  for (const [key, value] of Object.entries(fields)) {
    if (value === undefined || value === null) continue;
    if (Array.isArray(value)) lines.push(`${key}: [${value.map(yamlString).join(", ")}]`);
    else if (typeof value === "boolean") lines.push(`${key}: ${value ? "true" : "false"}`);
    else lines.push(`${key}: ${yamlString(value)}`);
  }
  lines.push("---", "");
  return lines.join("\n");
}
function documentTitle(type: KnowledgeDocumentType): string {
  return ({
    "requirements": "需求文档",
    "product-design": "产品与交互设计",
    "architecture": "架构设计",
    "frontend-tech": "前端技术方案",
    "backend-tech": "后端技术方案",
    "test-cases": "用例与验收",
    "api": "API 文档",
  } satisfies Record<KnowledgeDocumentType, string>)[type];
}
function codeIndexGeneration(snapshot: { indexRef?: unknown }): string | undefined {
  const indexRef = snapshot.indexRef as Record<string, unknown> | undefined;
  return typeof indexRef?.generation === "string" ? indexRef.generation : undefined;
}
function slug(value: string): string {
  return value.trim().toLowerCase().replace(/[^\p{L}\p{N}]+/gu, "-").replace(/^-|-$/g, "").slice(0, 48) || "document";
}
function titleFromMarkdown(relativePath: string, content: string): string {
  const heading = content.match(/^#\s+(.+)$/m)?.[1]?.trim();
  return heading || path.basename(relativePath).replace(/\.[^.]+$/, "");
}
function headingsFromMarkdown(content: string): string[] {
  return [...content.matchAll(/^#{1,4}\s+(.+)$/gm)].map(match => match[1].trim()).slice(0, 40);
}
function excerpt(content: string): string {
  return content.replace(/^---[\s\S]*?---\s*/m, "").replace(/\s+/g, " ").trim().slice(0, 500);
}
function classifyDocument(document: SourceDocument): DocumentClassification {
  const haystack = `${document.path}\n${document.title}\n${document.headings.join("\n")}\n${document.excerpt}`.toLowerCase();
  const reasons: string[] = [];
  const hit = (type: KnowledgeDocumentType, patterns: RegExp[]) => {
    const matched = patterns.find(pattern => pattern.test(haystack));
    if (!matched) return false;
    reasons.push(`${type}:${matched.source}`);
    return true;
  };
  let documentType: KnowledgeDocumentType | "unknown" = "unknown";
  if (hit("api", [/\bapi\b/, /接口/, /openapi/, /swagger/])) documentType = "api";
  else if (hit("test-cases", [/test[-_ ]?case/, /测试/, /用例/, /验收/, /acceptance/])) documentType = "test-cases";
  else if (hit("frontend-tech", [/frontend/, /前端/, /\bh5\b/, /admin/, /react|vue|vite|router|组件/])) documentType = "frontend-tech";
  else if (hit("backend-tech", [/backend/, /后端/, /server/, /服务端/, /handler|service|repository|gorm|gin/])) documentType = "backend-tech";
  else if (hit("architecture", [/architecture/, /架构/, /系统设计/, /数据流/, /部署/])) documentType = "architecture";
  else if (hit("product-design", [/product[-_ ]?design/, /交互/, /产品设计/, /原型/, /页面设计/, /wireframe/])) documentType = "product-design";
  else if (hit("requirements", [/requirement/, /\bprd\b/, /需求/, /feature/, /用户故事/])) documentType = "requirements";
  const requirementMatch = haystack.match(/\b(req[-_ ]?\d{1,5})\b/i) ?? haystack.match(/需求[\s：:]*([a-z0-9_-]{2,40})/i);
  const requirementId = requirementMatch?.[1]?.toUpperCase().replace(/[_ ]/g, "-");
  const pathLooksRequirement = /(^|\/)(features?|requirements?|需求|prd)(\/|$)/i.test(document.path) || Boolean(requirementId);
  return {
    sourceDocumentId: document.id,
    scope: documentType === "unknown" ? "unknown" : pathLooksRequirement ? "requirement" : "project",
    documentType,
    requirementId,
    requirementTitle: requirementId ? document.title : undefined,
    confidence: requirementId ? "exact" : documentType === "unknown" ? "heuristic" : pathLooksRequirement ? "inferred" : "heuristic",
    reasons: reasons.length ? reasons : ["no strong classifier match"],
  };
}
function shouldInspectDocument(relativePath: string): boolean {
  if (!/\.(?:md|mdx|txt|adoc|rst|yaml|yml|json)$/i.test(relativePath)) return false;
  if (/^(?:\.git|node_modules|dist|build|coverage|vendor|target)\//.test(relativePath)) return false;
  if (/^\.planning\/code-index\//.test(relativePath)) return false;
  if (/^\.context\/requirements\/_template\//.test(relativePath)) return false;
  return /^(?:README|docs\/|features\/|requirements\/|\.context\/|\.planning\/requirements\/|\.planning\/features\/|test-environment-guide|skit\/docs\/|text\/)/i.test(relativePath);
}
async function discoverSourceDocuments(root: string): Promise<DocumentKnowledge> {
  const documents: SourceDocument[] = [];
  async function walk(directory: string, relative = ""): Promise<void> {
    let entries;
    try { entries = await readdir(path.join(directory, relative), { withFileTypes: true }); } catch { return; }
    for (const entry of entries.sort((a, b) => a.name.localeCompare(b.name))) {
      const child = path.join(relative, entry.name).replaceAll("\\", "/");
      if (entry.isDirectory()) {
        if (/^(?:\.git|node_modules|dist|build|coverage|vendor|target)(\/|$)/.test(child)) continue;
        await walk(directory, child); continue;
      }
      if (!entry.isFile() || !shouldInspectDocument(child)) continue;
      const absolute = path.join(directory, child);
      const info = await stat(absolute);
      if (info.size > 512 * 1024) continue;
      const content = await readFile(absolute, "utf8");
      const contentHash = sha(content);
      documents.push({ id: `DOC-${String(documents.length + 1).padStart(4, "0")}`, path: child, contentHash, title: titleFromMarkdown(child, content), headings: headingsFromMarkdown(content), excerpt: excerpt(content) });
    }
  }
  await walk(root);
  const sources = documents.slice(0, 500);
  return { sources, classifications: sources.map(classifyDocument) };
}
function sourcesFor(type: KnowledgeDocumentType, knowledge: DocumentKnowledge, scope?: "project" | "requirement"): Array<{ source: SourceDocument; classification: DocumentClassification }> {
  return knowledge.classifications
    .filter(classification => classification.documentType === type && (!scope || classification.scope === scope))
    .map(classification => ({ classification, source: knowledge.sources.find(source => source.id === classification.sourceDocumentId)! }))
    .filter(item => Boolean(item.source));
}
function factRef(fact: Fact): string {
  if (fact.kind === "api") return `api_route:${String(fact.value.method ?? "UNKNOWN").toUpperCase()}:${apiPath(fact)}`;
  return `${fact.kind}:${fact.repository}:${fact.objectKey}`;
}
function relatedCodeFactsForSource(source: SourceDocument, classification: DocumentClassification, facts: Fact[]): string[] {
  const haystack = `${source.path}\n${source.title}\n${source.headings.join("\n")}\n${source.excerpt}`.toLowerCase();
  const candidates = facts.filter(fact => {
    const values = Object.values(fact.value).filter(value => typeof value === "string" || typeof value === "number").map(String);
    return values.some(value => {
      const token = value.toLowerCase();
      return token.length >= 3 && (haystack.includes(token) || token.includes(slug(source.title).replaceAll("-", "")));
    });
  });
  const fallback = classification.documentType === "api" ? facts.filter(fact => fact.kind === "api")
    : classification.documentType === "frontend-tech" || classification.documentType === "product-design" ? facts.filter(fact => fact.kind === "page")
      : classification.documentType === "backend-tech" || classification.documentType === "architecture" ? facts.filter(fact => ["api", "data_model", "integration"].includes(fact.kind))
        : [];
  return [...new Set([...candidates, ...fallback].slice(0, 12).map(factRef))];
}
function extractKnowledgeNames(source: SourceDocument): Array<{ kind: "entity" | "concept"; title: string; confidence: "medium" | "low" }> {
  const names = new Map<string, { kind: "entity" | "concept"; title: string; confidence: "medium" | "low" }>();
  const text = `${source.title}\n${source.headings.join("\n")}\n${source.excerpt}`;
  for (const match of text.matchAll(/(?:用户|管理员|客户|商户|运营|后台|前台|服务|系统|订单|资源|内容|权限|支付|审核|任务|消息|通知|接口|页面|角色|数据|模型|流程)/g)) {
    const title = match[0];
    const kind: "entity" | "concept" = /用户|管理员|客户|商户|运营|服务|系统|订单|资源|内容|任务|消息|通知|角色|数据|模型/.test(title) ? "entity" : "concept";
    names.set(`${kind}:${title}`, { kind, title, confidence: "low" });
  }
  for (const heading of source.headings.slice(0, 8)) {
    const clean = heading.replace(/[：:：].*$/, "").trim();
    if (clean.length >= 2 && clean.length <= 32) names.set(`concept:${clean}`, { kind: "concept", title: clean, confidence: "medium" });
  }
  return [...names.values()].slice(0, 40);
}
function sourcePageId(source: SourceDocument): string { return `source-${source.id.toLowerCase()}`; }
function knowledgePagePath(type: KnowledgePageType, titleOrId: string): string {
  const folder = ({
    "source-summary": "sources",
    entity: "entities",
    concept: "concepts",
    requirement: "requirements",
    synthesis: "synthesis",
    "bug-report": "bugs",
    "bug-fix": "fixes",
    incident: "incidents",
    lesson: "lessons",
  } satisfies Record<KnowledgePageType, string>)[type];
  return `.context/knowledge/${folder}/${slug(titleOrId)}.md`;
}
function sourceTable(items: Array<{ source: SourceDocument; classification: DocumentClassification }>): string {
  if (!items.length) return "未发现可归类的已有文档。";
  return table(["来源", "标题", "置信度", "理由"], items.map(item => [item.source.path, item.source.title, item.classification.confidence, item.classification.reasons.join("；")]));
}
function apiPath(fact: Fact): string {
  return String(fact.value.path ?? fact.objectKey.replace(/^route:[^:]+:/, ""));
}
function apiDomain(route: string): string {
  const first = route.split("?")[0].split("/").filter(Boolean)[0];
  if (!first) return "root";
  if (/^\{.+\}$/.test(first) || first.startsWith(":")) return "parameterized";
  return first;
}
function humanDomain(value: string): string {
  return value === "root" ? "根路径" : value === "parameterized" ? "参数化根路径" : value;
}
function validModelName(value: unknown): string | undefined {
  const name = String(value ?? "").trim();
  if (!/^[A-Za-z_][A-Za-z0-9_.-]{2,63}$/.test(name)) return undefined;
  const stop = new Set(["and", "are", "for", "not", "is", "etc", "data", "arguments", "options", "output", "phrase", "definition", "converts"]);
  if (stop.has(name.toLowerCase().replace(/[.]+$/, ""))) return undefined;
  return name;
}
function architectureBody(context: ProjectContext, facts: Fact[], intro: string, snapshotId: string, knowledge: DocumentKnowledge, generation?: string): string {
  const repositories = [...new Set(facts.map(fact => fact.repository).filter(Boolean))].sort();
  const apis = facts.filter(fact => fact.kind === "api");
  const models = [...new Set(facts.filter(fact => fact.kind === "data_model").map(fact => validModelName(fact.value.name ?? fact.objectKey)).filter((item): item is string => Boolean(item)))].sort();
  const integrations = facts.filter(fact => fact.kind === "integration");
  const groupedApis = new Map<string, string[]>();
  for (const fact of apis) {
    const route = apiPath(fact);
    const domain = apiDomain(route);
    groupedApis.set(domain, [...(groupedApis.get(domain) ?? []), route]);
  }
  const apiRows = [...groupedApis.entries()]
    .sort(([a], [b]) => a.localeCompare(b))
    .slice(0, 30)
    .map(([domain, routes]) => [humanDomain(domain), [...new Set(routes)].slice(0, 8).join("、"), routes.length]);
  const evidence = [
    "",
    "## 已发现资料",
    "",
    sourceTable(sourcesFor("architecture", knowledge, "project")),
    "",
    "## 证据绑定",
    "",
    `- 初始化快照：${snapshotId}`,
    `- 代码索引 generation：${generation ?? "未生成或已跳过"}`,
    "- 本文档由项目初始化创建骨架，人工确认前均为 draft。",
    "",
  ].join("\n");
  return `${frontmatter({ type: "project-architecture", scope: "project", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 项目架构设计\n\n${intro}\n\n## 当前结论\n\n- 当前项目识别到 ${repositories.length || "未确认"} 个代码库：${repositories.join("、") || "未确认"}。\n- 后端/API 侧观察到 ${apis.length} 条静态路由声明，以下按路径前缀粗分能力域。\n- 数据模型候选已过滤明显普通词；仍需人工确认真实表、实体和持久化边界。\n- 本文档只陈述静态可观察事实，不确认部署拓扑、运行时依赖或权限链路。\n\n## 系统组成\n\n${table(["代码库", "观察到的文件数", "观察到的能力"], repositories.map(repository => [repository, facts.filter(fact => fact.repository === repository && fact.kind === "asset").length, [...new Set(facts.filter(fact => fact.repository === repository && ["api", "page", "data_model", "integration"].includes(fact.kind)).map(fact => fact.kind))].join("、") || "未发现"]))}\n\n## API 能力分组\n\n${apiRows.length ? table(["能力域", "代表路径", "路径数量"], apiRows) : "未发现可分组 API。"}\n\n## 数据模型候选\n\n${models.length ? table(["模型或表名"], models.slice(0, 80).map(name => [name])) : "未发现可信数据模型候选。"}\n\n## 集成与运行时线索\n\n${integrations.length ? table(["类型", "对象"], integrations.slice(0, 80).map(fact => [fact.value.kind ?? fact.value.name ? "配置/任务" : "外部主机", fact.value.host ?? fact.value.name ?? fact.objectKey])) : "未发现外部集成线索。"}\n\n## 待确认架构问题\n\n- 前端、后端、任务执行器和外部服务的部署边界是什么？\n- 认证、授权和用户上下文如何在前后端之间传递？\n- 是否存在异步任务队列、调度器、设备/浏览器执行器或后台 worker？\n- 核心数据模型、数据库类型和迁移策略是什么？\n- 关键能力域之间的数据流、失败降级和监控指标是什么？\n${evidence}`;
}

async function narrative(context: ProjectContext, document: string, facts: Fact[], mode: DocumentGenerationMode, organizer?: LowTierDocumentOrganizer): Promise<{ text: string; modelRole?: string }> {
  const knownFacts = facts.slice(0, 80).map(fact => `${fact.kind}: ${Object.values(fact.value).filter(value => typeof value === "string" || typeof value === "number").slice(0, 3).join(" / ")}`);
  const unknowns = facts.filter(fact => fact.evidenceLevel === "candidate").map(fact => fact.objectKey).slice(0, 20);
  if (mode === "template") return { text: "以下内容由固定项目上下文快照整理。源码静态声明不代表运行环境已启用，未确认项保持未确认。", modelRole: "template" };
  if (!organizer) throw new Error("Low-tier document generation was requested but no organizer is configured");
  if (!/low|small|mini|flash|lite/i.test(`${organizer.role} ${organizer.model}`)) throw new Error("Document organizer is not configured as a low-tier model");
  return { text: safeNarrative(await organizer.organize({ document, project: context.manifest.name, knownFacts, unknowns })), modelRole: `${organizer.role}:${organizer.model}` };
}

function renderDocuments(context: ProjectContext, facts: Fact[], intro: string, collections: Array<{ repository?: string; kind?: string; status?: string; diagnostics?: unknown[] }>): Array<{ targetPath: string; body: string }> {
  const assets = facts.filter(fact => fact.kind === "asset");
  const technologies = facts.filter(fact => fact.kind === "technology");
  const commands = facts.filter(fact => fact.kind === "toolchain");
  const rules = facts.filter(fact => fact.kind === "rule");
  const capabilities = facts.filter(fact => fact.kind === "capability");
  const repositories = [...new Set(facts.map(fact => fact.repository).filter(Boolean))].sort();
  const outputs: Array<{ targetPath: string; body: string }> = [];
  outputs.push({ targetPath: ".planning/PROJECT.md", body: [
    `# ${context.manifest.name}`, "", intro, "", "## 代码库组成", "",
    table(["代码库", "文件数量", "已观察能力"], repositories.map(repository => [repository, assets.filter(fact => fact.repository === repository).length, [...new Set(facts.filter(fact => fact.repository === repository && ["api", "page", "data_model", "integration"].includes(fact.kind)).map(fact => fact.kind))].join("、") || "未发现"])),
    "", "## 技术概况", "", table(["声明", "来源类型"], technologies.slice(0, 100).map(fact => [fact.value.name ?? fact.value.extension, fact.value.declaration ?? "文件统计"])),
    "", "## 已知边界", "", "当前信息来自本地静态扫描。接口声明不证明服务可访问，页面声明不证明运行时渲染，命令声明不表示已经执行。", "",
  ].join("\n") });
  outputs.push({ targetPath: ".planning/toolchain.md", body: ["# 工具链", "", intro, "", table(["代码库", "名称", "命令", "工作目录", "执行状态"], commands.map(fact => [fact.repository, fact.value.name, fact.value.command, fact.value.workingDirectory || ".", "仅发现，未执行"])), ""].join("\n") });
  outputs.push({ targetPath: ".planning/navigation/INDEX.md", body: ["# 项目导航", "", intro, "", table(["代码库", "导航说明", "扫描文件数"], repositories.map(repository => [repository, `.planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, assets.filter(fact => fact.repository === repository).length])), ""].join("\n") });
  for (const repository of repositories) {
    const local = facts.filter(fact => fact.repository === repository);
    outputs.push({ targetPath: `.planning/navigation/${repository.replace(/[^\p{L}\p{N}._-]/gu, "-")}.md`, body: [
      `# ${repository} 导航`, "", intro, "", "## 文件与入口", "",
      table(["路径", "类型"], local.filter(fact => fact.kind === "asset").slice(0, 200).map(fact => [fact.value.path, fact.value.extension || "无扩展名"])),
      "", "## 已发现能力", "", table(["类型", "对象"], local.filter(fact => ["api", "page", "data_model", "integration"].includes(fact.kind)).slice(0, 200).map(fact => [fact.kind, fact.value.path ?? fact.value.route ?? fact.value.name ?? fact.value.host ?? "未确认"])), "",
    ].join("\n") });
  }
  outputs.push({ targetPath: ".planning/rules/INDEX.md", body: ["# 项目规则来源", "", intro, "", table(["代码库", "规则文件", "性质"], rules.map(fact => [fact.repository, fact.value.path, fact.value.explicit ? "显式规则" : "惯例候选"])), "", "未登记的惯例候选不会自动成为强制规则。", ""].join("\n") });
  outputs.push({ targetPath: ".planning/initialization/report.md", body: [
    "# 项目初始化报告", "", intro, "", "## 数据可用范围", "",
    table(["采集器", "支持范围", "限制"], capabilities.map(fact => [fact.objectKey, Array.isArray(fact.value.supported) ? fact.value.supported.join("、") : "未确认", Array.isArray(fact.value.limitations) ? fact.value.limitations.join("；") : "未确认"])),
    "", "## 分项采集状态", "", table(["代码库", "专题", "状态", "诊断数"], collections.map(item => [item.repository || "全局", item.kind, item.status, item.diagnostics?.length ?? 0])),
    "", "## 未知项", "", ...((facts.some(fact => fact.evidenceLevel === "candidate") ? facts.filter(fact => fact.evidenceLevel === "candidate").slice(0, 30).map(fact => `- ${markdownCell(fact.objectKey)}：候选，尚未核对`) : ["未记录候选未知项。"])), "",
  ].join("\n") });
  return outputs;
}

function projectKnowledgeBody(context: ProjectContext, type: KnowledgeDocumentType, facts: Fact[], intro: string, snapshotId: string, knowledge: DocumentKnowledge, generation?: string): string {
  const apis = facts.filter(fact => fact.kind === "api");
  const pages = facts.filter(fact => fact.kind === "page");
  const models = facts.filter(fact => fact.kind === "data_model");
  const tech = facts.filter(fact => fact.kind === "technology");
  const commands = facts.filter(fact => fact.kind === "toolchain");
  const integrations = facts.filter(fact => fact.kind === "integration");
  const repositoryNames = [...new Set(facts.map(fact => fact.repository).filter(Boolean))].sort();
  const header = frontmatter({
    type: `project-${type}`,
    scope: "project",
    project: context.manifest.name,
    status: "draft",
    source_snapshot: snapshotId,
    code_index_generation: generation,
    managed: true,
  });
  const evidence = [
    "",
    "## 已发现资料",
    "",
    sourceTable(sourcesFor(type, knowledge, "project")),
    "",
    "## 证据绑定",
    "",
    `- 初始化快照：${snapshotId}`,
    `- 代码索引 generation：${generation ?? "未生成或已跳过"}`,
    "- 本文档由项目初始化创建骨架，人工确认前均为 draft。",
    "",
  ].join("\n");
  if (type === "requirements") return `${header}# 项目总体需求\n\n${intro}\n\n## 项目范围\n\n${table(["代码库", "扫描事实数"], repositoryNames.map(repository => [repository, facts.filter(fact => fact.repository === repository).length]))}\n\n## 已观察业务能力\n\n${table(["类型", "对象"], [...pages, ...apis, ...models].slice(0, 80).map(fact => [fact.kind, fact.value.route ?? fact.value.path ?? fact.value.name ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 项目目标与非目标\n- 用户角色与核心流程\n- 业务约束与优先级\n${evidence}`;
  if (type === "product-design") return `${header}# 项目产品与交互设计\n\n${intro}\n\n## 已发现页面/入口\n\n${table(["代码库", "页面或入口"], pages.slice(0, 100).map(fact => [fact.repository, fact.value.route ?? fact.value.componentPath ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 信息架构\n- 页面状态与异常态\n- 文案、权限与可访问性约束\n${evidence}`;
  if (type === "architecture") return architectureBody(context, facts, intro, snapshotId, knowledge, generation);
  if (type === "frontend-tech") return `${header}# 项目前端技术方案\n\n${intro}\n\n## 已观察前端技术\n\n${table(["声明", "来源"], tech.filter(fact => /react|vue|vite|router|zustand|tailwind|typescript|eslint|vitest/i.test(String(fact.value.name ?? fact.value.extension ?? ""))).slice(0, 120).map(fact => [fact.value.name ?? fact.value.extension, fact.value.declaration ?? fact.value.path ?? "文件统计"]))}\n\n## 待人工补充\n\n- 路由、状态管理与组件边界\n- API 调用约定\n- 构建、测试与发布规则\n${evidence}`;
  if (type === "backend-tech") return `${header}# 项目后端技术方案\n\n${intro}\n\n## 已观察后端事实\n\n${table(["类型", "对象"], [...apis, ...models, ...integrations].slice(0, 120).map(fact => [fact.kind, fact.value.path ?? fact.value.name ?? fact.value.host ?? fact.objectKey]))}\n\n## 待人工补充\n\n- handler/service/repository 分层\n- 数据库迁移与任务调度规则\n- 鉴权、错误码、日志与监控\n${evidence}`;
  if (type === "test-cases") return `${header}# 项目用例与验收\n\n${intro}\n\n## 已发现测试/命令线索\n\n${table(["代码库", "名称", "命令或路径"], commands.slice(0, 120).map(fact => [fact.repository, fact.value.name ?? "test-definition", fact.value.command ?? fact.value.testDefinition ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 核心主流程验收用例\n- 异常路径与权限用例\n- 回归范围与自动化策略\n${evidence}`;
  return `${header}# 项目 API 文档\n\n${intro}\n\n## 已发现 API 声明\n\n${table(["代码库", "方法", "路径"], apis.slice(0, 160).map(fact => [fact.repository, fact.value.method ?? "未确认", fact.value.path ?? fact.objectKey]))}\n\n## 待人工补充\n\n- 认证与公共请求头\n- 通用响应结构与错误码\n- 兼容性与废弃策略\n${evidence}`;
}

function requirementTemplateBody(context: ProjectContext, type: KnowledgeDocumentType, snapshotId: string, generation?: string): string {
  const header = frontmatter({
    type: type === "requirements" ? "requirement-spec" : `requirement-${type}`,
    scope: "requirement",
    project: context.manifest.name,
    requirement_id: "REQ-TEMPLATE",
    status: "template",
    source_snapshot: snapshotId,
    code_index_generation: generation,
    related_paths: [],
    related_apis: [],
    managed: true,
  });
  return `${header}# ${documentTitle(type)}\n\n本文件是需求级文档模板。创建具体需求时复制到 \`.context/requirements/REQ-xxx-短名/${type === "requirements" ? "requirement" : type}.md\`，并把 frontmatter 中的 \`requirement_id\`、\`status\`、关联路径和 API 补全。\n\n## 当前结论\n\n- 待补充。\n\n## 证据与约束\n\n- 初始化快照：${snapshotId}\n- 代码索引 generation：${generation ?? "未生成或已跳过"}\n- 需求级内容必须引用项目级文档和实际源码证据，不能只凭推断确认。\n\n## 待补充\n\n- 背景与目标\n- 范围与非目标\n- 设计决策\n- 验收条件\n`;
}

function inboxRequirementBody(context: ProjectContext, source: SourceDocument, classification: DocumentClassification, snapshotId: string, generation?: string): Array<{ targetPath: string; body: string }> {
  const requirementId = classification.requirementId ?? source.id;
  const folder = `${requirementId}-${slug(source.title)}`;
  const base = `.context/requirements/_inbox/${folder}`;
  const documentType = classification.documentType === "unknown" ? "requirements" : classification.documentType;
  const body = `${frontmatter({ type: documentType === "requirements" ? "requirement-spec" : `requirement-${documentType}`, scope: "requirement", project: context.manifest.name, requirement_id: requirementId, status: "inbox", source_snapshot: snapshotId, code_index_generation: generation, source_documents: [source.path], managed: true })}# ${source.title}\n\n本文档由初始化阶段从已有资料归档而来，尚未确认是否为正式需求。\n\n## 来源摘要\n\n${source.excerpt || "无可用摘要。"}\n\n## 来源文档\n\n${table(["来源", "标题", "内容哈希"], [[source.path, source.title, source.contentHash]])}\n\n## 归类信息\n\n- 类型：${classification.documentType}\n- 置信度：${classification.confidence}\n- 理由：${classification.reasons.join("；")}\n\n## 待确认\n\n- 是否转为正式 REQ 编号\n- 是否拆分为多个需求\n- 是否补齐产品、技术、API 与用例文档\n`;
  return [
    { targetPath: `${base}/index.md`, body },
    { targetPath: `${base}/${documentType === "requirements" ? "requirement" : documentType}.md`, body },
  ];
}

function buildKnowledgePages(knowledge: DocumentKnowledge, facts: Fact[]): KnowledgePage[] {
  const pages = new Map<string, KnowledgePage>();
  for (const source of knowledge.sources) {
    const classification = knowledge.classifications.find(item => item.sourceDocumentId === source.id);
    const requirementId = classification?.requirementId ? classification.requirementId : classification?.scope === "requirement" ? source.id : undefined;
    const sourceRef = sourcePageId(source);
    const relatedCodeFacts = classification ? relatedCodeFactsForSource(source, classification, facts) : [];
    pages.set(sourceRef, {
      id: sourceRef,
      path: knowledgePagePath("source-summary", sourceRef),
      type: "source-summary",
      title: source.title,
      status: classification?.scope === "requirement" && classification.confidence !== "exact" ? "inbox" : "draft",
      sourceRefs: [source.path],
      relatedRequirements: requirementId ? [requirementId] : [],
      relatedCodeFacts,
      confidence: classification?.confidence === "exact" ? "high" : classification?.confidence === "inferred" ? "medium" : "low",
    });
    if (classification?.scope === "requirement") {
      const id = requirementId ?? `REQ-${source.id}`;
      pages.set(`requirement-${slug(id)}`, {
        id: `requirement-${slug(id)}`,
        path: knowledgePagePath("requirement", id),
        type: "requirement",
        title: classification.requirementTitle ?? source.title,
        status: classification.confidence === "exact" ? "draft" : "inbox",
        sourceRefs: [sourceRef],
        relatedRequirements: [id],
        relatedCodeFacts,
        confidence: classification.confidence === "exact" ? "high" : "medium",
      });
    }
    for (const extracted of extractKnowledgeNames(source)) {
      const id = `${extracted.kind}-${slug(extracted.title)}`;
      const existing = pages.get(id);
      pages.set(id, {
        id,
        path: knowledgePagePath(extracted.kind, extracted.title),
        type: extracted.kind,
        title: extracted.title,
        status: "draft",
        sourceRefs: [...new Set([...(existing?.sourceRefs ?? []), sourceRef])],
        relatedRequirements: [...new Set([...(existing?.relatedRequirements ?? []), ...(requirementId ? [requirementId] : [])])],
        relatedCodeFacts: [...new Set([...(existing?.relatedCodeFacts ?? []), ...relatedCodeFacts])].slice(0, 20),
        confidence: existing?.confidence === "medium" || extracted.confidence === "medium" ? "medium" : "low",
      });
    }
  }
  const allSourceRefs = [...new Set([...pages.values()].flatMap(page => page.sourceRefs).filter(ref => ref.startsWith("source-")))];
  pages.set("synthesis-project-overview", {
    id: "synthesis-project-overview",
    path: ".context/knowledge/synthesis/project-overview.md",
    type: "synthesis",
    title: "项目知识总览",
    status: "draft",
    sourceRefs: allSourceRefs,
    relatedRequirements: [...new Set([...pages.values()].flatMap(page => page.relatedRequirements))].slice(0, 100),
    relatedCodeFacts: [...new Set([...pages.values()].flatMap(page => page.relatedCodeFacts))].slice(0, 100),
    confidence: "medium",
  });
  return [...pages.values()].sort((a, b) => a.path.localeCompare(b.path));
}

function renderKnowledgePage(context: ProjectContext, page: KnowledgePage, sourceByRef: Map<string, SourceDocument>, snapshotId: string, generation?: string): string {
  const sourceRows = page.sourceRefs.map(ref => {
    const source = sourceByRef.get(ref);
    return source ? [ref, source.path, source.title] : [ref, ref, "外部或原始来源"];
  });
  const overview = page.type === "source-summary"
    ? "本页是原始资料的结构化摘要，用于替代直接对原始长文档做向量召回。"
    : page.type === "synthesis"
      ? "本页综合多个来源与代码事实，作为项目级知识入口。"
      : "本页沉淀初始化阶段识别出的项目知识单元，人工确认前保持草稿状态。";
  const sourceSummary = page.sourceRefs.map(ref => sourceByRef.get(ref)?.excerpt).filter(Boolean).slice(0, 5).join("\n\n");
  return `${frontmatter({
    type: page.type,
    id: page.id,
    title: page.title,
    status: page.status,
    project: context.manifest.name,
    source_refs: page.sourceRefs,
    related_requirements: page.relatedRequirements,
    related_code_facts: page.relatedCodeFacts,
    confidence: page.confidence,
    source_snapshot: snapshotId,
    code_index_generation: generation,
    managed: true,
  })}# ${page.title}

## Overview

${overview}

${sourceSummary ? `## Key Claims\n\n${sourceSummary.split(/[。；;\n]+/).map(item => item.trim()).filter(Boolean).slice(0, 12).map(item => `- ${item}`).join("\n")}\n\n` : ""}## Relationships

${page.relatedRequirements.length ? `- 关联需求：${page.relatedRequirements.join("、")}` : "- 关联需求：未确认"}
${page.relatedCodeFacts.length ? `- 关联代码事实：${page.relatedCodeFacts.slice(0, 20).join("、")}` : "- 关联代码事实：未确认"}

## Sources

${sourceRows.length ? table(["引用", "路径", "标题"], sourceRows) : "未绑定来源。"}

## Maintenance

- 状态：${page.status}
- 置信度：${page.confidence}
- 初始化快照：${snapshotId}
- 代码索引 generation：${generation ?? "未生成或已跳过"}
  `;
}

function bugKnowledgeTemplates(context: ProjectContext, snapshotId: string, generation?: string): Array<{ targetPath: string; body: string }> {
  const common = { project: context.manifest.name, status: "template", source_snapshot: snapshotId, code_index_generation: generation, managed: true };
  return [
    {
      targetPath: ".context/knowledge/bugs/_template.md",
      body: `${frontmatter({ ...common, type: "bug-report", id: "BUG-TEMPLATE", title: "BUG 模板", source_refs: [], related_requirements: [], related_code_facts: [], related_fixes: [], regression_tests: [], confidence: "manual" })}# BUG 模板

## Symptom

- 用户或系统观察到的异常现象。

## Impact

- 影响范围、严重程度、受影响角色或能力。

## Reproduction

1. 复现步骤待补充。

## Root Cause

- 根因待确认；不能只凭猜测填写为 confirmed。

## Evidence

- 日志、截图、报错、source_refs、related_code_facts。

## Fix Link

- 关联 FIX 记录。

## Regression

- 回归测试、验证命令或人工验证证据。
`,
    },
    {
      targetPath: ".context/knowledge/fixes/_template.md",
      body: `${frontmatter({ ...common, type: "bug-fix", id: "FIX-TEMPLATE", title: "FIX 模板", bug_refs: [], changed_code_facts: [], regression_tests: [], confidence: "manual" })}# FIX 模板

## Change Summary

- 修复动作摘要。

## Why This Fix

- 为什么选择这个修复方式，替代方案和风险是什么。

## Files Changed

- 关联文件或 changed_code_facts。

## Regression Coverage

- 新增或复用的回归测试。

## Remaining Risk

- 尚未覆盖或需观察的风险。
`,
    },
    {
      targetPath: ".context/knowledge/incidents/_template.md",
      body: `${frontmatter({ ...common, type: "incident", id: "INC-TEMPLATE", title: "INCIDENT 模板", related_bugs: [], related_fixes: [], related_code_facts: [], confidence: "manual" })}# INCIDENT 模板

## Timeline

- 发现、止血、修复、恢复的时间线。

## User Impact

- 用户影响和业务影响。

## Root Cause

- 事故根因和促成因素。

## Mitigation

- 临时缓解措施和永久修复。

## Follow-ups

- 后续行动、负责人和验证方式。
`,
    },
    {
      targetPath: ".context/knowledge/lessons/_template.md",
      body: `${frontmatter({ ...common, type: "lesson", id: "LESSON-TEMPLATE", title: "LESSON 模板", related_bugs: [], related_fixes: [], related_code_facts: [], confidence: "manual" })}# LESSON 模板

## Lesson

- 可复用经验或规则。

## Applies To

- 适用模块、能力、需求或技术边界。

## Check Rule

- 下次如何检查同类问题。

## Evidence

- 关联 BUG、FIX、事故或代码事实。
`,
    },
  ];
}

function initializationInterfaceContracts(): InterfaceContract[] {
  return [
    {
      id: "tool-devflow-project-context",
      name: "devflow_project_context",
      kind: "tool",
      path: "xd://devflow_project_context",
      purpose: "项目初始化控制面：扫描、查询、刷新、恢复、导入导出和权威切换。",
      approval: "exec",
      actions: [
        {
          name: "scan",
          purpose: "执行项目初始化扫描并发布上下文快照。",
          request: { action: "scan", mode: "full|fast", repositories: [{ name: "app", path: ".", role: "frontend|backend|shared|other" }], requiredKinds: FACT_KINDS, documentMode: "template|none", requestId: "stable-id" },
          response: { runId: "uuid", snapshotId: "uuid", completeness: "complete|partial", diagnostics: [], documents: [{ targetPath: ".context/project/api.md", status: "complete|conflict|failed" }] },
          reads: ["source tree", ".devflow/project.json"],
          writes: ["control.db", ".planning/code-index/", ".planning/document-index.json", ".planning/knowledge-index.json", ".context/", ".planning/PROJECT.md"],
        },
        {
          name: "query",
          purpose: "按固定 snapshot 查询初始化事实。",
          request: { action: "query", snapshotId: "uuid", kind: "api|page|data_model|...", repository: "optional", after: "cursor", limit: 100 },
          response: { snapshot: {}, facts: [], truncated: false, nextCursor: null },
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "diff",
          purpose: "比较两个明确快照，返回可比变化。",
          request: { action: "diff", fromSnapshotId: "uuid", toSnapshotId: "uuid" },
          response: { comparable: true, added: [], changed: [], removed: [], unavailable: [] },
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "generate-docs",
          purpose: "针对已发布快照重试生成文档投影。",
          request: { action: "generate-docs", snapshotId: "uuid" },
          response: [{ targetPath: ".context/project/api.md", status: "complete|conflict|failed", outputPath: "path" }],
          reads: ["control.db", ".planning/code-index/", "source documents"],
          writes: [".context/", ".planning/document-index.json", ".planning/knowledge-index.json"],
        },
        {
          name: "config",
          purpose: "读取当前工作区的初始化扫描配置。",
          request: { action: "config", workspaceId: "optional" },
          response: { mode: "full|fast", repositories: [], requiredKinds: FACT_KINDS },
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "workspaces",
          purpose: "列出当前项目已登记的工作区。",
          request: { action: "workspaces" },
          response: [{ id: "uuid", root: "path", active: true }],
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "rebind-workspace",
          purpose: "将当前项目根重新绑定到指定 workspaceId。",
          request: { action: "rebind-workspace", workspaceId: "uuid" },
          response: { id: "uuid", root: "path", active: true },
          reads: ["control.db", ".devflow/project.json"],
          writes: ["control.db"],
        },
        {
          name: "run-status",
          purpose: "读取初始化 run 的状态和发布快照。",
          request: { action: "run-status", runId: "uuid" },
          response: { id: "uuid", status: "preparing|collecting|validating|finished|failed|cancelled", publishedSnapshotId: "uuid|null", error: "string|null" },
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "cancel",
          purpose: "取消仍处于可写状态的初始化 run。",
          request: { action: "cancel", runId: "uuid" },
          response: { runId: "uuid", cancelled: true },
          reads: ["control.db"],
          writes: ["control.db"],
        },
        {
          name: "recover",
          purpose: "恢复租约过期但未结束的初始化 run。",
          request: { action: "recover", runId: "uuid" },
          response: { runId: "uuid", leaseToken: "opaque-token" },
          reads: ["control.db"],
          writes: ["control.db"],
        },
        {
          name: "import-legacy",
          purpose: "把旧版初始化产物导入数据库控制面。",
          request: { action: "import-legacy", sources: [".planning/PROJECT.md"], requestId: "stable-id" },
          response: { runId: "uuid", snapshotId: "uuid", imports: [] },
          reads: ["legacy initialization files"],
          writes: ["control.db"],
        },
        {
          name: "export",
          purpose: "把指定初始化快照导出为兼容文件。",
          request: { action: "export", snapshotId: "uuid", targetPath: ".planning/project-context-export.json" },
          response: { path: "path", contentHash: "sha256:..." },
          reads: ["control.db"],
          writes: ["targetPath"],
        },
        {
          name: "authority",
          purpose: "读取当前初始化权威来源和修订号。",
          request: { action: "authority" },
          response: { authority: "legacy|shadow|database", revision: 0 },
          reads: ["control.db"],
          writes: [],
        },
        {
          name: "switch-authority",
          purpose: "按期望 revision 切换初始化权威来源。",
          request: { action: "switch-authority", authority: "legacy|shadow|database", expectedRevision: 0, compatibilityExport: "optional" },
          response: { status: "action-specific", revision: "number when applicable" },
          reads: ["control.db"],
          writes: ["control.db", "compatibilityExport when requested"],
        },
        {
          name: "backup",
          purpose: "备份初始化控制数据库和内容状态。",
          request: { action: "backup", targetPath: ".planning/backups/initialization.db" },
          response: { backup: "absolute-path" },
          reads: ["control.db", "managed initialization documents"],
          writes: ["targetPath", "targetPath.manifest.json"],
        },
        {
          name: "restore",
          purpose: "从已验证备份恢复初始化控制数据库。",
          request: { action: "restore", targetPath: ".planning/backups/initialization.db" },
          response: { restored: true, database: "control.db" },
          reads: ["targetPath", "targetPath.manifest.json"],
          writes: ["control.db"],
        },
      ],
    },
    {
      id: "tool-devflow-skill-script",
      name: "devflow_skill_script",
      kind: "tool",
      path: "xd://devflow_skill_script",
      purpose: "执行 allowlisted 的确定性 skill 脚本，初始化主要使用数据库校验。",
      approval: "exec",
      actions: [
        {
          name: "project-context-db-validate",
          purpose: "校验初始化控制数据库、run、workspace、project 和快照关系。",
          request: { script: "project-context-db-validate", args: ["--database", "<control.db>", "--project-id", "<project-id>", "--workspace-id", "<workspace-id>", "--run-id", "<run-id>"] },
          response: { ok: true, diagnostics: [] },
          reads: ["control.db"],
          writes: [],
        },
      ],
    },
    {
      id: "skill-workflow-project-context",
      name: "skill://workflow-project-context",
      kind: "skill",
      path: "plugins/devflow-core/skills/workflow-project-context/SKILL.md",
      purpose: "项目上下文接口包装 skill：供委托子 agent 选择 action、调用初始化 contract、整理证据包并返回给父 agent。",
      approval: "none",
      actions: [
        {
          name: "delegated-project-context-task",
          purpose: "由子 agent 根据用户意图读取 interface-index/knowledge-index，调用 devflow_project_context 或 devflow_skill_script，并返回 result pack。",
          request: {
            intent: "initialize-project|refresh-after-code-change|refresh-after-bug-fix|query-facts|query-knowledge|query-api|query-bug-knowledge|compare-snapshots|regenerate-docs|diagnose-initialization",
            snapshotId: "optional fixed snapshot",
            repositories: "optional confirmed project repositories",
            question: "parent-agent request",
          },
          response: {
            conclusion: "concise answer or initialization status",
            actionsUsed: [],
            snapshot: { runId: "uuid|null", snapshotId: "uuid|null", codeIndexGeneration: "generation|null", sourceSnapshot: "uuid|null" },
            evidence: [],
            changesOrImpact: [],
            gaps: [],
            recommendedNextAction: "query|diff|generate-docs|scan|human-review|none",
          },
          reads: [".planning/interface-index.json", ".planning/knowledge-index.json", ".planning/code-index/", ".context/knowledge/", "control interfaces"],
          writes: ["initialization projections through tools only"],
        },
      ],
    },
    {
      id: "skill-workflow-code-index",
      name: "skill://workflow-code-index",
      kind: "skill",
      path: "plugins/devflow-intel/skills/workflow-code-index/SKILL.md",
      purpose: "代码事实索引生命周期：scan、validate、audit、query。",
      approval: "exec",
      actions: [
        {
          name: "scan/validate/audit/query",
          purpose: "生成并查询 .planning/code-index/ 机器代码事实。",
          request: { action: "scan|validate|audit|query", index: ".planning/code-index", root: "." },
          response: { generation: "uuid", files: "jsonl", symbols: "jsonl", relations: "jsonl", diagnostics: "jsonl" },
          reads: ["source tree"],
          writes: [".planning/code-index/"],
        },
      ],
    },
    {
      id: "cli-devflow-pi-init",
      name: "devflow-pi init --scan",
      kind: "cli",
      path: "src/cli.ts",
      purpose: "命令行初始化入口，创建项目身份并可触发 scan。",
      approval: "exec",
      actions: [
        {
          name: "init",
          purpose: "创建 .devflow/project.json，输出 projectId/workspaceId/database/planning，可选执行 scan。",
          request: { argv: ["devflow-pi", "init", "<project>", "--scan", "--request-id", "<id>"] },
          response: { projectId: "uuid", workspaceId: "uuid", database: "control.db", initializationRun: "uuid", contextSnapshot: "uuid" },
          reads: ["project path"],
          writes: [".devflow/project.json", "control.db", "initialization projections when --scan"],
        },
      ],
    },
    {
      id: "artifact-knowledge-index",
      name: ".planning/knowledge-index.json",
      kind: "artifact",
      path: ".planning/knowledge-index.json",
      purpose: "知识页、来源文档、需求、代码事实和预留 BUG 节点的桥接索引。",
      approval: "read",
      actions: [
        {
          name: "read",
          purpose: "让 LLM 或工具快速发现知识层节点和绑定关系。",
          request: { path: ".planning/knowledge-index.json" },
          response: { schemaVersion: 1, bundleRoot: ".context/knowledge", pages: [], reservedBugKnowledgeNodes: [] },
          reads: [".planning/knowledge-index.json"],
          writes: [],
        },
      ],
    },
  ];
}

function renderInterfaceContract(context: ProjectContext, contract: InterfaceContract, snapshotId: string, generation?: string): string {
  return `${frontmatter({ type: "interface-contract", id: contract.id, title: contract.name, interface_kind: contract.kind, project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${contract.name}

## Purpose

${contract.purpose}

## Endpoint

- 类型：${contract.kind}
- 入口：\`${contract.path}\`
- 权限：${contract.approval}

## Actions

${contract.actions.map(action => `### ${action.name}

${action.purpose}

#### Request

\`\`\`json
${JSON.stringify(action.request, null, 2)}
\`\`\`

#### Response

\`\`\`json
${JSON.stringify(action.response, null, 2)}
\`\`\`

#### Reads

${action.reads.map(item => `- ${item}`).join("\n") || "- 无"}

#### Writes

${action.writes.map(item => `- ${item}`).join("\n") || "- 无"}
`).join("\n")}
`;
}

function renderInterfaceOutputs(context: ProjectContext, snapshot: { id?: unknown; indexRef?: unknown }): { outputs: Array<{ targetPath: string; body: string }>; contracts: InterfaceContract[] } {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  const contracts = initializationInterfaceContracts();
  const outputs: Array<{ targetPath: string; body: string }> = [];
  outputs.push({ targetPath: ".context/knowledge/interfaces/index.md", body: `${frontmatter({ type: "interface-index", title: "初始化接口目录", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 初始化接口目录\n\n${contracts.map(contract => `- [${contract.name}](./${slug(contract.id)}.md) — ${contract.purpose}`).join("\n")}\n` });
  for (const contract of contracts) outputs.push({ targetPath: `.context/knowledge/interfaces/${slug(contract.id)}.md`, body: renderInterfaceContract(context, contract, snapshotId, generation) });
  return { outputs, contracts };
}

function renderOpenWikiKnowledge(context: ProjectContext, facts: Fact[], snapshot: { id?: unknown; indexRef?: unknown }, knowledge: DocumentKnowledge): { outputs: Array<{ targetPath: string; body: string }>; pages: KnowledgePage[] } {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  const pages = buildKnowledgePages(knowledge, facts);
  const sourceByRef = new Map(knowledge.sources.map(source => [sourcePageId(source), source] as const));
  const outputs: Array<{ targetPath: string; body: string }> = [];
  outputs.push({ targetPath: ".context/knowledge/index.md", body: `${frontmatter({ type: "knowledge-bundle-index", title: `${context.manifest.name} Knowledge`, project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${context.manifest.name} Knowledge\n\n## Sources\n\n${pages.filter(page => page.type === "source-summary").map(page => `- [${page.title}](./sources/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Entities\n\n${pages.filter(page => page.type === "entity").map(page => `- [${page.title}](./entities/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Concepts\n\n${pages.filter(page => page.type === "concept").map(page => `- [${page.title}](./concepts/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Requirements\n\n${pages.filter(page => page.type === "requirement").map(page => `- [${page.title}](./requirements/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Synthesis\n\n${pages.filter(page => page.type === "synthesis").map(page => `- [${page.title}](./synthesis/${path.basename(page.path)})`).join("\n") || "- 暂无"}\n\n## Bugs / Fixes / Incidents / Lessons\n\n- [BUG 模板](./bugs/_template.md)\n- [FIX 模板](./fixes/_template.md)\n- [INCIDENT 模板](./incidents/_template.md)\n- [LESSON 模板](./lessons/_template.md)\n` });
  outputs.push({ targetPath: ".context/knowledge/log.md", body: `${frontmatter({ type: "knowledge-log", title: "Knowledge Operation Log", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# Knowledge Operation Log\n\n- 初始化生成 OpenWiki 风格知识层。\n- 原始文档保持在项目原位置，source-summary 记录摘要和引用。\n- 代码事实来自 \`.planning/code-index/\`，桥接关系写入 \`.planning/knowledge-index.json\`。\n` });
  outputs.push({ targetPath: ".context/knowledge/raw/README.md", body: `${frontmatter({ type: "raw-source-readme", title: "Raw Source Policy", project: context.manifest.name, status: "draft", managed: true })}# Raw Source Policy\n\n本目录保留给后续需要复制或归档的原始资料。当前初始化默认不复制原文，只在 source-summary 中记录原路径和内容哈希，避免制造第二份权威原文。\n` });
  outputs.push(...bugKnowledgeTemplates(context, snapshotId, generation));
  for (const page of pages) outputs.push({ targetPath: page.path, body: renderKnowledgePage(context, page, sourceByRef, snapshotId, generation) });
  return { outputs, pages };
}

function renderKnowledgeDocuments(context: ProjectContext, facts: Fact[], intro: string, snapshot: { id?: unknown; indexRef?: unknown }, knowledge: DocumentKnowledge): Array<{ targetPath: string; body: string }> {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  const outputs: Array<{ targetPath: string; body: string }> = [];
  outputs.push({ targetPath: ".context/index.md", body: `${frontmatter({ type: "knowledge-index", scope: "project", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# ${context.manifest.name} 项目知识库\n\n## 项目级文档\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](/project/${type}.md)`).join("\n")}\n\n## 需求级文档\n\n- 新需求使用 [.context/requirements/_template/index.md](/requirements/_template/index.md) 作为文档包模板。\n\n## 机器索引\n\n- 文档索引：\`.planning/document-index.json\`\n- 代码索引：\`.planning/code-index/\`\n` });
  outputs.push({ targetPath: ".context/project/index.md", body: `${frontmatter({ type: "project-document-index", scope: "project", project: context.manifest.name, status: "draft", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 项目级文档\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](./${type}.md)`).join("\n")}\n` });
  for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/project/${type}.md`, body: projectKnowledgeBody(context, type, facts, intro, snapshotId, knowledge, generation) });
  outputs.push({ targetPath: ".context/requirements/_template/index.md", body: `${frontmatter({ type: "requirement-document-index", scope: "requirement", project: context.manifest.name, requirement_id: "REQ-TEMPLATE", status: "template", source_snapshot: snapshotId, code_index_generation: generation, managed: true })}# 需求级文档模板\n\n${KNOWLEDGE_DOCUMENT_TYPES.map(type => `- [${documentTitle(type)}](./${type === "requirements" ? "requirement" : type}.md)`).join("\n")}\n` });
  for (const type of KNOWLEDGE_DOCUMENT_TYPES) outputs.push({ targetPath: `.context/requirements/_template/${type === "requirements" ? "requirement" : type}.md`, body: requirementTemplateBody(context, type, snapshotId, generation) });
  for (const item of knowledge.classifications.filter(item => item.scope === "requirement" && item.confidence !== "exact").slice(0, 100)) {
    const source = knowledge.sources.find(source => source.id === item.sourceDocumentId);
    if (source) outputs.push(...inboxRequirementBody(context, source, item, snapshotId, generation));
  }
  return outputs;
}

function renderDocumentIndex(context: ProjectContext, snapshot: { id?: unknown; indexRef?: unknown }, outputs: Array<{ targetPath: string }>, knowledge: DocumentKnowledge, pages: KnowledgePage[]): { targetPath: string; body: string } {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  const documents = outputs
    .filter(output => output.targetPath.startsWith(".context/"))
    .map(output => {
      const parts = output.targetPath.split("/");
      const file = parts.at(-1) ?? "";
      const scope: IndexedDocumentScope = parts[1] === "project" ? "project" : parts[1] === "requirements" ? "requirement" : parts[1] === "knowledge" ? "knowledge" : "project";
      const type = output.targetPath === ".context/index.md" ? "knowledge-index"
        : output.targetPath === ".context/knowledge/index.md" ? "knowledge-bundle-index"
          : scope === "knowledge" ? `knowledge-${parts[2] === "log.md" ? "log" : parts[2] ?? "page"}`
        : scope === "project" ? `project-${file.replace(/\.md$/, "")}`
          : `requirement-${file.replace(/^requirement\.md$/, "requirements").replace(/\.md$/, "")}`;
      return {
        path: output.targetPath,
        type,
        scope,
        requirementId: scope === "requirement" ? (parts[2] === "_template" ? "REQ-TEMPLATE" : parts[2]) : undefined,
        status: parts.includes("_template") ? "template" : parts.includes("_inbox") ? "inbox" : "draft",
        managed: true,
        sourceSnapshot: snapshotId,
        codeIndexGeneration: generation ?? null,
      };
    });
  return {
    targetPath: ".planning/document-index.json",
    body: `${JSON.stringify({
      schemaVersion: 2,
      project: context.manifest.name,
      projectId: context.manifest.projectId,
      sourceSnapshot: snapshotId,
      codeIndexGeneration: generation ?? null,
      sourceDocuments: knowledge.sources,
      classifications: knowledge.classifications,
      knowledgePages: pages.map(page => ({ id: page.id, path: page.path, type: page.type, status: page.status, sourceRefs: page.sourceRefs, relatedRequirements: page.relatedRequirements, relatedCodeFacts: page.relatedCodeFacts, confidence: page.confidence })),
      documents,
    }, null, 2)}\n`,
  };
}

function renderKnowledgeIndex(context: ProjectContext, snapshot: { id?: unknown; indexRef?: unknown }, knowledge: DocumentKnowledge, pages: KnowledgePage[], interfaces: InterfaceContract[]): { targetPath: string; body: string } {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  return {
    targetPath: ".planning/knowledge-index.json",
    body: `${JSON.stringify({
      schemaVersion: 1,
      project: context.manifest.name,
      projectId: context.manifest.projectId,
      sourceSnapshot: snapshotId,
      codeIndexGeneration: generation ?? null,
      bundleRoot: ".context/knowledge",
      sourceDocuments: knowledge.sources,
      classifications: knowledge.classifications,
      pages: pages.map(page => ({
        id: page.id,
        path: page.path,
        type: page.type,
        title: page.title,
        status: page.status,
        sourceRefs: page.sourceRefs,
        relatedRequirements: page.relatedRequirements,
        relatedCodeFacts: page.relatedCodeFacts,
        confidence: page.confidence,
      })),
      reservedBugKnowledgeNodes: [
        { id: "BUG-TEMPLATE", path: ".context/knowledge/bugs/_template.md", type: "bug-report", purpose: "问题现象、影响、复现、根因和回归绑定模板" },
        { id: "FIX-TEMPLATE", path: ".context/knowledge/fixes/_template.md", type: "bug-fix", purpose: "修复动作、变更原因、变更文件和回归覆盖模板" },
        { id: "INC-TEMPLATE", path: ".context/knowledge/incidents/_template.md", type: "incident", purpose: "事故时间线、影响、缓解和后续行动模板" },
        { id: "LESSON-TEMPLATE", path: ".context/knowledge/lessons/_template.md", type: "lesson", purpose: "可复用排障经验和检查规则模板" },
      ],
      interfaces: interfaces.map(item => ({ id: item.id, name: item.name, kind: item.kind, path: item.path, purpose: item.purpose, approval: item.approval, actions: item.actions.map(action => action.name) })),
    }, null, 2)}\n`,
  };
}

function renderInterfaceIndex(context: ProjectContext, snapshot: { id?: unknown; indexRef?: unknown }, interfaces: InterfaceContract[]): { targetPath: string; body: string } {
  const snapshotId = String(snapshot.id ?? "");
  const generation = codeIndexGeneration(snapshot);
  return {
    targetPath: ".planning/interface-index.json",
    body: `${JSON.stringify({
      schemaVersion: 1,
      project: context.manifest.name,
      projectId: context.manifest.projectId,
      sourceSnapshot: snapshotId,
      codeIndexGeneration: generation ?? null,
      interfaces: interfaces.map(contract => ({
        id: contract.id,
        name: contract.name,
        kind: contract.kind,
        path: contract.path,
        purpose: contract.purpose,
        approval: contract.approval,
        actions: contract.actions.map(action => ({
          name: action.name,
          purpose: action.purpose,
          request: action.request,
          response: action.response,
          reads: action.reads,
          writes: action.writes,
        })),
      })),
    }, null, 2)}\n`,
  };
}

export async function generateProjectDocuments(context: ProjectContext, snapshotId: string, options: { mode: DocumentGenerationMode; organizer?: LowTierDocumentOrganizer }): Promise<DocumentGenerationResult[]> {
  const store = await InitializationStore.open(context);
  try {
    const facts: Fact[] = []; let after: string | undefined; let runId: string | undefined;
    do {
      const page = store.queryContext({ snapshotId, limit: 500, after }) as { snapshot: { runId: string }; facts: Fact[]; truncated: boolean; nextCursor: string | null };
      runId = page.snapshot.runId;
      facts.push(...page.facts); after = page.nextCursor ?? undefined;
      if (facts.length > 50_000) throw new Error("Document manifest exceeds the bounded 50,000 fact limit");
    } while (after);
    const run = runId ? store.getRun(runId) as { collections?: Array<{ repository?: string; kind?: string; status?: string; diagnostics?: unknown[] }> } | null : null;
    const collections = run?.collections ?? [];
    const batchJob = store.createDocumentJob(snapshotId, { targetPath: ".planning/initialization/report.md", ownership: "generated", templateVersion: TEMPLATE_VERSION, modelRole: options.organizer ? `${options.organizer.role}:${options.organizer.model}` : options.mode, input: facts.map(fact => fact.objectKey) });
    let organized;
    try { organized = await narrative(context, "project-initialization", facts, options.mode, options.organizer); }
    catch (error) { store.failDocumentJob(batchJob, error instanceof Error ? error.message : String(error)); throw error; }
    const snapshotMeta = (store.queryContext({ snapshotId, limit: 1 }) as { snapshot: { id?: unknown; indexRef?: unknown } }).snapshot;
    const knowledge = await discoverSourceDocuments(context.root);
    const openWiki = renderOpenWikiKnowledge(context, facts, snapshotMeta, knowledge);
    const interfaces = renderInterfaceOutputs(context, snapshotMeta);
    const outputs = [...renderDocuments(context, facts, organized.text, collections), ...renderKnowledgeDocuments(context, facts, organized.text, snapshotMeta, knowledge), ...openWiki.outputs, ...interfaces.outputs];
    outputs.push(renderDocumentIndex(context, snapshotMeta, outputs, knowledge, openWiki.pages));
    outputs.push(renderKnowledgeIndex(context, snapshotMeta, knowledge, openWiki.pages, interfaces.contracts));
    outputs.push(renderInterfaceIndex(context, snapshotMeta, interfaces.contracts));
    const results: DocumentGenerationResult[] = [];
    for (const output of outputs) {
      const job = store.createDocumentJob(snapshotId, { targetPath: output.targetPath, ownership: "generated", templateVersion: TEMPLATE_VERSION, modelRole: organized.modelRole, input: facts.map(fact => fact.objectKey) });
      try {
        const knownHash = store.latestDocumentHash(output.targetPath);
        const existingHash = await currentHash(`${context.root}/${output.targetPath}`);
        const expected = existingHash && !knownHash ? "unmanaged-existing-file" : knownHash ?? undefined;
        const written = await store.writeDocument(job, output.body, { expectedCurrentHash: expected });
        results.push({ targetPath: output.targetPath, status: written.status, outputPath: written.path });
      } catch (error) {
        const message = error instanceof Error ? error.message : String(error); store.failDocumentJob(job, message);
        results.push({ targetPath: output.targetPath, status: "failed", error: message });
      }
    }
    return results;
  } finally { store.close(); }
}
