import { NodeType, EdgeType } from "../../repopilot/types";
import { loadRepoIndex, readWikiManifest } from "./readers";

export function buildWikiChatContext(sourcePath: string): string | null {
  const sections: string[] = [];

  // --- Section 1: Wiki discovery hints (if wiki exists) ---
  const manifest = readWikiManifest(sourcePath);
  if (manifest) {
    const wikiLines: string[] = [];
    wikiLines.push("## Repository Wiki Context");
    wikiLines.push("Wiki is available under `.codewiki/`.");
    wikiLines.push("Use the wiki as a discovery index for architecture, workflows, domains, and onboarding context.");
    wikiLines.push("Verify important implementation claims in source files when precision matters.");
    wikiLines.push("Suggested search order when wiki is relevant:");
    wikiLines.push("1. `.codewiki/overview.md`");
    wikiLines.push("2. `.codewiki/guides/*.md`");
    wikiLines.push("3. `.codewiki/manifest.json` for page inventory");
    wikiLines.push("4. `.codewiki/pages/*.json` for structured page content");
    wikiLines.push("Use `Glob`/`Grep`/`Read` on `.codewiki/**/*` to locate relevant wiki pages before diving into source code.");
    sections.push(wikiLines.join("\n"));
  }

  // --- Section 2: Pre-indexed code structure from scanner ---
  const indexContext = buildIndexContext(sourcePath);
  if (indexContext) {
    sections.push(indexContext);
  }

  return sections.length > 0 ? sections.join("\n\n") : null;
}

/**
 * Build a compact code-structure summary from the scanner index.
 *
 * Strategy to stay within token limits:
 *  - For ALL classes: emit a one-line summary (name, file, method/field counts).
 *  - For "large" classes (>=30 methods): also list every method name with line range
 *    so the agent can answer enumeration questions without reading the file.
 *  - Total output is capped at ~20 000 characters (~5 000 tokens).
 */
const INDEX_CONTEXT_CHAR_LIMIT = 40_000;
const LARGE_CLASS_METHOD_THRESHOLD = 30;

function buildIndexContext(sourcePath: string): string | null {
  const index = loadRepoIndex(sourcePath);
  if (!index) {
    return null;
  }

  const graph = index.graph;

  const classIds = graph.getNodesByType(NodeType.CLASS);
  const interfaceIds = graph.getNodesByType(NodeType.INTERFACE);
  const enumIds = graph.getNodesByType(NodeType.ENUM);
  const allTypeIds = [...classIds, ...interfaceIds, ...enumIds];

  if (allTypeIds.length === 0) {
    return null;
  }

  // Collect type info
  interface TypeInfo {
    name: string;
    kind: string;
    filePath: string;
    lineStart: number;
    lineEnd: number;
    methods: Array<{ name: string; line: number; lineEnd: number; javadoc?: string }>;
    fields: Array<{ name: string; line: number; value?: string }>;
    constructors: number;
  }

  const types: TypeInfo[] = [];

  for (const typeId of allTypeIds) {
    const attrs = graph.getNodeAttrs(typeId);
    const children = graph.getNeighbors(typeId, EdgeType.CONTAINS, "out");

    const methods: TypeInfo["methods"] = [];
    const fields: TypeInfo["fields"] = [];
    let constructorCount = 0;

    for (const childId of children) {
      let ca: Record<string, unknown>;
      try {
        ca = graph.getNodeAttrs(childId);
      } catch {
        continue;
      }
      const ct = ca.type as string;
      if (ct === "method") {
        methods.push({
          name: ca.name as string,
          line: (ca.line_start as number) || 0,
          lineEnd: (ca.line_end as number) || 0,
          javadoc: (ca.javadoc as string) || undefined,
        });
      } else if (ct === "field") {
        fields.push({
          name: ca.name as string,
          line: (ca.line_start as number) || 0,
          value: ca.initial_value as string | undefined,
        });
      } else if (ct === "constructor") {
        constructorCount++;
      }
    }

    methods.sort((a, b) => a.line - b.line);
    fields.sort((a, b) => a.line - b.line);

    types.push({
      name: attrs.name as string,
      kind: attrs.type as string,
      filePath: (attrs.file_path as string) || "",
      lineStart: (attrs.line_start as number) || 0,
      lineEnd: (attrs.line_end as number) || 0,
      methods,
      fields,
      constructors: constructorCount,
    });
  }

  types.sort((a, b) => a.name.localeCompare(b.name));

  const totalMethods = types.reduce((s, t) => s + t.methods.length, 0);
  const totalFields = types.reduce((s, t) => s + t.fields.length, 0);

  const lines: string[] = [];
  lines.push("## Pre-indexed Code Structure (AUTHORITATIVE)");
  lines.push(`Repository contains ${types.length} types, ${totalMethods} methods, ${totalFields} fields.`);
  lines.push("IMPORTANT: This index includes method Javadoc descriptions extracted directly from source code.");
  lines.push("For questions about method counts, signatures, CRUD classification, SC call grouping, or class metrics — answer from this index FIRST. Do NOT re-read every method individually from source when the answer is available below.");
  lines.push("Only use Read/Grep on source files when you need implementation details not present in this index (e.g., method bodies, control flow, variable values).");
  lines.push("When a user asks to read a very large file (>5000 LOC), use Read with offset+limit to read specific line ranges shown below instead of trying to read the whole file.");
  lines.push("");

  // Summary table (compact)
  lines.push("| Type | Name | File | Methods | Fields |");
  lines.push("|------|------|------|---------|--------|");
  for (const t of types) {
    const shortPath = t.filePath.replace(/^.*?Source\//, "Source/");
    const loc = t.lineEnd > t.lineStart ? `${t.lineEnd - t.lineStart + 1}` : "?";
    lines.push(`| ${t.kind} | ${t.name} | ${shortPath} (${loc} LOC) | ${t.methods.length} | ${t.fields.length} |`);
  }
  lines.push("");

  // Detailed listings for large classes only
  const largeTypes = types.filter((t) => t.methods.length >= LARGE_CLASS_METHOD_THRESHOLD);
  let charCount = lines.join("\n").length;

  for (const t of largeTypes) {
    if (charCount > INDEX_CONTEXT_CHAR_LIMIT) {
      lines.push("*(Index listing truncated to stay within token budget)*");
      break;
    }

    const loc = t.lineEnd > t.lineStart ? t.lineEnd - t.lineStart + 1 : 0;
    lines.push(`### ${t.kind} ${t.name} — ${t.methods.length} methods, ${t.fields.length} fields, ${loc} LOC`);
    lines.push(`File: ${t.filePath}`);
    lines.push("");

    if (t.fields.length > 0) {
      lines.push("**Fields/Constants:**");
      for (const f of t.fields) {
        const v = f.value ? ` = ${f.value}` : "";
        lines.push(`- \`${f.name}\`${v} (line ${f.line})`);
      }
      lines.push("");
    }

    lines.push("**Methods** (name — lines start:end — Javadoc description):");
    for (const m of t.methods) {
      const span = m.lineEnd > m.line ? m.lineEnd - m.line + 1 : 0;
      const doc = m.javadoc ? ` — ${m.javadoc}` : "";
      lines.push(`- ${m.name} — ${m.line}:${m.lineEnd} (${span} LOC)${doc}`);
    }
    lines.push("");

    charCount = lines.join("\n").length;
  }

  return lines.join("\n");
}
