// engine/detectors/library-detector-langs3a.ts — Functional lang library detection (part A)
// SPEC-004 tech-debt S4: Clojure, Haskell, OCaml, F#
import { readFile } from 'node:fs/promises';
import { join } from 'node:path';
import { glob } from 'glob';
// === Catalog constants ===
const CLOJURE_WEB_LIBS = [
    'ring/ring-core',
    'compojure/compojure',
    'metosin/reitit',
    'http-kit/http-kit',
];
const CLOJURE_DB_LIBS = [
    'org.clojure/java.jdbc',
    'seancorfield/next.jdbc',
    'com.datomic/datomic-free',
];
const CLOJURE_TESTING_LIBS = ['org.clojure/test.check', 'midje/midje'];
const HASKELL_WEB_LIBS = ['warp', 'servant', 'servant-server', 'scotty', 'yesod'];
const HASKELL_DB_LIBS = ['persistent', 'esqueleto', 'postgresql-simple', 'beam-postgres'];
const HASKELL_TESTING_LIBS = ['hspec', 'tasty', 'QuickCheck', 'hedgehog'];
const OCAML_WEB_LIBS = ['dream', 'cohttp', 'httpaf', 'opium'];
const OCAML_DB_LIBS = ['caqti', 'caqti-driver-postgresql', 'irmin'];
const OCAML_TESTING_LIBS = ['alcotest', 'ppx_inline_test', 'qcheck'];
const FSHARP_WEB_LIBS = ['Giraffe', 'Saturn', 'Falco', 'Suave'];
const FSHARP_DB_LIBS = ['Dapper', 'SqlProvider', 'FSharp.Data.SqlClient'];
const FSHARP_TESTING_LIBS = ['Expecto', 'FsCheck', 'Unquote'];
// === Helpers ===
function filterPresent(catalog, deps) {
    return catalog.filter((lib) => deps.has(lib));
}
// === Clojure parsers ===
/**
 * Parse deps.edn for package coordinates like `ring/ring-core {:mvn/version ...}`.
 * Extracts namespace-qualified symbols as "ns/name".
 */
export function parseDepsEdn(content) {
    const deps = new Set();
    // Match lines with mvn/version — symbol before { is the dep coord
    const matches = content.matchAll(/([a-zA-Z0-9._-]+\/[a-zA-Z0-9._-]+)\s*\{:mvn\/version/g);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
/**
 * Parse project.clj for Leiningen-style dependency vectors.
 * Matches `[group/artifact "version"]` or `[group "version"]`.
 */
export function parseProjectClj(content) {
    const deps = new Set();
    // Match [group/artifact "version"] or [artifact "version"]
    const matches = content.matchAll(/\[([a-zA-Z0-9._-]+(?:\/[a-zA-Z0-9._-]+)?)\s+"[^"]+"\]/g);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
async function collectClojureDeps(projectPath) {
    const [edn, clj] = await Promise.all([
        readFile(join(projectPath, 'deps.edn'), 'utf-8').catch(() => ''),
        readFile(join(projectPath, 'project.clj'), 'utf-8').catch(() => ''),
    ]);
    return new Set([...parseDepsEdn(edn), ...parseProjectClj(clj)]);
}
// === Haskell parsers ===
/**
 * Parse *.cabal build-depends section.
 * Matches package names listed after `build-depends:` (comma-separated, one per line).
 */
export function parseCabalFile(content) {
    const deps = new Set();
    // Find build-depends block — package name before optional version constraint
    const bdMatch = /build-depends\s*:((?:[^:]+(?:\n[ \t]+[^\n]*)*)*)/gi.exec(content);
    if (bdMatch?.[1]) {
        const block = bdMatch[1];
        const lines = block.split(/[,\n]/);
        for (const line of lines) {
            const trimmed = line.trim();
            if (!trimmed || trimmed.startsWith('--')) {
                continue;
            }
            // Package name is everything before whitespace or >= <= etc.
            const nameMatch = /^([a-zA-Z0-9_-]+)/.exec(trimmed);
            /* v8 ignore start */
            if (nameMatch?.[1]) {
                /* v8 ignore stop */
                deps.add(nameMatch[1]);
            }
        }
    }
    return deps;
}
async function collectHaskellDeps(projectPath) {
    try {
        const files = await glob('**/*.cabal', {
            cwd: projectPath,
            absolute: false,
            ignore: ['node_modules/**'],
        });
        const sets = await Promise.all(files.map((f) => readFile(join(projectPath, f), 'utf-8')
            .then(parseCabalFile)
            .catch(() => new Set())));
        const merged = new Set();
        for (const s of sets) {
            for (const v of s) {
                merged.add(v);
            }
        }
        return merged;
    }
    catch {
        return new Set();
    }
}
// === OCaml parsers ===
/**
 * Parse *.opam depends field.
 * Matches quoted package names inside `depends: [...]` block.
 */
export function parseOpamFile(content) {
    const deps = new Set();
    const depsMatch = /depends\s*:\s*\[([\s\S]*?)\]/m.exec(content);
    if (depsMatch?.[1]) {
        const block = depsMatch[1];
        const matches = block.matchAll(/"([a-zA-Z0-9_-]+)"/g);
        for (const match of matches) {
            /* v8 ignore start */
            if (match[1]) {
                /* v8 ignore stop */
                deps.add(match[1]);
            }
        }
    }
    return deps;
}
async function collectOcamlDeps(projectPath) {
    try {
        const files = await glob('**/*.opam', {
            cwd: projectPath,
            absolute: false,
            ignore: ['node_modules/**'],
        });
        const sets = await Promise.all(files.map((f) => readFile(join(projectPath, f), 'utf-8')
            .then(parseOpamFile)
            .catch(() => new Set())));
        const merged = new Set();
        for (const s of sets) {
            for (const v of s) {
                merged.add(v);
            }
        }
        return merged;
    }
    catch {
        return new Set();
    }
}
// === F# parsers ===
/**
 * Parse *.fsproj for <PackageReference Include="..." /> entries.
 */
export function parseFsproj(content) {
    const deps = new Set();
    const matches = content.matchAll(/<PackageReference\s+Include="([^"]+)"/gi);
    for (const match of matches) {
        /* v8 ignore start */
        if (match[1]) {
            /* v8 ignore stop */
            deps.add(match[1]);
        }
    }
    return deps;
}
async function collectFsharpDeps(projectPath) {
    try {
        const files = await glob('**/*.fsproj', {
            cwd: projectPath,
            absolute: false,
            ignore: ['node_modules/**'],
        });
        const sets = await Promise.all(files.map((f) => readFile(join(projectPath, f), 'utf-8')
            .then(parseFsproj)
            .catch(() => new Set())));
        const merged = new Set();
        for (const s of sets) {
            for (const v of s) {
                merged.add(v);
            }
        }
        return merged;
    }
    catch {
        return new Set();
    }
}
// === Public API ===
/** Detect Clojure library dependencies (deps.edn + project.clj). */
export async function detectClojureLibraries(projectPath) {
    const deps = await collectClojureDeps(projectPath);
    return {
        web: filterPresent(CLOJURE_WEB_LIBS, deps),
        db: filterPresent(CLOJURE_DB_LIBS, deps),
        testing: filterPresent(CLOJURE_TESTING_LIBS, deps),
    };
}
/** Detect Haskell library dependencies from *.cabal files. */
export async function detectHaskellLibraries(projectPath) {
    const deps = await collectHaskellDeps(projectPath);
    return {
        web: filterPresent(HASKELL_WEB_LIBS, deps),
        db: filterPresent(HASKELL_DB_LIBS, deps),
        testing: filterPresent(HASKELL_TESTING_LIBS, deps),
    };
}
/** Detect OCaml library dependencies from *.opam files. */
export async function detectOcamlLibraries(projectPath) {
    const deps = await collectOcamlDeps(projectPath);
    return {
        web: filterPresent(OCAML_WEB_LIBS, deps),
        db: filterPresent(OCAML_DB_LIBS, deps),
        testing: filterPresent(OCAML_TESTING_LIBS, deps),
    };
}
/** Detect F# NuGet dependencies from *.fsproj files. */
export async function detectFsharpLibraries(projectPath) {
    const deps = await collectFsharpDeps(projectPath);
    return {
        web: filterPresent(FSHARP_WEB_LIBS, deps),
        db: filterPresent(FSHARP_DB_LIBS, deps),
        testing: filterPresent(FSHARP_TESTING_LIBS, deps),
    };
}
//# sourceMappingURL=library-detector-langs3a.js.map