import { promises as fs } from 'node:fs';
import path from 'node:path';
import { buildTypesFsPath, getTypesDirectoryDisplay, isInsideTypesDirectory } from '../../../core/config/index.js';
import { messages } from '../../../core/messages/index.js';
import { isInStringOrComment } from '../../../shared/helpers/index.js';
let tiposExistentesCache = null;
async function scanExistingTypeNames() {
    if (tiposExistentesCache)
        return tiposExistentesCache;
    const names = new Set();
    try {
        const tiposDir = buildTypesFsPath('');
        await scanDirForTypeNames(tiposDir, names);
    }
    catch {
    }
    tiposExistentesCache = names;
    return names;
}
async function scanDirForTypeNames(dir, names) {
    try {
        const entries = await fs.readdir(dir, { withFileTypes: true });
        for (const entry of entries) {
            const fullPath = path.join(dir, entry.name);
            if (entry.isDirectory()) {
                await scanDirForTypeNames(fullPath, names);
            }
            else if (entry.name.endsWith('.ts') && entry.name !== 'index.ts') {
                try {
                    const content = await fs.readFile(fullPath, 'utf-8');
                    const interfaceRe = /export\s+(?:default\s+)?interface\s+(\w+)/g;
                    const typeRe = /export\s+(?:default\s+)?type\s+(\w+)/g;
                    let m;
                    while ((m = interfaceRe.exec(content)) !== null)
                        names.add(m[1]);
                    while ((m = typeRe.exec(content)) !== null)
                        names.add(m[1]);
                }
                catch {
                }
            }
        }
    }
    catch {
    }
}
const ANALISTA = {
    nome: 'detector-interfaces-inline',
    categoria: 'code-organization',
    descricao: 'Detecta interfaces e tipos complexos inline que deveriam estar em arquivos de tipos',
    test: (relPath) => {
        if (isInsideTypesDirectory(relPath)) {
            return false;
        }
        if (relPath.endsWith('.d.ts')) {
            return false;
        }
        if (relPath.includes('/.deprecados/') || relPath.includes('\\.deprecados\\')) {
            return false;
        }
        if (relPath.includes('/node_modules/') || relPath.includes('\\node_modules\\')) {
            return false;
        }
        return relPath.endsWith('.ts') || relPath.endsWith('.tsx');
    },
    aplicar: async (srcParam, relPath, _ast, _fullPath) => {
        const ocorrencias = [];
        const src = srcParam.replace(/\r\n/g, '\n');
        const _linhas = src.split('\n');
        const interfaceInlinePadrao = /export\s+function\s+\w+\s*\([^)]*\):\s*{[^}]+}/g;
        let match;
        while ((match = interfaceInlinePadrao.exec(src)) !== null) {
            const position = match.index || 0;
            const linha = src.substring(0, position).split('\n').length;
            if (isInStringOrComment(src, position)) {
                continue;
            }
            const detection = analyzeInlineInterface(match[0], linha, 'function-return');
            if (detection && detection.complexidade >= 5) {
                ocorrencias.push(createOcorrencia(detection, relPath));
            }
        }
        const complexParamPadrao = /\w+\s*:\s*{\s*[^}]+;\s*[^}]+;\s*[^}]+}/g;
        while ((match = complexParamPadrao.exec(src)) !== null) {
            const position = match.index || 0;
            const linha = src.substring(0, position).split('\n').length;
            if (isInStringOrComment(src, position)) {
                continue;
            }
            const detection = analyzeInlineInterface(match[0], linha, 'parameter');
            if (detection && detection.complexidade >= 5) {
                ocorrencias.push(createOcorrencia(detection, relPath));
            }
        }
        const typeInicioPadrao = /type\s+(\w+)\s*=/g;
        let startMatch;
        while ((startMatch = typeInicioPadrao.exec(src)) !== null) {
            const position = startMatch.index || 0;
            const nomeTipo = startMatch[1];
            if (isInStringOrComment(src, position)) {
                continue;
            }
            const afterEquals = src.substring(position + startMatch[0].length);
            const tipoCompleto = extractTypeDefinition(afterEquals);
            if (!tipoCompleto) {
                continue;
            }
            const linha = src.substring(0, position).split('\n').length;
            const complexidade = calculateComplexidade(tipoCompleto);
            if (complexidade >= 5) {
                const detection = {
                    tipo: 'type-alias',
                    nome: nomeTipo,
                    linha,
                    complexidade,
                    contexto: tipoCompleto.substring(0, 100),
                    sugestao: messages.DetectorInterfacesInlineMensagens.moverTipoParaTipos(nomeTipo)
                };
                ocorrencias.push(createOcorrencia(detection, relPath));
            }
        }
        const exportInterfacePadrao = /export\s+interface\s+(\w+)\s*(<[^>]*>)?\s*\{/g;
        const existingTypes = await scanExistingTypeNames();
        while ((match = exportInterfacePadrao.exec(src)) !== null) {
            const position = match.index || 0;
            const linha = src.substring(0, position).split('\n').length;
            const nomeInterface = match[1];
            if (isInStringOrComment(src, position)) {
                continue;
            }
            if (isInterfaceEspecificaDeModulo(nomeInterface, relPath)) {
                continue;
            }
            if (existingTypes.has(nomeInterface)) {
                const detection = {
                    tipo: 'interface',
                    nome: nomeInterface,
                    linha,
                    complexidade: 0,
                    contexto: `export interface ${nomeInterface}`,
                    sugestao: `Tipo '${nomeInterface}' já existe em ${getTypesDirectoryDisplay()} - reutilize-o em vez de redefini-lo`
                };
                ocorrencias.push(createOcorrencia(detection, relPath));
                continue;
            }
            const detection = {
                tipo: 'interface',
                nome: nomeInterface,
                linha,
                complexidade: 0,
                contexto: `export interface ${nomeInterface}`,
                sugestao: messages.DetectorInterfacesInlineMensagens.interfaceExportadaParaTipos(nomeInterface)
            };
            ocorrencias.push(createOcorrencia(detection, relPath));
        }
        const interfaceDeclarationPadrao = /(?<!export\s+)interface\s+(\w+)\s*\{[\s\S]+?\}/g;
        while ((match = interfaceDeclarationPadrao.exec(src)) !== null) {
            const position = match.index || 0;
            const linha = src.substring(0, position).split('\n').length;
            const nomeInterface = match[1];
            if (isInStringOrComment(src, position)) {
                continue;
            }
            const jaDetetada = ocorrencias.some(ocorrencia => ocorrencia.linha === linha && ocorrencia.mensagem?.includes(nomeInterface));
            if (jaDetetada) {
                continue;
            }
            const interfaceCompleta = match[0];
            const complexidade = calculateComplexidade(interfaceCompleta);
            if (complexidade >= 10) {
                const detection = {
                    tipo: 'interface',
                    nome: nomeInterface,
                    linha,
                    complexidade,
                    contexto: interfaceCompleta.substring(0, 100),
                    sugestao: messages.DetectorInterfacesInlineMensagens.interfaceComplexaParaTipos(nomeInterface)
                };
                ocorrencias.push(createOcorrencia(detection, relPath));
            }
        }
        const tiposInline = extractAllInlineTypes(src);
        const tiposRepetidos = findDuplicateTypes(tiposInline);
        for (const [estrutura, ocorrenciasArray] of tiposRepetidos.entries()) {
            const totalOcorrencias = ocorrenciasArray.length;
            const primeiraOcorrencia = ocorrenciasArray[0];
            const propriedades = estrutura.split(';').map(caminho => caminho.split(':')[0]).slice(0, 3);
            const nomesSugeridos = propriedades.join('_');
            const contextosUnicos = [...new Set(ocorrenciasArray.map(ocorrencia => ocorrencia.contexto))];
            const contextoDesc = contextosUnicos.length > 1 ? `em ${contextosUnicos.length} contextos diferentes` : 'no mesmo contexto';
            ocorrencias.push({
                tipo: 'interface-inline-duplicada',
                nivel: 'aviso',
                mensagem: messages.DetectorInterfacesInlineMensagens.tipoDuplicado({
                    propriedades,
                    totalOcorrencias,
                    contextoDesc,
                    nomesSugeridos
                }),
                relPath,
                linha: primeiraOcorrencia.linha,
                detalhes: {
                    estrutura,
                    ocorrencias: ocorrenciasArray.length,
                    linhas: ocorrenciasArray.map(ocorrencia => ocorrencia.linha),
                    contextos: contextosUnicos
                }
            });
        }
        return ocorrencias;
    }
};
export const detectorInterfacesInline = ANALISTA;
function analyzeInlineInterface(code, linha, contexto) {
    const tiposDirDisplay = getTypesDirectoryDisplay();
    const complexidade = calculateComplexidade(code);
    if (complexidade < 5) {
        return null;
    }
    let sugestao = '';
    switch (contexto) {
        case 'function-return':
            sugestao = `Extrair tipo de retorno para interface em ${tiposDirDisplay}`;
            break;
        case 'parameter':
            sugestao = `Extrair tipo de parâmetro para interface em ${tiposDirDisplay}`;
            break;
        case 'variable':
            sugestao = `Extrair tipo da variável para interface em ${tiposDirDisplay}`;
            break;
    }
    return {
        tipo: 'object-literal-type',
        linha,
        complexidade,
        contexto: code.substring(0, 100),
        sugestao
    };
}
function calculateComplexidade(tipoString) {
    let score = 0;
    const propriedadesMultilinha = (tipoString.match(/[;:]\s*\n/g) || []).length;
    const propriedadesInline = (tipoString.match(/;\s*\w+\s*:/g) || []).length;
    const propriedadesTotal = (tipoString.match(/\w+\s*\??\s*:/g) || []).length;
    score += Math.max(propriedadesMultilinha, propriedadesInline + 1, propriedadesTotal);
    const unioes = (tipoString.match(/\|/g) || []).length;
    score += unioes * 0.5;
    const intersecoes = (tipoString.match(/&/g) || []).length;
    score += intersecoes * 0.5;
    const genericosAninhados = (tipoString.match(/<[^>]*</g) || []).length;
    score += genericosAninhados * 2;
    const aninhamento = (tipoString.match(/{\s*\w+\s*:/g) || []).length;
    score += aninhamento;
    return Math.floor(score);
}
function extractTypeStructure(tipoString) {
    const props = [];
    const propPadrao = /(\w+)\??\s*:\s*([^;,}]+)/g;
    let match;
    while ((match = propPadrao.exec(tipoString)) !== null) {
        const propNome = match[1];
        let propTipo = match[2].trim();
        propTipo = propTipo.replace(/\s+/g, ' ').replace(/string|number|boolean|null|undefined/gi, metrica => metrica.toLowerCase()).replace(/\[\]/g, 'Array').replace(/Record<[^>]+>/g, 'Record').replace(/Promise<[^>]+>/g, 'Promise').trim();
        props.push(`${propNome}:${propTipo}`);
    }
    return props.sort().join(';');
}
function extractAllInlineTypes(src) {
    const tipos = [];
    const lines = src.split('\n');
    for (let indice = 0; indice < lines.length; indice++) {
        const line = lines[indice];
        const typeObjInicio = /[:=<]\s*\{\s*$/;
        const inlineTipoObj = /:\s*\{[^}]+\}/g;
        let match;
        while ((match = inlineTipoObj.exec(line)) !== null) {
            const matchIndex = match.index ?? 0;
            const position = src.substring(0, src.indexOf(lines.slice(0, indice + 1).join('\n'))).length + matchIndex;
            if (!isInStringOrComment(src, position)) {
                const tipoOriginal = match[0].substring(match[0].indexOf('{'));
                const complexidade = calculateComplexidade(tipoOriginal);
                if (complexidade >= 4) {
                    const estrutura = extractTypeStructure(tipoOriginal);
                    const contexto = line.substring(0, matchIndex).trim().substring(0, 60);
                    const linha = indice + 1;
                    tipos.push({
                        tipo: tipoOriginal.replace(/\s+/g, ' ').trim(),
                        estrutura,
                        linha,
                        contexto
                    });
                }
            }
        }
        if (typeObjInicio.test(line) && !line.includes('//')) {
            let depth = 1;
            const tipoLines = ['{'];
            let innerIndice = indice + 1;
            while (innerIndice < lines.length && depth > 0) {
                const nextLine = lines[innerIndice];
                tipoLines.push(nextLine);
                for (const char of nextLine) {
                    if (char === '{')
                        depth++;
                    if (char === '}')
                        depth--;
                    if (depth === 0)
                        break;
                }
                innerIndice++;
                if (innerIndice - indice > 50)
                    break;
            }
            if (depth === 0) {
                const tipoOriginal = tipoLines.join('\n').trim();
                const position = src.indexOf(tipoOriginal);
                if (position !== -1 && !isInStringOrComment(src, position)) {
                    const complexidade = calculateComplexidade(tipoOriginal);
                    if (complexidade >= 4) {
                        const estrutura = extractTypeStructure(tipoOriginal);
                        const contexto = line.substring(0, line.indexOf('{')).trim().substring(0, 60);
                        const linha = indice + 1;
                        tipos.push({
                            tipo: tipoOriginal.replace(/\s+/g, ' ').trim(),
                            estrutura,
                            linha,
                            contexto
                        });
                    }
                }
            }
        }
    }
    return tipos;
}
function findDuplicateTypes(tipos) {
    const mapa = new Map();
    for (const { estrutura, linha, tipo, contexto } of tipos) {
        if (!mapa.has(estrutura)) {
            mapa.set(estrutura, []);
        }
        const arr = mapa.get(estrutura);
        if (arr) {
            arr.push({
                linha,
                tipo,
                contexto
            });
        }
    }
    const duplicados = new Map();
    for (const [estrutura, ocorrencias] of mapa.entries()) {
        const contextosUnicos = new Set(ocorrencias.map(ocorrencia => ocorrencia.contexto)).size;
        if (ocorrencias.length >= 4 || ocorrencias.length >= 3 && contextosUnicos >= 2) {
            duplicados.set(estrutura, ocorrencias);
        }
    }
    return duplicados;
}
function createOcorrencia(detection, relPath) {
    const tiposDirDisplay = getTypesDirectoryDisplay();
    let tipo = '';
    let nivel = 'info';
    let mensagem = '';
    switch (detection.tipo) {
        case 'interface':
            tipo = 'interface-inline-exportada';
            nivel = 'aviso';
            mensagem = detection.nome ? `Interface '${detection.nome}' deve estar em ${tiposDirDisplay}` : `Interface inline deve estar em ${tiposDirDisplay}`;
            break;
        case 'type-alias':
            tipo = 'type-alias-inline-complexo';
            nivel = 'aviso';
            mensagem = detection.nome ? `Tipo '${detection.nome}' complexo deve estar em ${tiposDirDisplay}` : `Tipo complexo deve estar em ${tiposDirDisplay}`;
            break;
        case 'object-literal-type':
            tipo = 'tipo-literal-inline-complexo';
            nivel = 'info';
            mensagem = `Tipo literal complexo (${detection.complexidade} propriedades) - considere extrair`;
            break;
    }
    return {
        tipo,
        nivel,
        mensagem,
        relPath,
        linha: detection.linha
    };
}
function isInterfaceEspecificaDeModulo(nomeInterface, relPath) {
    const pathLower = relPath.toLowerCase();
    const nomeLower = nomeInterface.toLowerCase();
    if (/analistas\/[^/]+\/(corrections|pontuadores|relatorios|analistas)\//.test(pathLower)) {
        return true;
    }
    const moduleMatch = pathLower.match(/(?:analistas|integrations)\/([^/]+)/);
    if (moduleMatch) {
        const moduleName = moduleMatch[1].toLowerCase();
        if (nomeLower.startsWith(moduleName)) {
            return true;
        }
        const parts = moduleName.split(/[-_]/);
        for (const part of parts) {
            if (part.length >= 3 && nomeLower.startsWith(part)) {
                return true;
            }
        }
    }
    return false;
}
function extractTypeDefinition(afterEquals) {
    let depth = 0;
    let inString = false;
    let stringChar = '';
    let result = '';
    for (let indice = 0; indice < afterEquals.length; indice++) {
        const char = afterEquals[indice];
        const prevChar = indice > 0 ? afterEquals[indice - 1] : '';
        if ((char === '"' || char === "'" || char === '`') && prevChar !== '\\') {
            if (inString && char === stringChar) {
                inString = false;
            }
            else if (!inString) {
                inString = true;
                stringChar = char;
            }
        }
        if (inString) {
            result += char;
            continue;
        }
        if (char === '{' || char === '<' || char === '[' || char === '(') {
            depth++;
        }
        else if (char === '}' || char === '>' || char === ']' || char === ')') {
            depth--;
        }
        result += char;
        if (char === ';' && depth === 0) {
            return result;
        }
    }
    return null;
}
//# sourceMappingURL=detector-interfaces-inline.js.map