"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
const common_1 = require("../common");
const traverse_1 = require("@glas/traverse");
const ast_1 = require("../ast");
const createScopeMaps_1 = __importDefault(require("../createScopeMaps"));
function mergeDeclarations(base, sub) {
    // this should actually check that the types can be merged.
    return sub;
}
// cannot extend from ion.Object until we provide ability to change reserved names.
// const rootClassReference = new Reference({ name: getAbsoluteName("ion.Object", "Object")})
function inheritBaseClasses(root, options) {
    let ancestorsMap = new Map();
    let scopes = createScopeMaps_1.default(root, { ancestorsMap });
    let finished = new Map();
    let inprogress = new Set();
    function ensureDeclarationsInherited(classDeclaration, source) {
        let result = finished.get(classDeclaration);
        if (result == null) {
            if (inprogress.has(classDeclaration)) {
                throw common_1.SemanticError(`Circular class extension`, source);
            }
            inprogress.add(classDeclaration);
            let baseDeclarations = new Map();
            let baseClasses = new Map([...classDeclaration.baseClasses].map(r => [r.name, r]));
            function addDeclarations(declarations, patch) {
                for (let declaration of declarations) {
                    if (!ast_1.Identifier.is(declaration.id)) {
                        throw common_1.SemanticError("invalid destructuring on class variable", declaration.id);
                    }
                    if (declaration.id.name === "constructor") {
                        // we don't inherit constructors, they are added automatically one per concrete class
                        continue;
                    }
                    let current = baseDeclarations.get(declaration.id.name);
                    let newValue = current ? mergeDeclarations(current, declaration) : declaration;
                    if (patch != null) {
                        newValue = newValue.patch(patch);
                    }
                    baseDeclarations.set(declaration.id.name, newValue);
                }
            }
            for (let baseClass of classDeclaration.baseClasses) {
                let declarator = common_1.getDeclarator(baseClass, scopes, ancestorsMap, true);
                let baseDeclaration = ancestorsMap.get(declarator);
                if (!ast_1.ClassDeclaration.is(baseDeclaration)) {
                    continue;
                    // we now just skip if not found to allow single file compilation.
                    // if (!options.emit) {
                    //     // probably a quick incremental compile, so we just ignore
                    // }
                    // throw SemanticError(`Not a class declaration`, baseClass)
                }
                if (classDeclaration.isStruct && !baseDeclaration.isStruct) {
                    throw common_1.SemanticError(`Structs cannot inherit from classes`, baseClass);
                }
                if (!baseDeclaration.isData) {
                    throw common_1.SemanticError(`Data classes can only inherit from other data classes`, baseClass);
                }
                baseDeclaration = ensureDeclarationsInherited(baseDeclaration, source);
                for (let ref of baseDeclaration.baseClasses) {
                    baseClasses.set(ref.name, ref);
                }
                addDeclarations(baseDeclaration.instance.declarations, { inherited: true });
            }
            // // now insert the current class declarations
            addDeclarations(classDeclaration.instance.declarations);
            // // override properties with the same name
            result = classDeclaration.patch({
                baseClasses: Array.from(baseClasses.values()),
                instance: classDeclaration.instance.patch({
                    declarations: [
                        ...baseDeclarations.values(),
                    ]
                })
            });
            finished.set(classDeclaration, result);
        }
        inprogress.delete(classDeclaration);
        return result;
    }
    return traverse_1.traverse(root, {
        leave(node) {
            if (ast_1.ClassDeclaration.is(node) && node.isData) {
                let declaration = ensureDeclarationsInherited(node, node);
                return declaration;
            }
        }
    });
}
exports.default = inheritBaseClasses;
