"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.parseFile = parseFile;
exports.clearWikiIndexCache = clearWikiIndexCache;
const path_1 = __importDefault(require("path"));
const fs_1 = __importDefault(require("fs"));
const codeblock_1 = require("../../lib/parsers/codeblock");
const defaults_1 = require("../../lib/defaults");
// Parse a markdown file into chunks split by headings
async function parseFile(content, absolutePath, projectDir, chunkDepth) {
    const fileId = path_1.default.relative(projectDir, absolutePath);
    const lines = content.split('\n');
    const rawChunks = [];
    let current = {
        level: 1,
        title: extractFileTitle(content, absolutePath),
        lines: [],
    };
    for (const line of lines) {
        const heading = matchHeading(line, chunkDepth);
        if (heading) {
            rawChunks.push(current);
            current = { level: heading.level, title: heading.title, lines: [line] };
        }
        else {
            current.lines.push(line);
        }
    }
    rawChunks.push(current);
    // Build Chunk objects
    const seenIds = new Set();
    const textChunks = rawChunks
        .filter(c => c.lines.join('\n').trim() || c.level === 1)
        .map(c => {
        const chunkContent = c.lines.join('\n').trim();
        const baseId = c.level === 1 ? fileId : `${fileId}::${c.title}`;
        let id = baseId;
        let counter = 2;
        while (seenIds.has(id))
            id = `${baseId}::${counter++}`;
        seenIds.add(id);
        return {
            id,
            fileId,
            title: c.title,
            content: chunkContent,
            level: c.level,
            links: extractLinks(chunkContent, absolutePath, projectDir),
            embedding: [], // filled later by embedder
            symbols: [],
        };
    });
    return await spliceCodeBlocks(textChunks, seenIds);
}
// --- code block extraction ---
const FENCE_RE = /^(`{3,}|~{3,})(\S*)\s*\n([\s\S]*?)^\1\s*$/gm;
async function spliceCodeBlocks(chunks, seenIds) {
    const result = [];
    for (const chunk of chunks) {
        // Skip chunks that are already code blocks (shouldn't happen, but guard)
        if (chunk.language !== undefined) {
            result.push(chunk);
            continue;
        }
        const codeBlocks = [];
        let match;
        FENCE_RE.lastIndex = 0;
        while ((match = FENCE_RE.exec(chunk.content)) !== null) {
            const lang = match[2].toLowerCase();
            const code = match[3].trimEnd();
            if (code)
                codeBlocks.push({ language: lang, code, index: match.index });
        }
        result.push(chunk);
        // Create child chunks for each code block
        let codeIdx = 0;
        for (const cb of codeBlocks) {
            codeIdx++;
            const baseId = `${chunk.id}::code-${codeIdx}`;
            let id = baseId;
            let counter = 2;
            while (seenIds.has(id))
                id = `${baseId}::${counter++}`;
            seenIds.add(id);
            const lang = cb.language || undefined;
            const symbols = lang ? await (0, codeblock_1.extractSymbols)(cb.code, lang) : [];
            result.push({
                id,
                fileId: chunk.fileId,
                title: lang || 'code',
                content: cb.code,
                level: chunk.level + 1,
                links: [],
                embedding: [],
                language: lang,
                symbols,
            });
        }
    }
    return result;
}
// --- helpers ---
function matchHeading(line, maxDepth) {
    const match = line.match(/^(#{1,6})\s+(.+)$/);
    if (!match)
        return null;
    const level = match[1].length;
    if (level < 2 || level > maxDepth)
        return null; // # is file title, not a chunk split
    return { level, title: match[2].trim() };
}
function extractFileTitle(content, filePath) {
    const match = content.match(/^#\s+(.+)$/m);
    if (match)
        return match[1].trim();
    return path_1.default.basename(filePath, '.md');
}
function extractLinks(content, fromFile, projectDir) {
    const results = new Set();
    const fileDir = path_1.default.dirname(fromFile);
    // Strip fenced code blocks before extracting links (links inside code are not real references)
    const textOnly = content.replace(FENCE_RE, '');
    // [text](./path.md)
    const mdLinks = textOnly.matchAll(/\[[^\]]*\]\(([^)#\s]+)/g);
    for (const [, href] of mdLinks) {
        if (isExternal(href))
            continue;
        const resolved = path_1.default.resolve(fileDir, href);
        const fileId = toFileId(resolved, projectDir);
        if (fileId)
            results.add(fileId);
    }
    // [[wiki link]] or [[wiki link|alias]]
    const wikiLinks = textOnly.matchAll(/\[\[([^\]|]+)(?:\|[^\]]+)?\]\]/g);
    for (const [, name] of wikiLinks) {
        const resolved = findWikiFile(name.trim(), projectDir);
        if (!resolved)
            continue;
        const fileId = toFileId(resolved, projectDir); // same guard as md links
        if (fileId)
            results.add(fileId);
    }
    return [...results];
}
// Reject anything that looks like an external URL
function isExternal(href) {
    return /^[a-zA-Z][a-zA-Z\d+\-.]*:\/\//.test(href) // http://, ftp://, mailto://, etc.
        || href.startsWith('//') // protocol-relative //cdn.example.com
        || href.startsWith('data:') // data URIs
        || href.startsWith('mailto:');
}
function toFileId(absolutePath, projectDir) {
    const rel = path_1.default.relative(projectDir, absolutePath);
    if (rel.startsWith('..') || path_1.default.isAbsolute(rel))
        return null;
    if (fs_1.default.existsSync(absolutePath))
        return rel;
    // try adding .md
    const withMd = absolutePath + '.md';
    if (fs_1.default.existsSync(withMd))
        return path_1.default.relative(projectDir, withMd);
    return null;
}
/**
 * Cache: projectDir → Map<lowercased basename (without .md), absolute path>.
 * Built once per project via a single recursive walk, then reused for all wiki link lookups.
 */
const _wikiIndex = new Map();
/** Clear cached wiki link index (call when files change or between projects). */
function clearWikiIndexCache(projectDir) {
    if (projectDir)
        _wikiIndex.delete(projectDir);
    else
        _wikiIndex.clear();
}
function getWikiIndex(projectDir) {
    if (_wikiIndex.has(projectDir))
        return _wikiIndex.get(projectDir);
    const index = new Map();
    const MAX_DEPTH = defaults_1.WIKI_MAX_DEPTH;
    function walk(dir, depth) {
        if (depth >= MAX_DEPTH)
            return;
        let entries;
        try {
            entries = fs_1.default.readdirSync(dir, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const entry of entries) {
            if (entry.name.startsWith('.') || entry.isSymbolicLink())
                continue;
            const full = path_1.default.join(dir, entry.name);
            if (entry.isDirectory()) {
                walk(full, depth + 1);
            }
            else if (entry.isFile()) {
                // Index by basename without extension (lowercased)
                const key = path_1.default.basename(entry.name, path_1.default.extname(entry.name)).toLowerCase();
                if (!index.has(key))
                    index.set(key, full);
                // Also index by full filename (lowercased)
                const fullKey = entry.name.toLowerCase();
                if (!index.has(fullKey))
                    index.set(fullKey, full);
            }
        }
    }
    walk(projectDir, 0);
    _wikiIndex.set(projectDir, index);
    return index;
}
function findWikiFile(name, projectDir) {
    // Direct path check first (fast path)
    const direct = path_1.default.join(projectDir, name);
    if (fs_1.default.existsSync(direct))
        return direct;
    const withMd = direct + '.md';
    if (fs_1.default.existsSync(withMd))
        return withMd;
    // Fall back to cached index lookup
    const index = getWikiIndex(projectDir);
    return index.get(name.toLowerCase()) ?? index.get(name.toLowerCase() + '.md') ?? null;
}
