import { equals, flatten, coalesce, isNonEmptyArray } from "../../../../base/common/arrays.mjs";
import { CancellationToken } from "../../../../base/common/cancellation.mjs";
import { illegalArgument, isCancellationError, onUnexpectedExternalError } from "../../../../base/common/errors.mjs";
import { DisposableStore, Disposable } from "../../../../base/common/lifecycle.mjs";
import { URI } from "../../../../base/common/uri.mjs";
import { TextModelCancellationTokenSource } from "../../editorState/browser/editorState.mjs";
import { Range } from "../../../common/core/range.mjs";
import { Selection } from "../../../common/core/selection.mjs";
import { IModelService } from "../../../common/services/model.mjs";
import { CommandsRegistry } from "../../../../platform/commands/common/commands.mjs";
import { Progress } from "../../../../platform/progress/common/progress.mjs";
import { filtersAction, mayIncludeActionsOfKind, CodeActionKind } from "./types.mjs";
import { ILanguageFeaturesService } from "../../../common/services/languageFeatures.mjs";
var __awaiter = globalThis && globalThis.__awaiter || function(thisArg, _arguments, P, generator) {
  function adopt(value) {
    return value instanceof P ? value : new P(function(resolve) {
      resolve(value);
    });
  }
  return new (P || (P = Promise))(function(resolve, reject) {
    function fulfilled(value) {
      try {
        step(generator.next(value));
      } catch (e) {
        reject(e);
      }
    }
    function rejected(value) {
      try {
        step(generator["throw"](value));
      } catch (e) {
        reject(e);
      }
    }
    function step(result) {
      result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected);
    }
    step((generator = generator.apply(thisArg, _arguments || [])).next());
  });
};
const codeActionCommandId = "editor.action.codeAction";
const refactorCommandId = "editor.action.refactor";
const sourceActionCommandId = "editor.action.sourceAction";
const organizeImportsCommandId = "editor.action.organizeImports";
const fixAllCommandId = "editor.action.fixAll";
class CodeActionItem {
  constructor(action, provider) {
    this.action = action;
    this.provider = provider;
  }
  resolve(token) {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
      if (((_a = this.provider) === null || _a === void 0 ? void 0 : _a.resolveCodeAction) && !this.action.edit) {
        let action;
        try {
          action = yield this.provider.resolveCodeAction(this.action, token);
        } catch (err) {
          onUnexpectedExternalError(err);
        }
        if (action) {
          this.action.edit = action.edit;
        }
      }
      return this;
    });
  }
}
class ManagedCodeActionSet extends Disposable {
  constructor(actions, documentation, disposables) {
    super();
    this.documentation = documentation;
    this._register(disposables);
    this.allActions = [...actions].sort(ManagedCodeActionSet.codeActionsComparator);
    this.validActions = this.allActions.filter(({ action }) => !action.disabled);
  }
  static codeActionsComparator({ action: a }, { action: b }) {
    if (a.isPreferred && !b.isPreferred) {
      return -1;
    } else if (!a.isPreferred && b.isPreferred) {
      return 1;
    }
    if (isNonEmptyArray(a.diagnostics)) {
      if (isNonEmptyArray(b.diagnostics)) {
        return a.diagnostics[0].message.localeCompare(b.diagnostics[0].message);
      } else {
        return -1;
      }
    } else if (isNonEmptyArray(b.diagnostics)) {
      return 1;
    } else {
      return 0;
    }
  }
  get hasAutoFix() {
    return this.validActions.some(({ action: fix }) => !!fix.kind && CodeActionKind.QuickFix.contains(new CodeActionKind(fix.kind)) && !!fix.isPreferred);
  }
}
const emptyCodeActionsResponse = { actions: [], documentation: void 0 };
function getCodeActions(registry, model, rangeOrSelection, trigger, progress, token) {
  var _a;
  const filter = trigger.filter || {};
  const codeActionContext = {
    only: (_a = filter.include) === null || _a === void 0 ? void 0 : _a.value,
    trigger: trigger.type
  };
  const cts = new TextModelCancellationTokenSource(model, token);
  const providers = getCodeActionProviders(registry, model, filter);
  const disposables = new DisposableStore();
  const promises = providers.map((provider) => __awaiter(this, void 0, void 0, function* () {
    try {
      progress.report(provider);
      const providedCodeActions = yield provider.provideCodeActions(model, rangeOrSelection, codeActionContext, cts.token);
      if (providedCodeActions) {
        disposables.add(providedCodeActions);
      }
      if (cts.token.isCancellationRequested) {
        return emptyCodeActionsResponse;
      }
      const filteredActions = ((providedCodeActions === null || providedCodeActions === void 0 ? void 0 : providedCodeActions.actions) || []).filter((action) => action && filtersAction(filter, action));
      const documentation = getDocumentation(provider, filteredActions, filter.include);
      return {
        actions: filteredActions.map((action) => new CodeActionItem(action, provider)),
        documentation
      };
    } catch (err) {
      if (isCancellationError(err)) {
        throw err;
      }
      onUnexpectedExternalError(err);
      return emptyCodeActionsResponse;
    }
  }));
  const listener = registry.onDidChange(() => {
    const newProviders = registry.all(model);
    if (!equals(newProviders, providers)) {
      cts.cancel();
    }
  });
  return Promise.all(promises).then((actions) => {
    const allActions = flatten(actions.map((x) => x.actions));
    const allDocumentation = coalesce(actions.map((x) => x.documentation));
    return new ManagedCodeActionSet(allActions, allDocumentation, disposables);
  }).finally(() => {
    listener.dispose();
    cts.dispose();
  });
}
function getCodeActionProviders(registry, model, filter) {
  return registry.all(model).filter((provider) => {
    if (!provider.providedCodeActionKinds) {
      return true;
    }
    return provider.providedCodeActionKinds.some((kind) => mayIncludeActionsOfKind(filter, new CodeActionKind(kind)));
  });
}
function getDocumentation(provider, providedCodeActions, only) {
  if (!provider.documentation) {
    return void 0;
  }
  const documentation = provider.documentation.map((entry) => ({ kind: new CodeActionKind(entry.kind), command: entry.command }));
  if (only) {
    let currentBest;
    for (const entry of documentation) {
      if (entry.kind.contains(only)) {
        if (!currentBest) {
          currentBest = entry;
        } else {
          if (currentBest.kind.contains(entry.kind)) {
            currentBest = entry;
          }
        }
      }
    }
    if (currentBest) {
      return currentBest === null || currentBest === void 0 ? void 0 : currentBest.command;
    }
  }
  for (const action of providedCodeActions) {
    if (!action.kind) {
      continue;
    }
    for (const entry of documentation) {
      if (entry.kind.contains(new CodeActionKind(action.kind))) {
        return entry.command;
      }
    }
  }
  return void 0;
}
CommandsRegistry.registerCommand("_executeCodeActionProvider", function(accessor, resource, rangeOrSelection, kind, itemResolveCount) {
  return __awaiter(this, void 0, void 0, function* () {
    if (!(resource instanceof URI)) {
      throw illegalArgument();
    }
    const { codeActionProvider } = accessor.get(ILanguageFeaturesService);
    const model = accessor.get(IModelService).getModel(resource);
    if (!model) {
      throw illegalArgument();
    }
    const validatedRangeOrSelection = Selection.isISelection(rangeOrSelection) ? Selection.liftSelection(rangeOrSelection) : Range.isIRange(rangeOrSelection) ? model.validateRange(rangeOrSelection) : void 0;
    if (!validatedRangeOrSelection) {
      throw illegalArgument();
    }
    const include = typeof kind === "string" ? new CodeActionKind(kind) : void 0;
    const codeActionSet = yield getCodeActions(codeActionProvider, model, validatedRangeOrSelection, { type: 1, filter: { includeSourceActions: true, include } }, Progress.None, CancellationToken.None);
    const resolving = [];
    const resolveCount = Math.min(codeActionSet.validActions.length, typeof itemResolveCount === "number" ? itemResolveCount : 0);
    for (let i = 0; i < resolveCount; i++) {
      resolving.push(codeActionSet.validActions[i].resolve(CancellationToken.None));
    }
    try {
      yield Promise.all(resolving);
      return codeActionSet.validActions.map((item) => item.action);
    } finally {
      setTimeout(() => codeActionSet.dispose(), 100);
    }
  });
});
export {
  CodeActionItem,
  codeActionCommandId,
  fixAllCommandId,
  getCodeActions,
  organizeImportsCommandId,
  refactorCommandId,
  sourceActionCommandId
};
