"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DeleguaFuncao = void 0;
const chamavel_1 = require("./chamavel");
const espaco_memoria_1 = require("../espaco-memoria");
const quebras_1 = require("../../quebras");
const declaracoes_1 = require("../../declaracoes");
/**
 * Qualquer função declarada em código é uma DeleguaFuncao.
 */
class DeleguaFuncao extends chamavel_1.Chamavel {
    constructor(nome, declaracao, instancia = undefined, eInicializador = false) {
        super();
        this.nome = nome;
        this.declaracao = declaracao;
        this.instancia = instancia;
        this.eInicializador = eInicializador;
    }
    aridade() {
        var _a, _b;
        return ((_b = (_a = this.declaracao) === null || _a === void 0 ? void 0 : _a.parametros) === null || _b === void 0 ? void 0 : _b.length) || 0;
    }
    /**
     * Método utilizado por Delégua para representar esta função quando impressa.
     * @returns {string} A representação da função como texto.
     */
    paraTexto() {
        var _a;
        if (!this.nome)
            return '<função />';
        let resultado = `<função nome=${this.nome}`;
        let parametros = '';
        let retorno = '';
        for (let parametro of this.declaracao.parametros) {
            parametros += `${parametro.nome.lexema}: ${parametro.tipoDado || 'qualquer'}, `;
        }
        if (this.declaracao.parametros.length > 0) {
            parametros = `argumentos=<${parametros.slice(0, -2)}>`;
        }
        const retorna = this.declaracao.corpo.filter((c) => c instanceof declaracoes_1.Retorna)[0];
        if (retorna instanceof declaracoes_1.Retorna) {
            const valor = (_a = retorna === null || retorna === void 0 ? void 0 : retorna.valor) === null || _a === void 0 ? void 0 : _a.valor;
            retorno = `retorna=<${typeof valor === 'number' ? valor : `'${valor}'`}>`;
        }
        if (parametros) {
            resultado += ` ${parametros}`;
        }
        if (retorno) {
            resultado += ` ${retorno}`;
        }
        resultado += ' />';
        return resultado;
    }
    /**
     * Método utilizado pelo VSCode para inspecionar esta função em depuração.
     * @returns {string} A representação da função como texto.
     */
    toString() {
        return this.paraTexto();
    }
    resolverParametrosEspalhados(argumentos, indiceArgumentoAtual) {
        const argumentosResolvidos = [];
        for (let i = indiceArgumentoAtual; i < argumentos.length; i++) {
            const argumentoAtual = argumentos[i];
            argumentosResolvidos.push(argumentoAtual && argumentoAtual.hasOwnProperty('valor')
                ? argumentoAtual.valor
                : argumentoAtual);
        }
        return argumentosResolvidos;
    }
    resolverAmbiente(argumentos) {
        const ambiente = new espaco_memoria_1.EspacoMemoria();
        const parametros = this.declaracao.parametros || [];
        for (let i = 0; i < parametros.length; i++) {
            const parametro = parametros[i];
            const nome = parametro['nome'].lexema;
            if (parametro.abrangencia === 'multiplo') {
                const argumentosResolvidos = this.resolverParametrosEspalhados(argumentos, i);
                // TODO: Verificar se `imutavel` é `true` aqui mesmo.
                ambiente.valores[nome] = {
                    tipo: 'vetor',
                    valor: argumentosResolvidos,
                    imutavel: true,
                };
            }
            else {
                let argumento = argumentos[i];
                if (argumento.valor === null) {
                    argumentos[i].valor = parametro['padrao'] ? parametro['padrao'].valor : null;
                }
                ambiente.valores[nome] =
                    argumento && argumento.hasOwnProperty('valor') ? argumento.valor : argumento;
                // Se o argumento é `DeleguaFuncao`, para habilitar o recurso de _currying_,
                // copiamos seu valor para o escopo atual. Nem sempre podemos contar com a tipagem explícita aqui.
                if (argumento.valor && ['funcao', 'função'].includes(argumento.valor.tipo)) {
                    parametro.referencia = true;
                }
            }
        }
        return ambiente;
    }
    async chamar(visitante, argumentos) {
        const ambiente = this.resolverAmbiente(argumentos);
        if (this.instancia !== undefined) {
            ambiente.valores['isto'] = {
                valor: this.instancia,
                tipo: 'objeto',
                imutavel: false,
            };
        }
        // TODO: Repensar essa dinâmica para análise semântica (levar toda a lógica abaixo para
        // o interpretador).
        const interpretador = visitante;
        interpretador.proximoEscopo = 'funcao';
        const retornoBloco = await interpretador.executarBloco(this.declaracao.corpo, ambiente);
        const referencias = this.declaracao.parametros
            .map((p, indice) => {
            if (p.referencia) {
                return {
                    indice: indice,
                    parametro: p,
                };
            }
        })
            .filter((r) => r);
        const pilha = interpretador.pilhaEscoposExecucao;
        for (let referencia of referencias) {
            let argumentoReferencia = ambiente.valores[referencia.parametro.nome.lexema];
            pilha.definirVariavel(referencia.parametro.nome.lexema, argumentoReferencia.valor);
        }
        if (retornoBloco instanceof quebras_1.RetornoQuebra) {
            return retornoBloco.valor;
        }
        if (this.eInicializador) {
            return this.instancia;
        }
        return retornoBloco;
    }
    funcaoPorMetodoDeClasse(instancia) {
        return new DeleguaFuncao(this.nome, this.declaracao, instancia, this.eInicializador);
    }
}
exports.DeleguaFuncao = DeleguaFuncao;
//# sourceMappingURL=delegua-funcao.js.map