"use strict";
Object.defineProperty(exports, "__esModule", { value: true });
exports.SveltePlugin = void 0;
const path_1 = require("path");
const vscode_languageserver_1 = require("vscode-languageserver");
const importPackage_1 = require("../../importPackage");
const logger_1 = require("../../logger");
const utils_1 = require("../../utils");
const getCodeActions_1 = require("./features/getCodeActions");
const getCompletions_1 = require("./features/getCompletions");
const getDiagnostics_1 = require("./features/getDiagnostics");
const getHoverInfo_1 = require("./features/getHoverInfo");
const getSelectionRanges_1 = require("./features/getSelectionRanges");
const SvelteDocument_1 = require("./SvelteDocument");
class SveltePlugin {
    constructor(configManager) {
        this.configManager = configManager;
        this.__name = 'svelte';
        this.docManager = new Map();
    }
    async getCodeLens(document) {
        if (!this.featureEnabled('runesLegacyModeCodeLens'))
            return null;
        if (!document.isSvelte5)
            return null;
        const doc = await this.getSvelteDoc(document);
        try {
            const result = await doc.getCompiled();
            // @ts-ignore
            const runes = result.metadata.runes;
            return [
                {
                    range: {
                        start: { line: 0, character: 0 },
                        end: { line: 0, character: 0 }
                    },
                    command: {
                        title: runes ? 'Runes mode' : 'Legacy mode',
                        command: 'svelte.openLink',
                        arguments: ['https://svelte.dev/docs/svelte/legacy-overview']
                    }
                }
            ];
        }
        catch (e) {
            // show an empty code lens in case of a compilation error to prevent code from jumping around
            return [
                {
                    range: {
                        start: { line: 0, character: 0 },
                        end: { line: 0, character: 0 }
                    },
                    command: {
                        title: '',
                        command: ''
                    }
                }
            ];
        }
    }
    async getDiagnostics(document, cancellationToken) {
        if (!this.featureEnabled('diagnostics') || !this.configManager.getIsTrusted()) {
            return [];
        }
        return (0, getDiagnostics_1.getDiagnostics)(document, await this.getSvelteDoc(document), this.configManager.getConfig().svelte.compilerWarnings, cancellationToken);
    }
    async getDiagnosticsForPullMode(document, previousResultId, cancellationToken) {
        if (!this.featureEnabled('diagnostics')) {
            return {
                kind: 'full',
                items: []
            };
        }
        const resultId = document.version.toString();
        if (previousResultId === resultId) {
            return {
                kind: 'unchanged',
                resultId
            };
        }
        const diagnostics = await this.getDiagnostics(document, cancellationToken);
        return {
            kind: 'full',
            items: diagnostics,
            resultId
        };
    }
    async getCompiledResult(document) {
        try {
            const svelteDoc = await this.getSvelteDoc(document);
            const options = { generate: 'dom' }; // 'client' in Svelte 5
            // @ts-ignore Svelte 5 only; we gotta write it like this else Svelte 4 fails on unknown key
            if (document.config?.compilerOptions?.experimental) {
                // @ts-ignore Svelte 5 only
                options.experimental = document.config.compilerOptions.experimental;
            }
            return await svelteDoc.getCompiledWith(options);
        }
        catch (error) {
            return null;
        }
    }
    async formatDocument(document, options) {
        if (!this.featureEnabled('format')) {
            return [];
        }
        const filePath = document.getFilePath();
        /**
         * Prettier v2 can't use v3 plugins and vice versa. Therefore, we need to check
         * which version of prettier is used in the workspace and import the correct
         * version of the Svelte plugin. If user uses Prettier < 3 and has no Svelte plugin
         * then fall back to our built-in versions which are both v3 and compatible with
         * each other.
         */
        const importFittingPrettier = async () => {
            const getConfig = async (p) => {
                // Try resolving the config through prettier and fall back to possible editor config
                return this.configManager.getMergedPrettierConfig(await p.resolveConfig(filePath, {
                    editorconfig: this.configManager.getPrettierConfigLoadingOptions()
                }), 
                // Be defensive here because IDEs other than VSCode might not have these settings
                options && {
                    tabWidth: options.tabSize,
                    useTabs: !options.insertSpaces
                });
            };
            const prettier1 = (0, importPackage_1.importPrettier)(filePath);
            const config1 = await getConfig(prettier1);
            const resolvedPlugins1 = resolvePlugins(config1.plugins);
            const pluginLoaded = await hasSveltePluginLoaded(prettier1, resolvedPlugins1);
            if (Number(prettier1.version[0]) >= 3 || pluginLoaded) {
                // plugin loaded, or referenced in user config as a plugin, or same version as our fallback version -> ok
                return {
                    prettier: prettier1,
                    config: config1,
                    isFallback: false,
                    resolvedPlugins: resolvedPlugins1
                };
            }
            // User either only has Plugin or incompatible Prettier major version installed or none
            // -> load our fallback version
            const prettier2 = (0, importPackage_1.importPrettier)(__dirname);
            const config2 = await getConfig(prettier2);
            const resolvedPlugins2 = resolvePlugins(config2.plugins);
            return {
                prettier: prettier2,
                config: config2,
                isFallback: true,
                resolvedPlugins: resolvedPlugins2
            };
        };
        const { prettier, config, isFallback, resolvedPlugins } = await importFittingPrettier();
        // If user has prettier-plugin-svelte 1.x, then remove `options` from the sort
        // order or else it will throw a config error (`options` was not present back then).
        if (config?.svelteSortOrder &&
            (0, importPackage_1.getPackageInfo)('prettier-plugin-svelte', filePath)?.version.major < 2) {
            config.svelteSortOrder = config.svelteSortOrder
                .replace('-options', '')
                .replace('options-', '');
        }
        // If user has prettier-plugin-svelte 3.x, then add `options` from the sort
        // order or else it will throw a config error (now required).
        if (config?.svelteSortOrder &&
            !config.svelteSortOrder.includes('options') &&
            config.svelteSortOrder !== 'none' &&
            (0, importPackage_1.getPackageInfo)('prettier-plugin-svelte', filePath)?.version.major >= 3) {
            config.svelteSortOrder = 'options-' + config.svelteSortOrder;
        }
        // Take .prettierignore into account
        const fileInfo = await prettier.getFileInfo(filePath, {
            ignorePath: this.configManager.getPrettierConfig()?.ignorePath ?? '.prettierignore',
            // Sapper places stuff within src/node_modules, we want to format that, too
            withNodeModules: true
        });
        if (fileInfo.ignored) {
            logger_1.Logger.debug('File is ignored, formatting skipped');
            return [];
        }
        if (isFallback || !(await hasSveltePluginLoaded(prettier, resolvedPlugins))) {
            // If the user uses Svelte 5 but doesn't have prettier installed, we need to provide
            // the compiler path to the plugin so it can use its parser method; else it will crash.
            const svelteCompilerInfo = (0, importPackage_1.getPackageInfo)('svelte', filePath);
            if (svelteCompilerInfo.version.major >= 5) {
                config.svelte5CompilerPath = svelteCompilerInfo.path + '/compiler';
            }
        }
        // Prettier v3 format is async, v2 is not
        const formattedCode = await prettier.format(document.getText(), {
            ...config,
            filepath: filePath,
            plugins: Array.from(new Set([...resolvedPlugins, ...(await getSveltePlugin(resolvedPlugins))])),
            parser: 'svelte'
        });
        return document.getText() === formattedCode
            ? []
            : [
                vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(document.positionAt(0), document.positionAt(document.getTextLength())), formattedCode)
            ];
        async function getSveltePlugin(plugins = []) {
            // Only provide our version of the svelte plugin if the user doesn't have one in
            // the workspace already. If we did it, Prettier would - for some reason - use
            // the workspace version for parsing and the extension version for printing,
            // which could crash if the contract of the parser output changed.
            return !isFallback && (await hasSveltePluginLoaded(prettier, plugins))
                ? []
                : [require.resolve('prettier-plugin-svelte')];
        }
        async function hasSveltePluginLoaded(p, plugins = []) {
            if (plugins.some(SveltePlugin.isPrettierPluginSvelte))
                return true;
            if (Number(p.version[0]) >= 3)
                return false; // Prettier version 3 has removed the "search plugins" feature
            // Prettier v3 getSupportInfo is async, v2 is not
            const info = await p.getSupportInfo();
            return info.languages.some((l) => l.name === 'svelte');
        }
        function resolvePlugins(plugins) {
            return (plugins ?? []).map(resolvePlugin).filter(utils_1.isNotNullOrUndefined);
        }
        function resolvePlugin(plugin) {
            // https://github.com/prettier/prettier-vscode/blob/160b0e92d88fa19003dce2745d5ab8c67e886a04/src/ModuleResolver.ts#L373
            if (typeof plugin != 'string' || (0, path_1.isAbsolute)(plugin) || plugin.startsWith('.')) {
                return plugin;
            }
            try {
                return require.resolve(plugin, {
                    paths: [filePath]
                });
            }
            catch (error) {
                logger_1.Logger.error(`failed to resolve plugin ${plugin} with error:\n`, error);
            }
        }
    }
    static isPrettierPluginSvelte(plugin) {
        if (typeof plugin === 'string') {
            return plugin.includes('prettier-plugin-svelte');
        }
        return !!plugin?.languages?.find((l) => l.name === 'svelte');
    }
    async getCompletions(document, position, _, cancellationToken) {
        if (!this.featureEnabled('completions')) {
            return null;
        }
        const svelteDoc = await this.getSvelteDoc(document);
        if (cancellationToken?.isCancellationRequested) {
            return null;
        }
        return (0, getCompletions_1.getCompletions)(document, svelteDoc, position);
    }
    async doHover(document, position) {
        if (!this.featureEnabled('hover')) {
            return null;
        }
        return (0, getHoverInfo_1.getHoverInfo)(document, await this.getSvelteDoc(document), position);
    }
    async getCodeActions(document, range, context, cancellationToken) {
        if (!this.featureEnabled('codeActions')) {
            return [];
        }
        const svelteDoc = await this.getSvelteDoc(document);
        if (cancellationToken?.isCancellationRequested) {
            return [];
        }
        try {
            return (0, getCodeActions_1.getCodeActions)(svelteDoc, range, context);
        }
        catch (error) {
            return [];
        }
    }
    async executeCommand(document, command, args) {
        if (command === 'migrate_to_svelte_5') {
            return this.migrate(document);
        }
        if (!this.featureEnabled('codeActions')) {
            return null;
        }
        const svelteDoc = await this.getSvelteDoc(document);
        try {
            return (0, getCodeActions_1.executeCommand)(svelteDoc, command, args);
        }
        catch (error) {
            return null;
        }
    }
    migrate(document) {
        try {
            const compiler = document.compiler;
            if (!compiler.migrate) {
                return 'Your installed Svelte version does not support migration';
            }
            const migrated = compiler.migrate(document.getText(), {
                filename: document.getFilePath() ?? undefined
            });
            return {
                changes: {
                    [document.uri]: [
                        vscode_languageserver_1.TextEdit.replace(vscode_languageserver_1.Range.create(document.positionAt(0), document.positionAt(document.getTextLength())), migrated.code)
                    ]
                }
            };
        }
        catch (error) {
            logger_1.Logger.error('Failed to migrate Svelte file', error);
            return error?.message ?? 'Failed to migrate Svelte file';
        }
    }
    async getSelectionRange(document, position) {
        if (!this.featureEnabled('selectionRange')) {
            return null;
        }
        const svelteDoc = await this.getSvelteDoc(document);
        return (0, getSelectionRanges_1.getSelectionRange)(svelteDoc, position);
    }
    featureEnabled(feature) {
        return (this.configManager.enabled('svelte.enable') &&
            this.configManager.enabled(`svelte.${feature}.enable`));
    }
    async getSvelteDoc(document) {
        let svelteDoc = this.docManager.get(document);
        if (!svelteDoc || svelteDoc.version !== document.version) {
            svelteDoc = new SvelteDocument_1.SvelteDocument(document);
            this.docManager.set(document, svelteDoc);
        }
        return svelteDoc;
    }
}
exports.SveltePlugin = SveltePlugin;
//# sourceMappingURL=SveltePlugin.js.map