import { promises as fs } from 'node:fs';
import { config } from '../config/index.js';
import { messages } from '../messages/index.js';
import { lerArquivoTexto, lerEstado } from '../../shared/persistence/index.js';
import micromatch from 'micromatch';
import path from 'path';
function getErrMsg(err) {
    return err instanceof Error ? err.message : String(err);
}
function ensureStringArray(val) {
    if (!Array.isArray(val))
        return [];
    for (const item of val) {
        if (typeof item !== 'string')
            return [];
    }
    return val;
}
export async function scanRepository(baseDir, options = {}) {
    messages.logVarredor.iniciarVarredura(baseDir);
    const toPosix = (string) => string.replace(/\\+/g, '/');
    const trimDotSlash = (string) => string.replace(/^\.\/?/, '');
    const { includeContent = true, filter = () => true, onProgress = () => {
        return undefined;
    } } = options;
    const efetivoIncluirConteudo = includeContent && !config.SCAN_ONLY;
    const fileMap = {};
    const statCache = new Map();
    const cliIncludeGroups = config.CLI_INCLUDE_GROUPS;
    const includeGroups = Array.isArray(cliIncludeGroups) ? cliIncludeGroups : [];
    const includeGroupsNorm = includeGroups.map(grupo => (grupo || []).map(caminho => toPosix(trimDotSlash(String(caminho || '')))));
    const includePadroes = ensureStringArray(config.CLI_INCLUDE_PATTERNS);
    const includePadroesNorm = includePadroes.map(caminho => toPosix(trimDotSlash(String(caminho || ''))));
    const excludePadroesNorm = ensureStringArray(config.CLI_EXCLUDE_PATTERNS).map(caminho => toPosix(String(caminho || '')));
    const legacyExcludes = config.INCLUDE_EXCLUDE_RULES?.globalExcludeGlob || [];
    const ignorePadroesNorm = ensureStringArray(legacyExcludes).map((caminho) => toPosix(String(caminho || '')));
    const hasIncluir = includeGroupsNorm.length > 0 || includePadroesNorm.length > 0;
    const pedeOcorrenciasGlobais = hasIncluir ? [...includePadroesNorm, ...includeGroupsNorm.flat()].some(caminho => caminho.startsWith('**/')) : false;
    const includeNodeModulesExplicit = hasIncluir ? [...includePadroesNorm, ...includeGroupsNorm.flat()].some(caminho => /(^|\/)node_modules(\/|$)/.test(String(caminho || ''))) : false;
    function adicionarArquivoAoMapa(fullCaminho, relPath, content, mtimeMs) {
        fileMap[`$${relPath}`] = { fullCaminho, relPath, content, ultimaModificacao: mtimeMs };
        if (!config.REPORT_SILENCE_LOGS) {
            onProgress(`[OK] Arquivo lido: ${relPath}`);
        }
    }
    function calcularIncludeRoots(padroes, grupos) {
        const roots = new Set();
        const candidatos = new Set();
        if (Array.isArray(padroes))
            padroes.forEach(caminho => candidatos.add(toPosix(trimDotSlash(caminho))));
        if (Array.isArray(grupos))
            for (const grupo of grupos)
                grupo.forEach(caminho => candidatos.add(toPosix(trimDotSlash(caminho))));
        if (candidatos.size === 0)
            return [];
        const META = /[\\*\?\{\}\[\]]/;
        for (const raw of candidatos) {
            let caminho = String(raw).trim();
            if (!caminho)
                continue;
            caminho = toPosix(trimDotSlash(caminho));
            let anchor = '';
            if (caminho.includes('/**'))
                anchor = caminho.slice(0, caminho.indexOf('/**'));
            else if (caminho.includes('/*'))
                anchor = caminho.slice(0, caminho.indexOf('/*'));
            else if (caminho.includes('/'))
                anchor = caminho.split('/')[0];
            else
                anchor = '';
            anchor = anchor.replace(/\/+/g, '/').replace(/\/$/, '');
            if (anchor && anchor !== '.' && anchor !== '**' && !META.test(anchor)) {
                const baseNorm = toPosix(String(baseDir)).replace(/\/$/, '');
                const rootPosix = `${baseNorm}/${anchor}`.replace(/\/+/g, '/');
                roots.add(rootPosix);
            }
        }
        return Array.from(roots);
    }
    function matchIncluir(relPath) {
        const matchesPadrao = (rp, caminho) => {
            if (!caminho)
                return false;
            if (micromatch.isMatch(rp, [caminho]))
                return true;
            if (caminho.endsWith('/**')) {
                const base = caminho.slice(0, -3);
                if (base && rp.startsWith(base))
                    return true;
            }
            const META = /[\\*\?\{\}\[\]]/;
            if (!META.test(caminho)) {
                const pat = caminho.replace(/\/+$|\/+$|^\.\/?/g, '').replace(/\/+/g, '/');
                if (!pat)
                    return false;
                if (pat.includes('/')) {
                    if (rp === pat)
                        return true;
                    if (rp.startsWith(`${pat}/`))
                        return true;
                    if (rp.includes(`/${pat}/`))
                        return true;
                    if (rp.endsWith(`/${pat}`))
                        return true;
                    return false;
                }
                if (rp === pat)
                    return true;
                if (rp.startsWith(`${pat}/`))
                    return true;
                if (rp.includes(`/${pat}/`))
                    return true;
                if (rp.endsWith(`/${pat}`))
                    return true;
                return false;
            }
            return false;
        };
        const baseFromPadrao = (caminho) => {
            let base = caminho.trim();
            base = base.replace(/^\*\*\//, '');
            base = base.replace(/\/\*\*$/, '');
            base = base.replace(/^\.\/?/, '');
            base = base.replace(/\/+/g, '/').replace(/\/$/, '');
            return base;
        };
        if (includeGroupsNorm.length > 0) {
            for (const grupo of includeGroupsNorm) {
                const porBase = new Map();
                for (const caminho of grupo) {
                    const base = baseFromPadrao(caminho);
                    const patternVariants = porBase.get(base) || [];
                    patternVariants.push(caminho);
                    porBase.set(base, patternVariants);
                }
                const allBasesMatch = Array.from(porBase.values()).every(lista => lista.some(caminho => matchesPadrao(relPath, caminho)));
                if (allBasesMatch)
                    return true;
            }
            return false;
        }
        if (includePadroesNorm.length && micromatch.isMatch(relPath, includePadroesNorm))
            return true;
        for (const caminho of includePadroesNorm || [])
            if (matchesPadrao(relPath, caminho))
                return true;
        return false;
    }
    async function getStat(caminho) {
        try {
            const sanitizedPath = String(caminho).replace(/\0/g, '');
            const st = await fs.stat(sanitizedPath);
            statCache.set(sanitizedPath, st);
            return st;
        }
        catch {
            return null;
        }
    }
    function isStatsWithMtime(value) {
        return typeof value === 'object' && value !== null && 'mtimeMs' in value;
    }
    function extractMtimeMs(stat) {
        if (isStatsWithMtime(stat)) {
            return typeof stat.mtimeMs === 'number' ? stat.mtimeMs : 0;
        }
        return 0;
    }
    async function readFileContent(caminho, relPath, prog) {
        try {
            if (!!process.env.VITEST) {
                return await lerEstado(caminho);
            }
            return await lerArquivoTexto(caminho);
        }
        catch (erro) {
            prog(JSON.stringify({
                tipo: 'erro',
                acao: 'ler',
                caminho: relPath,
                mensagem: getErrMsg(erro)
            }));
            return null;
        }
    }
    async function scan(dir) {
        const sanitizedDir = String(dir).replace(/\0/g, '');
        let entries;
        try {
            entries = await fs.readdir(sanitizedDir, {
                withFileTypes: true
            });
            entries.sort((arquivo, base) => arquivo.name.localeCompare(base.name));
        }
        catch (err) {
            onProgress(JSON.stringify({
                tipo: 'erro',
                acao: 'acessar',
                caminho: dir,
                mensagem: getErrMsg(err)
            }));
            return;
        }
        onProgress(JSON.stringify({
            tipo: 'diretorio',
            acao: 'examinar',
            caminho: dir
        }));
        for (const entry of entries) {
            const fullCaminho = path.join(sanitizedDir, entry.name);
            const relPathRaw = path.relative(baseDir, fullCaminho);
            const relPath = toPosix(relPathRaw);
            const isThirdParty = (caminho) => {
                const rp = toPosix(caminho);
                if (/^THIRD-PARTY[-_]/.test(rp))
                    return true;
                if (/^(LICENSE|CHANGELOG|CONTRIBUTING|CODE_OF_CONDUCT)/i.test(rp))
                    return true;
                return false;
            };
            if (isThirdParty(relPath)) {
                continue;
            }
            const isTestLike = (caminho) => {
                const rp = toPosix(caminho);
                if (/(^|\/)__(tests|mocks)__(\/|$)/.test(rp))
                    return true;
                if (/(^|\/)(tests?|test)(\/|$)/.test(rp))
                    return true;
                if (/\.(test|spec)\.[jt]sx?$/.test(rp))
                    return true;
                return false;
            };
            if (isTestLike(relPath)) {
                continue;
            }
            if (entry.isDirectory() && !entry.isSymbolicLink()) {
                if (!hasIncluir && micromatch.isMatch(relPath, excludePadroesNorm))
                    continue;
                if (!hasIncluir && micromatch.isMatch(relPath, ignorePadroesNorm))
                    continue;
                if (/(^|\/)node_modules(\/|$)/.test(relPath) && !includeNodeModulesExplicit)
                    continue;
                await scan(fullCaminho);
                continue;
            }
            if (hasIncluir && !matchIncluir(relPath))
                continue;
            if (!hasIncluir && micromatch.isMatch(relPath, excludePadroesNorm))
                continue;
            if (!hasIncluir && micromatch.isMatch(relPath, ignorePadroesNorm))
                continue;
            if (!filter(relPath, entry))
                continue;
            try {
                let stat = statCache.get(fullCaminho);
                if (!stat) {
                    stat = await getStat(fullCaminho);
                    if (!stat)
                        continue;
                }
                const mtimeMs = extractMtimeMs(stat);
                let content = null;
                if (efetivoIncluirConteudo) {
                    content = await readFileContent(fullCaminho, relPath, onProgress);
                }
                adicionarArquivoAoMapa(fullCaminho, relPath, content, mtimeMs);
            }
            catch (err) {
                onProgress(JSON.stringify({
                    tipo: 'erro',
                    acao: 'ler',
                    caminho: relPath,
                    mensagem: getErrMsg(err)
                }));
            }
        }
    }
    async function processarStartDirs(dirs, base, hasInc, efetivoInc, scanFn, matchFn, filterFn, prog, exclPad, ignPad, statC) {
        const vistos = new Set();
        for (const diretorio of dirs) {
            let norm = diretorio;
            let end = norm.length;
            while (end > 0 && (norm[end - 1] === '\\' || norm[end - 1] === '/'))
                end--;
            norm = norm.slice(0, end);
            if (vistos.has(norm))
                continue;
            vistos.add(norm);
            try {
                await fs.readdir(norm);
                await scanFn(norm);
                continue;
            }
            catch { }
            try {
                let st = statC.get(norm);
                if (!st) {
                    st = await fs.stat(norm);
                    statC.set(norm, st);
                }
                let isDir = st?.isDirectory() ?? false;
                if (!isDir) {
                    try {
                        await fs.readdir(norm);
                        isDir = true;
                    }
                    catch { }
                }
                if (isDir) {
                    await scanFn(norm);
                    continue;
                }
                const relPath = toPosix(path.relative(base, norm));
                if (hasInc && !matchFn(relPath))
                    continue;
                if (!hasInc && (micromatch.isMatch(relPath, exclPad) || micromatch.isMatch(relPath, ignPad)))
                    continue;
                const fakeDirent = {
                    name: path.basename(norm), isDirectory: () => false, isSymbolicLink: () => false
                };
                if (!filterFn(relPath, fakeDirent))
                    continue;
                let content = null;
                if (efetivoInc) {
                    try {
                        content = !!process.env.VITEST ? await lerEstado(norm) : await lerArquivoTexto(norm);
                    }
                    catch (erro) {
                        prog(JSON.stringify({ tipo: 'erro', acao: 'ler', caminho: relPath, mensagem: getErrMsg(erro) }));
                        content = null;
                    }
                }
                adicionarArquivoAoMapa(norm, relPath, content, (isStatsWithMtime(st) ? st.mtimeMs : Date.now()) || Date.now());
            }
            catch (erro) {
                prog(JSON.stringify({ tipo: 'erro', acao: 'acessar', caminho: norm, mensagem: getErrMsg(erro) }));
            }
        }
    }
    let startDirs = hasIncluir ? calcularIncludeRoots(config.CLI_INCLUDE_PATTERNS, cliIncludeGroups) : [];
    if (hasIncluir && pedeOcorrenciasGlobais) {
        const baseNorm = toPosix(String(baseDir)).replace(/\/$/, '');
        if (!startDirs.includes(baseNorm))
            startDirs = [baseNorm, ...startDirs];
    }
    if (hasIncluir && startDirs.length === 0) {
        await scan(baseDir);
        return fileMap;
    }
    if (startDirs.length === 0) {
        await scan(baseDir);
    }
    else {
        await processarStartDirs(startDirs, baseDir, hasIncluir, efetivoIncluirConteudo, scan, matchIncluir, filter, onProgress, excludePadroesNorm, ignorePadroesNorm, statCache);
    }
    const totalArquivos = Object.keys(fileMap).length;
    const totalDiretorios = new Set(Object.values(fileMap).map(arquivo => path.dirname(arquivo.relPath))).size;
    messages.logVarredor.completo(totalArquivos, totalDiretorios);
    return fileMap;
}
//# sourceMappingURL=scanner.js.map