"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.getExistingDefinitionBlock = exports.MdOrganizeLinkDefinitionProvider = void 0;
const range_1 = require("../types/range");
const textDocument_1 = require("../types/textDocument");
const string_1 = require("../util/string");
const documentLinks_1 = require("./documentLinks");
class MdOrganizeLinkDefinitionProvider {
    #linkProvider;
    constructor(linkProvider) {
        this.#linkProvider = linkProvider;
    }
    async getOrganizeLinkDefinitionEdits(doc, options, token) {
        const links = await this.#linkProvider.getLinks(doc);
        if (token.isCancellationRequested) {
            return [];
        }
        const definitions = links.links.filter(link => link.kind === documentLinks_1.MdLinkKind.Definition);
        if (!definitions.length) {
            return [];
        }
        const existingDefBlockRange = getExistingDefinitionBlock(doc, definitions);
        const edits = [];
        // First replace all inline definitions that are not the definition block
        for (const group of this.#getDefinitionBlockGroups(doc, definitions)) {
            if (!existingDefBlockRange || group.startLine < existingDefBlockRange.startLine) {
                // Don't replace trailing newline of last definition in group
                edits.push({
                    newText: '',
                    range: (0, range_1.makeRange)(group.startLine, 0, group.endLine, (0, textDocument_1.getLine)(doc, group.endLine).length),
                });
            }
        }
        // Then replace the actual block
        const sortedDefs = [...definitions];
        sortedDefs.sort((a, b) => a.ref.text.localeCompare(b.ref.text));
        const newDefs = sortedDefs
            .filter(def => {
            if (!options.removeUnused) {
                return true;
            }
            return links.links.some(link => {
                return link.kind === documentLinks_1.MdLinkKind.Link && link.href.kind === documentLinks_1.HrefKind.Reference && link.href.ref === def.ref.text;
            });
        });
        const defBlock = newDefs
            .map((def => `[${def.ref.text}]: ${def.source.hrefText}`))
            .join('\n');
        if (existingDefBlockRange) {
            // We still may need to insert a newline
            const hasLeadingWhiteSpace = existingDefBlockRange.startLine <= 0
                || (0, string_1.isEmptyOrWhitespace)((0, textDocument_1.getLine)(doc, existingDefBlockRange.startLine - 1));
            // See if we already have the expected definitions in order
            if (!edits.length && newDefs.length === definitions.length && definitions.every((def, i) => def.ref === newDefs[i].ref)) {
                return [];
            }
            edits.push({
                newText: (hasLeadingWhiteSpace ? '' : '\n') + defBlock,
                range: (0, range_1.makeRange)(existingDefBlockRange.startLine, 0, existingDefBlockRange.endLine, (0, textDocument_1.getLine)(doc, existingDefBlockRange.endLine).length)
            });
        }
        else {
            const line = this.#getLastNonWhitespaceLine(doc, definitions);
            edits.push({
                newText: (line === doc.lineCount - 1 ? '\n\n' : '\n') + defBlock,
                range: (0, range_1.makeRange)(line + 1, 0, doc.lineCount, 0),
            });
        }
        return edits;
    }
    *#getDefinitionBlockGroups(doc, definitions) {
        if (!definitions.length) {
            return;
        }
        let i = 0;
        const startDef = definitions[i];
        let endDef = startDef;
        for (; i < definitions.length - 1; ++i) {
            const nextDef = definitions[i + 1];
            if (nextDef.source.range.start.line === endDef.source.range.start.line + 1) {
                endDef = nextDef;
            }
            else {
                break;
            }
        }
        yield { startLine: startDef.source.range.start.line, endLine: endDef.source.range.start.line };
        yield* this.#getDefinitionBlockGroups(doc, definitions.slice(i + 1));
    }
    #getLastNonWhitespaceLine(doc, orderedDefinitions) {
        const lastDef = orderedDefinitions[orderedDefinitions.length - 1];
        const textAfter = doc.getText((0, range_1.makeRange)(lastDef.source.range.end.line + 1, 0, Infinity, 0));
        const lines = textAfter.split(/\r\n|\n/g);
        for (let i = lines.length - 1; i >= 0; --i) {
            if (!(0, string_1.isEmptyOrWhitespace)(lines[i])) {
                return lastDef.source.range.start.line + 1 + i;
            }
        }
        return lastDef.source.range.start.line;
    }
}
exports.MdOrganizeLinkDefinitionProvider = MdOrganizeLinkDefinitionProvider;
function getExistingDefinitionBlock(doc, orderedDefinitions) {
    if (!orderedDefinitions.length) {
        return undefined;
    }
    const lastDef = orderedDefinitions[orderedDefinitions.length - 1];
    const textAfter = doc.getText((0, range_1.makeRange)(lastDef.source.range.end.line + 1, 0, Infinity, 0));
    if ((0, string_1.isEmptyOrWhitespace)(textAfter)) {
        let prevDef = lastDef;
        for (let i = orderedDefinitions.length - 1; i >= 0; --i) {
            const def = orderedDefinitions[i];
            if (def.source.range.start.line < prevDef.source.range.start.line - 1) {
                break;
            }
            prevDef = def;
        }
        return {
            startLine: prevDef.source.range.start.line,
            endLine: lastDef.source.range.start.line
        };
    }
    return undefined;
}
exports.getExistingDefinitionBlock = getExistingDefinitionBlock;
//# sourceMappingURL=organizeLinkDefs.js.map