"use strict";
var __create = Object.create;
var __defProp = Object.defineProperty;
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
var __getOwnPropNames = Object.getOwnPropertyNames;
var __getProtoOf = Object.getPrototypeOf;
var __hasOwnProp = Object.prototype.hasOwnProperty;
var __export = (target, all) => {
  for (var name in all)
    __defProp(target, name, { get: all[name], enumerable: true });
};
var __copyProps = (to, from, except, desc) => {
  if (from && typeof from === "object" || typeof from === "function") {
    for (let key of __getOwnPropNames(from))
      if (!__hasOwnProp.call(to, key) && key !== except)
        __defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
  }
  return to;
};
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
  // If the importer is in node compatibility mode or this is not an ESM
  // file that has been converted to a CommonJS file using a Babel-
  // compatible transform (i.e. "__esModule" has not been set), then set
  // "default" to the CommonJS "module.exports" for node compatibility.
  isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
  mod
));
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
var indexer_exports = {};
__export(indexer_exports, {
  DocsIndexer: () => DocsIndexer
});
module.exports = __toCommonJS(indexer_exports);
var fs = __toESM(require("fs"));
var path = __toESM(require("path"));
var crypto = __toESM(require("crypto"));
var import_picomatch = __toESM(require("picomatch"));
var import_section_id = require("./section-id");
var import_formats = require("./formats/index");
var import_linker = require("./linker");
var import_vectors = require("./vectors");
class DocsIndexer {
  constructor(db, config, projectRoot) {
    this.db = db;
    this.config = config;
    this.projectRoot = projectRoot;
  }
  /**
   * Index all documentation files in the project.
   * Skips files whose content hash hasn't changed (incremental).
   */
  async indexAll(opts) {
    const start = Date.now();
    const result = {
      filesIndexed: 0,
      sectionsCreated: 0,
      sectionsUpdated: 0,
      sectionsRemoved: 0,
      codeRefsCreated: 0,
      errors: [],
      duration: 0
    };
    const files = this.scanDocFiles();
    opts?.onProgress?.(`docs: found ${files.length} documentation files`);
    for (const relPath of files) {
      try {
        const changed = await this.indexFile(relPath, opts?.force ?? false);
        if (changed) {
          result.filesIndexed++;
          result.sectionsCreated += changed.created;
          result.sectionsUpdated += changed.updated;
          result.sectionsRemoved += changed.removed;
          result.codeRefsCreated += changed.codeRefs;
        }
      } catch (err) {
        result.errors.push(`${relPath}: ${err instanceof Error ? err.message : String(err)}`);
      }
    }
    const removedFromDeleted = this.removeDeletedFiles(files);
    result.sectionsRemoved += removedFromDeleted;
    if (this.config.enableEmbeddings) {
      try {
        opts?.onProgress?.("docs: embedding sections");
        const vectorMgr = new import_vectors.DocsVectorManager(this.config, this.db);
        const allSections = this.db.all("SELECT * FROM doc_sections ORDER BY file_path, position");
        const sections = allSections.map((row) => ({
          id: row.id,
          filePath: row.file_path,
          title: row.title,
          level: row.level,
          parentId: row.parent_id ?? null,
          summary: row.summary ?? null,
          byteStart: row.byte_start,
          byteEnd: row.byte_end,
          contentHash: row.content_hash,
          tags: row.tags ? JSON.parse(row.tags) : [],
          position: row.position,
          updatedAt: row.updated_at
        }));
        await vectorMgr.embedBatch(sections);
      } catch {
      }
    }
    result.duration = Date.now() - start;
    return result;
  }
  /**
   * Index a single file. Returns null if unchanged (hash match).
   */
  async indexFile(relPath, force = false) {
    const absPath = path.join(this.projectRoot, relPath);
    if (!fs.existsSync(absPath)) return null;
    const stat = fs.statSync(absPath);
    if (stat.size > this.config.docsMaxFileSize) return null;
    const content = fs.readFileSync(absPath, "utf8");
    const fileHash = crypto.createHash("sha256").update(content).digest("hex");
    if (!force) {
      const existing = this.db.get(
        "SELECT content_hash FROM doc_sections WHERE file_path = ? LIMIT 1",
        [relPath]
      );
      if (existing) {
        const allSections = this.db.all(
          "SELECT content_hash FROM doc_sections WHERE file_path = ?",
          [relPath]
        );
        const firstSection = this.db.get(
          "SELECT tags FROM doc_sections WHERE file_path = ? AND parent_id IS NULL LIMIT 1",
          [relPath]
        );
        if (firstSection?.tags) {
          try {
            const tags = JSON.parse(firstSection.tags);
            if (tags._fileHash === fileHash) return null;
          } catch {
          }
        }
      }
    }
    const parseResult = (0, import_formats.parseDocFile)(content, relPath);
    if (!parseResult) return null;
    const flatSections = this.flattenSections(parseResult.sections, relPath, fileHash);
    const existingIds = new Set(
      this.db.all("SELECT id FROM doc_sections WHERE file_path = ?", [relPath]).map((r) => r.id)
    );
    let created = 0;
    let updated = 0;
    let codeRefsCount = 0;
    this.db.run("BEGIN");
    try {
      for (const section of flatSections) {
        if (existingIds.has(section.id)) {
          this.db.run(`
            UPDATE doc_sections SET
              title = ?, level = ?, parent_id = ?, summary = ?,
              byte_start = ?, byte_end = ?, content_hash = ?,
              tags = ?, position = ?, updated_at = ?
            WHERE id = ?
          `, [
            section.title,
            section.level,
            section.parentId,
            section.summary,
            section.byteStart,
            section.byteEnd,
            section.contentHash,
            JSON.stringify(section.tags),
            section.position,
            section.updatedAt,
            section.id
          ]);
          existingIds.delete(section.id);
          updated++;
        } else {
          this.db.run(`
            INSERT INTO doc_sections (id, file_path, title, level, parent_id, summary, byte_start, byte_end, content_hash, tags, position, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
          `, [
            section.id,
            section.filePath,
            section.title,
            section.level,
            section.parentId,
            section.summary,
            section.byteStart,
            section.byteEnd,
            section.contentHash,
            JSON.stringify(section.tags),
            section.position,
            section.updatedAt
          ]);
          created++;
        }
        this.db.run("INSERT OR REPLACE INTO doc_sections_fts (id, title, summary) VALUES (?, ?, ?)", [
          section.id,
          section.title,
          section.summary ?? ""
        ]);
      }
      for (const staleId of existingIds) {
        this.db.run("DELETE FROM doc_sections WHERE id = ?", [staleId]);
        this.db.run("DELETE FROM doc_sections_fts WHERE id = ?", [staleId]);
        this.db.run("DELETE FROM doc_code_refs WHERE section_id = ?", [staleId]);
      }
      if (this.config.docsLinkCode) {
        for (const section of flatSections) {
          const sectionContent = Buffer.from(content, "utf8").slice(section.byteStart, section.byteEnd).toString("utf8");
          const refs = (0, import_linker.linkSection)(section.id, sectionContent, this.db);
          this.db.run("DELETE FROM doc_code_refs WHERE section_id = ?", [section.id]);
          for (const ref of refs) {
            this.db.run(
              "INSERT OR REPLACE INTO doc_code_refs (section_id, qualified_name, ref_type, confidence) VALUES (?, ?, ?, ?)",
              [ref.sectionId, ref.qualifiedName, ref.refType, ref.confidence]
            );
            codeRefsCount++;
          }
        }
      }
      this.db.run("COMMIT");
    } catch (err) {
      this.db.run("ROLLBACK");
      throw err;
    }
    return { created, updated, removed: existingIds.size, codeRefs: codeRefsCount };
  }
  // ── Private ─────────────────────────────────────────────────────────────────
  /**
   * Scan the project for documentation files matching include/exclude patterns.
   */
  scanDocFiles() {
    const includeMatchers = this.config.docsInclude.map((p) => (0, import_picomatch.default)(p));
    const excludeMatchers = this.config.docsExclude.map((p) => (0, import_picomatch.default)(p));
    const results = [];
    const walk = (dir) => {
      let entries;
      try {
        entries = fs.readdirSync(dir, { withFileTypes: true });
      } catch {
        return;
      }
      for (const entry of entries) {
        const full = path.join(dir, entry.name);
        const rel = path.relative(this.projectRoot, full).replace(/\\/g, "/");
        if (entry.isDirectory()) {
          if (excludeMatchers.some((m) => m(rel + "/"))) continue;
          walk(full);
        } else if (entry.isFile()) {
          if (excludeMatchers.some((m) => m(rel))) continue;
          if (includeMatchers.some((m) => m(rel))) {
            results.push(rel);
          }
        }
      }
    };
    walk(this.projectRoot);
    return results;
  }
  /**
   * Flatten a parsed section tree into a flat array of DocSection objects.
   */
  flattenSections(sections, filePath, fileHash) {
    const flat = [];
    const now = Date.now();
    const walk = (nodes, parentId, ancestorSlugs) => {
      for (let i = 0; i < nodes.length; i++) {
        const node = nodes[i];
        const id = (0, import_section_id.generateSectionId)(filePath, ancestorSlugs, node.title, node.level);
        const contentHash = crypto.createHash("sha256").update(node.content).digest("hex");
        const summary = this.extractFirstSentence(node.content, node.title);
        const tags = [];
        if (parentId === null && i === 0) {
          tags._fileHash = fileHash;
        }
        flat.push({
          id,
          filePath,
          title: node.title,
          level: node.level,
          parentId,
          summary,
          byteStart: node.byteStart,
          byteEnd: node.byteEnd,
          contentHash,
          tags: parentId === null && i === 0 ? [JSON.stringify({ _fileHash: fileHash })] : tags,
          position: i,
          updatedAt: now
        });
        if (node.children.length > 0) {
          const currentSlug = (0, import_section_id.slugify)(node.title);
          walk(node.children, id, [...ancestorSlugs, currentSlug]);
        }
      }
    };
    walk(sections, null, []);
    return flat;
  }
  /**
   * Extract the first meaningful sentence from section content.
   * Skips the heading line itself.
   */
  extractFirstSentence(content, title) {
    const lines = content.split("\n");
    const bodyLines = lines.filter((l) => {
      const trimmed = l.trim();
      if (trimmed === "" || trimmed.startsWith("#") || trimmed.startsWith("```") || trimmed.startsWith("~~~")) return false;
      if (/^[=-]+\s*$/.test(trimmed)) return false;
      return true;
    });
    if (bodyLines.length === 0) return null;
    const firstLine = bodyLines[0].trim();
    const sentenceEnd = firstLine.search(/[.!?]\s|[.!?]$/);
    if (sentenceEnd > 0) {
      return firstLine.slice(0, sentenceEnd + 1).trim();
    }
    return firstLine.length > 200 ? firstLine.slice(0, 200) + "\u2026" : firstLine;
  }
  /**
   * Remove sections for files that no longer exist on disk.
   */
  removeDeletedFiles(currentFiles) {
    const currentSet = new Set(currentFiles);
    const indexedFiles = this.db.all("SELECT DISTINCT file_path FROM doc_sections").map((r) => r.file_path);
    let removed = 0;
    for (const filePath of indexedFiles) {
      if (!currentSet.has(filePath)) {
        const count = this.db.get("SELECT COUNT(*) as cnt FROM doc_sections WHERE file_path = ?", [filePath])?.cnt ?? 0;
        this.db.run("DELETE FROM doc_sections WHERE file_path = ?", [filePath]);
        this.db.run("DELETE FROM doc_sections_fts WHERE id IN (SELECT id FROM doc_sections WHERE file_path = ?)", [filePath]);
        this.db.run("DELETE FROM doc_code_refs WHERE section_id IN (SELECT id FROM doc_sections WHERE file_path = ?)", [filePath]);
        removed += count;
      }
    }
    return removed;
  }
}
// Annotate the CommonJS export names for ESM import in node:
0 && (module.exports = {
  DocsIndexer
});
//# sourceMappingURL=indexer.js.map
