/*---------------------------------------------------------------------------------------------
 *  Copyright (c) Microsoft Corporation. All rights reserved.
 *  Licensed under the MIT License. See License.txt in the project root for license information.
 *--------------------------------------------------------------------------------------------*/
import * as path from 'node:path';
import { isExcludedPath } from '../config.js';
import { HrefKind } from '../types/documentLink.js';
import { getDocUri } from '../types/textDocument.js';
import { WorkspaceEditBuilder } from '../util/editBuilder.js';
import { removeNewUriExtIfNeeded, resolveInternalDocumentLink } from '../util/mdLinks.js';
import { isParentDir, isSameResource, looksLikeMarkdownUri } from '../util/path.js';
import { URI, Utils } from '../util/vscodeUri.js';
import { MdReferenceKind } from './references.js';
import { getLinkRenameEdit, getLinkRenameText } from './rename.js';
export class MdFileRenameProvider {
    #config;
    #workspace;
    #linkCache;
    #referencesProvider;
    constructor(config, workspace, linkCache, referencesProvider) {
        this.#config = config;
        this.#workspace = workspace;
        this.#linkCache = linkCache;
        this.#referencesProvider = referencesProvider;
    }
    async getRenameFilesInWorkspaceEdit(edits, token) {
        const builder = new 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 !== HrefKind.Internal || link.source.hrefText.startsWith('#')) {
                    continue;
                }
                // Update links to the moved dir
                if (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.hrefText.startsWith('..') && isParentDir(edit.newUri, docUri)) {
                    // Resolve the link relative to the old file path
                    const oldDocUri = docUri.with({
                        path: Utils.joinPath(edit.oldUri, path.posix.relative(edit.newUri.path, docUri.path)).path
                    });
                    const oldLink = resolveInternalDocumentLink(oldDocUri, link.source.hrefText, this.#workspace);
                    if (oldLink) {
                        let newPathText;
                        if (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 = Utils.dirname(oldDocUri);
                            newPathText = './' + path.posix.relative(rootDir.path, oldLink.resource.path);
                        }
                        else {
                            const rootDir = Utils.dirname(docUri);
                            newPathText = path.posix.relative(rootDir.path, oldLink.resource.path);
                        }
                        const replacementPath = encodeURI(newPathText);
                        if (replacementPath !== link.source.hrefPathText) {
                            const { range, newText } = 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 (!looksLikeMarkdownUri(this.#config, edit.newUri)) {
            return false;
        }
        if (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 !== 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 = 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 (isSameResource(edit.oldUri, oldLink.resource) || isParentDir(edit.oldUri, oldLink.resource)) {
                oldLink = { resource: Utils.joinPath(edit.newUri, path.posix.relative(edit.oldUri.path, oldLink.resource.path)), linkFragment: oldLink.linkFragment };
                break;
            }
        }
        return this.#addLinkRenameEdit(getDocUri(doc), link, oldLink.resource, builder);
    }
    /**
     * Update links across the workspace for the new file name
     */
    async #addEditsForReferencesToFile(edit, builder, token) {
        if (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 === MdReferenceKind.Link) {
                if (this.#addLinkRenameEdit(URI.parse(ref.location.uri), ref.link, edit.newUri, builder)) {
                    didParticipate = true;
                }
            }
        }
        return didParticipate;
    }
    #addLinkRenameEdit(doc, link, newUri, builder) {
        if (link.href.kind !== HrefKind.Internal) {
            return false;
        }
        const newFilePath = removeNewUriExtIfNeeded(this.#config, link.href, newUri);
        const newLinkText = getLinkRenameText(this.#workspace, link.source, newFilePath, link.source.hrefText.startsWith('.'));
        if (typeof newLinkText === 'string') {
            const { range, newText } = getLinkRenameEdit(link, newLinkText);
            builder.replace(doc, range, newText);
            return true;
        }
        return false;
    }
}
//# sourceMappingURL=fileRename.js.map