"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.MdFileRenameProvider = void 0;
/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
const path = require("path");
const vscode_uri_1 = require("vscode-uri");
const config_1 = require("../config");
const textDocument_1 = require("../types/textDocument");
const dispose_1 = require("../util/dispose");
const editBuilder_1 = require("../util/editBuilder");
const file_1 = require("../util/file");
const path_1 = require("../util/path");
const documentLinks_1 = require("./documentLinks");
const references_1 = require("./references");
const rename_1 = require("./rename");
class MdFileRenameProvider extends dispose_1.Disposable {
    #config;
    #workspace;
    #linkCache;
    #referencesProvider;
    constructor(config, workspace, linkCache, referencesProvider) {
        super();
        this.#config = config;
        this.#workspace = workspace;
        this.#linkCache = linkCache;
        this.#referencesProvider = referencesProvider;
    }
    async getRenameFilesInWorkspaceEdit(edits, token) {
        const builder = new editBuilder_1.WorkspaceEditBuilder();
        const participatingRenames = [];
        for (const edit of edits) {
            const stat = await this.#workspace.stat(edit.newUri);
            if (token.isCancellationRequested) {
                return undefined;
            }
            if (await (stat?.isDirectory ? this.#addDirectoryRenameEdits(edit, builder, token) : this.#addSingleFileRenameEdits(edit, edits, builder, token))) {
                participatingRenames.push(edit);
            }
            if (token.isCancellationRequested) {
                return undefined;
            }
        }
        return { participatingRenames, edit: builder.getEdit() };
    }
    async #addSingleFileRenameEdits(edit, allEdits, builder, token) {
        let didParticipate = false;
        // Update all references to the file
        if (await this.#addEditsForReferencesToFile(edit, builder, token)) {
            didParticipate = true;
        }
        if (token.isCancellationRequested) {
            return false;
        }
        // If the file moved was markdown, we also need to update links in the file itself
        if (await this.#tryAddEditsInSelf(edit, allEdits, builder)) {
            didParticipate = true;
        }
        return didParticipate;
    }
    async #addDirectoryRenameEdits(edit, builder, token) {
        // First update every link that points to something in the moved dir
        const allLinksInWorkspace = await this.#linkCache.entries();
        if (token.isCancellationRequested) {
            return false;
        }
        let didParticipate = false;
        for (const [docUri, links] of allLinksInWorkspace) {
            for (const link of links) {
                if (link.href.kind !== documentLinks_1.HrefKind.Internal || link.source.hrefText.startsWith('#')) {
                    continue;
                }
                // Update links to the moved dir
                if ((0, path_1.isParentDir)(edit.oldUri, link.href.path)) {
                    const relative = path.posix.relative(edit.oldUri.path, link.href.path.path);
                    const newUri = edit.newUri.with({
                        path: path.posix.join(edit.newUri.path, relative)
                    });
                    if (this.#addLinkRenameEdit(docUri, link, newUri, builder)) {
                        didParticipate = true;
                        continue;
                    }
                }
                // If the link was within a file in the moved dir but traversed out of it, we also need to update the path
                if (link.source.pathText.startsWith('..') && (0, path_1.isParentDir)(edit.newUri, docUri)) {
                    // Resolve the link relative to the old file path
                    const oldDocUri = docUri.with({
                        path: vscode_uri_1.Utils.joinPath(edit.oldUri, path.posix.relative(edit.newUri.path, docUri.path)).path
                    });
                    const oldLink = (0, documentLinks_1.resolveInternalDocumentLink)(oldDocUri, link.source.hrefText, this.#workspace);
                    if (oldLink) {
                        let newPathText;
                        if ((0, path_1.isParentDir)(edit.oldUri, oldLink.resource)) {
                            // The link still points within the directory being moved.
                            // This means we just need to normalize the path it in case it was referencing any old names.
                            const rootDir = vscode_uri_1.Utils.dirname(oldDocUri);
                            newPathText = './' + path.posix.relative(rootDir.path, oldLink.resource.path);
                        }
                        else {
                            const rootDir = vscode_uri_1.Utils.dirname(docUri);
                            newPathText = path.posix.relative(rootDir.path, oldLink.resource.path);
                        }
                        const replacementPath = encodeURI(newPathText);
                        if (replacementPath !== link.source.pathText) {
                            const { range, newText } = (0, rename_1.getLinkRenameEdit)(link, replacementPath);
                            builder.replace(docUri, range, newText);
                            didParticipate = true;
                        }
                    }
                }
            }
        }
        return didParticipate;
    }
    /**
     * Try to add edits for when a markdown file has been renamed.
     * In this case we also need to update links within the file.
     */
    async #tryAddEditsInSelf(edit, allEdits, builder) {
        if (!(0, file_1.looksLikeMarkdownUri)(this.#config, edit.newUri)) {
            return false;
        }
        if ((0, config_1.isExcludedPath)(this.#config, edit.newUri)) {
            return false;
        }
        const doc = await this.#workspace.openMarkdownDocument(edit.newUri);
        if (!doc) {
            return false;
        }
        const links = (await this.#linkCache.getForDocs([doc]))[0];
        let didParticipate = false;
        for (const link of links) {
            if (await this.#addEditsForLinksInSelf(doc, link, edit, allEdits, builder)) {
                didParticipate = true;
            }
        }
        return didParticipate;
    }
    async #addEditsForLinksInSelf(doc, link, edit, allEdits, builder) {
        if (link.href.kind !== documentLinks_1.HrefKind.Internal) {
            return false;
        }
        if (link.source.hrefText.startsWith('#')) {
            // No rewrite needed as we are referencing the current doc implicitly
            return false;
        }
        if (link.source.hrefText.startsWith('/')) {
            // We likely don't need to update anything since an absolute path is used
            return false;
        }
        // Resolve the link relative to the old file path
        let oldLink = (0, documentLinks_1.resolveInternalDocumentLink)(edit.oldUri, link.source.hrefText, this.#workspace);
        if (!oldLink) {
            return false;
        }
        // See if the old link was effected by one of the renames
        for (const edit of allEdits) {
            if (edit.oldUri.toString() === oldLink.resource.toString() || (0, path_1.isParentDir)(edit.oldUri, oldLink.resource)) {
                oldLink = { resource: vscode_uri_1.Utils.joinPath(edit.newUri, path.posix.relative(edit.oldUri.path, oldLink.resource.path)), linkFragment: oldLink.linkFragment };
                break;
            }
        }
        return this.#addLinkRenameEdit((0, textDocument_1.getDocUri)(doc), link, oldLink.resource, builder);
    }
    /**
     * Update links across the workspace for the new file name
     */
    async #addEditsForReferencesToFile(edit, builder, token) {
        if ((0, config_1.isExcludedPath)(this.#config, edit.newUri)) {
            return false;
        }
        const refs = await this.#referencesProvider.getReferencesToFileInWorkspace(edit.oldUri, token);
        if (token.isCancellationRequested) {
            return false;
        }
        let didParticipate = false;
        for (const ref of refs) {
            if (ref.kind === references_1.MdReferenceKind.Link) {
                if (this.#addLinkRenameEdit(vscode_uri_1.URI.parse(ref.location.uri), ref.link, edit.newUri, builder)) {
                    didParticipate = true;
                }
            }
        }
        return didParticipate;
    }
    #addLinkRenameEdit(doc, link, newUri, builder) {
        if (link.href.kind !== documentLinks_1.HrefKind.Internal) {
            return false;
        }
        let newFilePath = newUri;
        if (this.#shouldRemoveFileExtensionForRename(link.href, newUri)) {
            const editExt = vscode_uri_1.Utils.extname(newUri);
            newFilePath = newUri.with({
                path: newUri.path.slice(0, newUri.path.length - editExt.length)
            });
        }
        const newLinkText = (0, rename_1.getLinkRenameText)(this.#workspace, link.source, newFilePath, link.source.pathText.startsWith('.'));
        if (typeof newLinkText === 'string') {
            const { range, newText } = (0, rename_1.getLinkRenameEdit)(link, newLinkText);
            builder.replace(doc, range, newText);
            return true;
        }
        return false;
    }
    #shouldRemoveFileExtensionForRename(originalHref, newUri) {
        if (!(0, file_1.looksLikeMarkdownUri)(this.#config, newUri)) {
            return false;
        }
        if (this.#config.preferredMdPathExtensionStyle === config_1.PreferredMdPathExtensionStyle.removeExtension) {
            return true;
        }
        // If the original markdown link did not use a file extension, remove ours too
        return !vscode_uri_1.Utils.extname(originalHref.path);
    }
}
exports.MdFileRenameProvider = MdFileRenameProvider;
//# sourceMappingURL=fileRename.js.map