"use strict";
var __importDefault = (this && this.__importDefault) || function (mod) {
    return (mod && mod.__esModule) ? mod : { "default": mod };
};
Object.defineProperty(exports, "__esModule", { value: true });
exports.UpdateImportsProviderImpl = void 0;
const path_1 = __importDefault(require("path"));
const vscode_languageserver_1 = require("vscode-languageserver");
const documents_1 = require("../../../lib/documents");
const utils_1 = require("../../../utils");
const service_1 = require("../service");
const utils_2 = require("../utils");
const utils_3 = require("./utils");
class UpdateImportsProviderImpl {
    constructor(lsAndTsDocResolver, useCaseSensitiveFileNames) {
        this.lsAndTsDocResolver = lsAndTsDocResolver;
        this.getCanonicalFileName = (0, utils_1.createGetCanonicalFileName)(useCaseSensitiveFileNames);
    }
    async updateImports(fileRename) {
        // TODO does this handle folder moves/renames correctly? old/new path isn't a file then
        const oldPath = (0, utils_1.urlToPath)(fileRename.oldUri);
        const newPath = (0, utils_1.urlToPath)(fileRename.newUri);
        if (!oldPath || !newPath) {
            return null;
        }
        const services = [];
        await (0, service_1.forAllServices)((ls) => {
            services.push(ls);
        });
        const documentChanges = new Map();
        for (const service of services) {
            await this.updateImportForSingleService(oldPath, newPath, service, documentChanges);
        }
        return {
            documentChanges: Array.from(documentChanges.values())
        };
    }
    async updateImportForSingleService(oldPath, newPath, lsContainer, documentChanges) {
        const ls = lsContainer.getService();
        const program = ls.getProgram();
        if (!program) {
            return;
        }
        const canonicalOldPath = this.getCanonicalFileName((0, utils_1.normalizePath)(oldPath));
        const canonicalNewPath = this.getCanonicalFileName((0, utils_1.normalizePath)(newPath));
        const hasFile = program.getSourceFiles().some((sf) => {
            const normalizedFileName = this.getCanonicalFileName((0, utils_1.normalizePath)(sf.fileName));
            return (normalizedFileName.startsWith(canonicalOldPath) ||
                normalizedFileName.startsWith(canonicalNewPath));
        });
        if (!hasFile) {
            return;
        }
        const oldPathTsProgramCasing = ls.getProgram()?.getSourceFile(oldPath)?.fileName ?? oldPath;
        // `getEditsForFileRename` might take a while
        const fileChanges = ls
            .getEditsForFileRename(oldPathTsProgramCasing, newPath, {}, {})
            // Assumption: Updating imports will not create new files, and to make sure just filter those out
            // who - for whatever reason - might be new ones.
            .filter((change) => !change.isNewFile || change.fileName === oldPathTsProgramCasing);
        await this.lsAndTsDocResolver.updateSnapshotPath(oldPathTsProgramCasing, newPath);
        const editInOldPath = fileChanges.find((change) => change.fileName.startsWith(oldPathTsProgramCasing) &&
            (oldPathTsProgramCasing.includes(newPath) || !change.fileName.startsWith(newPath)));
        const editInNewPath = fileChanges.find((change) => change.fileName.startsWith(newPath) &&
            (newPath.includes(oldPathTsProgramCasing) ||
                !change.fileName.startsWith(oldPathTsProgramCasing)));
        const updateImportsChanges = fileChanges
            .filter((change) => {
            if ((0, utils_3.isKitTypePath)(change.fileName)) {
                // These types are generated from the route files, so we don't want to update them
                return false;
            }
            if (!editInOldPath || !editInNewPath) {
                return true;
            }
            // If both present, take the one that has more text changes to it (more likely to be the correct one)
            return editInOldPath.textChanges.length > editInNewPath.textChanges.length
                ? change !== editInNewPath
                : change !== editInOldPath;
        })
            .map((change) => {
            if (change === editInOldPath) {
                // The language service might want to do edits to the old path, not the new path -> rewire it.
                // If there is a better solution for this, please file a PR :)
                change.fileName = change.fileName.replace(oldPathTsProgramCasing, newPath);
            }
            if (path_1.default.basename(change.fileName).startsWith('+')) {
                // Filter out changes to './$type' imports for Kit route files,
                // you'll likely want these to stay as-is
                change.textChanges = change.textChanges.filter((textChange) => !(0, utils_3.isKitTypePath)(textChange.newText));
            }
            return change;
        });
        const docs = new utils_3.SnapshotMap(this.lsAndTsDocResolver, lsContainer);
        await Promise.all(updateImportsChanges.map(async (change) => {
            if (documentChanges.has(change.fileName)) {
                return;
            }
            const snapshot = await docs.retrieve(change.fileName);
            const edit = vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.OptionalVersionedTextDocumentIdentifier.create(snapshot.getURL(), null), change.textChanges.map((edit) => {
                const range = (0, documents_1.mapRangeToOriginal)(snapshot, (0, utils_2.convertRange)(snapshot, edit.span));
                return vscode_languageserver_1.TextEdit.replace(range, edit.newText);
            }));
            documentChanges.set(change.fileName, edit);
        }));
    }
}
exports.UpdateImportsProviderImpl = UpdateImportsProviderImpl;
//# sourceMappingURL=UpdateImportsProvider.js.map