"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    var desc = Object.getOwnPropertyDescriptor(m, k);
    if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) {
      desc = { enumerable: true, get: function() { return m[k]; } };
    }
    Object.defineProperty(o, k2, desc);
}) : (function(o, m, k, k2) {
    if (k2 === undefined) k2 = k;
    o[k2] = m[k];
}));
var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) {
    Object.defineProperty(o, "default", { enumerable: true, value: v });
}) : function(o, v) {
    o["default"] = v;
});
var __importStar = (this && this.__importStar) || function (mod) {
    if (mod && mod.__esModule) return mod;
    var result = {};
    if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k);
    __setModuleDefault(result, mod);
    return result;
};
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.YamlDocumenter = void 0;
const path = __importStar(require("path"));
const yaml = require("js-yaml");
const node_core_library_1 = require("@rushstack/node-core-library");
const tsdoc_1 = require("@microsoft/tsdoc");
const api_extractor_model_1 = require("@microsoft/api-extractor-model");
const DeclarationReference_1 = require("@microsoft/tsdoc/lib-commonjs/beta/DeclarationReference");
const Utilities_1 = require("../utils/Utilities");
const CustomMarkdownEmitter_1 = require("../markdown/CustomMarkdownEmitter");
const ToSdpConvertHelper_1 = require("../utils/ToSdpConvertHelper");
const typescript_schema_json_1 = __importDefault(require("../yaml/typescript.schema.json"));
const yamlApiSchema = node_core_library_1.JsonSchema.fromLoadedObject(typescript_schema_json_1.default);
var FlattenMode;
(function (FlattenMode) {
    /** Include entries for nested namespaces and non-namespace children. */
    FlattenMode[FlattenMode["NestedNamespacesAndChildren"] = 0] = "NestedNamespacesAndChildren";
    /** Include entries for nested namespaces only. */
    FlattenMode[FlattenMode["NestedNamespacesOnly"] = 1] = "NestedNamespacesOnly";
    /** Include entries for non-namespace immediate children. */
    FlattenMode[FlattenMode["ImmediateChildren"] = 2] = "ImmediateChildren";
    /** Include entries for nested non-namespace children. */
    FlattenMode[FlattenMode["NestedChildren"] = 3] = "NestedChildren";
})(FlattenMode || (FlattenMode = {}));
/**
 * Writes documentation in the Universal Reference YAML file format, as defined by typescript.schema.json.
 */
class YamlDocumenter {
    constructor(apiModel, newDocfxNamespaces = false, yamlFormat = 'sdp') {
        this._apiModel = apiModel;
        this.newDocfxNamespaces = newDocfxNamespaces;
        this._yamlFormat = yamlFormat;
        this._markdownEmitter = new CustomMarkdownEmitter_1.CustomMarkdownEmitter(this._apiModel);
        this._apiItemsByCanonicalReference = new Map();
        this._initApiItems();
    }
    /** @virtual */
    generateFiles(outputFolder) {
        console.log();
        this._deleteOldOutputFiles(outputFolder);
        for (const apiPackage of this._apiModel.packages) {
            console.log(`Writing ${apiPackage.name} package`);
            this._visitApiItems(outputFolder, apiPackage, undefined);
        }
        if (this._yamlFormat === 'sdp') {
            (0, ToSdpConvertHelper_1.convertUDPYamlToSDP)(outputFolder);
        }
        this._writeTocFile(outputFolder, this._apiModel.packages);
    }
    /** @virtual */
    onGetTocRoot() {
        return {
            name: 'SharePoint Framework reference',
            href: '~/overview/sharepoint.md',
            items: []
        };
    }
    /** @virtual */
    onCustomizeYamlItem(yamlItem) {
        // virtual
        // (overridden by child class)
    }
    _visitApiItems(outputFolder, apiItem, parentYamlFile) {
        let savedYamlReferences;
        if (!this._shouldEmbed(apiItem.kind)) {
            savedYamlReferences = this._yamlReferences;
            this._yamlReferences = undefined;
        }
        const yamlItem = this._generateYamlItem(apiItem);
        if (!yamlItem) {
            return false;
        }
        this.onCustomizeYamlItem(yamlItem);
        if (this._shouldEmbed(apiItem.kind)) {
            if (!parentYamlFile) {
                throw new node_core_library_1.InternalError('Missing file context');
            }
            parentYamlFile.items.push(yamlItem);
        }
        else {
            const newYamlFile = {
                items: []
            };
            newYamlFile.items.push(yamlItem);
            const children = this._getLogicalChildren(apiItem);
            for (const child of children) {
                if (child instanceof api_extractor_model_1.ApiDocumentedItem) {
                    if (this._visitApiItems(outputFolder, child, newYamlFile)) {
                        if (!yamlItem.children) {
                            yamlItem.children = [];
                        }
                        yamlItem.children.push(this._getUid(child));
                    }
                }
            }
            if (this._yamlReferences && this._yamlReferences.references.length > 0) {
                newYamlFile.references = this._yamlReferences.references;
            }
            this._yamlReferences = savedYamlReferences;
            const yamlFilePath = this._getYamlFilePath(outputFolder, apiItem);
            if (apiItem.kind === api_extractor_model_1.ApiItemKind.Package) {
                console.log('Writing ' + yamlFilePath);
            }
            this._writeYamlFile(newYamlFile, yamlFilePath, 'UniversalReference', yamlApiSchema);
            if (parentYamlFile) {
                // References should be recorded in the parent YAML file with the local name of the embedded item.
                // This avoids unnecessary repetition when listing items inside of a namespace.
                this._recordYamlReference(this._ensureYamlReferences(), this._getUid(apiItem), this._getYamlItemName(apiItem, {
                    includeNamespace: !this.newDocfxNamespaces,
                    includeSignature: true
                }), this._getYamlItemName(apiItem, { includeNamespace: true, includeSignature: true }));
            }
        }
        return true;
    }
    _getLogicalChildren(apiItem) {
        const children = [];
        if (apiItem.kind === api_extractor_model_1.ApiItemKind.Package) {
            // Skip over the entry point, since it's not part of the documentation hierarchy
            this._flattenNamespaces(apiItem.members[0].members, children, this.newDocfxNamespaces ? FlattenMode.NestedNamespacesAndChildren : FlattenMode.NestedChildren);
        }
        else {
            this._flattenNamespaces(apiItem.members, children, this.newDocfxNamespaces ? FlattenMode.ImmediateChildren : FlattenMode.NestedChildren);
        }
        return children;
    }
    // Flattens nested namespaces into top level entries so that the following:
    //   namespace X { export namespace Y { export namespace Z { } }
    // Is represented as:
    //   - X
    //   - X.Y
    //   - X.Y.Z
    _flattenNamespaces(items, childrenOut, mode) {
        let hasNonNamespaceChildren = false;
        for (const item of items) {
            if (item.kind === api_extractor_model_1.ApiItemKind.Namespace) {
                switch (mode) {
                    case FlattenMode.NestedChildren:
                        // Include children of namespaces, but not the namespaces themselves. This matches existing legacy behavior.
                        this._flattenNamespaces(item.members, childrenOut, FlattenMode.NestedChildren);
                        break;
                    case FlattenMode.NestedNamespacesOnly:
                    case FlattenMode.NestedNamespacesAndChildren:
                        // At any level, always include a nested namespace if it has non-namespace children, but do not include its
                        // non-namespace children in the result.
                        // Record the offset at which the namespace is added in case we need to remove it later.
                        const index = childrenOut.length;
                        childrenOut.push(item);
                        if (!this._flattenNamespaces(item.members, childrenOut, FlattenMode.NestedNamespacesOnly)) {
                            // This namespace had no non-namespace children, remove it.
                            childrenOut.splice(index, 1);
                        }
                        break;
                }
            }
            else if (this._shouldInclude(item.kind)) {
                switch (mode) {
                    case FlattenMode.NestedChildren:
                    case FlattenMode.NestedNamespacesAndChildren:
                    case FlattenMode.ImmediateChildren:
                        // At the top level, include non-namespace children as well.
                        childrenOut.push(item);
                        break;
                }
                hasNonNamespaceChildren = true;
            }
        }
        return hasNonNamespaceChildren;
    }
    /**
     * Write the table of contents
     */
    _writeTocFile(outputFolder, apiItems) {
        const tocFile = this.buildYamlTocFile(apiItems);
        const tocFilePath = path.join(outputFolder, 'toc.yml');
        console.log('Writing ' + tocFilePath);
        this._writeYamlFile(tocFile, tocFilePath, '', undefined);
    }
    /** @virtual */
    buildYamlTocFile(apiItems) {
        const tocFile = {
            items: []
        };
        const rootItem = this.onGetTocRoot();
        tocFile.items.push(rootItem);
        rootItem.items.push(...this._buildTocItems(apiItems));
        return tocFile;
    }
    _buildTocItems(apiItems) {
        const tocItems = [];
        for (const apiItem of apiItems) {
            let tocItem;
            if (apiItem.kind === api_extractor_model_1.ApiItemKind.Namespace && !this.newDocfxNamespaces) {
                tocItem = {
                    name: this._getTocItemName(apiItem)
                };
            }
            else {
                if (this._shouldEmbed(apiItem.kind)) {
                    // Don't generate table of contents items for embedded definitions
                    continue;
                }
                tocItem = {
                    name: this._getTocItemName(apiItem),
                    uid: this._getUid(apiItem)
                };
            }
            tocItems.push(tocItem);
            const children = this._getLogicalChildren(apiItem);
            const childItems = this._buildTocItems(children);
            if (childItems.length > 0) {
                tocItem.items = childItems;
            }
        }
        return tocItems;
    }
    /** @virtual */
    _getTocItemName(apiItem) {
        let name;
        if (apiItem.kind === api_extractor_model_1.ApiItemKind.Package) {
            name = node_core_library_1.PackageName.getUnscopedName(apiItem.displayName);
        }
        else {
            name = this._getYamlItemName(apiItem);
        }
        if (name === apiItem.displayName && apiItem.getMergedSiblings().length > 1) {
            name += ` (${apiItem.kind})`;
        }
        return name;
    }
    _shouldEmbed(apiItemKind) {
        switch (apiItemKind) {
            case api_extractor_model_1.ApiItemKind.Class:
            case api_extractor_model_1.ApiItemKind.Package:
            case api_extractor_model_1.ApiItemKind.Interface:
            case api_extractor_model_1.ApiItemKind.Enum:
            case api_extractor_model_1.ApiItemKind.TypeAlias:
                return false;
            case api_extractor_model_1.ApiItemKind.Namespace:
                return !this.newDocfxNamespaces;
        }
        return true;
    }
    _shouldInclude(apiItemKind) {
        // Filter out known items that are not yet supported
        switch (apiItemKind) {
            case api_extractor_model_1.ApiItemKind.CallSignature:
            case api_extractor_model_1.ApiItemKind.ConstructSignature:
            case api_extractor_model_1.ApiItemKind.IndexSignature:
                return false;
        }
        return true;
    }
    _generateYamlItem(apiItem) {
        // Filter out known items that are not yet supported
        if (!this._shouldInclude(apiItem.kind)) {
            return undefined;
        }
        const uid = this._getUidObject(apiItem);
        const yamlItem = {
            uid: uid.toString()
        };
        if (apiItem.tsdocComment) {
            const tsdocComment = apiItem.tsdocComment;
            if (tsdocComment.summarySection) {
                const summary = this._renderMarkdown(tsdocComment.summarySection, apiItem);
                if (summary) {
                    yamlItem.summary = summary;
                }
            }
            if (tsdocComment.remarksBlock) {
                const remarks = this._renderMarkdown(tsdocComment.remarksBlock.content, apiItem);
                if (remarks) {
                    yamlItem.remarks = remarks;
                }
            }
            if (tsdocComment) {
                // Write the @example blocks
                const exampleBlocks = tsdocComment.customBlocks.filter((x) => x.blockTag.tagNameWithUpperCase === tsdoc_1.StandardTags.example.tagNameWithUpperCase);
                for (const exampleBlock of exampleBlocks) {
                    const example = this._renderMarkdown(exampleBlock.content, apiItem);
                    if (example) {
                        yamlItem.example = [...(yamlItem.example || []), example];
                    }
                }
            }
            if (tsdocComment.deprecatedBlock) {
                const deprecatedMessage = this._renderMarkdown(tsdocComment.deprecatedBlock.content, apiItem);
                if (deprecatedMessage.length > 0) {
                    yamlItem.deprecated = { content: deprecatedMessage };
                }
            }
        }
        if (api_extractor_model_1.ApiReleaseTagMixin.isBaseClassOf(apiItem)) {
            if (apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Alpha || apiItem.releaseTag === api_extractor_model_1.ReleaseTag.Beta) {
                yamlItem.isPreview = true;
            }
        }
        yamlItem.name = this._getYamlItemName(apiItem, {
            includeSignature: true,
            includeNamespace: !this.newDocfxNamespaces
        });
        yamlItem.fullName = this._getYamlItemName(apiItem, { includeSignature: true, includeNamespace: true });
        yamlItem.langs = ['typeScript'];
        // Add the namespace of the item if it is contained in one.
        // Do not add the namespace parent of a namespace as they are flattened in the documentation.
        if (apiItem.kind !== api_extractor_model_1.ApiItemKind.Namespace &&
            apiItem.parent &&
            apiItem.parent.kind === api_extractor_model_1.ApiItemKind.Namespace &&
            this.newDocfxNamespaces) {
            yamlItem.namespace = apiItem.parent.canonicalReference.toString();
        }
        switch (apiItem.kind) {
            case api_extractor_model_1.ApiItemKind.Enum:
                yamlItem.type = 'enum';
                break;
            case api_extractor_model_1.ApiItemKind.EnumMember:
                yamlItem.type = 'field';
                const enumMember = apiItem;
                if (enumMember.initializerExcerpt && enumMember.initializerExcerpt.text.length > 0) {
                    yamlItem.numericValue = enumMember.initializerExcerpt.text;
                }
                break;
            case api_extractor_model_1.ApiItemKind.Class:
                yamlItem.type = 'class';
                this._populateYamlClassOrInterface(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Interface:
                yamlItem.type = 'interface';
                this._populateYamlClassOrInterface(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Method:
            case api_extractor_model_1.ApiItemKind.MethodSignature:
                yamlItem.type = 'method';
                this._populateYamlFunctionLike(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Constructor:
                yamlItem.type = 'constructor';
                this._populateYamlFunctionLike(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Package:
                yamlItem.type = 'package';
                break;
            case api_extractor_model_1.ApiItemKind.Namespace:
                yamlItem.type = 'namespace';
                break;
            case api_extractor_model_1.ApiItemKind.Property:
            case api_extractor_model_1.ApiItemKind.PropertySignature:
                const apiProperty = apiItem;
                if (apiProperty.isEventProperty) {
                    yamlItem.type = 'event';
                }
                else {
                    yamlItem.type = 'property';
                }
                this._populateYamlProperty(uid, yamlItem, apiProperty);
                break;
            case api_extractor_model_1.ApiItemKind.Function:
                yamlItem.type = 'function';
                this._populateYamlFunctionLike(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.Variable:
                yamlItem.type = 'variable';
                this._populateYamlVariable(uid, yamlItem, apiItem);
                break;
            case api_extractor_model_1.ApiItemKind.TypeAlias:
                yamlItem.type = 'typealias';
                this._populateYamlTypeAlias(uid, yamlItem, apiItem);
                break;
            default:
                throw new Error('Unimplemented item kind: ' + apiItem.kind);
        }
        if (apiItem.kind !== api_extractor_model_1.ApiItemKind.Package && !this._shouldEmbed(apiItem.kind)) {
            const associatedPackage = apiItem.getAssociatedPackage();
            if (!associatedPackage) {
                throw new Error('Unable to determine associated package for ' + apiItem.displayName);
            }
            yamlItem.package = this._getUid(associatedPackage);
        }
        return yamlItem;
    }
    _populateYamlTypeParameters(contextUid, apiItem) {
        const typeParameters = [];
        for (const apiTypeParameter of apiItem.typeParameters) {
            const typeParameter = {
                id: apiTypeParameter.name
            };
            if (apiTypeParameter.tsdocTypeParamBlock) {
                typeParameter.description = this._renderMarkdown(apiTypeParameter.tsdocTypeParamBlock.content, apiItem);
            }
            if (!apiTypeParameter.constraintExcerpt.isEmpty) {
                typeParameter.type = [this._renderType(contextUid, apiTypeParameter.constraintExcerpt)];
            }
            typeParameters.push(typeParameter);
        }
        return typeParameters;
    }
    _populateYamlClassOrInterface(uid, yamlItem, apiItem) {
        if (apiItem instanceof api_extractor_model_1.ApiClass) {
            if (apiItem.extendsType) {
                yamlItem.extends = [this._renderType(uid, apiItem.extendsType.excerpt)];
                yamlItem.inheritance = this._renderInheritance(uid, [apiItem.extendsType]);
            }
            if (apiItem.implementsTypes.length > 0) {
                yamlItem.implements = [];
                for (const implementsType of apiItem.implementsTypes) {
                    yamlItem.implements.push(this._renderType(uid, implementsType.excerpt));
                }
            }
        }
        else if (apiItem instanceof api_extractor_model_1.ApiInterface) {
            if (apiItem.extendsTypes.length > 0) {
                yamlItem.extends = [];
                for (const extendsType of apiItem.extendsTypes) {
                    yamlItem.extends.push(this._renderType(uid, extendsType.excerpt));
                }
                yamlItem.inheritance = this._renderInheritance(uid, apiItem.extendsTypes);
            }
            const typeParameters = this._populateYamlTypeParameters(uid, apiItem);
            if (typeParameters.length) {
                yamlItem.syntax = { typeParameters };
            }
        }
        if (apiItem.tsdocComment) {
            if (apiItem.tsdocComment.modifierTagSet.isSealed()) {
                let sealedMessage;
                if (apiItem.kind === api_extractor_model_1.ApiItemKind.Class) {
                    sealedMessage = 'This class is marked as `@sealed`. Subclasses should not extend it.';
                }
                else {
                    sealedMessage = 'This interface is marked as `@sealed`. Other interfaces should not extend it.';
                }
                if (!yamlItem.remarks) {
                    yamlItem.remarks = sealedMessage;
                }
                else {
                    yamlItem.remarks = sealedMessage + '\n\n' + yamlItem.remarks;
                }
            }
        }
    }
    _populateYamlFunctionLike(uid, yamlItem, apiItem) {
        const syntax = {
            content: apiItem.getExcerptWithModifiers()
        };
        yamlItem.syntax = syntax;
        if (api_extractor_model_1.ApiReturnTypeMixin.isBaseClassOf(apiItem)) {
            const returnType = this._renderType(uid, apiItem.returnTypeExcerpt);
            let returnDescription = '';
            if (apiItem.tsdocComment && apiItem.tsdocComment.returnsBlock) {
                returnDescription = this._renderMarkdown(apiItem.tsdocComment.returnsBlock.content, apiItem);
                // temporary workaround for people who mistakenly add a hyphen, e.g. "@returns - blah"
                returnDescription = returnDescription.replace(/^\s*-\s+/, '');
            }
            if (returnType || returnDescription) {
                syntax.return = {
                    type: [returnType],
                    description: returnDescription
                };
            }
        }
        const parameters = [];
        for (const apiParameter of apiItem.parameters) {
            let parameterDescription = '';
            if (apiParameter.tsdocParamBlock) {
                parameterDescription = this._renderMarkdown(apiParameter.tsdocParamBlock.content, apiItem);
            }
            parameters.push({
                id: apiParameter.name,
                description: parameterDescription,
                type: [this._renderType(uid, apiParameter.parameterTypeExcerpt)],
                optional: apiParameter.isOptional
            });
        }
        if (parameters.length) {
            syntax.parameters = parameters;
        }
        if (api_extractor_model_1.ApiTypeParameterListMixin.isBaseClassOf(apiItem)) {
            const typeParameters = this._populateYamlTypeParameters(uid, apiItem);
            if (typeParameters.length) {
                syntax.typeParameters = typeParameters;
            }
        }
    }
    _populateYamlProperty(uid, yamlItem, apiItem) {
        const syntax = {
            content: apiItem.getExcerptWithModifiers()
        };
        yamlItem.syntax = syntax;
        if (apiItem.propertyTypeExcerpt.text) {
            syntax.return = {
                type: [this._renderType(uid, apiItem.propertyTypeExcerpt)]
            };
        }
    }
    _populateYamlVariable(uid, yamlItem, apiItem) {
        const syntax = {
            content: apiItem.getExcerptWithModifiers()
        };
        yamlItem.syntax = syntax;
        if (apiItem.variableTypeExcerpt.text) {
            syntax.return = {
                type: [this._renderType(uid, apiItem.variableTypeExcerpt)]
            };
        }
    }
    _populateYamlTypeAlias(uid, yamlItem, apiItem) {
        const syntax = {
            content: apiItem.getExcerptWithModifiers()
        };
        yamlItem.syntax = syntax;
        const typeParameters = this._populateYamlTypeParameters(uid, apiItem);
        if (typeParameters.length) {
            syntax.typeParameters = typeParameters;
        }
        if (apiItem.typeExcerpt.text) {
            syntax.return = {
                type: [this._renderType(uid, apiItem.typeExcerpt)]
            };
        }
    }
    _renderMarkdown(docSection, contextApiItem) {
        const stringBuilder = new tsdoc_1.StringBuilder();
        this._markdownEmitter.emit(stringBuilder, docSection, {
            contextApiItem,
            onGetFilenameForApiItem: (apiItem) => {
                // NOTE: GitHub's markdown renderer does not resolve relative hyperlinks correctly
                // unless they start with "./" or "../".
                // To ensure the xref is properly escaped, we first encode the entire xref
                // to handle escaping of reserved characters. Then we must replace '#' and '?'
                // characters so that they are not interpreted as a querystring or hash.
                // We must also backslash-escape unbalanced `(` and `)` characters as the
                // markdown spec insists that they are only valid when balanced. To reduce
                // the overhead we only support balanced parenthesis with a depth of 1.
                return encodeURI(`xref:${this._getUid(apiItem)}`)
                    .replace(/[#?]/g, (s) => encodeURIComponent(s))
                    .replace(/(\([^(]*\))|[()]/g, (s, balanced) => balanced || '\\' + s);
            }
        });
        return stringBuilder.toString().trim();
    }
    _writeYamlFile(dataObject, filePath, yamlMimeType, schema) {
        node_core_library_1.JsonFile.validateNoUndefinedMembers(dataObject);
        let stringified = yaml.safeDump(dataObject, {
            lineWidth: 120
        });
        if (yamlMimeType) {
            stringified = `### YamlMime:${yamlMimeType}\n` + stringified;
        }
        node_core_library_1.FileSystem.writeFile(filePath, stringified, {
            convertLineEndings: node_core_library_1.NewlineKind.CrLf,
            ensureFolderExists: true
        });
        if (schema) {
            schema.validateObject(dataObject, filePath);
        }
    }
    /**
     * Calculate the DocFX "uid" for the ApiItem
     * Example:  `node-core-library!JsonFile#load`
     */
    _getUid(apiItem) {
        return this._getUidObject(apiItem).toString();
    }
    _getUidObject(apiItem) {
        return apiItem.canonicalReference;
    }
    /**
     * Initialize the _apiItemsByCanonicalReference data structure.
     */
    _initApiItems() {
        this._initApiItemsRecursive(this._apiModel);
    }
    /**
     * Helper for _initApiItems()
     */
    _initApiItemsRecursive(apiItem) {
        if (apiItem.canonicalReference && !apiItem.canonicalReference.isEmpty) {
            this._apiItemsByCanonicalReference.set(apiItem.canonicalReference.toString(), apiItem);
        }
        // Recurse container members
        if (api_extractor_model_1.ApiItemContainerMixin.isBaseClassOf(apiItem)) {
            for (const apiMember of apiItem.members) {
                this._initApiItemsRecursive(apiMember);
            }
        }
    }
    _ensureYamlReferences() {
        if (!this._yamlReferences) {
            this._yamlReferences = {
                references: [],
                typeNameToUid: new Map(),
                uidTypeReferenceCounters: new Map()
            };
        }
        return this._yamlReferences;
    }
    _renderInheritance(contextUid, heritageTypes) {
        const result = [];
        for (const heritageType of heritageTypes) {
            const type = this._renderType(contextUid, heritageType.excerpt);
            const yamlInheritance = { type };
            const apiItem = this._apiItemsByCanonicalReference.get(type);
            if (apiItem) {
                if (apiItem instanceof api_extractor_model_1.ApiClass) {
                    if (apiItem.extendsType) {
                        yamlInheritance.inheritance = this._renderInheritance(this._getUidObject(apiItem), [
                            apiItem.extendsType
                        ]);
                    }
                }
                else if (apiItem instanceof api_extractor_model_1.ApiInterface) {
                    if (apiItem.extendsTypes.length > 0) {
                        yamlInheritance.inheritance = this._renderInheritance(this._getUidObject(apiItem), apiItem.extendsTypes);
                    }
                }
            }
            result.push(yamlInheritance);
        }
        return result;
    }
    _renderType(contextUid, typeExcerpt) {
        const excerptTokens = [...typeExcerpt.spannedTokens]; // copy the read-only array
        if (excerptTokens.length === 0) {
            return '';
        }
        // Remove the last token if it consists only of whitespace
        const lastToken = excerptTokens[excerptTokens.length - 1];
        if (lastToken.kind === api_extractor_model_1.ExcerptTokenKind.Content && !lastToken.text.trim()) {
            excerptTokens.pop();
            if (excerptTokens.length === 0) {
                return '';
            }
        }
        const typeName = typeExcerpt.text.trim();
        // If there are no references to be used for a complex type, return the type name.
        if (!excerptTokens.some((tok) => tok.kind === api_extractor_model_1.ExcerptTokenKind.Reference && !!tok.canonicalReference)) {
            return typeName;
        }
        const yamlReferences = this._ensureYamlReferences();
        const existingUid = yamlReferences.typeNameToUid.get(typeName);
        // If this type has already been referenced for the current file, return its uid.
        if (existingUid) {
            return existingUid;
        }
        // If the excerpt consists of a single reference token, record the reference.
        if (excerptTokens.length === 1 &&
            excerptTokens[0].kind === api_extractor_model_1.ExcerptTokenKind.Reference &&
            excerptTokens[0].canonicalReference) {
            const excerptRef = excerptTokens[0].canonicalReference.toString();
            const apiItem = this._apiItemsByCanonicalReference.get(excerptRef);
            return this._recordYamlReference(yamlReferences, excerptTokens[0].canonicalReference.toString(), apiItem ? this._getYamlItemName(apiItem) : typeName, apiItem ? this._getYamlItemName(apiItem, { includeNamespace: true }) : typeName);
        }
        // Otherwise, the type is complex and consists of one or more reference tokens. Record a reference
        // and return its uid.
        const baseUid = contextUid.withMeaning(undefined).withOverloadIndex(undefined).toString();
        // Keep track of the count for the base uid (without meaning or overload index) to ensure
        // that each complex type reference is unique.
        const counter = yamlReferences.uidTypeReferenceCounters.get(baseUid) || 0;
        yamlReferences.uidTypeReferenceCounters.set(baseUid, counter + 1);
        const uid = contextUid
            .addNavigationStep(DeclarationReference_1.Navigation.Locals, `${counter}`)
            .withMeaning(DeclarationReference_1.Meaning.ComplexType)
            .withOverloadIndex(undefined)
            .toString();
        return this._recordYamlReference(yamlReferences, uid, typeName, typeName, excerptTokens);
    }
    _recordYamlReference(yamlReferences, uid, name, fullName, excerptTokens) {
        if (yamlReferences.references.some((ref) => ref.uid === uid)) {
            return uid;
        }
        // Fill in the reference spec from the excerpt.
        const specs = [];
        if (excerptTokens) {
            for (const token of excerptTokens) {
                if (token.kind === api_extractor_model_1.ExcerptTokenKind.Reference) {
                    const spec = {};
                    const specUid = token.canonicalReference && token.canonicalReference.toString();
                    const apiItem = specUid
                        ? this._apiItemsByCanonicalReference.get(specUid)
                        : undefined;
                    if (specUid) {
                        spec.uid = specUid;
                    }
                    spec.name = token.text;
                    spec.fullName = apiItem
                        ? apiItem.getScopedNameWithinPackage()
                        : token.canonicalReference
                            ? token.canonicalReference
                                .withSource(undefined)
                                .withMeaning(undefined)
                                .withOverloadIndex(undefined)
                                .toString()
                            : token.text;
                    specs.push(spec);
                }
                else {
                    specs.push({
                        name: token.text,
                        fullName: token.text
                    });
                }
            }
        }
        const yamlReference = { uid };
        if (specs.length > 0) {
            yamlReference.name = specs
                .map((s) => s.name)
                .join('')
                .trim();
            yamlReference.fullName = specs
                .map((s) => s.fullName || s.name)
                .join('')
                .trim();
            yamlReference['spec.typeScript'] = specs;
        }
        else {
            if (name !== uid) {
                yamlReference.name = name;
            }
            if (fullName !== uid && fullName !== name) {
                yamlReference.fullName = fullName;
            }
        }
        yamlReferences.references.push(yamlReference);
        return uid;
    }
    _getYamlItemName(apiItem, options = {}) {
        const { includeSignature, includeNamespace } = options;
        const baseName = includeSignature ? Utilities_1.Utilities.getConciseSignature(apiItem) : apiItem.displayName;
        if ((includeNamespace || apiItem.kind === api_extractor_model_1.ApiItemKind.Namespace) &&
            apiItem.parent &&
            apiItem.parent.kind === api_extractor_model_1.ApiItemKind.Namespace) {
            // If the immediate parent is a namespace, then add the namespaces to the name.  For example:
            //
            //   // Name: "N1"
            //   export namespace N1 {
            //     // Name: "N1.N2"
            //     export namespace N2 {
            //       // Name: "N1.N2.f(x,y)"
            //       export function f(x: string, y: string): string {
            //         return x + y;
            //       }
            //
            //
            //       // Name: "N1.N2.C"
            //       export class C {
            //         // Name: "member(x,y)"  <===========
            //         public member(x: string, y: string): string {
            //           return x + y;
            //         }
            //       }
            //     }
            //   }
            //
            // In the above example, "member(x, y)" does not appear as "N1.N2.C.member(x,y)" because YamlDocumenter
            // embeds this entry in the web page for "N1.N2.C", so the container is obvious.  Whereas "N1.N2.f(x,y)"
            // needs to be qualified because the DocFX template doesn't make pages for namespaces.  Instead, they get
            // flattened into the package's page.
            const nameParts = [baseName];
            for (let current = apiItem.parent; current; current = current.parent) {
                if (current.kind !== api_extractor_model_1.ApiItemKind.Namespace) {
                    break;
                }
                nameParts.unshift(current.displayName);
            }
            return nameParts.join('.');
        }
        else {
            return baseName;
        }
    }
    _getYamlFilePath(outputFolder, apiItem) {
        let result = '';
        for (const current of apiItem.getHierarchy()) {
            switch (current.kind) {
                case api_extractor_model_1.ApiItemKind.Model:
                case api_extractor_model_1.ApiItemKind.EntryPoint:
                    break;
                case api_extractor_model_1.ApiItemKind.Package:
                    result += Utilities_1.Utilities.getSafeFilenameForName(node_core_library_1.PackageName.getUnscopedName(current.displayName));
                    break;
                default:
                    if (current.parent && current.parent.kind === api_extractor_model_1.ApiItemKind.EntryPoint) {
                        result += '/';
                    }
                    else {
                        result += '.';
                    }
                    result += Utilities_1.Utilities.getSafeFilenameForName(current.displayName);
                    break;
            }
        }
        let disambiguator = '';
        if (apiItem.getMergedSiblings().length > 1) {
            disambiguator = `-${apiItem.kind.toLowerCase()}`;
        }
        return path.join(outputFolder, result + disambiguator + '.yml');
    }
    _deleteOldOutputFiles(outputFolder) {
        console.log('Deleting old output from ' + outputFolder);
        node_core_library_1.FileSystem.ensureEmptyFolder(outputFolder);
    }
}
exports.YamlDocumenter = YamlDocumenter;
//# sourceMappingURL=YamlDocumenter.js.map