"use strict";
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
Object.defineProperty(exports, "__esModule", { value: true });
exports.MdTableOfContentsProvider = exports.TableOfContents = void 0;
const logging_1 = require("./logging");
const slugify_1 = require("./slugify");
const range_1 = require("./types/range");
const textDocument_1 = require("./types/textDocument");
const dispose_1 = require("./util/dispose");
const workspaceCache_1 = require("./workspaceCache");
class TableOfContents {
    entries;
    static async create(parser, document, token) {
        const entries = await this.#buildToc(parser, document, token);
        return new TableOfContents(entries, parser.slugifier);
    }
    static async createForContainingDoc(parser, workspace, document, token) {
        const context = workspace.getContainingDocument?.((0, textDocument_1.getDocUri)(document));
        if (context) {
            const entries = (await Promise.all(Array.from(context.children, async (cell) => {
                const doc = await workspace.openMarkdownDocument(cell.uri);
                if (!doc || token.isCancellationRequested) {
                    return [];
                }
                return this.#buildToc(parser, doc, token);
            }))).flat();
            return new TableOfContents(entries, parser.slugifier);
        }
        return this.create(parser, document, token);
    }
    static async #buildToc(parser, document, token) {
        const docUri = (0, textDocument_1.getDocUri)(document);
        const toc = [];
        const tokens = await parser.tokenize(document);
        if (token.isCancellationRequested) {
            return [];
        }
        const existingSlugEntries = new Map();
        const headers = [];
        let currentHeader;
        for (const token of tokens) {
            switch (token.type) {
                case 'heading_open': {
                    currentHeader = { open: token, body: [] };
                    headers.push(currentHeader);
                    break;
                }
                case 'heading_close': {
                    currentHeader = undefined;
                    break;
                }
                default: {
                    currentHeader?.body.push(token);
                    break;
                }
            }
        }
        for (const { open, body } of headers) {
            if (!open.map) {
                continue;
            }
            const lineNumber = open.map[0];
            const line = (0, textDocument_1.getLine)(document, lineNumber);
            const bodyText = TableOfContents.#getHeaderTitleAsPlainText(body);
            let slug = parser.slugifier.fromHeading(bodyText);
            const existingSlugEntry = existingSlugEntries.get(slug.value);
            if (existingSlugEntry) {
                ++existingSlugEntry.count;
                slug = parser.slugifier.fromHeading(slug.value + '-' + existingSlugEntry.count);
            }
            else {
                existingSlugEntries.set(slug.value, { count: 0 });
            }
            const headerLocation = {
                uri: docUri.toString(),
                range: (0, range_1.makeRange)(lineNumber, 0, lineNumber, line.length)
            };
            const headerTextLocation = {
                uri: docUri.toString(),
                range: (0, range_1.makeRange)(lineNumber, line.match(/^#+\s*/)?.[0].length ?? 0, lineNumber, line.length - (line.match(/\s*#*$/)?.[0].length ?? 0))
            };
            toc.push({
                slug,
                text: line.replace(/^\s*#+\s*(.*?)(\s+#+)?$/, (_, word) => word.trim()),
                level: TableOfContents.#getHeaderLevel(open.markup),
                line: lineNumber,
                sectionLocation: headerLocation,
                headerLocation,
                headerTextLocation
            });
        }
        // Get full range of section
        return toc.map((entry, startIndex) => {
            let end = undefined;
            for (let i = startIndex + 1; i < toc.length; ++i) {
                if (toc[i].level <= entry.level) {
                    end = toc[i].line - 1;
                    break;
                }
            }
            const endLine = end ?? document.lineCount - 1;
            return {
                ...entry,
                sectionLocation: {
                    uri: docUri.toString(),
                    range: (0, range_1.makeRange)(entry.sectionLocation.range.start, { line: endLine, character: (0, textDocument_1.getLine)(document, endLine).length })
                }
            };
        });
    }
    static #getHeaderLevel(markup) {
        if (markup === '=') {
            return 1;
        }
        else if (markup === '-') {
            return 2;
        }
        else { // '#', '##', ...
            return markup.length;
        }
    }
    static #tokenToPlainText(token) {
        if (token.children) {
            return token.children.map(TableOfContents.#tokenToPlainText).join('');
        }
        switch (token.type) {
            case 'text':
            case 'emoji':
            case 'code_inline':
                return token.content;
            default:
                return '';
        }
    }
    static #getHeaderTitleAsPlainText(headerTitleParts) {
        return headerTitleParts
            .map(TableOfContents.#tokenToPlainText)
            .join('')
            .trim();
    }
    static empty = new TableOfContents([], slugify_1.githubSlugifier);
    #slugifier;
    constructor(entries, slugifier) {
        this.entries = entries;
        this.#slugifier = slugifier;
    }
    lookup(fragment) {
        const slug = this.#slugifier.fromHeading(fragment);
        return this.entries.find(entry => entry.slug.equals(slug));
    }
}
exports.TableOfContents = TableOfContents;
class MdTableOfContentsProvider extends dispose_1.Disposable {
    #cache;
    #parser;
    #workspace;
    #logger;
    constructor(parser, workspace, logger) {
        super();
        this.#parser = parser;
        this.#workspace = workspace;
        this.#logger = logger;
        this.#cache = this._register(new workspaceCache_1.MdDocumentInfoCache(workspace, (doc, token) => {
            this.#logger.log(logging_1.LogLevel.Debug, 'TableOfContentsProvider.create', { document: doc.uri, version: doc.version });
            return TableOfContents.create(parser, doc, token);
        }));
    }
    async get(resource) {
        return await this.#cache.get(resource) ?? TableOfContents.empty;
    }
    getForDocument(doc) {
        return this.#cache.getForDocument(doc);
    }
    getForContainingDoc(doc, token) {
        return TableOfContents.createForContainingDoc(this.#parser, this.#workspace, doc, token);
    }
}
exports.MdTableOfContentsProvider = MdTableOfContentsProvider;
//# sourceMappingURL=tableOfContents.js.map