/**
 * Structured page builders for the wiki generation pipeline.
 *
 * Builds WikiPage objects for modules, entities, workflows, overview,
 * and the navigation tree.
 */

import { join } from "node:path";
import { mkdirSync, writeFileSync } from "node:fs";
import { EdgeType, NodeType } from "../../repopilot/types";
import type { KnowledgeGraph } from "../../repopilot/graph/knowledge-graph";
import type {
  WikiCodeReference,
  WikiDiagramSpec,
  WikiManifest,
  WikiNavigationNode,
  WikiPage,
  WikiPageSummary,
  WikiPageType,
  WikiSection,
  ClassEntry,
  MethodEntry,
  ModuleEvidence,
  ModuleNode,
  RootEvidence,
} from "../types";
import {
  collectModuleFiles,
  getString,
  humanTitle,
  mermaidId,
  mermaidText,
  sanitizeMermaidSource,
  toRelativePath,
  uniqueStrings,
} from "../utils";
import { buildCodeReference } from "../code-links";
import { CAP_WORKFLOW_EDGES } from "../constants";
// NOTE: This import will resolve once the analysis module is extracted.
import { getClassEntries, getMethodEntries } from "../analysis/graph-extractor";

/* ------------------------------------------------------------------ */
/*  ID helpers                                                         */
/* ------------------------------------------------------------------ */

export function makeEdgeKey(
  source: string,
  type: string,
  target: string,
): string {
  return `${source}|${type}|${target}`;
}

export function makeModulePageId(module: ModuleNode): string {
  return module.level === "root" ? "overview" : `module:${module.path}`;
}

export function makeEntityPageId(
  module: ModuleNode,
  classEntry: ClassEntry,
): string {
  return `entity:${module.path}:${classEntry.name}`;
}

/* ------------------------------------------------------------------ */
/*  Diagram section helper                                             */
/* ------------------------------------------------------------------ */

export function createDiagramSection(
  id: string,
  title: string,
  kind: WikiDiagramSpec["kind"],
  mermaid: string,
  sourceNodeIds: string[],
  sourceEdgeKeys: string[],
): WikiSection {
  return {
    id,
    title,
    type: "diagram",
    diagram: {
      id,
      title,
      kind,
      mermaid: sanitizeMermaidSource(mermaid),
      sourceNodeIds,
      sourceEdgeKeys,
    },
  };
}

/* ------------------------------------------------------------------ */
/*  Page summary                                                       */
/* ------------------------------------------------------------------ */

export function buildModulePageSummary(page: WikiPage): WikiPageSummary {
  return {
    id: page.id,
    title: page.title,
    pageType: page.pageType,
    path: page.path,
    summary: page.summary,
    sourceNodeIds: page.sourceNodeIds,
    sourceEdgeKeys: page.sourceEdgeKeys,
    primaryFiles: page.primaryFiles,
    childPageIds: page.childPageIds,
    markdownPath: page.markdownPath,
  };
}

/* ------------------------------------------------------------------ */
/*  Entity pages                                                       */
/* ------------------------------------------------------------------ */

export function buildEntityPages(
  module: ModuleNode,
  classes: ClassEntry[],
  methods: MethodEntry[],
  sourcePath: string,
  generatedAt: string,
  graph?: KnowledgeGraph,
): WikiPage[] {
  return classes.map((cls) => {
    const classMethods = methods.filter((m) => m.className === cls.name);
    const methodRefs = classMethods.map((m) => {
      const ref = buildCodeReference(
        `${m.className}.${m.name}(${m.parameters.join(", ")})`,
        sourcePath,
        m.filePath,
        m.lineStart,
        m.lineEnd,
        `${m.className}.${m.name}`,
      );
      if (m.javadoc) {
        ref.description = m.javadoc;
      }
      return ref;
    });

    // Count fields from the knowledge graph
    let fieldCount = 0;
    if (graph) {
      try {
        const members = graph.getNeighbors(cls.nodeId, EdgeType.CONTAINS, "out");
        for (const memberId of members) {
          try {
            const attrs = graph.getNodeAttrs(memberId);
            if (String(attrs.type) === NodeType.FIELD) fieldCount++;
          } catch { /* skip */ }
        }
      } catch { /* graph lookup failed */ }
    }

    // Calculate LOC from class line range
    const loc =
      cls.lineStart != null && cls.lineEnd != null
        ? cls.lineEnd - cls.lineStart + 1
        : undefined;

    const sections: WikiSection[] = [
      {
        id: "entity-summary",
        title: "Summary",
        type: "facts",
        sourceNodeIds: [
          cls.nodeId,
          ...classMethods.map((m) => m.nodeId),
        ],
        items: [
          `Type: ${cls.type}`,
          `Module: ${module.path}`,
          `Source: ${toRelativePath(sourcePath, cls.filePath)}:${cls.lineStart ?? 1}`,
          cls.extendsName
            ? `Extends: ${cls.extendsName}`
            : "Extends: none",
          cls.implementsNames.length > 0
            ? `Implements: ${cls.implementsNames.join(", ")}`
            : "Implements: none",
          `Methods/constructors: ${classMethods.length}`,
          `Fields: ${fieldCount}`,
          ...(loc != null ? [`Lines of code: ${loc.toLocaleString()}`] : []),
        ],
      },
      {
        id: "entity-source",
        title: "Source Location",
        type: "references",
        sourceNodeIds: [cls.nodeId],
        references: [
          buildCodeReference(
            cls.name,
            sourcePath,
            cls.filePath,
            cls.lineStart,
            cls.lineEnd,
            cls.name,
            cls.nodeId,
          ),
        ],
      },
      {
        id: "entity-methods",
        title: "Methods",
        type: "references",
        sourceNodeIds: classMethods.map((m) => m.nodeId),
        sourceEdgeKeys: classMethods.map((m) =>
          makeEdgeKey(cls.nodeId, EdgeType.CONTAINS, m.nodeId),
        ),
        references: methodRefs,
      },
    ];

    return {
      id: makeEntityPageId(module, cls),
      title: cls.name,
      pageType: "entity" as WikiPageType,
      path: `${module.path}/${cls.name}`,
      summary: `${cls.type} in ${module.path}`,
      sourceNodeIds: [
        cls.nodeId,
        ...classMethods.map(
          (m) =>
            `method:${m.filePath}:${m.className}.${m.name}`,
        ),
      ],
      sourceEdgeKeys: classMethods.map((m) =>
        makeEdgeKey(
          cls.nodeId,
          EdgeType.CONTAINS,
          `method:${m.filePath}:${m.className}.${m.name}`,
        ),
      ),
      primaryFiles: [toRelativePath(sourcePath, cls.filePath)],
      childPageIds: [],
      generatedAt,
      sections,
    };
  });
}

/* ------------------------------------------------------------------ */
/*  Workflow pages                                                     */
/* ------------------------------------------------------------------ */

/**
 * Builds workflow pages showing call-chain sequence diagrams for a module.
 *
 * NOTE: This function depends on `getModuleDependencyNames` which lives in
 * the analysis layer. To avoid a circular dependency at this point in the
 * refactor we inline a lightweight version. Once `../analysis/graph-extractor`
 * is available, this can be updated.
 */
export function buildWorkflowPages(
  module: ModuleNode,
  classes: ClassEntry[],
  methods: MethodEntry[],
  graph: KnowledgeGraph,
  sourcePath: string,
  generatedAt: string,
): WikiPage[] {
  const methodMap = new Map(methods.map((m) => [m.nodeId, m]));
  const workflowEdges = graph
    .getEdges(EdgeType.CALLS, "out")
    .filter(
      (edge) => methodMap.has(edge.source) && methodMap.has(edge.target),
    );

  if (workflowEdges.length === 0) return [];

  const selectedEdges = workflowEdges.slice(0, CAP_WORKFLOW_EDGES);
  const participants = new Map<string, string>();
  const mermaidLines = ["sequenceDiagram"];
  const sourceNodeIds = new Set<string>();
  const sourceEdgeKeys = new Set<string>();
  const references: WikiCodeReference[] = [];

  for (const edge of selectedEdges) {
    const srcM = methodMap.get(edge.source)!;
    const tgtM = methodMap.get(edge.target)!;
    const srcLabel = `${srcM.className}.${srcM.name}`;
    const tgtLabel = `${tgtM.className}.${tgtM.name}`;
    const srcId = mermaidId(`workflow_${srcLabel}`);
    const tgtId = mermaidId(`workflow_${tgtLabel}`);

    if (!participants.has(srcId)) {
      participants.set(srcId, srcLabel);
      mermaidLines.push(
        `  participant ${srcId} as ${mermaidText(srcLabel)}`,
      );
    }
    if (!participants.has(tgtId)) {
      participants.set(tgtId, tgtLabel);
      mermaidLines.push(
        `  participant ${tgtId} as ${mermaidText(tgtLabel)}`,
      );
    }

    mermaidLines.push(`  ${srcId}->>${tgtId}: calls`);
    sourceNodeIds.add(edge.source);
    sourceNodeIds.add(edge.target);
    sourceEdgeKeys.add(makeEdgeKey(edge.source, edge.type, edge.target));
    references.push(
      buildCodeReference(
        `${srcLabel} → ${tgtLabel}`,
        sourcePath,
        srcM.filePath,
        srcM.lineStart,
        srcM.lineEnd,
        srcLabel,
        srcM.nodeId,
        "calls",
      ),
    );
  }

  const depNames = getModuleDependencyNames(module, graph);
  const depFacts =
    depNames.length > 0
      ? depNames.map((n) => `Depends on ${n}`)
      : ["No cross-package dependencies resolved."];

  const pageId = `workflow:${module.path}:primary`;
  const title = `${humanTitle(module.path)} — Call Flow`;

  const sections: WikiSection[] = [
    {
      id: "workflow-summary",
      title: "Overview",
      type: "markdown",
      sourceNodeIds: [...sourceNodeIds],
      sourceEdgeKeys: [...sourceEdgeKeys],
      markdown: `This page shows the most visible method call chains indexed for \`${module.path}\`. It represents the interactions between classes within this module as detected by static analysis.`,
    },
    {
      id: "workflow-sequence",
      title: "Call Sequence",
      type: "diagram",
      sourceNodeIds: [...sourceNodeIds],
      sourceEdgeKeys: [...sourceEdgeKeys],
      diagram: {
        id: `${pageId}:diagram`,
        title: `${humanTitle(module.path)} Call Sequence`,
        kind: "sequence",
        mermaid: sanitizeMermaidSource(mermaidLines.join("\n")),
        sourceNodeIds: [...sourceNodeIds],
        sourceEdgeKeys: [...sourceEdgeKeys],
      },
    },
    {
      id: "workflow-references",
      title: "Participating Methods",
      type: "references",
      sourceNodeIds: [...sourceNodeIds],
      sourceEdgeKeys: [...sourceEdgeKeys],
      references,
    },
    {
      id: "workflow-dependencies",
      title: "Dependencies",
      type: "facts",
      items: depFacts,
    },
  ];

  return [
    {
      id: pageId,
      title,
      pageType: "workflow" as WikiPageType,
      path: `${module.path}/workflow`,
      summary: `Call-chain and dependency view for ${humanTitle(module.path)}`,
      sourceNodeIds: [...sourceNodeIds],
      sourceEdgeKeys: [...sourceEdgeKeys],
      primaryFiles: uniqueStrings(
        selectedEdges.flatMap((e) => {
          const s = methodMap.get(e.source)!;
          const t = methodMap.get(e.target)!;
          return [
            toRelativePath(sourcePath, s.filePath),
            toRelativePath(sourcePath, t.filePath),
          ];
        }),
      ),
      childPageIds: [],
      generatedAt,
      sections,
    },
  ];
}

/* ------------------------------------------------------------------ */
/*  Module structured pages                                            */
/* ------------------------------------------------------------------ */

export function buildModuleStructuredPages(
  module: ModuleNode,
  narrativeMarkdown: string,
  evidence: ModuleEvidence,
  sourcePath: string,
  generatedAt: string,
  graph: KnowledgeGraph,
  pageType: WikiPageType,
): WikiPage[] {
  const files = collectModuleFiles(module);
  const classes: ClassEntry[] = getClassEntries(graph, new Set(files));
  const methods: MethodEntry[] = getMethodEntries(graph, classes);

  const codeRefs: WikiCodeReference[] = classes.map((cls: ClassEntry) =>
    buildCodeReference(
      cls.name,
      sourcePath,
      cls.filePath,
      cls.lineStart,
      cls.lineEnd,
      cls.name,
      cls.nodeId,
    ),
  );

  const methodRefs: WikiCodeReference[] = methods.map((m: MethodEntry) =>
    buildCodeReference(
      `${m.className}.${m.name}(${m.parameters.join(", ")})`,
      sourcePath,
      m.filePath,
      m.lineStart,
      m.lineEnd,
      `${m.className}.${m.name}`,
      m.nodeId,
    ),
  );

  const moduleNodeIds = [
    ...classes.map((c: ClassEntry) => c.nodeId),
    ...methods.map((m: MethodEntry) => m.nodeId),
  ];
  const moduleEdgeKeys = methods.map((m: MethodEntry) =>
    makeEdgeKey(
      classes.find((c: ClassEntry) => c.name === m.className)?.nodeId || m.className,
      EdgeType.CONTAINS,
      m.nodeId,
    ),
  );

  const modulePageId = makeModulePageId(module);
  const entityPages = buildEntityPages(
    module,
    classes,
    methods,
    sourcePath,
    generatedAt,
    graph,
  );

  const sections: WikiSection[] = [
    {
      id: "narrative",
      title: "Documentation",
      type: "markdown",
      sourceNodeIds: moduleNodeIds,
      sourceEdgeKeys: moduleEdgeKeys,
      markdown: narrativeMarkdown,
    },
    {
      id: "source-evidence",
      title: "Module Facts",
      type: "facts",
      sourceNodeIds: moduleNodeIds,
      items: evidence.facts.split("\n").map((i) => i.replace(/^- /, "")),
    },
    {
      id: "related-pages",
      title: "Related Pages",
      type: "navigation",
      relatedPageIds: [
        ...module.children.map((c) => makeModulePageId(c)),
        ...entityPages.map((p) => p.id),
      ],
    },
    {
      id: "code-references",
      title: "Classes and Interfaces",
      type: "references",
      sourceNodeIds: classes.map((c: ClassEntry) => c.nodeId),
      references: codeRefs,
    },
    {
      id: "method-references",
      title: "Methods",
      type: "references",
      sourceNodeIds: methods.map((m: MethodEntry) => m.nodeId),
      sourceEdgeKeys: moduleEdgeKeys,
      references: methodRefs,
    },
    {
      id: "dependencies",
      title: "Dependencies",
      type: "facts",
      items: evidence.dependencySummary
        .split("\n")
        .map((i) => i.replace(/^- /, "")),
    },
    {
      id: "source-files",
      title: "Source Files",
      type: "references",
      references: files.map((fp) =>
        buildCodeReference(
          toRelativePath(sourcePath, fp),
          sourcePath,
          fp,
        ),
      ),
    },
  ];

  if (evidence.structureDiagram) {
    sections.push(
      createDiagramSection(
        `${modulePageId}:structure`,
        "Module Structure",
        "structure",
        evidence.structureDiagram,
        classes.map((c: ClassEntry) => c.nodeId),
        [],
      ),
    );
  }

  if (evidence.dependencyDiagram) {
    sections.push(
      createDiagramSection(
        `${modulePageId}:dependency`,
        "Dependency Diagram",
        "dependency",
        evidence.dependencyDiagram,
        moduleNodeIds,
        moduleEdgeKeys,
      ),
    );
  }

  const title = humanTitle(module.path || module.name);
  const modulePage: WikiPage = {
    id: modulePageId,
    title,
    pageType,
    path: module.path || "overview",
    summary:
      evidence.facts.split("\n")[0]?.replace(/^- /, "") ||
      `Documentation for ${title}`,
    sourceNodeIds: moduleNodeIds,
    sourceEdgeKeys: moduleEdgeKeys,
    primaryFiles: files.map((fp) => toRelativePath(sourcePath, fp)),
    childPageIds:
      sections.find((s) => s.id === "related-pages")?.relatedPageIds ||
      [],
    markdownPath: module.docPath,
    generatedAt,
    sections,
  };

  const workflowPages = buildWorkflowPages(
    module,
    classes,
    methods,
    graph,
    sourcePath,
    generatedAt,
  );

  const relatedSection = modulePage.sections.find(
    (s) => s.id === "related-pages",
  );
  if (relatedSection) {
    relatedSection.relatedPageIds = [
      ...(relatedSection.relatedPageIds || []),
      ...workflowPages.map((p) => p.id),
    ];
  }
  modulePage.childPageIds = relatedSection?.relatedPageIds || [];

  return [modulePage, ...workflowPages, ...entityPages];
}

/* ------------------------------------------------------------------ */
/*  Overview structured page                                           */
/* ------------------------------------------------------------------ */

export function buildOverviewStructuredPage(
  narrativeMarkdown: string,
  evidence: RootEvidence,
  tree: ModuleNode,
  graph: KnowledgeGraph,
  sourcePath: string,
  generatedAt: string,
  extraPageIds: string[],
): WikiPage {
  const topLevelPages = tree.children.map((c) => makeModulePageId(c));
  const files = tree.children.flatMap((c) => collectModuleFiles(c));
  const classes: ClassEntry[] = getClassEntries(graph, new Set(files));

  const sections: WikiSection[] = [
    {
      id: "overview-narrative",
      title: "Documentation",
      type: "markdown",
      sourceNodeIds: classes.map((c: ClassEntry) => c.nodeId),
      markdown: narrativeMarkdown,
    },
    {
      id: "repository-facts",
      title: "Repository Facts",
      type: "facts",
      sourceNodeIds: classes.map((c: ClassEntry) => c.nodeId),
      items: evidence.repoStats
        .split("\n")
        .map((i) => i.replace(/^- /, "")),
    },
    {
      id: "technology-stack",
      title: "Technology Stack",
      type: "facts",
      items: evidence.technologyStack
        .split("\n")
        .map((i) => i.replace(/^- /, "")),
    },
    {
      id: "top-level-pages",
      title: "Main Modules",
      type: "navigation",
      relatedPageIds: [...topLevelPages, ...extraPageIds],
    },
    {
      id: "top-level-summary",
      title: "Module Summary",
      type: "facts",
      items: evidence.topLevelSummary
        .split("\n")
        .map((i) => i.replace(/^- /, "")),
    },
    {
      id: "repository-references",
      title: "Key Code References",
      type: "references",
      sourceNodeIds: classes.slice(0, 30).map((c: ClassEntry) => c.nodeId),
      references: classes.slice(0, 30).map((cls: ClassEntry) =>
        buildCodeReference(
          cls.name,
          sourcePath,
          cls.filePath,
          cls.lineStart,
          cls.lineEnd,
          cls.name,
          cls.nodeId,
        ),
      ),
    },
  ];

  if (evidence.dependencyDiagram) {
    sections.push(
      createDiagramSection(
        "overview:dependency",
        "Module Dependency Map",
        "dependency",
        evidence.dependencyDiagram,
        classes.map((c: ClassEntry) => c.nodeId),
        [],
      ),
    );
  }

  return {
    id: "overview",
    title: "Repository Overview",
    pageType: "overview",
    path: "overview",
    summary: "Architecture, technology stack, and module guide for this repository.",
    sourceNodeIds: classes.map((c: ClassEntry) => c.nodeId),
    sourceEdgeKeys: [],
    primaryFiles: uniqueStrings(
      files.map((fp) => toRelativePath(sourcePath, fp)),
    ),
    childPageIds: [...topLevelPages, ...extraPageIds],
    markdownPath: "overview.md",
    generatedAt,
    sections,
  };
}

/* ------------------------------------------------------------------ */
/*  Navigation tree builder                                            */
/* ------------------------------------------------------------------ */

export function buildNavigationTree(
  root: ModuleNode,
  entityPageMap: Map<string, WikiNavigationNode[]>,
  guidePages: WikiPage[],
): WikiNavigationNode {
  const walk = (node: ModuleNode): WikiNavigationNode => {
    const childNav = node.children.map((c) => walk(c));
    const entityNav = entityPageMap.get(node.id) || [];
    return {
      id: node.id,
      title: node.level === "root" ? "Overview" : node.name,
      pageId: makeModulePageId(node),
      pageType: node.level === "root" ? "overview" : "module",
      path: node.path || "overview",
      children: [...childNav, ...entityNav],
    };
  };

  const rootNav = walk(root);

  // Separate guide/cross-cutting pages from DD pages
  const nonDDPages: WikiPage[] = [];
  const ddPages: WikiPage[] = [];
  for (const page of guidePages) {
    if (page.pageType === "dd") {
      ddPages.push(page);
    } else {
      nonDDPages.push(page);
    }
  }

  // Add guide/cross-cutting pages as top-level nav items
  for (const guidePage of nonDDPages) {
    rootNav.children.push({
      id: guidePage.id,
      title: guidePage.title,
      pageId: guidePage.id,
      pageType: guidePage.pageType,
      path: guidePage.path,
      children: [],
    });
  }

  // Group DD pages by class under a "Detailed Design" folder
  if (ddPages.length > 0) {
    const ddByClass = new Map<string, WikiNavigationNode[]>();
    for (const ddPage of ddPages) {
      // Extract class name from page id: "dd:ClassName:methodName"
      const parts = ddPage.id.split(":");
      const className = parts[1] || "Unknown";
      if (!ddByClass.has(className)) {
        ddByClass.set(className, []);
      }
      ddByClass.get(className)!.push({
        id: ddPage.id,
        title: ddPage.title,
        pageId: ddPage.id,
        pageType: "dd",
        path: ddPage.path,
        children: [],
      });
    }

    const ddFolderChildren: WikiNavigationNode[] = [];
    for (const [className, ddNavNodes] of ddByClass) {
      ddFolderChildren.push({
        id: `dd-class:${className}`,
        title: className,
        pageId: ddNavNodes[0]?.pageId || "",
        pageType: "dd",
        path: `dd/${className}`,
        children: ddNavNodes,
      });
    }

    rootNav.children.push({
      id: "dd-root",
      title: "Detailed Design",
      pageId: ddFolderChildren[0]?.pageId || "",
      pageType: "dd",
      path: "dd",
      children: ddFolderChildren,
    });
  }

  return rootNav;
}

/* ------------------------------------------------------------------ */
/*  Write structured wiki artifacts to disk                            */
/* ------------------------------------------------------------------ */

export function writeStructuredWikiArtifacts(
  wikiDir: string,
  navigation: WikiNavigationNode,
  pages: WikiPage[],
): void {
  const pagesDir = join(wikiDir, "pages");
  mkdirSync(pagesDir, { recursive: true });

  for (const page of pages) {
    writeFileSync(
      join(pagesDir, `${encodeURIComponent(page.id)}.json`),
      JSON.stringify(page, null, 2),
    );
  }

  const manifest: WikiManifest = {
    version: 1,
    generatedAt: new Date().toISOString(),
    rootPageId: "overview",
    navigation,
    pages: pages.map((p) => buildModulePageSummary(p)),
  };

  writeFileSync(
    join(wikiDir, "manifest.json"),
    JSON.stringify(manifest, null, 2),
  );
}

/* ------------------------------------------------------------------ */
/*  Private helpers                                                    */
/* ------------------------------------------------------------------ */

function getModuleDependencyNames(
  module: ModuleNode,
  graph: KnowledgeGraph,
): string[] {
  if (!module.path) return [];

  const packageIds = graph
    .getNodesByType(NodeType.PACKAGE)
    .filter((pkgId) => {
      const attrs = graph.getNodeAttrs(pkgId);
      const name = getString(attrs, "name");
      if (!name) return false;
      return name === module.path || name.endsWith(`.${module.path}`);
    });

  const deps: string[] = [];
  for (const pkgId of packageIds) {
    for (const targetId of graph.getNeighbors(
      pkgId,
      EdgeType.DEPENDS_ON,
      "out",
    )) {
      if (!graph.hasNode(targetId)) continue;
      const attrs = graph.getNodeAttrs(targetId);
      const name = getString(attrs, "name");
      if (name) deps.push(name);
    }
  }
  return uniqueStrings(deps).sort();
}
