"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.AnalisadorSemantico = void 0;
const construtos_1 = require("../construtos");
const declaracoes_1 = require("../declaracoes");
const erros_1 = require("../interfaces/erros");
const analisador_semantico_base_1 = require("./analisador-semantico-base");
const gerenciador_escopos_1 = require("./gerenciador-escopos");
const pilha_variaveis_1 = require("./pilha-variaveis");
/**
 * O Analisador Semântico de Delégua.
 */
class AnalisadorSemantico extends analisador_semantico_base_1.AnalisadorSemanticoBase {
    constructor() {
        super();
        this.pilhaVariaveis = new pilha_variaveis_1.PilhaVariaveis();
        this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos();
        this.funcoes = {};
        this.atual = 0;
        this.diagnosticos = [];
    }
    verificarTipoAtribuido(declaracao) {
        if (declaracao.tipo) {
            if (['vetor', 'qualquer[]', 'inteiro[]', 'texto[]'].includes(declaracao.tipo)) {
                if (declaracao.inicializador instanceof construtos_1.Vetor) {
                    const vetor = declaracao.inicializador;
                    const vetorSemSeparadores = vetor.valores.filter((v) => v.constructor !== construtos_1.Separador);
                    if (declaracao.tipo === 'inteiro[]') {
                        const apenasValores = vetorSemSeparadores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'number');
                        if (apenasValores) {
                            this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo vetor de inteiro ou real. Atual: ${vetor.tipo}.`);
                        }
                    }
                    if (declaracao.tipo === 'texto[]') {
                        const apenasValores = vetorSemSeparadores.find((v) => typeof (v === null || v === void 0 ? void 0 : v.valor) !== 'string');
                        if (apenasValores) {
                            this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo vetor de texto. Atual: ${vetor.tipo}.`);
                        }
                    }
                }
                else {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um vetor de elementos.`);
                }
            }
            if (declaracao.inicializador instanceof construtos_1.Literal) {
                const literal = declaracao.inicializador;
                if (declaracao.tipo === 'texto' && literal.tipo !== 'texto') {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo texto. Atual: ${literal.tipo}.`);
                }
                if (['inteiro', 'número', 'real'].includes(declaracao.tipo) &&
                    !['inteiro', 'número', 'real'].includes(literal.tipo)) {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}': é esperado um valor do tipo número. Atual: ${literal.tipo}.`);
                }
            }
            if (declaracao.inicializador instanceof construtos_1.Leia) {
                if (!['qualquer', 'texto'].includes(declaracao.tipo)) {
                    this.erro(declaracao.simbolo, `Atribuição inválida para '${declaracao.simbolo.lexema}', Função 'leia()' sempre retorna 'texto'.`);
                }
            }
        }
    }
    visitarExpressaoTipoDe(expressao) {
        return this.verificarTipoDe(expressao.valor);
    }
    verificarTipoDe(valor) {
        switch (valor.constructor) {
            case construtos_1.Agrupamento:
                const valorAgrupamento = valor;
                return this.verificarTipoDe(valorAgrupamento.expressao);
            case construtos_1.Binario:
                const valorBinario = valor;
                this.verificarTipoDe(valorBinario.direita);
                this.verificarTipoDe(valorBinario.esquerda);
                break;
            case construtos_1.Variavel:
                const valorVariavel = valor;
                return this.verificarVariavel(valorVariavel);
        }
        return Promise.resolve();
    }
    visitarExpressaoFalhar(expressao) {
        return this.verificarFalhar(expressao.explicacao);
    }
    verificarFalhar(valor) {
        if (valor instanceof construtos_1.Binario) {
            this.verificarFalhar(valor.direita);
            this.verificarFalhar(valor.esquerda);
        }
        if (valor instanceof construtos_1.Agrupamento) {
            return this.verificarFalhar(valor.expressao);
        }
        if (valor instanceof construtos_1.Variavel) {
            return this.verificarVariavel(valor);
        }
        return Promise.resolve();
    }
    comparacaoArgumentosContraParametrosFuncao(simboloFuncao, parametros, argumentos) {
        if (parametros.length !== argumentos.length) {
            this.erro(simboloFuncao, `Função '${simboloFuncao.lexema}' espera ${parametros.length} parâmetros. Atual: ${argumentos.length}.`);
        }
        for (let [indice, parametro] of parametros.entries()) {
            // TODO: `argumento` pode ser Literal (tipo já resolvido) ou variável (tipo inferido em outra etapa).
            const argumento = argumentos[indice];
            if (argumento) {
                if (parametro.tipoDado === 'texto' && argumento.tipo !== 'texto') {
                    this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${argumento.tipo}).`);
                }
                else if (['inteiro', 'número', 'real'].includes(parametro.tipoDado)) {
                    // Aqui, se houver diferença entre os tipos do parâmetro e do argumento, não há erro,
                    // porque Delégua pode trabalhar com conversões implícitas.
                    // Isso pode ou não mudar no futuro.
                    if (!['inteiro', 'número', 'real'].includes(argumento.tipo)) {
                        this.erro(simboloFuncao, `O valor passado para o parâmetro '${parametro.nome.lexema}' (${parametro.tipoDado}) é diferente do esperado pela função (${argumento.tipo}).`);
                    }
                }
            }
        }
    }
    visitarChamadaPorArgumentoReferenciaFuncao(argumentoReferenciaFuncao, argumentos) {
        var _a;
        const variavelCorrespondente = 
        // this.variaveis[argumentoReferenciaFuncao.simboloFuncao.lexema].valor;
        (_a = this.gerenciadorEscopos.buscar(argumentoReferenciaFuncao.simboloFuncao.lexema)) === null || _a === void 0 ? void 0 : _a.valor;
        if (!variavelCorrespondente) {
            return;
        }
        this.comparacaoArgumentosContraParametrosFuncao(argumentoReferenciaFuncao.simboloFuncao, variavelCorrespondente.parametros, argumentos);
    }
    visitarChamadaPorReferenciaFuncao(referenciaFuncao, argumentos) {
        const funcaoCorrespondente = this.funcoes[referenciaFuncao.simboloFuncao.lexema];
        if (!funcaoCorrespondente) {
            return;
        }
        this.comparacaoArgumentosContraParametrosFuncao(referenciaFuncao.simboloFuncao, funcaoCorrespondente.valor.parametros, argumentos);
    }
    visitarChamadaPorVariavel(entidadeChamadaVariavel, argumentos) {
        const variavel = entidadeChamadaVariavel;
        const funcaoChamada = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema) || this.funcoes[variavel.simbolo.lexema];
        if (!funcaoChamada) {
            this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${entidadeChamadaVariavel.simbolo.lexema}' não existe.`);
            return Promise.resolve();
        }
        const funcao = funcaoChamada.valor;
        this.comparacaoArgumentosContraParametrosFuncao(entidadeChamadaVariavel.simbolo, funcao.parametros, argumentos);
    }
    visitarExpressaoDeChamada(expressao) {
        for (const argumento of expressao.argumentos) {
            if (argumento instanceof construtos_1.Variavel) {
                this.gerenciadorEscopos.marcarComoUsada(argumento.simbolo.lexema);
            }
        }
        switch (expressao.entidadeChamada.constructor) {
            case construtos_1.AcessoMetodo:
                // Marca o objeto como usado quando seus métodos são chamados (ex: thor.corre())
                const entidadeChamadaAcessoMetodo = expressao.entidadeChamada;
                this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodo.objeto);
                break;
            case construtos_1.AcessoMetodoOuPropriedade:
                // Marca o objeto como usado quando seus métodos/propriedades são acessados (ex: thor.corre())
                const entidadeChamadaAcessoMetodoOuPropriedade = expressao.entidadeChamada;
                this.marcarVariaveisUsadasEmExpressao(entidadeChamadaAcessoMetodoOuPropriedade.objeto);
                break;
            case construtos_1.ArgumentoReferenciaFuncao:
                const entidadeChamadaArgumentoReferenciaFuncao = expressao.entidadeChamada;
                this.visitarChamadaPorArgumentoReferenciaFuncao(entidadeChamadaArgumentoReferenciaFuncao, expressao.argumentos);
                break;
            case construtos_1.ReferenciaFuncao:
                const entidadeChamadaReferenciaFuncao = expressao.entidadeChamada;
                this.visitarChamadaPorReferenciaFuncao(entidadeChamadaReferenciaFuncao, expressao.argumentos);
                break;
            case construtos_1.Variavel:
                const entidadeChamadaVariavel = expressao.entidadeChamada;
                this.visitarChamadaPorVariavel(entidadeChamadaVariavel, expressao.argumentos);
                break;
        }
        return Promise.resolve();
    }
    visitarExpressaoDeAtribuicao(expressao) {
        let simboloAlvo;
        switch (expressao.alvo.constructor) {
            case construtos_1.Variavel:
                const alvoVariavel = expressao.alvo;
                simboloAlvo = alvoVariavel.simbolo;
                break;
            default:
                return Promise.resolve();
        }
        const variavel = this.gerenciadorEscopos.buscar(simboloAlvo.lexema);
        if (!variavel) {
            this.erro(simboloAlvo, `Variável '${simboloAlvo.lexema}' ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        if (variavel.imutavel) {
            this.erro(simboloAlvo, `Constante '${simboloAlvo.lexema}' não pode ser modificada.`);
            return Promise.resolve();
        }
        // Marca como inicializada após atribuição
        this.gerenciadorEscopos.marcarComoInicializada(simboloAlvo.lexema, expressao.valor);
        // TODO: Readaptar para trabalhar com `expressao.alvo` sendo um construto.
        switch (expressao.alvo.constructor) {
            case construtos_1.Variavel:
                const alvoVariavel = expressao.alvo;
                simboloAlvo = alvoVariavel.simbolo;
                break;
            default:
                // throw new Error(`Implementar atribuição para ${expressao.alvo.constructor}.`);
                return Promise.resolve();
        }
        let valor = this.gerenciadorEscopos.buscar(simboloAlvo.lexema);
        if (!valor) {
            this.erro(simboloAlvo, `Variável ${simboloAlvo.lexema} ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        if (valor.tipo) {
            if (expressao.valor instanceof construtos_1.Literal && valor.tipo.includes('[]')) {
                this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`);
                return Promise.resolve();
            }
            if (expressao.valor instanceof construtos_1.Vetor && !valor.tipo.includes('[]')) {
                this.erro(simboloAlvo, `Atribuição inválida, esperado tipo '${valor.tipo}' na atribuição.`);
                return Promise.resolve();
            }
            if (expressao.valor instanceof construtos_1.Literal) {
                let valorLiteral = typeof expressao.valor.valor;
                if (!['qualquer'].includes(valor.tipo)) {
                    if (valorLiteral === 'string') {
                        if (valor.tipo != 'texto') {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                    if (valorLiteral === 'number') {
                        if (!['inteiro', 'número', 'real'].includes(valor.tipo)) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                }
            }
            if (expressao.valor instanceof construtos_1.Vetor) {
                let valoresSemSeparador = expressao.valor.valores.filter((v) => v.constructor !== construtos_1.Separador);
                if (!['qualquer[]'].includes(valor.tipo)) {
                    if (valor.tipo === 'texto[]') {
                        if (!valoresSemSeparador.every((v) => typeof v.valor === 'string')) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                    if (['inteiro[]', 'numero[]'].includes(valor.tipo)) {
                        if (!valoresSemSeparador.every((v) => typeof v.valor === 'number')) {
                            this.erro(simboloAlvo, `Esperado tipo '${valor.tipo}' na atribuição.`);
                            return Promise.resolve();
                        }
                    }
                }
            }
        }
        /* if (valor.imutavel) {
            this.erro(simboloAlvo, `Constante ${simboloAlvo.lexema} não pode ser modificada.`);
            return Promise.resolve();
        } else {
            if (this.variaveis[simboloAlvo.lexema]) {
                this.variaveis[simboloAlvo.lexema].valor = expressao.valor;
            }
        } */
    }
    async visitarDeclaracaoDeExpressao(declaracao) {
        return await declaracao.expressao.aceitar(this);
    }
    visitarDeclaracaoEscolha(declaracao) {
        const identificadorOuLiteral = declaracao.identificadorOuLiteral;
        const tipo = identificadorOuLiteral.tipo;
        for (let caminho of declaracao.caminhos) {
            for (let condicao of caminho.condicoes) {
                switch (condicao.constructor) {
                    case construtos_1.Literal:
                        const condicaoLiteral = condicao;
                        if (condicaoLiteral.tipo !== tipo) {
                            this.erro({
                                lexema: condicaoLiteral.valor,
                                tipo: condicaoLiteral.tipo,
                                linha: condicaoLiteral.linha,
                                hashArquivo: condicaoLiteral.hashArquivo,
                            }, `'caso ${condicaoLiteral.valor}:' não é do mesmo tipo esperado em 'escolha' (esperado: ${tipo}, atual: ${condicaoLiteral.tipo}).`);
                        }
                        break;
                    case construtos_1.Variavel:
                        const condicaoVariavel = condicao;
                        this.verificarVariavel(condicaoVariavel);
                        const variavelHipotetica = this.gerenciadorEscopos.buscar(condicaoVariavel.simbolo.lexema);
                        if (variavelHipotetica && typeof variavelHipotetica.valor !== tipo) {
                            this.erro(condicaoVariavel.simbolo, `'caso ${condicaoVariavel.simbolo.lexema}:' não é do mesmo tipo esperado em 'escolha'`);
                        }
                        break;
                }
            }
        }
        return Promise.resolve();
    }
    visitarDeclaracaoEnquanto(declaracao) {
        return this.verificarCondicao(declaracao.condicao);
    }
    visitarDeclaracaoFazer(declaracao) {
        // Marca variáveis usadas na condição
        this.marcarVariaveisUsadasEmExpressao(declaracao.condicaoEnquanto);
        // Verifica a condição
        return this.verificarCondicao(declaracao.condicaoEnquanto);
    }
    visitarDeclaracaoParaCada(declaracao) {
        // Marca o vetor/dicionário como usado
        this.marcarVariaveisUsadasEmExpressao(declaracao.vetorOuDicionario);
        return Promise.resolve();
    }
    visitarDeclaracaoSe(declaracao) {
        // Marca variáveis usadas na condição
        this.marcarVariaveisUsadasEmExpressao(declaracao.condicao);
        // Verifica a condição (incluindo validação de tipos para operadores lógicos)
        return this.verificarCondicao(declaracao.condicao);
    }
    /**
     * Verifica uma expressão recursivamente, incluindo operações binárias
     */
    verificarExpressao(expressao) {
        if (expressao instanceof construtos_1.Agrupamento) {
            this.verificarExpressao(expressao.expressao);
            return;
        }
        if (expressao instanceof construtos_1.Binario) {
            this.verificarBinario(expressao);
            return;
        }
        if (expressao instanceof construtos_1.Logico) {
            this.verificarLogico(expressao);
            return;
        }
        if (expressao instanceof construtos_1.Chamada) {
            this.verificarChamada(expressao);
            return;
        }
    }
    verificarCondicao(condicao) {
        if (condicao instanceof construtos_1.Agrupamento) {
            return this.verificarCondicao(condicao.expressao);
        }
        if (condicao instanceof construtos_1.Variavel) {
            return this.verificarVariavelBinaria(condicao);
        }
        if (condicao instanceof construtos_1.Binario) {
            return this.verificarBinario(condicao);
        }
        if (condicao instanceof construtos_1.Logico) {
            return this.verificarLogico(condicao);
        }
        if (condicao instanceof construtos_1.Chamada) {
            return this.verificarChamada(condicao);
        }
        return Promise.resolve();
    }
    verificarVariavelBinaria(variavel) {
        this.verificarVariavel(variavel);
        const variavelHipotetica = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
        if (variavelHipotetica &&
            !(variavelHipotetica.valor instanceof construtos_1.Binario) &&
            typeof variavelHipotetica.valor !== 'boolean') {
            this.erro(variavel.simbolo, `Esperado tipo 'lógico' na condição do 'enquanto'.`);
        }
        return Promise.resolve();
    }
    verificarVariavel(variavel) {
        const variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
        if (!variavelEscopo) {
            this.erro(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' ainda não foi declarada até este ponto.`);
            return Promise.resolve();
        }
        // Marca como usada
        this.gerenciadorEscopos.marcarComoUsada(variavel.simbolo.lexema);
        // Verifica se foi inicializada
        if (!variavelEscopo.inicializada) {
            this.aviso(variavel.simbolo, `Variável '${variavel.simbolo.lexema}' pode não ter sido inicializada antes do uso.`);
        }
        return Promise.resolve();
    }
    verificarBinario(binario) {
        this.verificarExistenciaConstruto(binario.direita);
        this.verificarExistenciaConstruto(binario.esquerda);
        this.verificarOperadorBinario(binario);
        return Promise.resolve();
    }
    verificarOperadorBinario(binario) {
        if (binario.esquerda instanceof construtos_1.Binario) {
            this.verificarOperadorBinario(binario.esquerda);
        }
        if (binario.direita instanceof construtos_1.Binario) {
            this.verificarOperadorBinario(binario.direita);
        }
        const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO'];
        if (operadoresMatematicos.includes(binario.operador.tipo)) {
            this.verificarTiposOperandos(binario);
        }
        if (binario.operador.tipo === 'DIVISAO') {
            this.verificarDivisaoPorZero(binario);
        }
    }
    /**
     * Verifica se os tipos dos operandos são compatíveis
     */
    verificarTiposOperandos(binario) {
        const tipoEsquerda = this.obterTipoExpressao(binario.esquerda);
        const tipoDireita = this.obterTipoExpressao(binario.direita);
        const tiposNumericos = ['inteiro', 'número', 'real'];
        // Verifica se algum operando é do tipo texto em operação aritmética
        if (tipoEsquerda === 'texto' || tipoDireita === 'texto') {
            // Verifica se é uma operação que precisa de números (não concatenação)
            const operadoresAritmeticos = ['SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO'];
            if (operadoresAritmeticos.includes(binario.operador.tipo)) {
                const ladoProblematico = tipoEsquerda === 'texto' ? 'esquerdo' : 'direito';
                const expressaoProblematica = tipoEsquerda === 'texto' ? binario.esquerda : binario.direita;
                // Verifica se a expressão problemática é um Leia ou uma variável inicializada com Leia
                let mensagemAdicional = '';
                if (expressaoProblematica instanceof construtos_1.Leia) {
                    mensagemAdicional = " Função 'leia()' retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter.";
                }
                else if (expressaoProblematica instanceof construtos_1.Variavel) {
                    const variavel = this.gerenciadorEscopos.buscar(expressaoProblematica.simbolo.lexema);
                    if (variavel && variavel.valor instanceof construtos_1.Leia) {
                        mensagemAdicional = " A variável foi inicializada com 'leia()' que retorna texto. Use 'inteiro(leia(...))' ou 'real(leia(...))' para converter.";
                    }
                    else {
                        mensagemAdicional = " Use 'inteiro(...)' ou 'real(...)' para converter texto em número.";
                    }
                }
                this.erro(binario.operador, `Operação aritmética com tipo incompatível: operando ${ladoProblematico} é do tipo 'texto', mas a operação requer número.${mensagemAdicional}`);
                return;
            }
        }
        if (tipoEsquerda && tipoDireita && tipoEsquerda !== tipoDireita) {
            // Verificar se são tipos numéricos compatíveis
            const ambosNumericos = tiposNumericos.includes(tipoEsquerda) &&
                tiposNumericos.includes(tipoDireita);
            if (!ambosNumericos) {
                this.aviso(binario.operador, `Operação entre tipos diferentes: tipo esquerdo '${tipoEsquerda}' e tipo direito '${tipoDireita}'. O resultado será resolvido implicitamente.`);
            }
        }
    }
    /**
     * Verifica divisão por zero recursivamente
     */
    verificarDivisaoPorZero(binario) {
        const valorDireita = this.avaliarExpressaoConstante(binario.direita);
        if (valorDireita === 0) {
            this.erro(binario.operador, `Divisão por zero.`);
        }
    }
    /**
     * Tenta avaliar uma expressão em tempo de compilação para detectar valores constantes
     * Retorna o valor se puder ser determinado, ou null caso contrário
     */
    avaliarExpressaoConstante(expressao) {
        if (expressao instanceof construtos_1.Literal) {
            return expressao.valor;
        }
        if (expressao instanceof construtos_1.Variavel) {
            const variavel = this.gerenciadorEscopos.buscar(expressao.simbolo.lexema);
            if (!variavel) {
                return null;
            }
            if (variavel.imutavel && variavel.inicializada) {
                return variavel.valor;
            }
            if (variavel.inicializada && variavel.valor !== undefined) {
                return variavel.valor;
            }
            return null;
        }
        if (expressao instanceof construtos_1.Binario) {
            const esquerda = this.avaliarExpressaoConstante(expressao.esquerda);
            const direita = this.avaliarExpressaoConstante(expressao.direita);
            if (esquerda !== null && direita !== null) {
                return this.calcularOperacaoBinaria(expressao.operador.tipo, esquerda, direita);
            }
        }
        if (expressao instanceof construtos_1.Agrupamento) {
            return this.avaliarExpressaoConstante(expressao.expressao);
        }
        return null;
    }
    /**
     * Calcula o resultado de uma operação binária em tempo de compilação
     */
    calcularOperacaoBinaria(operador, esquerda, direita) {
        try {
            switch (operador) {
                case 'ADICAO':
                    return esquerda + direita;
                case 'SUBTRACAO':
                    return esquerda - direita;
                case 'MULTIPLICACAO':
                    return esquerda * direita;
                case 'DIVISAO':
                    return esquerda / direita;
                case 'MODULO':
                    return esquerda % direita;
                case 'MAIOR':
                    return esquerda > direita;
                case 'MAIOR_IGUAL':
                    return esquerda >= direita;
                case 'MENOR':
                    return esquerda < direita;
                case 'MENOR_IGUAL':
                    return esquerda <= direita;
                case 'IGUAL':
                    return esquerda === direita;
                case 'DIFERENTE':
                    return esquerda !== direita;
                default:
                    return null;
            }
        }
        catch (e) {
            return null;
        }
    }
    /**
     * Obtém o tipo de uma expressão (pode ser Literal, Variavel, Binario, Leia, etc)
     */
    obterTipoExpressao(expressao) {
        if (expressao instanceof construtos_1.Literal) {
            return expressao.tipo;
        }
        if (expressao instanceof construtos_1.Variavel) {
            const variavel = this.gerenciadorEscopos.buscar(expressao.simbolo.lexema);
            return (variavel === null || variavel === void 0 ? void 0 : variavel.tipo) || null;
        }
        if (expressao instanceof construtos_1.Binario) {
            // Para binários, tentamos inferir o tipo baseado nos operandos
            return this.inferirTipoBinario(expressao);
        }
        if (expressao instanceof construtos_1.Logico) {
            // Operadores lógicos sempre retornam tipo lógico
            return 'lógico';
        }
        if (expressao instanceof construtos_1.Agrupamento) {
            return this.obterTipoExpressao(expressao.expressao);
        }
        if (expressao instanceof construtos_1.Leia) {
            // leia() sempre retorna texto
            return 'texto';
        }
        return null;
    }
    /**
     * Infere o tipo de resultado de uma operação binária
     */
    inferirTipoBinario(binario) {
        const operadoresMatematicos = ['ADICAO', 'SUBTRACAO', 'MULTIPLICACAO', 'DIVISAO', 'MODULO'];
        const operadoresComparacao = ['MAIOR', 'MAIOR_IGUAL', 'MENOR', 'MENOR_IGUAL', 'IGUAL', 'DIFERENTE'];
        // Operadores de comparação sempre retornam lógico
        if (operadoresComparacao.includes(binario.operador.tipo)) {
            return 'lógico';
        }
        const tipoEsquerda = this.obterTipoExpressao(binario.esquerda);
        const tipoDireita = this.obterTipoExpressao(binario.direita);
        if (!tipoEsquerda || !tipoDireita) {
            return null;
        }
        if (operadoresMatematicos.includes(binario.operador.tipo)) {
            const tiposNumericos = ['inteiro', 'número', 'real'];
            if (tiposNumericos.includes(tipoEsquerda) && tiposNumericos.includes(tipoDireita)) {
                // Se um dos lados é 'real', o resultado é 'real'
                if (tipoEsquerda === 'real' || tipoDireita === 'real') {
                    return 'real';
                }
                return 'número';
            }
            // Concatenação de textos
            if (tipoEsquerda === 'texto' || tipoDireita === 'texto') {
                return 'texto';
            }
        }
        return 'qualquer';
    }
    verificarExistenciaConstruto(construto) {
        if (construto instanceof construtos_1.Variavel) {
            if (!this.gerenciadorEscopos.buscar(construto.simbolo.lexema)) {
                this.erro(construto.simbolo, `Variável ${construto.simbolo.lexema} ainda não foi declarada até este ponto.`);
                return;
            }
            this.gerenciadorEscopos.marcarComoUsada(construto.simbolo.lexema);
            return;
        }
        if (construto instanceof construtos_1.Binario) {
            this.verificarBinario(construto);
        }
    }
    verificarLogico(logico) {
        this.verificarLadoLogico(logico.direita);
        this.verificarLadoLogico(logico.esquerda);
        return Promise.resolve();
    }
    verificarChamada(chamada) {
        switch (chamada.entidadeChamada.constructor) {
            case construtos_1.Variavel:
                let entidadeChamadaVariavel = chamada.entidadeChamada;
                const nomeFuncao = entidadeChamadaVariavel.simbolo.lexema;
                // Lista de funções built-in que não precisam ser declaradas
                const funcoesBuiltIn = ['inteiro', 'real', 'número', 'texto', 'leia', 'escreva', 'tipo'];
                // Classes/construtores geralmente começam com letra maiúscula
                const pareceSerClasse = nomeFuncao[0] === nomeFuncao[0].toUpperCase();
                // Só verifica se a função existe se não for built-in e não parecer ser classe
                if (!funcoesBuiltIn.includes(nomeFuncao) &&
                    !pareceSerClasse &&
                    !this.funcoes[nomeFuncao] &&
                    !this.gerenciadorEscopos.buscar(nomeFuncao)) {
                    this.erro(entidadeChamadaVariavel.simbolo, `Chamada da função '${nomeFuncao}' não existe.`);
                }
                break;
        }
        return Promise.resolve();
    }
    verificarLadoLogico(lado) {
        if (lado instanceof construtos_1.Variavel) {
            const variavel = lado;
            const variavelEscopo = this.gerenciadorEscopos.buscar(variavel.simbolo.lexema);
            if (variavelEscopo) {
                // Verifica se a variável tem um tipo definido
                if (variavelEscopo.tipo &&
                    variavelEscopo.tipo !== 'lógico' &&
                    variavelEscopo.tipo !== 'qualquer') {
                    // Se a variável foi inicializada com um binário que resulta em 'lógico',
                    // consideramos que o lado é válido (caso como: var x = 5 > 2; enquanto (x) {...}).
                    if (variavelEscopo.valor instanceof construtos_1.Binario &&
                        this.inferirTipoBinario(variavelEscopo.valor) === 'lógico') {
                        return;
                    }
                    this.erro(variavel.simbolo, `Operador lógico requer operandos do tipo 'lógico', mas recebeu '${variavelEscopo.tipo}'.`);
                }
            }
        }
        if (lado instanceof construtos_1.Logico) {
            this.verificarLogico(lado);
        }
    }
    /**
     * Verifica interpolações de texto e marca variáveis como usadas
     */
    verificarInterpolacaoTexto(texto, literal) {
        // Regex para encontrar ${identificador}
        const regexInterpolacao = /\$\{([a-zA-Z_][a-zA-Z0-9_]*)\}/g;
        let match;
        while ((match = regexInterpolacao.exec(texto)) !== null) {
            const nomeVariavel = match[1];
            // Verifica se a variável existe
            const variavel = this.gerenciadorEscopos.buscar(nomeVariavel);
            const funcao = this.funcoes[nomeVariavel];
            if (!variavel && !funcao) {
                this.erro({
                    lexema: nomeVariavel,
                    tipo: 'IDENTIFICADOR',
                    linha: literal.linha,
                    hashArquivo: literal.hashArquivo,
                    literal: null
                }, `Variável ou função '${nomeVariavel}' usada em interpolação não existe.`);
            }
            else if (variavel) {
                // Marca como usada
                this.gerenciadorEscopos.marcarComoUsada(nomeVariavel);
                // Verifica se foi inicializada
                if (!variavel.inicializada) {
                    this.aviso({
                        lexema: nomeVariavel,
                        tipo: 'IDENTIFICADOR',
                        linha: literal.linha,
                        hashArquivo: literal.hashArquivo,
                        literal: null
                    }, `Variável '${nomeVariavel}' usada em interpolação pode não ter sido inicializada.`);
                }
            }
        }
    }
    visitarDeclaracaoEscreva(declaracao) {
        if (declaracao.argumentos.length === 0) {
            const { linha, hashArquivo } = declaracao;
            const simbolo = {
                literal: '',
                tipo: '',
                lexema: 'escreva',
                linha,
                hashArquivo,
            };
            this.erro(simbolo, `É preciso ter um ou mais parametros para 'escreva(...)'`);
            return Promise.resolve();
        }
        for (const argumento of declaracao.argumentos) {
            this.marcarVariaveisUsadasEmExpressao(argumento);
            if (argumento instanceof construtos_1.Literal && argumento.tipo === 'texto') {
                this.verificarInterpolacaoTexto(String(argumento.valor), argumento);
            }
            if (argumento instanceof construtos_1.Variavel) {
                const possivelVariavel = this.gerenciadorEscopos.buscar(argumento.simbolo.lexema);
                const possivelFuncao = this.funcoes[argumento.simbolo.lexema];
                if (!possivelVariavel && !possivelFuncao) {
                    this.erro(argumento.simbolo, `Variável ou função '${argumento.simbolo.lexema}' não existe.`);
                    continue;
                }
                if (possivelVariavel && possivelVariavel.valor === undefined) {
                    this.aviso(argumento.simbolo, `Variável '${argumento.simbolo.lexema}' não foi inicializada.`);
                }
            }
        }
        return Promise.resolve();
    }
    visitarDeclaracaoConst(declaracao) {
        var _a;
        this.verificarTipoAtribuido(declaracao);
        if (declaracao.inicializador) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
            // Verifica operações binárias no inicializador
            this.verificarExpressao(declaracao.inicializador);
        }
        const constanteCorrespondente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema);
        if (constanteCorrespondente) {
            this.erro(declaracao.simbolo, 'Declaração de constante já feita.');
            return Promise.resolve();
        }
        this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, {
            nome: declaracao.simbolo.lexema,
            tipo: declaracao.tipo || 'qualquer',
            imutavel: true,
            valor: (_a = declaracao.inicializador) === null || _a === void 0 ? void 0 : _a.valor,
            inicializada: true,
            usada: false,
            hashArquivo: declaracao.simbolo.hashArquivo,
            linha: declaracao.simbolo.linha
        });
        // TODO: Verificar inicializador.
        return Promise.resolve();
    }
    visitarDeclaracaoVar(declaracao) {
        this.verificarTipoAtribuido(declaracao);
        if (declaracao.inicializador) {
            this.marcarVariaveisUsadasEmExpressao(declaracao.inicializador);
            // Verifica operações binárias no inicializador
            this.verificarExpressao(declaracao.inicializador);
            switch (declaracao.inicializador.constructor) {
                case construtos_1.FuncaoConstruto:
                    const funcaoConstruto = declaracao.inicializador;
                    if (funcaoConstruto.parametros.length >= 255) {
                        this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.');
                    }
                    // Valida tipo de retorno para funções anônimas atribuídas a variáveis
                    if (funcaoConstruto.tipo) {
                        const tipoRetornoFuncao = funcaoConstruto.tipo;
                        if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) {
                            const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(funcaoConstruto.corpo);
                            if (!todosOsCaminhosRetornam) {
                                this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`);
                            }
                            const funcaoContemRetorno = funcaoConstruto.corpo.find((c) => c instanceof declaracoes_1.Retorna);
                            if (funcaoContemRetorno && funcaoContemRetorno.valor) {
                                const tipoValor = typeof funcaoContemRetorno.valor.valor;
                                if (!['qualquer'].includes(tipoRetornoFuncao)) {
                                    if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') {
                                        this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                                    }
                                    if (tipoValor === 'number' && !['inteiro', 'real', 'número'].includes(tipoRetornoFuncao)) {
                                        this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                                    }
                                }
                            }
                        }
                    }
                    break;
            }
        }
        let valorInicializador = undefined;
        if (declaracao.inicializador) {
            if (declaracao.inicializador.hasOwnProperty('valor')) {
                valorInicializador = declaracao.inicializador.valor;
            }
            else {
                valorInicializador = declaracao.inicializador;
            }
        }
        // Inferir tipo do inicializador se não foi especificado explicitamente
        let tipoInferido = declaracao.tipo;
        if (!tipoInferido && declaracao.inicializador) {
            tipoInferido = this.obterTipoExpressao(declaracao.inicializador);
        }
        const variavel = {
            nome: declaracao.simbolo.lexema,
            tipo: tipoInferido || 'qualquer',
            imutavel: false,
            valor: valorInicializador,
            inicializada: declaracao.inicializador !== null && declaracao.inicializador !== undefined,
            usada: false,
            hashArquivo: declaracao.simbolo.hashArquivo,
            linha: declaracao.simbolo.linha
        };
        const declaradaComSucesso = this.gerenciadorEscopos.declarar(declaracao.simbolo.lexema, variavel);
        if (!declaradaComSucesso) {
            const variavelExistente = this.gerenciadorEscopos.buscarNoEscopoAtual(declaracao.simbolo.lexema);
            this.aviso(declaracao.simbolo, `Variável '${declaracao.simbolo.lexema}' já foi declarada na linha ${variavelExistente === null || variavelExistente === void 0 ? void 0 : variavelExistente.linha}.`);
        }
        return Promise.resolve();
    }
    visitarExpressaoFormatacaoEscrita(expressao) {
        if (expressao.expressao) {
            /* for (const expressao of expressao.expressoes) {
                if (expressao instanceof Variavel) {
                    this.gerenciadorEscopos.marcarComoUsada(expressao.simbolo.lexema);
                }
                // Recursively check for variables in more complex expressions
                this.marcarVariaveisUsadasEmExpressao(expressao);
            } */
        }
        return Promise.resolve();
    }
    visitarExpressaoRetornar(declaracao) {
        return Promise.resolve(null);
    }
    visitarExpressaoDeVariavel(expressao) {
        if (expressao instanceof construtos_1.Variavel) {
            return this.verificarVariavel(expressao);
        }
        return Promise.resolve();
    }
    visitarDeclaracaoDefinicaoFuncao(declaracao) {
        if (declaracao.funcao.tipo === undefined) {
            this.erro(declaracao.simbolo, `Declaração de retorno da função é inválido.`);
        }
        if (declaracao.funcao.parametros.length >= 255) {
            this.erro(declaracao.simbolo, 'Função não pode ter mais de 255 parâmetros.');
        }
        let tipoRetornoFuncao = declaracao.funcao.tipo;
        if (tipoRetornoFuncao) {
            if (!['vazio', 'qualquer'].includes(tipoRetornoFuncao)) {
                const todosOsCaminhosRetornam = this.todosOsCaminhosRetornam(declaracao.funcao.corpo);
                if (!todosOsCaminhosRetornam) {
                    this.erro(declaracao.simbolo, `Função '${declaracao.simbolo.lexema}' deve retornar '${tipoRetornoFuncao}' em todos os caminhos de execução.`);
                }
            }
            let funcaoContemRetorno = declaracao.funcao.corpo.find((c) => c instanceof declaracoes_1.Retorna);
            if (funcaoContemRetorno && funcaoContemRetorno.valor) {
                if (tipoRetornoFuncao === 'vazio') {
                    this.erro(declaracao.simbolo, `A função não pode ter nenhum tipo de retorno.`);
                }
                else {
                    const tipoValor = typeof funcaoContemRetorno.valor.valor;
                    if (!['qualquer'].includes(tipoRetornoFuncao)) {
                        if (tipoValor === 'string' && tipoRetornoFuncao !== 'texto') {
                            this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                        }
                        if (tipoValor === 'number' && !['inteiro', 'real', 'número'].includes(tipoRetornoFuncao)) {
                            this.erro(declaracao.simbolo, `Esperado retorno do tipo '${tipoRetornoFuncao}' dentro da função.`);
                        }
                    }
                }
            }
        }
        this.funcoes[declaracao.simbolo.lexema] = {
            valor: declaracao.funcao,
        };
        return Promise.resolve();
    }
    verificarVariaveisNaoUsadas() {
        const naoUsadas = this.gerenciadorEscopos.obterVariaveisNaoUsadas();
        for (let variavel of naoUsadas) {
            // Verifica se já existe um erro associado à variável.
            const temErro = this.diagnosticos.some(d => d.severidade === erros_1.DiagnosticoSeveridade.ERRO &&
                d.simbolo.lexema === variavel.nome);
            // Se a variável já tem um erro associado, não emitir aviso de não usada.
            if (temErro) {
                continue;
            }
            this.aviso({
                lexema: variavel.nome,
                linha: variavel.linha,
                tipo: variavel.tipo,
                hashArquivo: variavel.hashArquivo
            }, `Variável '${variavel.nome}' foi declarada mas nunca usada.`);
        }
    }
    async analisar(declaracoes) {
        this.gerenciadorEscopos = new gerenciador_escopos_1.GerenciadorEscopos();
        this.atual = 0;
        this.diagnosticos = [];
        try {
            while (this.atual < declaracoes.length) {
                await declaracoes[this.atual].aceitar(this);
                this.atual++;
            }
        }
        catch (erro) {
            console.error('Erro durante análise semântica:', erro);
        }
        // Verifica variáveis não usadas ao final
        this.verificarVariaveisNaoUsadas();
        return {
            diagnosticos: this.diagnosticos,
        };
    }
}
exports.AnalisadorSemantico = AnalisadorSemantico;
//# sourceMappingURL=analisador-semantico.js.map