import { createRequire } from 'node:module';
import * as nodeFs from 'node:fs';
import { existsSync, readFileSync, statSync, readdirSync } from 'node:fs';
import { join, extname, basename, dirname, sep } from 'node:path';
import { createHash, createHmac } from 'node:crypto';
const require = createRequire(import.meta.url);
function detectMusl() {
    if (process.platform !== 'linux') {
        return false;
    }
    // Layered detection — any positive signal wins. Order is cheapest first.
    // 1) Distro marker (Alpine and other musl distros) — covers distroless/scratch
    //    images where /usr/bin/ldd is absent.
    try {
        if (existsSync('/etc/alpine-release')) {
            return true;
        }
    }
    catch {
        /* ignore */
    }
    // 2) Node header report (Node 18+) — reports the libc family.
    try {
        const reportApi = process.report;
        if (reportApi) {
            const report = reportApi.getReport();
            if (typeof report === 'string' && /musl/i.test(report)) {
                return true;
            }
        }
    }
    catch {
        /* ignore */
    }
    // 3) /usr/bin/ldd shell script (musl-utils on full Alpine).
    try {
        if (existsSync('/usr/bin/ldd')) {
            const ldd = readFileSync('/usr/bin/ldd', 'utf8');
            return /musl/i.test(ldd);
        }
    }
    catch {
        /* ignore */
    }
    // 4) ld-musl-* dynamic linker present in /lib (final fallback for distroless).
    try {
        const libEntries = readdirSync('/lib');
        if (libEntries.some((e) => e.startsWith('ld-musl-'))) {
            return true;
        }
    }
    catch {
        /* ignore */
    }
    return false;
}
function getPlatformInfo() {
    return {
        platform: process.platform,
        arch: process.arch,
        libc: process.platform === 'linux' ? (detectMusl() ? 'musl' : 'glibc') : null,
    };
}
function platformPackageName(info) {
    const { platform, arch, libc } = info;
    if (platform === 'darwin') {
        if (arch === 'arm64') {
            return '@planu/core-darwin-arm64';
        }
        if (arch === 'x64') {
            return '@planu/core-darwin-x64';
        }
    }
    if (platform === 'linux') {
        if (arch === 'x64') {
            return libc === 'musl' ? '@planu/core-linux-x64-musl' : '@planu/core-linux-x64-gnu';
        }
        if (arch === 'arm64') {
            return libc === 'musl' ? '@planu/core-linux-arm64-musl' : '@planu/core-linux-arm64-gnu';
        }
    }
    if (platform === 'win32') {
        if (arch === 'x64') {
            return '@planu/core-win32-x64-msvc';
        }
        if (arch === 'arm64') {
            return '@planu/core-win32-arm64-msvc';
        }
    }
    return null;
}
function localBinaryName(info) {
    const { platform, arch, libc } = info;
    if (platform === 'linux') {
        const suffix = libc === 'musl' ? 'musl' : 'gnu';
        return `planu-core.linux-${arch}-${suffix}.node`;
    }
    if (platform === 'win32') {
        return `planu-core.win32-${arch}-msvc.node`;
    }
    return `planu-core.${platform}-${arch}.node`;
}
let nativeModule = null;
let loadDiagnostic = { attempted: [], errors: [] };
let loaded = false;
function loadNative() {
    if (process.env.DISABLE_NATIVE_CORE === '1') {
        loadDiagnostic = { attempted: [], errors: [] };
        loaded = true;
        return null;
    }
    if (loaded) {
        return nativeModule;
    }
    loaded = true;
    const info = getPlatformInfo();
    const attempts = [];
    const errors = [];
    const tryRequire = (id) => {
        attempts.push(id);
        try {
            return require(id);
        }
        catch (err) {
            errors.push(`${id}: ${err.message.split('\n')[0]}`);
            return null;
        }
    };
    // 1) Honour an explicit override.
    const override = process.env.PLANU_NATIVE_PATH;
    if (override) {
        const mod = tryRequire(override);
        if (mod) {
            loadDiagnostic = { attempted: attempts, errors, loaded: override };
            nativeModule = mod;
            return mod;
        }
    }
    // 2) Local platform-tagged binary in the same dir (dev builds + bundled tarball).
    const localName = localBinaryName(info);
    const localPath = join(import.meta.dirname, localName);
    if (existsSync(localPath)) {
        const mod = tryRequire(localPath);
        if (mod) {
            loadDiagnostic = { attempted: attempts, errors, loaded: localPath };
            nativeModule = mod;
            return mod;
        }
    }
    // 3) Legacy untagged binary shipped in older tarballs.
    const legacyPath = join(import.meta.dirname, 'planu-core.node');
    if (existsSync(legacyPath)) {
        const mod = tryRequire(legacyPath);
        if (mod) {
            loadDiagnostic = { attempted: attempts, errors, loaded: legacyPath };
            nativeModule = mod;
            return mod;
        }
    }
    // 4) Optional dependency subpackage installed by npm.
    const pkg = platformPackageName(info);
    if (pkg) {
        const mod = tryRequire(pkg);
        if (mod) {
            loadDiagnostic = { attempted: attempts, errors, loaded: pkg };
            nativeModule = mod;
            return mod;
        }
    }
    loadDiagnostic = { attempted: attempts, errors };
    return null;
}
const native = loadNative();
if (native) {
    console.error(`[Planu-RS] Native core loaded (${loadDiagnostic.loaded}).`);
}
else if (process.env.PLANU_DEBUG_NATIVE === '1') {
    console.warn(`[Planu-RS] Native core unavailable on ${process.platform}-${process.arch}. ` +
        `Falling back to TypeScript implementation. Attempts: ${loadDiagnostic.attempted.join(', ')}`);
}
const getNative = () => process.env.DISABLE_NATIVE_CORE === '1' ? null : nativeModule;
// TypeScript fallbacks -------------------------------------------------------
// These keep the surface area working when the native binary is unavailable
// (unsupported platform, sandboxed install, dev environment without build).
const PROJECT_ID_RE = /^[a-f0-9]{16,64}$/;
const SPEC_ANNOTATION_RE = /@spec\s+(SPEC-\d+)(?:\s+AC:([\d,]+))?(?:\s+(.*))?/;
const SCANNABLE_EXTS = new Set(['.ts', '.js', '.tsx', '.jsx', '.rs', '.py', '.go']);
const MAX_TS_WATCHERS = 128;
const IGNORED_DIRS = new Set([
    'node_modules',
    '.git',
    'dist',
    'build',
    'target',
    'coverage',
    '.next',
]);
function* walkFiles(root) {
    let entries;
    try {
        entries = readdirSync(root, { withFileTypes: true });
    }
    catch {
        return;
    }
    for (const entry of entries) {
        if (entry.isDirectory()) {
            if (IGNORED_DIRS.has(entry.name) || entry.name.startsWith('.')) {
                continue;
            }
            yield* walkFiles(join(root, entry.name));
        }
        else if (entry.isFile()) {
            yield join(root, entry.name);
        }
    }
}
function tsHashProjectPath(path) {
    return createHash('sha256').update(path).digest('hex');
}
function tsScanAndHashFiles(root) {
    const out = [];
    for (const path of walkFiles(root)) {
        try {
            const content = readFileSync(path);
            out.push({ path, hash: createHash('sha256').update(content).digest('hex') });
        }
        catch {
            /* skip unreadable */
        }
    }
    return out;
}
function tsScanProjectMetadata(root) {
    const out = [];
    for (const path of walkFiles(root)) {
        try {
            const stat = statSync(path);
            out.push({ path, mtime: stat.mtimeMs, size: stat.size });
        }
        catch {
            /* skip */
        }
    }
    return out;
}
function tsScanSpecAnnotations(root) {
    const out = [];
    for (const path of walkFiles(root)) {
        if (!SCANNABLE_EXTS.has(extname(path))) {
            continue;
        }
        try {
            const lines = readFileSync(path, 'utf8').split('\n');
            lines.forEach((line, idx) => {
                const m = SPEC_ANNOTATION_RE.exec(line);
                if (!m) {
                    return;
                }
                const raw = m[0];
                const specId = m[1];
                const criteriaStr = m[2];
                const description = m[3];
                if (!specId) {
                    return;
                }
                out.push({
                    specId,
                    criteria: criteriaStr
                        ? criteriaStr
                            .split(',')
                            .map((s) => Number(s.trim()))
                            .filter(Number.isFinite)
                        : [],
                    description: description ?? '',
                    file: path,
                    line: idx + 1,
                    raw,
                });
            });
        }
        catch {
            /* skip */
        }
    }
    return out;
}
function tsCheckAcCoverage(filePaths, keywords) {
    if (filePaths.length === 0 || keywords.length === 0) {
        return [];
    }
    let combined = '';
    for (const p of filePaths) {
        try {
            combined += readFileSync(p, 'utf8').toLowerCase() + '\n';
        }
        catch {
            /* skip */
        }
    }
    return keywords.filter((kw) => combined.includes(kw.toLowerCase()));
}
function tsFindFilesByName(root, names) {
    const set = new Set(names);
    const out = [];
    for (const path of walkFiles(root)) {
        if (set.has(basename(path))) {
            out.push(path);
        }
    }
    return out;
}
function tsFindFilesByExt(root, extensions) {
    const set = new Set(extensions.map((e) => (e.startsWith('.') ? e : `.${e}`)));
    const out = [];
    for (const path of walkFiles(root)) {
        if (set.size === 0 || set.has(extname(path))) {
            out.push(path);
        }
    }
    return out;
}
function tsReadFiles(paths) {
    return paths
        .map((p) => {
        try {
            return { path: p, content: readFileSync(p, 'utf8') };
        }
        catch {
            return null;
        }
    })
        .filter((x) => x !== null);
}
function tsCosineSimilarity(v1, v2) {
    if (v1.length !== v2.length || v1.length === 0) {
        return 0;
    }
    let dot = 0;
    let n1 = 0;
    let n2 = 0;
    for (let i = 0; i < v1.length; i++) {
        const a = v1[i] ?? 0;
        const b = v2[i] ?? 0;
        dot += a * b;
        n1 += a * a;
        n2 += b * b;
    }
    const norm = Math.sqrt(n1) * Math.sqrt(n2);
    return norm === 0 ? 0 : dot / norm;
}
function tsHnswSearchFlat(query, nodes, topK) {
    return nodes
        .map((n) => ({ id: n.id, similarity: tsCosineSimilarity(query, n.vector) }))
        .sort((a, b) => b.similarity - a.similarity)
        .slice(0, topK);
}
function tsHmacSign(secret, data) {
    // Must produce the same digest as the Rust `fastHmacSign` (HMAC-SHA256, not
    // a plain SHA-256 of `secret + data`) so signatures roundtrip across paths.
    return createHmac('sha256', secret).update(data).digest('hex');
}
function tsHmacVerify(secret, data, signature) {
    const expected = tsHmacSign(secret, data);
    if (expected.length !== signature.length) {
        return false;
    }
    let res = 0;
    for (let i = 0; i < expected.length; i++) {
        res |= expected.charCodeAt(i) ^ signature.charCodeAt(i);
    }
    return res === 0;
}
function tsFindPatterns(root, patterns, extensions, exclude) {
    if (patterns.length === 0) {
        return [];
    }
    const regexes = patterns
        .map((p) => {
        try {
            return { p, re: new RegExp(p) };
        }
        catch {
            return null;
        }
    })
        .filter((x) => x !== null);
    if (regexes.length === 0) {
        return [];
    }
    const extSet = new Set(extensions.map((e) => (e.startsWith('.') ? e : `.${e}`)));
    const excludeSet = new Set(exclude);
    const matches = [];
    for (const path of walkFiles(root)) {
        if (extSet.size > 0 && !extSet.has(extname(path))) {
            continue;
        }
        if (excludeSet.has(basename(path))) {
            continue;
        }
        let lines;
        try {
            lines = readFileSync(path, 'utf8').split('\n');
        }
        catch {
            continue;
        }
        lines.forEach((line, idx) => {
            for (const { p, re } of regexes) {
                const m = re.exec(line);
                if (m) {
                    matches.push({
                        pattern: p,
                        path,
                        line: idx + 1,
                        column: m.index + 1,
                        content: line.trim(),
                    });
                }
            }
        });
    }
    return matches;
}
function tsGetProjectDataPath(baseDir, projectId) {
    const path = join(baseDir, projectId);
    return existsSync(path) ? path : null;
}
// Public API -----------------------------------------------------------------
export function fastHashProjectPath(projectPath) {
    const n = getNative();
    return n ? n.hashProjectPath(projectPath) : tsHashProjectPath(projectPath);
}
export function fastGetProjectDataPath(baseDir, projectId) {
    const n = getNative();
    return n ? n.getProjectDataPath(baseDir, projectId) : tsGetProjectDataPath(baseDir, projectId);
}
export function fastIsSafeProjectId(projectId) {
    const n = getNative();
    return n ? n.isSafeProjectId(projectId) : PROJECT_ID_RE.test(projectId);
}
export function fastScanAndHashFiles(rootPath) {
    const n = getNative();
    return n ? n.scanAndHashFiles(rootPath) : tsScanAndHashFiles(rootPath);
}
export function fastScanProjectMetadata(rootPath) {
    const n = getNative();
    return n ? n.scanProjectMetadata(rootPath) : tsScanProjectMetadata(rootPath);
}
export function fastScanSpecAnnotations(rootPath) {
    const n = getNative();
    return n ? n.scanSpecAnnotations(rootPath) : tsScanSpecAnnotations(rootPath);
}
export function fastCheckAcCoverage(filePaths, keywords) {
    const n = getNative();
    return n ? n.checkAcCoverage(filePaths, keywords) : tsCheckAcCoverage(filePaths, keywords);
}
export function fastDetectDriftParallel(rootPath, keywords) {
    const n = getNative();
    if (n) {
        return n.fastDetectDriftParallel(rootPath, keywords);
    }
    // Fallback: simple sequential scan.
    const files = Array.from(walkFiles(rootPath));
    const lowered = keywords.map((k) => k.toLowerCase());
    const found = new Set();
    for (const file of files) {
        if (found.size === keywords.length) {
            break;
        }
        let content = '';
        try {
            content = readFileSync(file, 'utf8').toLowerCase();
        }
        catch {
            continue;
        }
        keywords.forEach((kw, i) => {
            const needle = lowered[i];
            if (needle && content.includes(needle)) {
                found.add(kw);
            }
        });
    }
    return { foundKeywords: Array.from(found), fileCount: files.length };
}
export function fastFindDuplicateBlocks(paths, minLines) {
    const n = getNative();
    if (n) {
        return n.fastFindDuplicateBlocks(paths, minLines);
    }
    // TS fallback: hash sliding windows of normalized lines.
    if (paths.length === 0 || minLines <= 0) {
        return [];
    }
    const map = new Map();
    for (const path of paths) {
        let raw;
        try {
            raw = readFileSync(path, 'utf8')
                .replace(/\/\*[\s\S]*?\*\//g, '')
                .split('\n');
        }
        catch {
            continue;
        }
        const norm = [];
        raw.forEach((line, i) => {
            const stripped = line
                .replace(/\/\/.*$/, '')
                .trim()
                .toLowerCase();
            if (stripped) {
                norm.push({ text: stripped, idx: i + 1 });
            }
        });
        if (norm.length < minLines) {
            continue;
        }
        for (let i = 0; i <= norm.length - minLines; i++) {
            const window = norm.slice(i, i + minLines);
            const lines = window.map((w) => w.text);
            const hash = createHash('sha256').update(lines.join('\n')).digest('hex').slice(0, 16);
            const first = window[0];
            const last = window[window.length - 1];
            if (!first || !last) {
                continue;
            }
            const loc = {
                path,
                startLine: first.idx,
                endLine: last.idx,
                lines,
            };
            const arr = map.get(hash);
            if (arr) {
                arr.push(loc);
            }
            else {
                map.set(hash, [loc]);
            }
        }
    }
    const out = [];
    for (const [hash, locations] of map) {
        if (locations.length >= 2) {
            out.push({ hash, locations });
        }
    }
    return out;
}
export function fastFindPatterns(rootPath, patterns, extensions = [], exclude = []) {
    const n = getNative();
    return n
        ? n.fastFindPatterns(rootPath, patterns, extensions, exclude)
        : tsFindPatterns(rootPath, patterns, extensions, exclude);
}
export function fastFindFilesByName(rootPath, names) {
    const n = getNative();
    return n ? n.fastFindFilesByName(rootPath, names) : tsFindFilesByName(rootPath, names);
}
export function fastFindFilesByExt(rootPath, extensions) {
    const n = getNative();
    return n ? n.fastFindFilesByExt(rootPath, extensions) : tsFindFilesByExt(rootPath, extensions);
}
export function fastReadFiles(paths) {
    const n = getNative();
    return n ? n.fastReadFiles(paths) : tsReadFiles(paths);
}
export function fastScanSpecs(rootPath) {
    const n = getNative();
    if (n) {
        return n.fastScanSpecs(rootPath);
    }
    // No TS fallback yet — callers should detect and use the regular spec
    // scanner instead. Returning null preserves the existing semantics.
    return null;
}
export function fastVerifyGapsChain(filePath) {
    const n = getNative();
    if (n) {
        return n.fastVerifyGapsChain(filePath);
    }
    // TS fallback: linear walk validating prevSha + sha pair.
    if (!existsSync(filePath)) {
        return { valid: true, totalEntries: 0, brokenAt: null };
    }
    let prevSha = '';
    let count = 0;
    let lines;
    try {
        lines = readFileSync(filePath, 'utf8').split('\n');
    }
    catch {
        return { valid: false, totalEntries: 0, brokenAt: 0 };
    }
    for (let i = 0; i < lines.length; i++) {
        const rawLine = lines[i];
        if (!rawLine) {
            continue;
        }
        const line = rawLine.trim();
        if (!line) {
            continue;
        }
        let entry;
        try {
            entry = JSON.parse(line);
        }
        catch {
            return { valid: false, totalEntries: count, brokenAt: i };
        }
        const actualPrev = typeof entry.prevSha === 'string' ? entry.prevSha : '';
        if (actualPrev !== prevSha) {
            return { valid: false, totalEntries: count, brokenAt: i };
        }
        const sha = typeof entry.sha === 'string' ? entry.sha : '';
        const { sha: _, ...rest } = entry;
        void _;
        const expected = createHash('sha256').update(JSON.stringify(rest)).digest('hex');
        if (sha !== expected) {
            return { valid: false, totalEntries: count, brokenAt: i };
        }
        prevSha = sha;
        count++;
    }
    return { valid: true, totalEntries: count, brokenAt: null };
}
export function fastAppendGapEntry(filePath, id, timestamp, specId, description, severity, affectedSpecs) {
    const n = getNative();
    if (n) {
        return n.fastAppendGapEntry(filePath, id, timestamp, specId, description, severity, affectedSpecs);
    }
    // TS fallback: read prev sha, append new entry.
    const fs = nodeFs;
    let prevSha = '';
    if (existsSync(filePath)) {
        const lines = readFileSync(filePath, 'utf8').trim().split('\n');
        const last = lines[lines.length - 1];
        if (last) {
            try {
                const v = JSON.parse(last);
                prevSha = v.sha ?? '';
            }
            catch {
                /* ignore */
            }
        }
    }
    else {
        fs.mkdirSync(dirname(filePath).split(sep).join(sep), { recursive: true });
    }
    const entry = { id, timestamp, specId, description, severity, affectedSpecs, prevSha };
    const sha = createHash('sha256').update(JSON.stringify(entry)).digest('hex');
    fs.appendFileSync(filePath, JSON.stringify({ ...entry, sha }) + '\n');
    return sha;
}
export function fastCosineSimilarity(v1, v2) {
    const n = getNative();
    return n ? n.fastCosineSimilarity(v1, v2) : tsCosineSimilarity(v1, v2);
}
export function fastHnswSearchFlat(query, nodes, topK) {
    const n = getNative();
    return n ? n.fastHnswSearchFlat(query, nodes, topK) : tsHnswSearchFlat(query, nodes, topK);
}
export function fastHmacSign(secret, data) {
    const n = getNative();
    return n ? n.fastHmacSign(secret, data) : tsHmacSign(secret, data);
}
export function fastHmacVerify(secret, data, signature) {
    const n = getNative();
    return n ? n.fastHmacVerify(secret, data, signature) : tsHmacVerify(secret, data, signature);
}
export function startNativeWatcher(rootPath, callback) {
    const n = getNative();
    if (n && typeof n.startProjectWatcher === 'function') {
        n.startProjectWatcher(rootPath, callback);
        return;
    }
    // TS fallback: watch each existing directory. Node's `recursive: true` is not
    // portable across all OS/libc combinations, and unsupported platforms must not
    // crash the MCP server when the native watcher is unavailable.
    const fs = nodeFs;
    let watcherCount = 0;
    const watchDir = (absDir, relDir) => {
        if (watcherCount >= MAX_TS_WATCHERS) {
            return;
        }
        const watcher = fs.watch(absDir, {}, (event, filename) => {
            callback(null, `${event}:${join(relDir, filename?.toString() ?? '')}`);
        });
        watcherCount++;
        watcher.on('error', (err) => {
            callback(err, '');
        });
    };
    const walkDirs = (absDir, relDir) => {
        watchDir(absDir, relDir);
        let entries;
        try {
            entries = readdirSync(absDir, { withFileTypes: true });
        }
        catch {
            return;
        }
        for (const entry of entries) {
            if (!entry.isDirectory() || IGNORED_DIRS.has(entry.name)) {
                continue;
            }
            const childRel = join(relDir, entry.name);
            walkDirs(join(absDir, entry.name), childRel);
        }
    };
    walkDirs(rootPath, '');
}
export const isNativeActive = () => nativeModule !== null && process.env.DISABLE_NATIVE_CORE !== '1';
export const nativeLoadDiagnostic = () => loadDiagnostic;
//# sourceMappingURL=core-bridge.js.map