"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.ProjectReflection = void 0;
const index_1 = require("../sources/index");
const abstract_1 = require("./abstract");
const container_1 = require("./container");
const utils_1 = require("./utils");
const reference_1 = require("./reference");
const types_1 = require("../types");
const utils_2 = require("../../utils");
const kind_1 = require("./kind");
/**
 * A reflection that represents the root of the project.
 *
 * The project reflection acts as a global index, one may receive all reflections
 * and source files of the processed project through this reflection.
 */
class ProjectReflection extends container_1.ContainerReflection {
    /**
     * Create a new ProjectReflection instance.
     *
     * @param name  The name of the project.
     */
    constructor(name) {
        super(name, kind_1.ReflectionKind.Project);
        // Used to resolve references.
        this.symbolToReflectionIdMap = new Map();
        this.reflectionIdToSymbolMap = new Map();
        /**
         * A list of all reflections within the project.
         * @deprecated use {@link getReflectionById}, this will eventually be removed.
         *   To iterate over all reflections, prefer {@link getReflectionsByKind}.
         */
        this.reflections = {};
        /**
         * The root directory of the project.
         */
        this.directory = new index_1.SourceDirectory();
        /**
         * A list of all source files within the project.
         */
        this.files = [];
    }
    /**
     * Return whether this reflection is the root / project reflection.
     */
    isProject() {
        return true;
    }
    /**
     * Return a list of all reflections in this project of a certain kind.
     *
     * @param kind  The desired kind of reflection.
     * @returns     An array containing all reflections with the desired kind.
     */
    getReflectionsByKind(kind) {
        return Object.values(this.reflections).filter((reflection) => reflection.kindOf(kind));
    }
    /**
     * Try to find a reflection by its name.
     *
     * @param names The name hierarchy to look for, if a string, the name will be split on "."
     * @return The found reflection or undefined.
     */
    findReflectionByName(arg) {
        const names = Array.isArray(arg)
            ? arg
            : (0, utils_1.splitUnquotedString)(arg, ".");
        const name = names.pop();
        search: for (const key in this.reflections) {
            const reflection = this.reflections[key];
            if (reflection.name !== name) {
                continue;
            }
            let depth = names.length - 1;
            let target = reflection;
            while ((target = target.parent) && depth >= 0) {
                if (target.name !== names[depth]) {
                    continue search;
                }
                depth -= 1;
            }
            return reflection;
        }
        return undefined;
    }
    /**
     * When excludeNotExported is set, if a symbol is exported only under a different name
     * there will be a reference which points to the symbol, but the symbol will not be converted
     * and the rename will point to nothing. Warn the user if this happens.
     */
    removeDanglingReferences() {
        const dangling = new Set();
        for (const ref of Object.values(this.reflections)) {
            if (ref instanceof reference_1.ReferenceReflection) {
                if (!ref.tryGetTargetReflection()) {
                    dangling.add(ref);
                }
            }
        }
        for (const refl of dangling) {
            this.removeReflection(refl);
        }
    }
    /**
     * Registers the given reflection so that it can be quickly looked up by helper methods.
     * Should be called for *every* reflection added to the project.
     */
    registerReflection(reflection, symbol) {
        var _a;
        this.referenceGraph = undefined;
        this.reflections[reflection.id] = reflection;
        if (symbol) {
            this.symbolToReflectionIdMap.set(symbol, (_a = this.symbolToReflectionIdMap.get(symbol)) !== null && _a !== void 0 ? _a : reflection.id);
            this.reflectionIdToSymbolMap.set(reflection.id, symbol);
        }
    }
    /**
     * Removes a reflection from the documentation. Can be used by plugins to filter reflections
     * out of the generated documentation. Has no effect if the reflection is not present in the
     * project.
     */
    removeReflection(reflection) {
        var _a;
        // Remove references
        for (const id of (_a = this.getReferenceGraph().get(reflection.id)) !== null && _a !== void 0 ? _a : []) {
            const ref = this.getReflectionById(id);
            if (ref) {
                this.removeReflection(ref);
            }
        }
        this.getReferenceGraph().delete(reflection.id);
        reflection.traverse((child) => (this.removeReflection(child), true));
        const parent = reflection.parent;
        parent === null || parent === void 0 ? void 0 : parent.traverse((child, property) => {
            if (child !== reflection) {
                return true; // Continue iteration
            }
            if (property === abstract_1.TraverseProperty.Children) {
                (0, utils_2.removeIfPresent)(parent.children, reflection);
            }
            else if (property === abstract_1.TraverseProperty.GetSignature) {
                delete parent.getSignature;
            }
            else if (property === abstract_1.TraverseProperty.IndexSignature) {
                delete parent.indexSignature;
            }
            else if (property === abstract_1.TraverseProperty.Parameters) {
                (0, utils_2.removeIfPresent)(reflection.parent.parameters, reflection);
            }
            else if (property === abstract_1.TraverseProperty.SetSignature) {
                delete parent.setSignature;
            }
            else if (property === abstract_1.TraverseProperty.Signatures) {
                (0, utils_2.removeIfPresent)(parent.signatures, reflection);
            }
            else if (property === abstract_1.TraverseProperty.TypeLiteral) {
                parent.type = new types_1.IntrinsicType("Object");
            }
            else if (property === abstract_1.TraverseProperty.TypeParameter) {
                (0, utils_2.removeIfPresent)(parent.typeParameters, reflection);
            }
            return false; // Stop iteration
        });
        const symbol = this.reflectionIdToSymbolMap.get(reflection.id);
        if (symbol &&
            this.symbolToReflectionIdMap.get(symbol) === reflection.id) {
            this.symbolToReflectionIdMap.delete(symbol);
        }
        delete this.reflections[reflection.id];
    }
    /**
     * Gets the reflection registered for the given reflection ID, or undefined if it is not present
     * in the project.
     */
    getReflectionById(id) {
        return this.reflections[id];
    }
    /**
     * Gets the reflection associated with the given symbol, if it exists.
     * @internal
     */
    getReflectionFromSymbol(symbol) {
        const id = this.symbolToReflectionIdMap.get(symbol);
        if (typeof id === "number") {
            return this.getReflectionById(id);
        }
    }
    /** @internal */
    getSymbolFromReflection(reflection) {
        return this.reflectionIdToSymbolMap.get(reflection.id);
    }
    getReferenceGraph() {
        var _a;
        if (!this.referenceGraph) {
            this.referenceGraph = new Map();
            for (const ref of Object.values(this.reflections)) {
                if (ref instanceof reference_1.ReferenceReflection) {
                    const target = ref.tryGetTargetReflection();
                    if (target) {
                        const refs = (_a = this.referenceGraph.get(target.id)) !== null && _a !== void 0 ? _a : [];
                        refs.push(ref.id);
                        this.referenceGraph.set(target.id, refs);
                    }
                }
            }
        }
        return this.referenceGraph;
    }
}
exports.ProjectReflection = ProjectReflection;
