"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.DescritorTipoClasse = void 0;
const excecoes_1 = require("../../excecoes");
const chamavel_1 = require("./chamavel");
const metodo_polimorfico_1 = require("./metodo-polimorfico");
const objeto_delegua_classe_1 = require("./objeto-delegua-classe");
const mapaDeNormalizacao = {
    numero: 'número',
    logico: 'lógico',
    funcao: 'função',
    dicionario: 'dicionário',
    modulo: 'módulo',
};
function normalizarTipo(tipo) {
    if (!tipo || tipo === 'qualquer')
        return 'qualquer';
    const tipoMinusculo = tipo.toLowerCase();
    return mapaDeNormalizacao[tipoMinusculo] || tipoMinusculo;
}
function assinaturasIguais(a, b) {
    var _a, _b;
    const paramsA = ((_a = a.declaracao) === null || _a === void 0 ? void 0 : _a.parametros) || [];
    const paramsB = ((_b = b.declaracao) === null || _b === void 0 ? void 0 : _b.parametros) || [];
    if (paramsA.length !== paramsB.length)
        return false;
    for (let i = 0; i < paramsA.length; i++) {
        if (normalizarTipo(paramsA[i].tipoDado) !== normalizarTipo(paramsB[i].tipoDado)) {
            return false;
        }
    }
    return true;
}
/**
 * Descritor de tipo de classe. Quando uma declaração de classe é visitada, o que
 * vai para a pilha de escopos de execução é esta estrutura. Quando uma nova instância
 * de classe é criada, a referência para a instância é implementada aqui.
 */
class DescritorTipoClasse extends chamavel_1.Chamavel {
    /** Obtenedor de compat: primeiro pai direto (usado por tradutores e partes do interpretador). */
    get superClasse() {
        var _a;
        return (_a = this.superClasses[0]) !== null && _a !== void 0 ? _a : null;
    }
    /** Definidor de compat: atribui um único pai direto (usado pela atribuição implícita de OBJETO_BASE). */
    set superClasse(v) {
        this.superClasses = v ? [v] : [];
    }
    constructor(simboloOriginal, superClasses, metodos, propriedades) {
        super();
        this.simboloOriginal = simboloOriginal;
        if (Array.isArray(superClasses)) {
            this.superClasses = superClasses;
        }
        else if (superClasses) {
            this.superClasses = [superClasses];
        }
        else {
            this.superClasses = [];
        }
        this.orem = [this];
        this.metodos = metodos || {};
        this.metodosEstaticos = {};
        this.membrosEstaticos = {};
        this.obtenedores = {};
        this.definidores = {};
        this.obtenedoresEstaticos = {};
        this.definidoresEstaticos = {};
        this.propriedades = propriedades || [];
        this.dialetoRequerDeclaracaoPropriedades = false;
        this.abstrata = false;
        this.classeEstatica = false;
        this.metodosAbstratos = [];
        this.acessoMetodos = {};
        this.acessoPropriedades = {};
    }
    // ─── C3 OReM ──────────────────────────────────────────────────────────────
    static mesclaC3(listas) {
        const resultado = [];
        while (true) {
            const listasNaoVazias = listas.filter((l) => l.length > 0);
            if (listasNaoVazias.length === 0)
                break;
            let candidato = null;
            for (const lista of listasNaoVazias) {
                const cabeca = lista[0];
                const naCauda = listasNaoVazias.some((l) => l.slice(1).indexOf(cabeca) >= 0);
                if (!naCauda) {
                    candidato = cabeca;
                    break;
                }
            }
            if (candidato === null) {
                throw new excecoes_1.ErroEmTempoDeExecucao(null, 'Hierarquia de classes inconsistente: não foi possível calcular o OReM (C3).');
            }
            resultado.push(candidato);
            for (const lista of listas) {
                const idx = lista.indexOf(candidato);
                if (idx === 0)
                    lista.shift();
            }
        }
        return resultado;
    }
    /** Calcula e armazena o OReM (linearização C3) para esta classe e retorna a lista resultante. */
    static computarOReM(cls) {
        if (cls.superClasses.length === 0) {
            return [cls];
        }
        const oremsDePais = cls.superClasses.map((p) => DescritorTipoClasse.computarOReM(p));
        const listas = [...oremsDePais.map((m) => [...m]), [...cls.superClasses]];
        return [cls, ...DescritorTipoClasse.mesclaC3(listas)];
    }
    // ─── Verificação abstrata ─────────────────────────────────────────────────
    /**
     * Verifica se todos os métodos abstratos da superclasse estão implementados
     * na subclasse fornecida. Lança erro em tempo de execução se algum faltar.
     */
    verificarImplementacaoAbstrata(subclasse) {
        var _a, _b;
        for (const nomeAbstrato of this.metodosAbstratos) {
            const implementado = subclasse.metodos.hasOwnProperty(nomeAbstrato);
            if (!implementado) {
                throw new excecoes_1.ErroEmTempoDeExecucao(subclasse.simboloOriginal, `Classe '${(_a = subclasse.simboloOriginal) === null || _a === void 0 ? void 0 : _a.lexema}' não implementa o método abstrato '${nomeAbstrato}' ` +
                    `da classe '${(_b = this.simboloOriginal) === null || _b === void 0 ? void 0 : _b.lexema}'.`);
            }
        }
    }
    // ─── Obtenedores e definidores ────────────────────────────────────────────
    encontrarObtenedor(nome, estatico = false) {
        const mapa = estatico ? this.obtenedoresEstaticos : this.obtenedores;
        if (Object.prototype.hasOwnProperty.call(mapa, nome)) {
            return mapa[nome];
        }
        for (const ancestral of this.orem.slice(1)) {
            const mapaAnc = estatico ? ancestral.obtenedoresEstaticos : ancestral.obtenedores;
            if (Object.prototype.hasOwnProperty.call(mapaAnc, nome)) {
                return mapaAnc[nome];
            }
        }
        return undefined;
    }
    encontrarDefinidor(nome, estatico = false) {
        const mapa = estatico ? this.definidoresEstaticos : this.definidores;
        if (Object.prototype.hasOwnProperty.call(mapa, nome)) {
            return mapa[nome];
        }
        for (const ancestral of this.orem.slice(1)) {
            const mapaAnc = estatico ? ancestral.definidoresEstaticos : ancestral.definidores;
            if (Object.prototype.hasOwnProperty.call(mapaAnc, nome)) {
                return mapaAnc[nome];
            }
        }
        return undefined;
    }
    // ─── Membros estáticos ────────────────────────────────────────────────────
    async obterEstatico(nome, visitante) {
        var _a;
        const obtenedor = this.encontrarObtenedor(nome, true);
        if (obtenedor) {
            if (!visitante) {
                throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Obtenedor estático '${nome}' requer contexto de execução.`);
            }
            return await obtenedor.chamar(visitante, []);
        }
        if (Object.prototype.hasOwnProperty.call(this.metodosEstaticos, nome)) {
            return this.metodosEstaticos[nome];
        }
        if (Object.prototype.hasOwnProperty.call(this.membrosEstaticos, nome)) {
            return this.membrosEstaticos[nome];
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Membro estático '${nome}' não encontrado na classe '${(_a = this.simboloOriginal) === null || _a === void 0 ? void 0 : _a.lexema}'.`);
    }
    async definirEstatico(nome, valor, visitante) {
        const definidor = this.encontrarDefinidor(nome, true);
        if (definidor) {
            if (!visitante) {
                throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Definidor estático '${nome}' requer contexto de execução.`);
            }
            await definidor.chamar(visitante, [{ nome: null, valor }]);
            return;
        }
        this.membrosEstaticos[nome] = valor;
    }
    // ─── Resolução de métodos via OReM ─────────────────────────────────────────
    /**
     * Mescla sobrecargas da classe atual com as de ancestrais (respeitando MRO).
     * Sobrecargas da subclasse com mesma assinatura substituem as do ancestral.
     */
    mesclarComSuperclasse(metodosAtuais, metodosSuperclasse) {
        const resultado = [...metodosAtuais];
        for (const metodoSuper of metodosSuperclasse) {
            const jaSobrescrito = metodosAtuais.some((m) => assinaturasIguais(m, metodoSuper));
            if (!jaSobrescrito) {
                resultado.push(metodoSuper);
            }
        }
        return resultado;
    }
    obterSobrecargasDaSuperclasse(nome) {
        let sobrecarga = [];
        // Percorre a OReM[1:] na ordem do OReM — o primeiro ancestral vence
        for (const ancestral of this.orem.slice(1)) {
            if (!ancestral.metodos.hasOwnProperty(nome))
                continue;
            const metodo = ancestral.metodos[nome];
            const novos = Array.isArray(metodo) ? metodo : [metodo];
            for (const m of novos) {
                if (!sobrecarga.some((s) => assinaturasIguais(s, m))) {
                    sobrecarga.push(m);
                }
            }
        }
        return sobrecarga;
    }
    encontrarMetodo(nome) {
        let metodosAtuais = [];
        if (this.metodos.hasOwnProperty(nome)) {
            const metodo = this.metodos[nome];
            metodosAtuais = Array.isArray(metodo) ? metodo : [metodo];
        }
        const metodosSuperclasse = this.obterSobrecargasDaSuperclasse(nome);
        let todasSobrecargas;
        if (metodosAtuais.length > 0 && metodosSuperclasse.length > 0) {
            todasSobrecargas = this.mesclarComSuperclasse(metodosAtuais, metodosSuperclasse);
        }
        else if (metodosAtuais.length > 0) {
            todasSobrecargas = metodosAtuais;
        }
        else if (metodosSuperclasse.length > 0) {
            todasSobrecargas = metodosSuperclasse;
        }
        else {
            return undefined;
        }
        if (todasSobrecargas.length === 1) {
            return todasSobrecargas[0];
        }
        return new metodo_polimorfico_1.MetodoPolimorfico(nome, todasSobrecargas);
    }
    encontrarPropriedade(nome) {
        if (nome in this.propriedades) {
            return this.propriedades[nome];
        }
        for (const ancestral of this.orem.slice(1)) {
            if (nome in ancestral.propriedades) {
                return ancestral.propriedades[nome];
            }
        }
        if (this.dialetoRequerDeclaracaoPropriedades) {
            throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Propriedade "${nome}" não declarada na classe ${this.simboloOriginal.lexema}.`);
        }
        return undefined;
    }
    // ─── Representação textual ────────────────────────────────────────────────
    /**
     * Método utilizado por Delégua para representar esta classe quando impressa.
     * @returns {string} A representação da classe como texto.
     */
    paraTexto() {
        var _a, _b;
        const nome = (_b = (_a = this.simboloOriginal) === null || _a === void 0 ? void 0 : _a.lexema) !== null && _b !== void 0 ? _b : 'Objeto';
        const nomesMetodos = Object.keys(this.metodos).join(', ');
        const nomesPropriedades = this.propriedades.map((p) => p.nome.lexema).join(', ');
        return `<[ ${nome} estático métodos=[${nomesMetodos}] propriedades=[${nomesPropriedades}] ]>`;
    }
    /**
     * Método utilizado pelo VSCode para inspecionar esta classe em depuração.
     * @returns {string} A representação da classe como texto.
     */
    toString() {
        return this.paraTexto();
    }
    aridade() {
        const inicializador = this.encontrarMetodo('construtor');
        if (inicializador instanceof metodo_polimorfico_1.MetodoPolimorfico) {
            return inicializador.aridade();
        }
        return inicializador ? inicializador.aridade() : 0;
    }
    async chamar(visitante, argumentos) {
        var _a, _b;
        if (this.classeEstatica) {
            throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Não é possível instanciar a classe estática '${(_a = this.simboloOriginal) === null || _a === void 0 ? void 0 : _a.lexema}'.`);
        }
        if (this.abstrata) {
            throw new excecoes_1.ErroEmTempoDeExecucao(this.simboloOriginal, `Não é possível instanciar a classe abstrata '${(_b = this.simboloOriginal) === null || _b === void 0 ? void 0 : _b.lexema}'.`);
        }
        const instancia = new objeto_delegua_classe_1.ObjetoDeleguaClasse(this);
        const inicializador = this.encontrarMetodo('construtor');
        if (inicializador) {
            if (inicializador instanceof metodo_polimorfico_1.MetodoPolimorfico) {
                const construtorVinculado = inicializador.funcaoPorMetodoDeClasse(instancia);
                await construtorVinculado.chamar(visitante, argumentos);
            }
            else {
                // Para construtor não polimórfico, completar os argumentos
                // não preenchidos com valores indefinidos.
                const aridadeConstrutor = inicializador.aridade();
                if (argumentos.length < aridadeConstrutor) {
                    const diferenca = aridadeConstrutor - argumentos.length;
                    for (let i = 0; i < diferenca; i++) {
                        argumentos.push({ nome: null, valor: null });
                    }
                }
                const metodoConstrutor = inicializador.funcaoPorMetodoDeClasse(instancia);
                await metodoConstrutor.chamar(visitante, argumentos);
            }
        }
        return instancia;
    }
}
exports.DescritorTipoClasse = DescritorTipoClasse;
//# sourceMappingURL=descritor-tipo-classe.js.map