text: "use strict";
  Object.defineProperty(exports, "__esModule", { value: true });
  exports.CodeActionsProviderImpl = void 0;
  const vscode_languageserver_1 = require("vscode-languageserver");
  const documents_1 = require("../../../lib/documents");
  const utils_1 = require("../../../utils");
  const DocumentSnapshot_1 = require("../DocumentSnapshot");
  const utils_2 = require("../utils");
  class CodeActionsProviderImpl {
      constructor(lsAndTsDocResolver, completionProvider) {
          this.lsAndTsDocResolver = lsAndTsDocResolver;
          this.completionProvider = completionProvider;
      }
      async getCodeActions(document, range, context) {
          var _a;
          if (((_a = context.only) === null || _a === void 0 ? void 0 : _a[0]) === vscode_languageserver_1.CodeActionKind.SourceOrganizeImports) {
              return await this.organizeImports(document);
          }
          if (context.diagnostics.length &&
              (!context.only || context.only.includes(vscode_languageserver_1.CodeActionKind.QuickFix))) {
              return await this.applyQuickfix(document, range, context);
          }
          if (!context.only || context.only.includes(vscode_languageserver_1.CodeActionKind.Refactor)) {
              return await this.getApplicableRefactors(document, range);
          }
          return [];
      }
      async organizeImports(document) {
          if (!document.scriptInfo && !document.moduleScriptInfo) {
              return [];
          }
          const { lang, tsDoc, userPreferences } = this.getLSAndTSDoc(document);
          const fragment = await tsDoc.getFragment();
          const changes = lang.organizeImports({
              fileName: tsDoc.filePath,
              type: 'file'
          }, {}, userPreferences);
          const documentChanges = await Promise.all(changes.map(async (change) => {
              // Organize Imports will only affect the current file, so no need to check the file path
              return vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(document.url, null), change.textChanges.map((edit) => {
                  let range = documents_1.mapRangeToOriginal(fragment, utils_2.convertRange(fragment, edit.span));
                  // Handle svelte2tsx wrong import mapping:
                  // The character after the last import maps to the start of the script
                  // TODO find a way to fix this in svelte2tsx and then remove this
                  if ((range.end.line === 0 && range.end.character === 1) ||
                      range.end.line < range.start.line) {
                      edit.span.length -= 1;
                      range = documents_1.mapRangeToOriginal(fragment, utils_2.convertRange(fragment, edit.span));
                      range.end.character += 1;
                  }
                  return vscode_languageserver_1.TextEdit.replace(range, edit.newText);
              }));
          }));
          return [
              vscode_languageserver_1.CodeAction.create('Organize Imports', { documentChanges }, vscode_languageserver_1.CodeActionKind.SourceOrganizeImports)
          ];
      }
      async applyQuickfix(document, range, context) {
          const { lang, tsDoc, userPreferences } = this.getLSAndTSDoc(document);
          const fragment = await tsDoc.getFragment();
          const start = fragment.offsetAt(fragment.getGeneratedPosition(range.start));
          const end = fragment.offsetAt(fragment.getGeneratedPosition(range.end));
          const errorCodes = context.diagnostics.map((diag) => Number(diag.code));
          const codeFixes = lang.getCodeFixesAtPosition(tsDoc.filePath, start, end, errorCodes, {}, userPreferences);
          const docs = new Map([[tsDoc.filePath, fragment]]);
          return await Promise.all(codeFixes.map(async (fix) => {
              const documentChanges = await Promise.all(fix.changes.map(async (change) => {
                  let doc = docs.get(change.fileName);
                  if (!doc) {
                      doc = await this.getSnapshot(change.fileName).getFragment();
                      docs.set(change.fileName, doc);
                  }
                  return vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(utils_1.pathToUrl(change.fileName), null), change.textChanges.map((edit) => {
                      if (fix.fixName === 'import' &&
                          doc instanceof DocumentSnapshot_1.SvelteSnapshotFragment) {
                          return this.completionProvider.codeActionChangeToTextEdit(document, doc, edit, true);
                      }
                      return vscode_languageserver_1.TextEdit.replace(documents_1.mapRangeToOriginal(doc, utils_2.convertRange(doc, edit.span)), edit.newText);
                  }));
              }));
              return vscode_languageserver_1.CodeAction.create(fix.description, {
                  documentChanges
              }, vscode_languageserver_1.CodeActionKind.QuickFix);
          }));
      }
      async getApplicableRefactors(document, range) {
          if (!documents_1.isRangeInTag(range, document.scriptInfo) &&
              !documents_1.isRangeInTag(range, document.moduleScriptInfo)) {
              return [];
          }
          // Don't allow refactorings when there is likely a store subscription.
          // Reason: Extracting that would lead to svelte2tsx' transformed store representation
          // showing up, which will confuse the user. In the long run, we maybe have to
          // setup a separate ts language service which only knows of the original script.
          const textInRange = document
              .getText()
              .substring(document.offsetAt(range.start), document.offsetAt(range.end));
          if (textInRange.includes('$')) {
              return [];
          }
          const { lang, tsDoc, userPreferences } = this.getLSAndTSDoc(document);
          const fragment = await tsDoc.getFragment();
          const textRange = {
              pos: fragment.offsetAt(fragment.getGeneratedPosition(range.start)),
              end: fragment.offsetAt(fragment.getGeneratedPosition(range.end))
          };
          const applicableRefactors = lang.getApplicableRefactors(document.getFilePath() || '', textRange, userPreferences);
          return (this.applicableRefactorsToCodeActions(applicableRefactors, document, range, textRange)
              // Only allow refactorings from which we know they work
              .filter((refactor) => {
              var _a, _b;
              return ((_a = refactor.command) === null || _a === void 0 ? void 0 : _a.command.includes('function_scope')) || ((_b = refactor.command) === null || _b === void 0 ? void 0 : _b.command.includes('constant_scope'));
          })
              // The language server also proposes extraction into const/function in module scope,
              // which is outside of the render function, which is svelte2tsx-specific and unmapped,
              // so it would both not work and confuse the user ("What is this render? Never declared that").
              // So filter out the module scope proposal and rename the render-title
              .filter((refactor) => !refactor.title.includes('module scope'))
              .map((refactor) => ({
              ...refactor,
              title: refactor.title
                  .replace('Extract to inner function in function \'render\'', 'Extract to function')
                  .replace('Extract to constant in function \'render\'', 'Extract to constant')
          })));
      }
      applicableRefactorsToCodeActions(applicableRefactors, document, originalRange, textRange) {
          return utils_1.flatten(applicableRefactors.map((applicableRefactor) => {
              if (applicableRefactor.inlineable === false) {
                  return [
                      vscode_languageserver_1.CodeAction.create(applicableRefactor.description, {
                          title: applicableRefactor.description,
                          command: applicableRefactor.name,
                          arguments: [
                              document.uri,
                              {
                                  type: 'refactor',
                                  textRange,
                                  originalRange,
                                  refactorName: 'Extract Symbol'
                              }
                          ]
                      })
                  ];
              }
              return applicableRefactor.actions.map((action) => {
                  return vscode_languageserver_1.CodeAction.create(action.description, {
                      title: action.description,
                      command: action.name,
                      arguments: [
                          document.uri,
                          {
                              type: 'refactor',
                              textRange,
                              originalRange,
                              refactorName: applicableRefactor.name
                          }
                      ]
                  });
              });
          }));
      }
      async executeCommand(document, command, args) {
          var _a;
          if (!(((_a = args === null || args === void 0 ? void 0 : args[1]) === null || _a === void 0 ? void 0 : _a.type) === 'refactor')) {
              return null;
          }
          const { lang, tsDoc, userPreferences } = this.getLSAndTSDoc(document);
          const fragment = await tsDoc.getFragment();
          const path = document.getFilePath() || '';
          const { refactorName, originalRange, textRange } = args[1];
          const edits = lang.getEditsForRefactor(path, {}, textRange, refactorName, command, userPreferences);
          if (!edits || edits.edits.length === 0) {
              return null;
          }
          const documentChanges = edits === null || edits === void 0 ? void 0 : edits.edits.map((edit) => vscode_languageserver_1.TextDocumentEdit.create(vscode_languageserver_1.VersionedTextDocumentIdentifier.create(document.uri, null), edit.textChanges.map((edit) => {
              let range = documents_1.mapRangeToOriginal(fragment, utils_2.convertRange(fragment, edit.span));
              // Some refactorings place the new code at the end of svelte2tsx' render function,
              // which is unmapped. In this case, add it to the end of the script tag ourselves.
              if (range.start.line < 0 || range.end.line < 0) {
                  if (documents_1.isRangeInTag(originalRange, document.scriptInfo)) {
                      range = vscode_languageserver_1.Range.create(document.scriptInfo.endPos, document.scriptInfo.endPos);
                  }
                  else if (documents_1.isRangeInTag(originalRange, document.moduleScriptInfo)) {
                      range = vscode_languageserver_1.Range.create(document.moduleScriptInfo.endPos, document.moduleScriptInfo.endPos);
                  }
              }
              return vscode_languageserver_1.TextEdit.replace(range, edit.newText);
          })));
          return { documentChanges };
      }
      getLSAndTSDoc(document) {
          return this.lsAndTsDocResolver.getLSAndTSDoc(document);
      }
      getSnapshot(filePath, document) {
          return this.lsAndTsDocResolver.getSnapshot(filePath, document);
      }
  }
  exports.CodeActionsProviderImpl = CodeActionsProviderImpl;
  //# sourceMappingURL=CodeActionsProvider.js.map