"use strict";
// Copyright (c) Microsoft Corporation. All rights reserved. Licensed under the MIT license.
// See LICENSE in the project root for license information.
Object.defineProperty(exports, "__esModule", { value: true });
exports.MarkdownEmitter = void 0;
const tsdoc_1 = require("@microsoft/tsdoc");
const node_core_library_1 = require("@rushstack/node-core-library");
const IndentedWriter_1 = require("../utils/IndentedWriter");
/**
 * Renders MarkupElement content in the Markdown file format.
 * For more info:  https://en.wikipedia.org/wiki/Markdown
 */
class MarkdownEmitter {
    emit(stringBuilder, docNode, options) {
        const writer = new IndentedWriter_1.IndentedWriter(stringBuilder);
        const context = {
            writer,
            insideTable: false,
            boldRequested: false,
            italicRequested: false,
            writingBold: false,
            writingItalic: false,
            options
        };
        this.writeNode(docNode, context, false);
        writer.ensureNewLine(); // finish the last line
        return writer.toString();
    }
    getEscapedText(text) {
        const textWithBackslashes = text
            .replace(/\\/g, '\\\\') // first replace the escape character
            .replace(/[*#[\]_|`~]/g, (x) => '\\' + x) // then escape any special characters
            .replace(/---/g, '\\-\\-\\-') // hyphens only if it's 3 or more
            .replace(/&/g, '&amp;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;');
        return textWithBackslashes;
    }
    getTableEscapedText(text) {
        return text
            .replace(/&/g, '&amp;')
            .replace(/"/g, '&quot;')
            .replace(/</g, '&lt;')
            .replace(/>/g, '&gt;')
            .replace(/\|/g, '&#124;');
    }
    /**
     * @virtual
     */
    writeNode(docNode, context, docNodeSiblings) {
        const writer = context.writer;
        switch (docNode.kind) {
            case tsdoc_1.DocNodeKind.PlainText: {
                const docPlainText = docNode;
                this.writePlainText(docPlainText.text, context);
                break;
            }
            case tsdoc_1.DocNodeKind.HtmlStartTag:
            case tsdoc_1.DocNodeKind.HtmlEndTag: {
                const docHtmlTag = docNode;
                // write the HTML element verbatim into the output
                writer.write(docHtmlTag.emitAsHtml());
                break;
            }
            case tsdoc_1.DocNodeKind.CodeSpan: {
                const docCodeSpan = docNode;
                if (context.insideTable) {
                    writer.write('<code>');
                }
                else {
                    writer.write('`');
                }
                if (context.insideTable) {
                    const code = this.getTableEscapedText(docCodeSpan.code);
                    const parts = code.split(/\r?\n/g);
                    writer.write(parts.join('</code><br/><code>'));
                }
                else {
                    writer.write(docCodeSpan.code);
                }
                if (context.insideTable) {
                    writer.write('</code>');
                }
                else {
                    writer.write('`');
                }
                break;
            }
            case tsdoc_1.DocNodeKind.LinkTag: {
                const docLinkTag = docNode;
                if (docLinkTag.codeDestination) {
                    this.writeLinkTagWithCodeDestination(docLinkTag, context);
                }
                else if (docLinkTag.urlDestination) {
                    this.writeLinkTagWithUrlDestination(docLinkTag, context);
                }
                else if (docLinkTag.linkText) {
                    this.writePlainText(docLinkTag.linkText, context);
                }
                break;
            }
            case tsdoc_1.DocNodeKind.Paragraph: {
                const docParagraph = docNode;
                const trimmedParagraph = tsdoc_1.DocNodeTransforms.trimSpacesInParagraph(docParagraph);
                if (context.insideTable) {
                    if (docNodeSiblings) {
                        // This tentative write is necessary to avoid writing empty paragraph tags (i.e. `<p></p>`). At the
                        // time this code runs, we do not know whether the `writeNodes` call below will actually write
                        // anything. Thus, we want to only write a `<p>` tag (as well as eventually a corresponding
                        // `</p>` tag) if something ends up being written within the tags.
                        writer.writeTentative('<p>', '</p>', () => {
                            this.writeNodes(trimmedParagraph.nodes, context);
                        });
                    }
                    else {
                        // Special case:  If we are the only element inside this table cell, then we can omit the <p></p> container.
                        this.writeNodes(trimmedParagraph.nodes, context);
                    }
                }
                else {
                    this.writeNodes(trimmedParagraph.nodes, context);
                    writer.ensureNewLine();
                    writer.writeLine();
                }
                break;
            }
            case tsdoc_1.DocNodeKind.FencedCode: {
                const docFencedCode = docNode;
                writer.ensureNewLine();
                writer.write('```');
                writer.write(docFencedCode.language);
                writer.writeLine();
                writer.write(docFencedCode.code);
                writer.ensureNewLine();
                writer.writeLine('```');
                break;
            }
            case tsdoc_1.DocNodeKind.Section: {
                const docSection = docNode;
                this.writeNodes(docSection.nodes, context);
                break;
            }
            case tsdoc_1.DocNodeKind.SoftBreak: {
                if (!/^\s?$/.test(writer.peekLastCharacter())) {
                    writer.write(' ');
                }
                break;
            }
            case tsdoc_1.DocNodeKind.EscapedText: {
                const docEscapedText = docNode;
                this.writePlainText(docEscapedText.decodedText, context);
                break;
            }
            case tsdoc_1.DocNodeKind.ErrorText: {
                const docErrorText = docNode;
                this.writePlainText(docErrorText.text, context);
                break;
            }
            case tsdoc_1.DocNodeKind.InlineTag: {
                break;
            }
            case tsdoc_1.DocNodeKind.BlockTag: {
                const tagNode = docNode;
                console.warn('Unsupported block tag: ' + tagNode.tagName);
                break;
            }
            default:
                throw new node_core_library_1.InternalError('Unsupported DocNodeKind kind: ' + docNode.kind);
        }
    }
    /** @virtual */
    writeLinkTagWithCodeDestination(docLinkTag, context) {
        // The subclass needs to implement this to support code destinations
        throw new node_core_library_1.InternalError('writeLinkTagWithCodeDestination()');
    }
    /** @virtual */
    writeLinkTagWithUrlDestination(docLinkTag, context) {
        const linkText = docLinkTag.linkText !== undefined ? docLinkTag.linkText : docLinkTag.urlDestination;
        const encodedLinkText = this.getEscapedText(linkText.replace(/\s+/g, ' '));
        context.writer.write('[');
        context.writer.write(encodedLinkText);
        context.writer.write(`](${docLinkTag.urlDestination})`);
    }
    writePlainText(text, context) {
        const writer = context.writer;
        // split out the [ leading whitespace, content, trailing whitespace ]
        const parts = text.match(/^(\s*)(.*?)(\s*)$/) || [];
        writer.write(parts[1]); // write leading whitespace
        const middle = parts[2];
        if (middle !== '') {
            switch (writer.peekLastCharacter()) {
                case '':
                case '\n':
                case ' ':
                case '[':
                case '>':
                    // okay to put a symbol
                    break;
                default:
                    // This is no problem:        "**one** *two* **three**"
                    // But this is trouble:       "**one***two***three**"
                    // The most general solution: "**one**<!-- -->*two*<!-- -->**three**"
                    writer.write('<!-- -->');
                    break;
            }
            if (context.boldRequested) {
                writer.write('**');
            }
            if (context.italicRequested) {
                writer.write('_');
            }
            writer.write(this.getEscapedText(middle));
            if (context.italicRequested) {
                writer.write('_');
            }
            if (context.boldRequested) {
                writer.write('**');
            }
        }
        writer.write(parts[3]); // write trailing whitespace
    }
    writeNodes(docNodes, context) {
        for (const docNode of docNodes) {
            this.writeNode(docNode, context, docNodes.length > 1);
        }
    }
}
exports.MarkdownEmitter = MarkdownEmitter;
//# sourceMappingURL=MarkdownEmitter.js.map