import { existsSync, promises as fs } from 'node:fs';
import path from 'node:path';
import { ExitCode, sair } from '../helpers/index.js';
import { config } from '../../core/config/index.js';
import { messages } from '../../core/messages/index.js';
import { Command } from 'commander';
import { extrairMensagemErro } from '../../types/index.js';
const log = messages.log;
const msgsImportScan = messages.MENSAGENS_IMPORTS_SCAN;
const MA = messages.MENSAGENS_IMPORTS_APPLY;
const msgsImports = messages.CliComandoImportsMensagens;
const SKIP_DIRS = ['node_modules', 'dist', 'coverage', '.git', '.deprecados'];
const TS_EXTENSIONS = ['.ts', '.tsx'];
function criarRegexImport(importPath, importToRewrite, line) {
    const variants = [importPath, importToRewrite];
    const uniqueVariants = [...new Set(variants)];
    for (const variant of uniqueVariants) {
        const escapedImport = variant.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
        const regex = new RegExp(`(from\\s+)(['"])${escapedImport}\\2`);
        if (regex.test(line))
            return regex;
    }
    return null;
}
function mesmoModuloPai(srcDir, arquivo, importToRewrite) {
    const arquivoDir = path.dirname(arquivo);
    const resolved = path.resolve(arquivoDir, importToRewrite);
    const arquivoRelativo = path.relative(srcDir, arquivoDir).replace(/\\/g, '/');
    const importRelativo = path.relative(srcDir, resolved).replace(/\\/g, '/');
    const arquivoParts = arquivoRelativo.split('/');
    const importParts = importRelativo.split('/');
    if (arquivoParts.length < 2 || importParts.length < 2)
        return false;
    if (arquivoParts[0] !== importParts[0])
        return false;
    return arquivoParts[0] === importParts[0];
}
function obterAliasPaiComBarrel(srcDir, relativeToSrc, aliasMap) {
    if (relativeToSrc.startsWith('types/') || relativeToSrc.includes('/types/')) {
        return null;
    }
    const parts = relativeToSrc.split('/');
    for (let indice = parts.length - 1; indice >= 1; indice--) {
        const dirPath = parts.slice(0, indice).join('/');
        const barrelPath = path.join(srcDir, dirPath);
        if (existsSync(path.join(barrelPath, 'index.ts'))) {
            for (const { alias, targetBase } of aliasMap) {
                if (targetBase === dirPath) {
                    const remaining = parts.slice(indice + 1).join('/');
                    return remaining ? `${alias}/${remaining}` : alias;
                }
            }
        }
    }
    return null;
}
async function* walk(dir) {
    const entries = await fs.readdir(dir, {
        withFileTypes: true
    });
    for (const entry of entries) {
        const full = path.join(dir, entry.name);
        if (entry.isDirectory()) {
            if (SKIP_DIRS.includes(entry.name))
                continue;
            yield* walk(full);
        }
        else if (entry.isFile() && entry.name === 'index.ts') {
            yield full;
        }
    }
}
async function isBarrel(filePath) {
    const content = await fs.readFile(filePath, 'utf-8');
    const exportFromMatches = content.match(/export\s+.*?\s+from\s+['"].*?['"]/g);
    const exportStarMatches = content.match(/export\s+\*\s+from\s+['"].*?['"]/g);
    const reexportCount = (exportFromMatches?.length ?? 0) + (exportStarMatches?.length ?? 0);
    return reexportCount >= 1;
}
async function detectarBarrels(srcDir) {
    const barrels = [];
    for await (const indexFile of walk(srcDir)) {
        if (await isBarrel(indexFile)) {
            const rel = path.relative(srcDir, path.dirname(indexFile));
            const content = await fs.readFile(indexFile, 'utf-8');
            const exports = [];
            for (const line of content.split('\n')) {
                const match = line.match(/from\s+['"](\.[^'"]+)['"]/);
                if (match) {
                    exports.push(match[1]);
                }
            }
            barrels.push({
                caminhoRelativo: rel,
                exports,
                pastaPai: rel,
                caminhoAbsoluto: indexFile
            });
        }
    }
    return barrels;
}
async function lerTsconfig(tsconfigPath) {
    const raw = await fs.readFile(tsconfigPath, 'utf-8');
    const json = JSON.parse(raw);
    return json.compilerOptions?.paths ?? {};
}
function analisarTsconfigPaths(paths) {
    const entries = [];
    for (const [alias, targets] of Object.entries(paths)) {
        const isFileSpecific = targets.some(textoTitulo => textoTitulo.endsWith('.ts') || textoTitulo.endsWith('.tsx') || textoTitulo.endsWith('.js'));
        const isBarrel = targets.some(textoTitulo => textoTitulo.endsWith('/*') || textoTitulo.endsWith('index.ts') || textoTitulo.endsWith('index.tsx'));
        entries.push({
            alias,
            targets,
            ehArquivoEspecifico: isFileSpecific,
            ehBarrel: isBarrel
        });
    }
    return entries;
}
async function gerarAliasesLimpos(barrels, pathsAtuais) {
    const novosPaths = {};
    for (const barrel of barrels) {
        const aliasKey = `@${barrel.pastaPai}`;
        const targetPath = `./src/${barrel.pastaPai}/index.ts`;
        novosPaths[aliasKey] = [targetPath];
    }
    for (const [alias, targets] of Object.entries(pathsAtuais)) {
        if (alias.includes('/*'))
            continue;
        const target = targets[0];
        const isBarrel = /\/index\.(ts|tsx)$/.test(target);
        const isFileSpecific = target.match(/\.(ts|tsx|js)$/) !== null && !isBarrel;
        if (isBarrel || isFileSpecific) {
            novosPaths[alias] = targets;
            continue;
        }
        novosPaths[alias] = targets;
    }
    const pathsOrdenados = {};
    const chaves = Object.keys(novosPaths).sort();
    for (const chave of chaves) {
        pathsOrdenados[chave] = novosPaths[chave];
    }
    return pathsOrdenados;
}
async function analisarDiferencas(pathsAtuais, pathsPropostos) {
    const entradasAtuais = analisarTsconfigPaths(pathsAtuais);
    const entradasPropostas = analisarTsconfigPaths(pathsPropostos);
    const entradasRemovidas = [];
    for (const entrada of entradasAtuais) {
        if (!pathsPropostos[entrada.alias]) {
            entradasRemovidas.push(entrada);
        }
    }
    const entradasNovas = [];
    for (const [alias, targets] of Object.entries(pathsPropostos)) {
        if (!pathsAtuais[alias]) {
            entradasNovas.push({
                alias,
                targets,
                ehArquivoEspecifico: false,
                ehBarrel: true
            });
        }
    }
    return {
        entradasAtuais,
        entradasPropostas,
        entradasRemovidas,
        entradasNovas
    };
}
async function atualizarTsconfig(tsconfigPath, novosPaths) {
    const raw = await fs.readFile(tsconfigPath, 'utf-8');
    const json = JSON.parse(raw);
    if (!json.compilerOptions || typeof json.compilerOptions !== 'object') {
        json.compilerOptions = {};
    }
    const compilerOptions = json.compilerOptions;
    compilerOptions.paths = novosPaths;
    await fs.writeFile(tsconfigPath, `${JSON.stringify(json, null, 2)}\n`, 'utf-8');
}
async function aplicarImports(projectRoot, srcDir, paths) {
    const resultado = {
        arquivosProcessados: 0,
        arquivosModificados: 0,
        importsReescritos: 0,
        erros: []
    };
    const aliasMap = [];
    for (const [alias, targets] of Object.entries(paths)) {
        const target = targets[0].replace('./', '').replace(/^src\//, '').replace(/\/index\.(ts|tsx|js)$/, '');
        aliasMap.push({
            alias,
            targetBase: target
        });
    }
    aliasMap.sort((arquivo, base) => base.targetBase.length - arquivo.targetBase.length);
    async function* walkTs(dir) {
        const entries = await fs.readdir(dir, {
            withFileTypes: true
        });
        for (const entry of entries) {
            const full = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                if (SKIP_DIRS.includes(entry.name))
                    continue;
                yield* walkTs(full);
            }
            else if (entry.isFile() && TS_EXTENSIONS.some(ext => entry.name.endsWith(ext))) {
                yield full;
            }
        }
    }
    for await (const arquivo of walkTs(srcDir)) {
        resultado.arquivosProcessados++;
        try {
            const content = await fs.readFile(arquivo, 'utf-8');
            let novoContent = content;
            let reescritosNoArquivo = 0;
            const importRegex = /(?:import|export)\s+(?:[\s\S]*?\s+from\s+)?['"]([^'"]+)['"]/g;
            const dynamicImportRegex = /import\s*\(\s*['"]([^'"]+)['"]\s*\)/g;
            let match;
            const imports = [];
            while ((match = importRegex.exec(content)) !== null) {
                imports.push(match[1]);
            }
            while ((match = dynamicImportRegex.exec(content)) !== null) {
                imports.push(match[1]);
            }
            const lines = novoContent.split('\n');
            let modified = false;
            for (let indice = 0; indice < lines.length; indice++) {
                const line = lines[indice];
                const trimmed = line.trim();
                if (!trimmed.startsWith('import') && !trimmed.startsWith('export') && !trimmed.includes('import('))
                    continue;
                for (const importPath of new Set(imports)) {
                    let importToRewrite = importPath;
                    if (importPath.endsWith('.js') || importPath.endsWith('.ts') || importPath.endsWith('.tsx')) {
                        importToRewrite = importPath.replace(/\.(ts|tsx|js)$/, '');
                    }
                    if (importPath.startsWith('@')) {
                        let barrelAlias = importToRewrite;
                        if (paths[importToRewrite]) {
                            barrelAlias = importToRewrite;
                        }
                        else {
                            const lastSlash = importToRewrite.lastIndexOf('/');
                            if (lastSlash > 0) {
                                const potentialBarrel = importToRewrite.substring(0, lastSlash);
                                if (paths[potentialBarrel]) {
                                    barrelAlias = potentialBarrel;
                                }
                            }
                        }
                        if (barrelAlias !== importPath) {
                            const escapedImport = importPath.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
                            const fromRegex = new RegExp(`(import\\(|from\\s+)(['"])${escapedImport}\\2`);
                            if (fromRegex.test(line)) {
                                lines[indice] = line.replace(fromRegex, `$1$2${barrelAlias}$2`);
                                reescritosNoArquivo++;
                                modified = true;
                            }
                        }
                        continue;
                    }
                    if (!importPath.startsWith('.'))
                        continue;
                    if (path.basename(arquivo) === 'index.ts' && importPath.startsWith('./')) {
                        continue;
                    }
                    const arquivoDir = path.dirname(arquivo);
                    const resolved = path.resolve(arquivoDir, importToRewrite);
                    const relativeToSrc = path.relative(srcDir, resolved).replace(/\\/g, '/');
                    for (const { alias, targetBase } of aliasMap) {
                        if (relativeToSrc === targetBase) {
                            if (alias !== importToRewrite) {
                                const regexResult = criarRegexImport(importPath, importToRewrite, line);
                                if (regexResult) {
                                    lines[indice] = line.replace(regexResult, `$1$2${alias}$2`);
                                    reescritosNoArquivo++;
                                    modified = true;
                                }
                            }
                            break;
                        }
                        if (relativeToSrc.startsWith(`${targetBase}/`)) {
                            const remainder = relativeToSrc.slice(targetBase.length + 1);
                            const novoImport = `${alias}/${remainder}`;
                            if (mesmoModuloPai(srcDir, arquivo, importToRewrite)) {
                                break;
                            }
                            if (paths[novoImport] && novoImport !== importPath) {
                                const regexResult = criarRegexImport(importPath, importToRewrite, line);
                                if (regexResult) {
                                    lines[indice] = line.replace(regexResult, `$1$2${novoImport}$2`);
                                    reescritosNoArquivo++;
                                    modified = true;
                                }
                            }
                            else if (!paths[novoImport]) {
                                const aliasDoPai = obterAliasPaiComBarrel(srcDir, relativeToSrc, aliasMap);
                                if (aliasDoPai && aliasDoPai !== importPath) {
                                    const regexResult = criarRegexImport(importPath, importToRewrite, line);
                                    if (regexResult) {
                                        lines[indice] = line.replace(regexResult, `$1$2${aliasDoPai}$2`);
                                        reescritosNoArquivo++;
                                        modified = true;
                                    }
                                }
                            }
                            break;
                        }
                    }
                }
            }
            if (modified) {
                novoContent = lines.join('\n');
            }
            if (novoContent !== content) {
                await fs.writeFile(arquivo, novoContent, 'utf-8');
                resultado.arquivosModificados++;
                resultado.importsReescritos += reescritosNoArquivo;
            }
        }
        catch (err) {
            resultado.erros.push({
                arquivo: path.relative(projectRoot, arquivo),
                erro: err instanceof Error ? err.message : String(err)
            });
        }
    }
    return resultado;
}
async function executarScan(projectRoot, dryRun = false) {
    const srcDir = path.join(projectRoot, 'src');
    const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
    log.fase?.(msgsImportScan.titulo);
    log.info(msgsImportScan.buscandoBarrels);
    const barrels = await detectarBarrels(srcDir);
    log.sucesso(msgsImportScan.barrelsEncontrados(barrels.length));
    log.info(msgsImportScan.analisandoTsconfig);
    const pathsAtuais = await lerTsconfig(tsconfigPath);
    log.info(msgsImportScan.pathsAtuais(Object.keys(pathsAtuais).length));
    log.info('');
    log.info(msgsImportScan.planoAlias);
    log.info('');
    const pathsPropostos = await gerarAliasesLimpos(barrels, pathsAtuais);
    const diferencas = await analisarDiferencas(pathsAtuais, pathsPropostos);
    for (const entrada of diferencas.entradasPropostas) {
        const exists = pathsAtuais[entrada.alias];
        if (exists) {
            const igual = JSON.stringify(exists) === JSON.stringify(entrada.targets);
            if (igual) {
                log.info(`  ${msgsImportScan.manterAlias}: ${entrada.alias} → ${entrada.targets.join(', ')}`);
            }
            else {
                log.info(`  ${msgsImportScan.aliasProposto}: ${entrada.alias} → ${entrada.targets.join(', ')}`);
            }
        }
        else {
            log.sucesso(`  ${msgsImportScan.aliasNovo}: ${entrada.alias} → ${entrada.targets.join(', ')}`);
        }
    }
    for (const entrada of diferencas.entradasRemovidas) {
        log.aviso(`  ${msgsImportScan.aliasRemovido}: ${entrada.alias} → ${entrada.targets.join(', ')}`);
    }
    log.info('');
    log.sucesso(msgsImportScan.aliasesLimpos(Object.keys(pathsPropostos).length));
    if (!dryRun) {
        const readline = await import('node:readline/promises');
        const rl = readline.createInterface({
            input: process.stdin,
            output: process.stdout
        });
        const answer = await rl.question(`\n${msgsImportScan.confirmarAtualizacao} (s/N) `);
        rl.close();
        if (answer.trim().toLowerCase() === 's') {
            await atualizarTsconfig(tsconfigPath, pathsPropostos);
            log.sucesso(msgsImportScan.tsconfigAtualizado);
        }
        else {
            log.info(msgsImportScan.tsconfigNaoModificado);
        }
    }
    return {
        barrelsEncontrados: barrels,
        analisePaths: diferencas,
        tsconfigCaminho: tsconfigPath,
        tsconfigOriginal: JSON.stringify(pathsAtuais, null, 2)
    };
}
async function executarApply(projectRoot) {
    const srcDir = path.join(projectRoot, 'src');
    const tsconfigPath = path.join(projectRoot, 'tsconfig.json');
    log.fase?.(MA.titulo);
    log.info(MA.lendoTsconfig);
    const paths = await lerTsconfig(tsconfigPath);
    log.info(MA.varrendoArquivos);
    const resultado = await aplicarImports(projectRoot, srcDir, paths);
    log.info('');
    log.info(MA.arquivosProcessados(resultado.arquivosProcessados));
    log.info(MA.arquivosModificados(resultado.arquivosModificados));
    log.info(MA.importsReescritos(resultado.importsReescritos));
    if (resultado.erros.length > 0) {
        log.aviso(MA.errosEncontrados(resultado.erros.length));
        for (const erro of resultado.erros) {
            log.erro(`  ${erro.arquivo}: ${erro.erro}`);
        }
    }
    if (resultado.importsReescritos === 0) {
        log.info(MA.nenhumImportParaReescrever);
    }
    else {
        log.sucesso(MA.aplicacaoConcluida);
    }
    return resultado;
}
export function comandoImports() {
    const cmd = new Command('imports');
    cmd.description(msgsImports.descricao);
    cmd.option('--scan', msgsImports.scanDescricao, false);
    cmd.option('--apply', msgsImports.applyDescricao, false);
    cmd.option('--dry-run', msgsImports.dryRunDescricao, false);
    cmd.action(async (opts) => {
        try {
            await executarAcaoImports(opts);
        }
        catch (err) {
            const { log } = await import('../../core/messages/index.js');
            log.erro(msgsImports.erroExecutar(extrairMensagemErro(err)));
            if (config.DEV_MODE) {
                console.error(err);
            }
            sair(ExitCode.Failure);
        }
    });
    return cmd;
}
async function executarAcaoImports(opts) {
    if (!opts.scan && !opts.apply) {
        const { log } = await import('../../core/messages/index.js');
        log.aviso(msgsImports.useScanOuApply);
        sair(ExitCode.Failure);
        return;
    }
    if (opts.scan && opts.apply) {
        const { log } = await import('../../core/messages/index.js');
        log.aviso(msgsImports.scanApplyMutuamenteExclusivos);
        sair(ExitCode.Failure);
        return;
    }
    const projectRoot = process.cwd();
    if (opts.scan) {
        const resultado = await executarScan(projectRoot, opts.dryRun ?? false);
        if (resultado.barrelsEncontrados.length === 0) {
            const { log } = await import('../../core/messages/index.js');
            log.info(msgsImports.nenhumBarrelEncontrado);
        }
    }
    if (opts.apply) {
        const resultado = await executarApply(projectRoot);
        if (resultado.erros.length > 0) {
            sair(ExitCode.Failure);
        }
    }
}
//# sourceMappingURL=command-imports.js.map