// engine/detectors/library-detector-langs4.ts — Scientific/Scripting/Systems lang detection
// SPEC-004 tech-debt S4: Julia, R, D lang, Perl
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// === Catalog constants ===
const JULIA_HTTP_LIBS = ['HTTP', 'Genie', 'Oxygen', 'Mux'];
const JULIA_DATA_LIBS = ['DataFrames', 'CSV', 'Arrow'];
const JULIA_ML_LIBS = ['Flux', 'MLJ', 'Lux'];
const JULIA_TESTING_LIBS = ['Test', 'ReTest', 'Aqua'];
const R_DATA_LIBS = ['dplyr', 'tidyverse', 'data.table', 'ggplot2'];
const R_ML_LIBS = ['caret', 'tidymodels', 'xgboost', 'keras'];
const R_TESTING_LIBS = ['testthat', 'testthis', 'covr'];
const DLANG_WEB_LIBS = ['vibe-d', 'arsd'];
const DLANG_DATA_LIBS = ['mir-algorithm', 'kaleidic'];
const DLANG_TESTING_LIBS = ['unit-threaded', 'dunit'];
const PERL_WEB_LIBS = ['Mojolicious', 'Dancer2', 'Catalyst'];
const PERL_DB_LIBS = ['DBI', 'DBIx::Class', 'Mojo::Pg'];
const PERL_TESTING_LIBS = ['Test::More', 'Test::Exception', 'Moo'];
// === Helpers ===
function filterPresent(catalog, deps) {
    return catalog.filter((lib) => deps.has(lib));
}
// === Julia parsers ===
/**
 * Parse Project.toml [deps] section.
 * Matches `PackageName = "uuid"` lines.
 */
export function parseJuliaProjectToml(content) {
    const deps = new Set();
    const sectionMatch = /\[deps\]([\s\S]*?)(?:\n\[|$)/s.exec(content);
    if (sectionMatch?.[1]) {
        const matches = sectionMatch[1].matchAll(/^([A-Za-z][A-Za-z0-9_]*)\s*=/gm);
        for (const match of matches) {
            /* v8 ignore start */
            if (match[1]) {
                /* v8 ignore stop */
                deps.add(match[1]);
            }
        }
    }
    return deps;
}
async function collectJuliaDeps(projectPath) {
    try {
        const content = await readFile(join(projectPath, 'Project.toml'), 'utf-8');
        return parseJuliaProjectToml(content);
    }
    catch {
        return new Set();
    }
}
// === R parsers ===
/**
 * Parse DESCRIPTION file Imports/Depends fields.
 * Matches comma-separated package names after `Imports:` or `Depends:`.
 */
export function parseRDescription(content) {
    const deps = new Set();
    const matches = content.matchAll(/^(?:Imports|Depends)\s*:\s*((?:[^\n]+\n(?:[ \t]+[^\n]*\n?)*)*)/gm);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            const pkgs = match[1].split(/[,\n]/).map((s) => s
                .trim()
                .split(/[\s(>=]/)[0]
                ?.trim() ?? '');
            for (const pkg of pkgs) {
                if (pkg && /^[a-zA-Z]/.test(pkg)) {
                    deps.add(pkg);
                }
            }
        }
    }
    return deps;
}
/**
 * Parse renv.lock JSON for package names.
 * Matches `"Package": "name"` entries.
 */
export function parseRenvLock(content) {
    const deps = new Set();
    const matches = content.matchAll(/"Package"\s*:\s*"([^"]+)"/g);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
async function collectRDeps(projectPath) {
    const [desc, renv] = await Promise.all([
        readFile(join(projectPath, 'DESCRIPTION'), 'utf-8').catch(() => ''),
        readFile(join(projectPath, 'renv.lock'), 'utf-8').catch(() => ''),
    ]);
    return new Set([...parseRDescription(desc), ...parseRenvLock(renv)]);
}
// === D lang parsers ===
/**
 * Parse dub.json for dependency names.
 * Standard JSON `"dependencies": { "pkg": "version" }`.
 */
export function parseDubJson(content) {
    const deps = new Set();
    try {
        const obj = JSON.parse(content);
        for (const pkg of Object.keys(obj.dependencies ?? {})) {
            deps.add(pkg);
        }
    }
    catch {
        // malformed JSON — return empty
    }
    return deps;
}
/**
 * Parse dub.sdl for `dependency "pkg" version="..."` lines.
 */
export function parseDubSdl(content) {
    const deps = new Set();
    const matches = content.matchAll(/^dependency\s+"([^"]+)"/gm);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
async function collectDlangDeps(projectPath) {
    const [json, sdl] = await Promise.all([
        readFile(join(projectPath, 'dub.json'), 'utf-8').catch(() => ''),
        readFile(join(projectPath, 'dub.sdl'), 'utf-8').catch(() => ''),
    ]);
    return new Set([...parseDubJson(json), ...parseDubSdl(sdl)]);
}
// === Perl parsers ===
/**
 * Parse cpanfile for `requires 'Module::Name'` lines.
 */
export function parseCpanfile(content) {
    const deps = new Set();
    const matches = content.matchAll(/^requires\s+['"]([^'"]+)['"]/gm);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
/**
 * Parse Makefile.PL for `requires 'Module::Name'` or PREREQ_PM entries.
 */
export function parseMakefilePl(content) {
    const deps = new Set();
    // Match requires 'Module'
    const reqMatches = content.matchAll(/requires\s+['"]([^'"]+)['"]/g);
    for (const match of reqMatches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    // Match PREREQ_PM => { 'Module' => version }
    const prereqMatches = content.matchAll(/['"]([A-Za-z][A-Za-z0-9:_]+)['"]\s*=>/g);
    for (const match of prereqMatches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
async function collectPerlDeps(projectPath) {
    const [cpanfile, makefilePl] = await Promise.all([
        readFile(join(projectPath, 'cpanfile'), 'utf-8').catch(() => ''),
        readFile(join(projectPath, 'Makefile.PL'), 'utf-8').catch(() => ''),
    ]);
    // Scan all *.pl files for requires statements as well
    const plFiles = await glob('**/*.pl', {
        cwd: projectPath,
        absolute: false,
        ignore: ['node_modules/**'],
    }).catch(() => []);
    const plContents = await Promise.all(plFiles.map((f) => readFile(join(projectPath, f), 'utf-8').catch(() => '')));
    const allContent = [cpanfile, makefilePl, ...plContents].join('\n');
    return new Set([...parseCpanfile(allContent), ...parseMakefilePl(allContent)]);
}
// === Public API ===
/** Detect Julia package dependencies from Project.toml. */
export async function detectJuliaLibraries(projectPath) {
    const deps = await collectJuliaDeps(projectPath);
    return {
        http: filterPresent(JULIA_HTTP_LIBS, deps),
        data: filterPresent(JULIA_DATA_LIBS, deps),
        ml: filterPresent(JULIA_ML_LIBS, deps),
        testing: filterPresent(JULIA_TESTING_LIBS, deps),
    };
}
/** Detect R package dependencies from DESCRIPTION and renv.lock. */
export async function detectRLibraries(projectPath) {
    const deps = await collectRDeps(projectPath);
    return {
        data: filterPresent(R_DATA_LIBS, deps),
        ml: filterPresent(R_ML_LIBS, deps),
        testing: filterPresent(R_TESTING_LIBS, deps),
    };
}
/** Detect D lang dependencies from dub.json and dub.sdl. */
export async function detectDlangLibraries(projectPath) {
    const deps = await collectDlangDeps(projectPath);
    return {
        web: filterPresent(DLANG_WEB_LIBS, deps),
        data: filterPresent(DLANG_DATA_LIBS, deps),
        testing: filterPresent(DLANG_TESTING_LIBS, deps),
    };
}
/** Detect Perl module dependencies from cpanfile and Makefile.PL. */
export async function detectPerlLibraries(projectPath) {
    const deps = await collectPerlDeps(projectPath);
    return {
        web: filterPresent(PERL_WEB_LIBS, deps),
        db: filterPresent(PERL_DB_LIBS, deps),
        testing: filterPresent(PERL_TESTING_LIBS, deps),
    };
}
//# sourceMappingURL=library-detector-langs4.js.map