"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.Reflection = exports.TraverseProperty = exports.ReflectionFlags = exports.ReflectionFlag = exports.resetReflectionID = void 0;
const assert_1 = require("assert");
const utils_1 = require("./utils");
const kind_1 = require("./kind");
/**
 * Holds all data models used by TypeDoc.
 *
 * The {@link BaseReflection} is base class of all reflection models. The subclass {@link ProjectReflection}
 * serves as the root container for the current project while {@link DeclarationReflection} instances
 * form the structure of the project. Most of the other classes in this namespace are referenced by this
 * two base classes.
 *
 * The models {@link NavigationItem} and {@link UrlMapping} are special as they are only used by the {@link Renderer}
 * while creating the final output.
 */
/**
 * Current reflection id.
 */
let REFLECTION_ID = 0;
/**
 * Reset the reflection id.
 *
 * Used by the test cases to ensure the reflection ids won't change between runs.
 */
function resetReflectionID() {
    REFLECTION_ID = 0;
}
exports.resetReflectionID = resetReflectionID;
var ReflectionFlag;
(function (ReflectionFlag) {
    ReflectionFlag[ReflectionFlag["None"] = 0] = "None";
    ReflectionFlag[ReflectionFlag["Private"] = 1] = "Private";
    ReflectionFlag[ReflectionFlag["Protected"] = 2] = "Protected";
    ReflectionFlag[ReflectionFlag["Public"] = 4] = "Public";
    ReflectionFlag[ReflectionFlag["Static"] = 8] = "Static";
    ReflectionFlag[ReflectionFlag["ExportAssignment"] = 16] = "ExportAssignment";
    ReflectionFlag[ReflectionFlag["External"] = 32] = "External";
    ReflectionFlag[ReflectionFlag["Optional"] = 64] = "Optional";
    ReflectionFlag[ReflectionFlag["DefaultValue"] = 128] = "DefaultValue";
    ReflectionFlag[ReflectionFlag["Rest"] = 256] = "Rest";
    ReflectionFlag[ReflectionFlag["Abstract"] = 512] = "Abstract";
    ReflectionFlag[ReflectionFlag["Const"] = 1024] = "Const";
    ReflectionFlag[ReflectionFlag["Let"] = 2048] = "Let";
    ReflectionFlag[ReflectionFlag["Readonly"] = 4096] = "Readonly";
})(ReflectionFlag = exports.ReflectionFlag || (exports.ReflectionFlag = {}));
const relevantFlags = [
    ReflectionFlag.Private,
    ReflectionFlag.Protected,
    ReflectionFlag.Static,
    ReflectionFlag.ExportAssignment,
    ReflectionFlag.Optional,
    ReflectionFlag.DefaultValue,
    ReflectionFlag.Rest,
    ReflectionFlag.Abstract,
    ReflectionFlag.Const,
    ReflectionFlag.Readonly,
];
/**
 * This must extend Array in order to work with Handlebar's each helper.
 */
class ReflectionFlags extends Array {
    constructor() {
        super(...arguments);
        this.flags = ReflectionFlag.None;
    }
    hasFlag(flag) {
        return (flag & this.flags) !== 0;
    }
    /**
     * Is this a private member?
     */
    get isPrivate() {
        return this.hasFlag(ReflectionFlag.Private);
    }
    /**
     * Is this a protected member?
     */
    get isProtected() {
        return this.hasFlag(ReflectionFlag.Protected);
    }
    /**
     * Is this a public member?
     */
    get isPublic() {
        return this.hasFlag(ReflectionFlag.Public);
    }
    /**
     * Is this a static member?
     */
    get isStatic() {
        return this.hasFlag(ReflectionFlag.Static);
    }
    /**
     * Is this a declaration from an external document?
     */
    get isExternal() {
        return this.hasFlag(ReflectionFlag.External);
    }
    /**
     * Whether this reflection is an optional component or not.
     *
     * Applies to function parameters and object members.
     */
    get isOptional() {
        return this.hasFlag(ReflectionFlag.Optional);
    }
    /**
     * Whether it's a rest parameter, like `foo(...params);`.
     */
    get isRest() {
        return this.hasFlag(ReflectionFlag.Rest);
    }
    get hasExportAssignment() {
        return this.hasFlag(ReflectionFlag.ExportAssignment);
    }
    get isAbstract() {
        return this.hasFlag(ReflectionFlag.Abstract);
    }
    get isConst() {
        return this.hasFlag(ReflectionFlag.Const);
    }
    get isReadonly() {
        return this.hasFlag(ReflectionFlag.Readonly);
    }
    setFlag(flag, set) {
        switch (flag) {
            case ReflectionFlag.Private:
                this.setSingleFlag(ReflectionFlag.Private, set);
                if (set) {
                    this.setFlag(ReflectionFlag.Protected, false);
                    this.setFlag(ReflectionFlag.Public, false);
                }
                break;
            case ReflectionFlag.Protected:
                this.setSingleFlag(ReflectionFlag.Protected, set);
                if (set) {
                    this.setFlag(ReflectionFlag.Private, false);
                    this.setFlag(ReflectionFlag.Public, false);
                }
                break;
            case ReflectionFlag.Public:
                this.setSingleFlag(ReflectionFlag.Public, set);
                if (set) {
                    this.setFlag(ReflectionFlag.Private, false);
                    this.setFlag(ReflectionFlag.Protected, false);
                }
                break;
            default:
                this.setSingleFlag(flag, set);
        }
    }
    setSingleFlag(flag, set) {
        const name = ReflectionFlag[flag].replace(/(.)([A-Z])/g, (_m, a, b) => a + " " + b.toLowerCase());
        if (!set && this.hasFlag(flag)) {
            if (relevantFlags.includes(flag)) {
                this.splice(this.indexOf(name), 1);
            }
            this.flags ^= flag;
        }
        else if (set && !this.hasFlag(flag)) {
            if (relevantFlags.includes(flag)) {
                this.push(name);
            }
            this.flags |= flag;
        }
    }
}
exports.ReflectionFlags = ReflectionFlags;
var TraverseProperty;
(function (TraverseProperty) {
    TraverseProperty[TraverseProperty["Children"] = 0] = "Children";
    TraverseProperty[TraverseProperty["Parameters"] = 1] = "Parameters";
    TraverseProperty[TraverseProperty["TypeLiteral"] = 2] = "TypeLiteral";
    TraverseProperty[TraverseProperty["TypeParameter"] = 3] = "TypeParameter";
    TraverseProperty[TraverseProperty["Signatures"] = 4] = "Signatures";
    TraverseProperty[TraverseProperty["IndexSignature"] = 5] = "IndexSignature";
    TraverseProperty[TraverseProperty["GetSignature"] = 6] = "GetSignature";
    TraverseProperty[TraverseProperty["SetSignature"] = 7] = "SetSignature";
})(TraverseProperty = exports.TraverseProperty || (exports.TraverseProperty = {}));
/**
 * Base class for all reflection classes.
 *
 * While generating a documentation, TypeDoc generates an instance of {@link ProjectReflection}
 * as the root for all reflections within the project. All other reflections are represented
 * by the {@link DeclarationReflection} class.
 *
 * This base class exposes the basic properties one may use to traverse the reflection tree.
 * You can use the {@link ContainerReflection.children} and {@link parent} properties to walk the tree. The {@link groups} property
 * contains a list of all children grouped and sorted for being rendered.
 */
class Reflection {
    /**
     * Create a new BaseReflection instance.
     */
    constructor(name, kind, parent) {
        this.flags = new ReflectionFlags();
        this.id = REFLECTION_ID++;
        this.parent = parent;
        this.name = name;
        this.originalName = name;
        this.kind = kind;
        // If our parent is external, we are too.
        if (parent === null || parent === void 0 ? void 0 : parent.flags.isExternal) {
            this.setFlag(ReflectionFlag.External);
        }
    }
    get project() {
        if (this.isProject())
            return this;
        (0, assert_1.ok)(this.parent, "Tried to get the project on a reflection not in a project");
        return this.parent.project;
    }
    /**
     * Test whether this reflection is of the given kind.
     */
    kindOf(kind) {
        const kindArray = Array.isArray(kind) ? kind : [kind];
        return kindArray.some((kind) => (this.kind & kind) !== 0);
    }
    /**
     * Return the full name of this reflection. Intended for use in debugging. For log messages
     * intended to be displayed to the user for them to fix, prefer {@link getFriendlyFullName} instead.
     *
     * The full name contains the name of this reflection and the names of all parent reflections.
     *
     * @param separator  Separator used to join the names of the reflections.
     * @returns The full name of this reflection.
     */
    getFullName(separator = ".") {
        if (this.parent && !this.parent.isProject()) {
            return this.parent.getFullName(separator) + separator + this.name;
        }
        else {
            return this.name;
        }
    }
    /**
     * Return the full name of this reflection, with signature names dropped if possible without
     * introducing ambiguity in the name.
     */
    getFriendlyFullName() {
        if (this.parent && !this.parent.isProject()) {
            if (this.kindOf(kind_1.ReflectionKind.ConstructorSignature |
                kind_1.ReflectionKind.CallSignature |
                kind_1.ReflectionKind.GetSignature |
                kind_1.ReflectionKind.SetSignature)) {
                return this.parent.getFriendlyFullName();
            }
            return this.parent.getFriendlyFullName() + "." + this.name;
        }
        else {
            return this.name;
        }
    }
    /**
     * Set a flag on this reflection.
     */
    setFlag(flag, value = true) {
        this.flags.setFlag(flag, value);
    }
    /**
     * Return an url safe alias for this reflection.
     */
    getAlias() {
        if (!this._alias) {
            let alias = this.name.replace(/[^a-z0-9]/gi, "_");
            if (alias === "") {
                alias = "reflection-" + this.id;
            }
            let target = this;
            while (target.parent &&
                !target.parent.isProject() &&
                !target.hasOwnDocument) {
                target = target.parent;
            }
            if (!target._aliases) {
                target._aliases = new Map();
            }
            let suffix = "";
            if (!target._aliases.has(alias)) {
                target._aliases.set(alias, 1);
            }
            else {
                const count = target._aliases.get(alias);
                suffix = "-" + count.toString();
                target._aliases.set(alias, count + 1);
            }
            alias += suffix;
            this._alias = alias;
        }
        return this._alias;
    }
    /**
     * Has this reflection a visible comment?
     *
     * @returns TRUE when this reflection has a visible comment.
     */
    hasComment() {
        return this.comment ? this.comment.hasVisibleComponent() : false;
    }
    hasGetterOrSetter() {
        return false;
    }
    /**
     * Return a child by its name.
     *
     * @param names The name hierarchy of the child to look for.
     * @returns The found child or undefined.
     */
    getChildByName(arg) {
        const names = Array.isArray(arg)
            ? arg
            : (0, utils_1.splitUnquotedString)(arg, ".");
        const name = names[0];
        let result;
        this.traverse((child) => {
            if (child.name === name) {
                if (names.length <= 1) {
                    result = child;
                }
                else {
                    result = child.getChildByName(names.slice(1));
                }
                return false;
            }
            return true;
        });
        return result;
    }
    /**
     * Return whether this reflection is the root / project reflection.
     */
    isProject() {
        return false;
    }
    /**
     * Try to find a reflection by its name.
     *
     * @return The found reflection or null.
     */
    findReflectionByName(arg) {
        const names = Array.isArray(arg)
            ? arg
            : (0, utils_1.splitUnquotedString)(arg, ".");
        const reflection = this.getChildByName(names);
        if (reflection) {
            return reflection;
        }
        else if (this.parent) {
            return this.parent.findReflectionByName(names);
        }
    }
    /**
     * Traverse all potential child reflections of this reflection.
     *
     * The given callback will be invoked for all children, signatures and type parameters
     * attached to this reflection.
     *
     * @param callback  The callback function that should be applied for each child reflection.
     */
    traverse(_callback) {
        // do nothing here, overridden by child classes
    }
    /**
     * Return a string representation of this reflection.
     */
    toString() {
        return kind_1.ReflectionKind[this.kind] + " " + this.name;
    }
    /**
     * Return a string representation of this reflection and all of its children.
     *
     * @param indent  Used internally to indent child reflections.
     */
    toStringHierarchy(indent = "") {
        const lines = [indent + this.toString()];
        indent += "  ";
        this.traverse((child) => {
            lines.push(child.toStringHierarchy(indent));
            return true;
        });
        return lines.join("\n");
    }
}
exports.Reflection = Reflection;
