"use strict";
// src/orchestrator/rag/Indexer.ts
// v1.13.0 — Filesystem walker → chunker → embedder → vector store.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || (function () {
    var ownKeys = function(o) {
        ownKeys = Object.getOwnPropertyNames || function (o) {
            var ar = [];
            for (var k in o) if (Object.prototype.hasOwnProperty.call(o, k)) ar[ar.length] = k;
            return ar;
        };
        return ownKeys(o);
    };
    return function (mod) {
        if (mod && mod.__esModule) return mod;
        var result = {};
        if (mod != null) for (var k = ownKeys(mod), i = 0; i < k.length; i++) if (k[i] !== "default") __createBinding(result, mod, k[i]);
        __setModuleDefault(result, mod);
        return result;
    };
})();
Object.defineProperty(exports, "__esModule", { value: true });
exports.Indexer = void 0;
const crypto = __importStar(require("crypto"));
const fs = __importStar(require("fs"));
const path = __importStar(require("path"));
const Chunker_1 = require("./Chunker");
const DEFAULT_MAX_BYTES = 1024 * 1024;
const DEFAULT_IGNORE = [
    'node_modules', 'dist', 'build', '.git', '.openlife',
    '.artifacts', '.catalog', '.cache', '.next', '.nuxt',
    'coverage', '.idea', '.vscode', '__pycache__',
];
const ALLOWED_EXTENSIONS = new Set([
    '.ts', '.tsx', '.js', '.jsx', '.mjs', '.cjs',
    '.py', '.go', '.rs', '.java', '.rb',
    '.md', '.txt', '.json', '.yaml', '.yml',
]);
function sha1(text) {
    return crypto.createHash('sha1').update(text).digest('hex');
}
function chunkId(file, startLine, endLine, hash) {
    return `${file}:${startLine}-${endLine}:${hash.slice(0, 12)}`;
}
function loadGitignorePatterns(root) {
    const patterns = [];
    const candidates = ['.gitignore', '.openlifeignore'];
    for (const c of candidates) {
        const full = path.join(root, c);
        if (!fs.existsSync(full))
            continue;
        try {
            const txt = fs.readFileSync(full, 'utf-8');
            for (const raw of txt.split(/\r?\n/)) {
                const line = raw.trim();
                if (!line || line.startsWith('#'))
                    continue;
                patterns.push(line);
            }
        }
        catch { /* ignore */ }
    }
    return patterns;
}
function isIgnoredPath(rel, patterns) {
    // Minimal glob: support 'foo', 'foo/', '*.ext', 'dir/'.
    const segments = rel.split('/').filter(s => s.length > 0);
    if (segments.length === 0)
        return false;
    for (const p of patterns) {
        const pat = p.replace(/^\/+/, '').replace(/\/$/, '');
        if (pat.length === 0)
            continue;
        if (pat.startsWith('*.')) {
            const ext = pat.slice(1);
            if (rel.endsWith(ext))
                return true;
            continue;
        }
        if (segments.includes(pat))
            return true;
        if (rel === pat)
            return true;
    }
    return false;
}
function isDefaultIgnoredDir(name) {
    return DEFAULT_IGNORE.includes(name);
}
function walk(root, ignorePatterns, maxBytes) {
    const out = [];
    function visit(dir) {
        let entries;
        try {
            entries = fs.readdirSync(dir, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const ent of entries) {
            const full = path.join(dir, ent.name);
            const rel = path.relative(root, full).replace(/\\/g, '/');
            if (ent.isDirectory()) {
                if (isDefaultIgnoredDir(ent.name))
                    continue;
                if (isIgnoredPath(rel, ignorePatterns))
                    continue;
                visit(full);
            }
            else if (ent.isFile()) {
                const ext = path.extname(ent.name).toLowerCase();
                if (!ALLOWED_EXTENSIONS.has(ext))
                    continue;
                if (isIgnoredPath(rel, ignorePatterns))
                    continue;
                try {
                    const stat = fs.statSync(full);
                    if (stat.size > maxBytes)
                        continue;
                }
                catch {
                    continue;
                }
                out.push(full);
            }
        }
    }
    visit(root);
    return out;
}
class Indexer {
    async build(opts) {
        const root = path.resolve(opts.root);
        const maxBytes = opts.maxFileBytes ?? DEFAULT_MAX_BYTES;
        const patterns = [...loadGitignorePatterns(root), ...(opts.extraIgnore || [])];
        const files = walk(root, patterns, maxBytes);
        const report = { scannedFiles: files.length, indexedFiles: 0, skippedFiles: 0, upsertedChunks: 0, removedFiles: 0 };
        for (let i = 0; i < files.length; i++) {
            const file = files[i];
            opts.onProgress?.(file, i + 1, files.length);
            try {
                const stat = fs.statSync(file);
                const mtime = stat.mtimeMs;
                const existing = opts.store.getFileChunks?.(file) || [];
                if (!opts.rebuild && existing.length > 0 && existing.every(c => c.mtime === mtime)) {
                    report.skippedFiles++;
                    continue;
                }
                const content = fs.readFileSync(file, 'utf-8');
                const chunks = (0, Chunker_1.chunkFile)(file, content);
                if (chunks.length === 0) {
                    report.skippedFiles++;
                    continue;
                }
                const vectors = await opts.embed.embedBatch(chunks.map(c => c.content));
                await opts.store.deleteByFile(file);
                await opts.store.upsert(chunks.map((c, idx) => {
                    const hash = sha1(c.content);
                    return {
                        id: chunkId(file, c.startLine, c.endLine, hash),
                        vector: vectors[idx],
                        payload: {
                            file,
                            startLine: c.startLine,
                            endLine: c.endLine,
                            content: c.content,
                            mtime,
                            hash,
                            language: c.language ?? (0, Chunker_1.inferLanguage)(file),
                        },
                    };
                }));
                report.indexedFiles++;
                report.upsertedChunks += chunks.length;
            }
            catch (err) {
                console.warn(`[INDEXER] skip ${file}: ${err instanceof Error ? err.message : String(err)}`);
                report.skippedFiles++;
            }
        }
        return report;
    }
}
exports.Indexer = Indexer;
