"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ObjetoDeleguaClasse = void 0;
const excecoes_1 = require("../../excecoes");
/**
 * A instância de uma classe em Delégua.
 * Possui propriedades e métodos. Propriedades são definidas localmente.
 * Métodos são extraídos do descritor da classe, uma `DeleguaClasse`.
 */
class ObjetoDeleguaClasse {
    valorPadraoParaTipo(tipo) {
        switch (tipo) {
            case 'numero':
                return 0;
            case 'texto':
                return '';
            case 'logico':
                return false;
            default:
                return undefined;
        }
    }
    constructor(classe) {
        this.classe = classe;
        this.propriedades = {};
        // Inicializar propriedades herdadas do OReM (do mais genérico ao mais específico)
        // para que ancestrais mais próximos sobrescrevam os mais distantes.
        const ancestrais = classe.orem.slice(1).reverse();
        for (const ancestral of ancestrais) {
            for (const propriedade of ancestral.propriedades) {
                if (propriedade.estatico)
                    continue;
                if (propriedade.autoObter || propriedade.autoDefinir) {
                    this.propriedades['_' + propriedade.nome.lexema] = this.valorPadraoParaTipo(propriedade.tipo);
                }
                else {
                    this.propriedades[propriedade.nome.lexema] = this.valorPadraoParaTipo(propriedade.tipo);
                }
            }
        }
        // Propriedades da própria classe (têm prioridade máxima no valor padrão)
        for (const propriedade of classe.propriedades) {
            if (propriedade.estatico)
                continue;
            if (propriedade.autoObter || propriedade.autoDefinir) {
                this.propriedades['_' + propriedade.nome.lexema] = this.valorPadraoParaTipo(propriedade.tipo);
            }
            else {
                this.propriedades[propriedade.nome.lexema] = this.valorPadraoParaTipo(propriedade.tipo);
            }
        }
    }
    verificarAcessoLeitura(nome, simbolo, visitante) {
        var _a, _b, _c, _d, _e, _f, _g;
        // Percorre o OReM para encontrar a classe que declarou o membro com um modificador de acesso.
        let declaradorClasse = undefined;
        let acesso = undefined;
        for (const cls of this.classe.orem) {
            const acessoCls = (_b = (_a = cls.acessoPropriedades) === null || _a === void 0 ? void 0 : _a[nome]) !== null && _b !== void 0 ? _b : (_c = cls.acessoMetodos) === null || _c === void 0 ? void 0 : _c[nome];
            if (acessoCls) {
                acesso = acessoCls;
                declaradorClasse = cls;
                break;
            }
        }
        if (!acesso || acesso === 'publico')
            return;
        const classeAtual = visitante === null || visitante === void 0 ? void 0 : visitante.classeAtualEmExecucao;
        if (acesso === 'privado') {
            if (classeAtual !== declaradorClasse) {
                throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Membro '${nome}' é privado e não pode ser acessado fora da classe '${(_d = declaradorClasse.simboloOriginal) === null || _d === void 0 ? void 0 : _d.lexema}'.`);
            }
        }
        else if (acesso === 'protegido') {
            const eAcessivel = (_f = (_e = classeAtual === null || classeAtual === void 0 ? void 0 : classeAtual.orem) === null || _e === void 0 ? void 0 : _e.includes(declaradorClasse)) !== null && _f !== void 0 ? _f : false;
            if (!eAcessivel) {
                throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Membro '${nome}' é protegido e não pode ser acessado fora da hierarquia da classe '${(_g = declaradorClasse.simboloOriginal) === null || _g === void 0 ? void 0 : _g.lexema}'.`);
            }
        }
    }
    async obter(simbolo, visitante) {
        const obtenedor = this.classe.encontrarObtenedor(simbolo.lexema);
        if (obtenedor) {
            if (!visitante) {
                throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Obtenedor '${simbolo.lexema}' requer contexto de execução.`);
            }
            const metodoObtenedor = obtenedor.funcaoPorMetodoDeClasse(this);
            return await metodoObtenedor.chamar(visitante, []);
        }
        // Auto-property: acessa o campo de armazenamento interno '_nome'
        const propAuto = this.classe.propriedades.find((p) => p.nome.lexema === simbolo.lexema);
        if (propAuto === null || propAuto === void 0 ? void 0 : propAuto.autoObter) {
            this.verificarAcessoLeitura(simbolo.lexema, simbolo, visitante);
            return this.propriedades['_' + simbolo.lexema];
        }
        if (propAuto && propAuto.autoDefinir && !propAuto.autoObter) {
            throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Propriedade '${simbolo.lexema}' é somente-escrita.`);
        }
        if (this.propriedades.hasOwnProperty(simbolo.lexema)) {
            this.verificarAcessoLeitura(simbolo.lexema, simbolo, visitante);
            return this.propriedades[simbolo.lexema];
        }
        if (Object.prototype.hasOwnProperty.call(this.classe.membrosEstaticos, simbolo.lexema)) {
            return await this.classe.obterEstatico(simbolo.lexema, visitante);
        }
        const metodo = this.classe.encontrarMetodo(simbolo.lexema);
        if (metodo) {
            this.verificarAcessoLeitura(simbolo.lexema, simbolo, visitante);
            return metodo.funcaoPorMetodoDeClasse(this);
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Método ou propriedade "${simbolo.lexema}" não existe neste objeto.`);
    }
    obterMetodo(nomeMetodo) {
        const metodo = this.classe.encontrarMetodo(nomeMetodo);
        if (metodo)
            return metodo.funcaoPorMetodoDeClasse(this);
        throw new excecoes_1.ErroEmTempoDeExecucao(null, `Método "${nomeMetodo}" não existe neste objeto.`);
    }
    obterPropriedade(nomePropriedade) {
        if (this.propriedades.hasOwnProperty(nomePropriedade)) {
            return this.propriedades[nomePropriedade];
        }
        throw new excecoes_1.ErroEmTempoDeExecucao(null, `Propriedade "${nomePropriedade}" não existe neste objeto.`);
    }
    async definir(simbolo, valor, visitante) {
        const definidor = this.classe.encontrarDefinidor(simbolo.lexema);
        if (definidor) {
            if (!visitante) {
                throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Definidor '${simbolo.lexema}' requer contexto de execução.`);
            }
            const metodoDefinidor = definidor.funcaoPorMetodoDeClasse(this);
            await metodoDefinidor.chamar(visitante, [{ nome: null, valor }]);
            return;
        }
        // Auto-property: armazena no campo interno '_nome'
        const propAuto = this.classe.propriedades.find((p) => p.nome.lexema === simbolo.lexema);
        if (propAuto === null || propAuto === void 0 ? void 0 : propAuto.autoDefinir) {
            this.verificarAcessoLeitura(simbolo.lexema, simbolo, visitante);
            this.propriedades['_' + simbolo.lexema] = valor;
            return;
        }
        if (propAuto && propAuto.autoObter && !propAuto.autoDefinir) {
            throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Propriedade '${simbolo.lexema}' é somente-leitura.`);
        }
        if (Object.prototype.hasOwnProperty.call(this.classe.membrosEstaticos, simbolo.lexema)) {
            await this.classe.definirEstatico(simbolo.lexema, valor, visitante);
            return;
        }
        // Verificar acesso antes da atribuição.
        this.verificarAcessoLeitura(simbolo.lexema, simbolo, visitante);
        if (this.classe.dialetoRequerDeclaracaoPropriedades &&
            !this.propriedades.hasOwnProperty(simbolo.lexema)) {
            throw new excecoes_1.ErroEmTempoDeExecucao(simbolo, `Propriedade "${simbolo.lexema}" não foi definida na declaração da classe ${this.classe.simboloOriginal.lexema}.`);
        }
        this.propriedades[simbolo.lexema] = valor;
    }
    /**
     * Método utilizado por Delégua para inspecionar este objeto em depuração.
     * @returns {string} A representação do objeto como texto.
     */
    paraTexto() {
        var _a, _b;
        const nome = (_b = (_a = this.classe.simboloOriginal) === null || _a === void 0 ? void 0 : _a.lexema) !== null && _b !== void 0 ? _b : 'Objeto';
        const nomesMetodos = Object.keys(this.classe.metodos).join(', ');
        const nomesPropriedades = Object.keys(this.propriedades).join(', ');
        return `<[ ${nome} métodos=[${nomesMetodos}] propriedades=[${nomesPropriedades}] ]>`;
    }
    /**
     * Método utilizado pelo VSCode para representar este objeto quando impresso.
     * @returns {string} A representação do objeto como texto.
     */
    toString() {
        return this.paraTexto();
    }
}
exports.ObjetoDeleguaClasse = ObjetoDeleguaClasse;
//# sourceMappingURL=objeto-delegua-classe.js.map