"use strict";
Object.defineProperty(exports, Symbol.toStringTag, { value: "Module" });
const async = require("../../../../base/common/async.js");
const cancellation = require("../../../../base/common/cancellation.js");
const errors = require("../../../../base/common/errors.js");
const lifecycle = require("../../../../base/common/lifecycle.js");
const map = require("../../../../base/common/map.js");
const types = require("../../../../base/common/types.js");
const uri = require("../../../../base/common/uri.js");
const editorDom = require("../../../browser/editorDom.js");
const editorOptions = require("../../../common/config/editorOptions.js");
const range = require("../../../common/core/range.js");
const languages = require("../../../common/languages.js");
const model = require("../../../common/model.js");
const textModel = require("../../../common/model/textModel.js");
const languageFeatureDebounce = require("../../../common/services/languageFeatureDebounce.js");
const languageFeatures = require("../../../common/services/languageFeatures.js");
const resolverService = require("../../../common/services/resolverService.js");
const clickLinkGesture = require("../../gotoSymbol/browser/link/clickLinkGesture.js");
const inlayHints = require("./inlayHints.js");
const inlayHintsLocations = require("./inlayHintsLocations.js");
const commands = require("../../../../platform/commands/common/commands.js");
const extensions = require("../../../../platform/instantiation/common/extensions.js");
const instantiation = require("../../../../platform/instantiation/common/instantiation.js");
const notification = require("../../../../platform/notification/common/notification.js");
const colorRegistry = require("../../../../platform/theme/common/colorRegistry.js");
const themeService = require("../../../../platform/theme/common/themeService.js");
var __decorate = globalThis && globalThis.__decorate || function(decorators, target, key, desc) {
  var c = arguments.length, r = c < 3 ? target : desc === null ? desc = Object.getOwnPropertyDescriptor(target, key) : desc, d;
  if (typeof Reflect === "object" && typeof Reflect.decorate === "function")
    r = Reflect.decorate(decorators, target, key, desc);
  else
    for (var i = decorators.length - 1; i >= 0; i--)
      if (d = decorators[i])
        r = (c < 3 ? d(r) : c > 3 ? d(target, key, r) : d(target, key)) || r;
  return c > 3 && r && Object.defineProperty(target, key, r), r;
};
var __param = globalThis && globalThis.__param || function(paramIndex, decorator) {
  return function(target, key) {
    decorator(target, key, paramIndex);
  };
};
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());
  });
};
class InlayHintsCache {
  constructor() {
    this._entries = new map.LRUCache(50);
  }
  get(model2) {
    const key = InlayHintsCache._key(model2);
    return this._entries.get(key);
  }
  set(model2, value) {
    const key = InlayHintsCache._key(model2);
    this._entries.set(key, value);
  }
  static _key(model2) {
    return `${model2.uri.toString()}/${model2.getVersionId()}`;
  }
}
const IInlayHintsCache = instantiation.createDecorator("IInlayHintsCache");
extensions.registerSingleton(IInlayHintsCache, InlayHintsCache, true);
class RenderedInlayHintLabelPart {
  constructor(item, index) {
    this.item = item;
    this.index = index;
  }
  get part() {
    const label = this.item.hint.label;
    if (typeof label === "string") {
      return { label };
    } else {
      return label[this.index];
    }
  }
}
class ActiveInlayHintInfo {
  constructor(part, hasTriggerModifier) {
    this.part = part;
    this.hasTriggerModifier = hasTriggerModifier;
  }
}
exports.InlayHintsController = class InlayHintsController {
  constructor(_editor, _languageFeaturesService, _featureDebounce, _inlayHintsCache, _commandService, _notificationService, _instaService) {
    this._editor = _editor;
    this._languageFeaturesService = _languageFeaturesService;
    this._inlayHintsCache = _inlayHintsCache;
    this._commandService = _commandService;
    this._notificationService = _notificationService;
    this._instaService = _instaService;
    this._disposables = new lifecycle.DisposableStore();
    this._sessionDisposables = new lifecycle.DisposableStore();
    this._decorationsMetadata = /* @__PURE__ */ new Map();
    this._ruleFactory = new editorDom.DynamicCssRules(this._editor);
    this._debounceInfo = _featureDebounce.for(_languageFeaturesService.inlayHintsProvider, "InlayHint", { min: 25 });
    this._disposables.add(_languageFeaturesService.inlayHintsProvider.onDidChange(() => this._update()));
    this._disposables.add(_editor.onDidChangeModel(() => this._update()));
    this._disposables.add(_editor.onDidChangeModelLanguage(() => this._update()));
    this._disposables.add(_editor.onDidChangeConfiguration((e) => {
      if (e.hasChanged(
        127
        /* inlayHints */
      )) {
        this._update();
      }
    }));
    this._update();
  }
  static get(editor) {
    var _a;
    return (_a = editor.getContribution(InlayHintsController.ID)) !== null && _a !== void 0 ? _a : void 0;
  }
  dispose() {
    this._sessionDisposables.dispose();
    this._removeAllDecorations();
    this._disposables.dispose();
  }
  _update() {
    this._sessionDisposables.clear();
    this._removeAllDecorations();
    if (!this._editor.getOption(
      127
      /* inlayHints */
    ).enabled) {
      return;
    }
    const model2 = this._editor.getModel();
    if (!model2 || !this._languageFeaturesService.inlayHintsProvider.has(model2)) {
      return;
    }
    const cached = this._inlayHintsCache.get(model2);
    if (cached) {
      this._updateHintsDecorators([model2.getFullModelRange()], cached);
    }
    this._sessionDisposables.add(lifecycle.toDisposable(() => {
      if (!model2.isDisposed()) {
        this._cacheHintsForFastRestore(model2);
      }
    }));
    let cts;
    let watchedProviders = /* @__PURE__ */ new Set();
    const scheduler = new async.RunOnceScheduler(() => __awaiter(this, void 0, void 0, function* () {
      const t1 = Date.now();
      cts === null || cts === void 0 ? void 0 : cts.dispose(true);
      cts = new cancellation.CancellationTokenSource();
      const listener = model2.onWillDispose(() => cts === null || cts === void 0 ? void 0 : cts.cancel());
      try {
        const myToken = cts.token;
        const inlayHints$1 = yield inlayHints.InlayHintsFragments.create(this._languageFeaturesService.inlayHintsProvider, model2, this._getHintsRanges(), myToken);
        scheduler.delay = this._debounceInfo.update(model2, Date.now() - t1);
        if (myToken.isCancellationRequested) {
          inlayHints$1.dispose();
          return;
        }
        for (const provider of inlayHints$1.provider) {
          if (typeof provider.onDidChangeInlayHints === "function" && !watchedProviders.has(provider)) {
            watchedProviders.add(provider);
            this._sessionDisposables.add(provider.onDidChangeInlayHints(() => {
              if (!scheduler.isScheduled()) {
                scheduler.schedule();
              }
            }));
          }
        }
        this._sessionDisposables.add(inlayHints$1);
        this._updateHintsDecorators(inlayHints$1.ranges, inlayHints$1.items);
        this._cacheHintsForFastRestore(model2);
      } catch (err) {
        errors.onUnexpectedError(err);
      } finally {
        cts.dispose();
        listener.dispose();
      }
    }), this._debounceInfo.get(model2));
    this._sessionDisposables.add(scheduler);
    this._sessionDisposables.add(lifecycle.toDisposable(() => cts === null || cts === void 0 ? void 0 : cts.dispose(true)));
    scheduler.schedule(0);
    this._sessionDisposables.add(this._editor.onDidScrollChange((e) => {
      if (e.scrollTopChanged || !scheduler.isScheduled()) {
        scheduler.schedule();
      }
    }));
    this._sessionDisposables.add(this._editor.onDidChangeModelContent((e) => {
      const delay = Math.max(scheduler.delay, 1250);
      scheduler.schedule(delay);
    }));
    this._sessionDisposables.add(this._installLinkGesture());
    this._sessionDisposables.add(this._installDblClickGesture());
    this._sessionDisposables.add(this._installContextMenu());
  }
  _installLinkGesture() {
    const store = new lifecycle.DisposableStore();
    const gesture = store.add(new clickLinkGesture.ClickLinkGesture(this._editor));
    const sessionStore = new lifecycle.DisposableStore();
    store.add(sessionStore);
    store.add(gesture.onMouseMoveOrRelevantKeyDown((e) => {
      const [mouseEvent] = e;
      const labelPart = this._getInlayHintLabelPart(mouseEvent);
      const model2 = this._editor.getModel();
      if (!labelPart || !model2) {
        sessionStore.clear();
        return;
      }
      const cts = new cancellation.CancellationTokenSource();
      sessionStore.add(lifecycle.toDisposable(() => cts.dispose(true)));
      labelPart.item.resolve(cts.token);
      this._activeInlayHintPart = labelPart.part.command || labelPart.part.location ? new ActiveInlayHintInfo(labelPart, mouseEvent.hasTriggerModifier) : void 0;
      const lineNumber = labelPart.item.hint.position.lineNumber;
      const range$1 = new range.Range(lineNumber, 1, lineNumber, model2.getLineMaxColumn(lineNumber));
      const lineHints = /* @__PURE__ */ new Set();
      for (const data of this._decorationsMetadata.values()) {
        if (range$1.containsRange(data.item.anchor.range)) {
          lineHints.add(data.item);
        }
      }
      this._updateHintsDecorators([range$1], Array.from(lineHints));
      sessionStore.add(lifecycle.toDisposable(() => {
        this._activeInlayHintPart = void 0;
        this._updateHintsDecorators([range$1], Array.from(lineHints));
      }));
    }));
    store.add(gesture.onCancel(() => sessionStore.clear()));
    store.add(gesture.onExecute((e) => __awaiter(this, void 0, void 0, function* () {
      const label = this._getInlayHintLabelPart(e);
      if (label) {
        const part = label.part;
        if (part.location) {
          this._instaService.invokeFunction(inlayHintsLocations.goToDefinitionWithLocation, e, this._editor, part.location);
        } else if (languages.Command.is(part.command)) {
          yield this._invokeCommand(part.command, label.item);
        }
      }
    })));
    return store;
  }
  _installDblClickGesture() {
    return this._editor.onMouseUp((e) => __awaiter(this, void 0, void 0, function* () {
      if (e.event.detail !== 2) {
        return;
      }
      const part = this._getInlayHintLabelPart(e);
      if (!part) {
        return;
      }
      e.event.preventDefault();
      yield part.item.resolve(cancellation.CancellationToken.None);
      if (part.item.hint.command) {
        yield this._invokeCommand(part.item.hint.command, part.item);
      }
    }));
  }
  _installContextMenu() {
    return this._editor.onContextMenu((e) => __awaiter(this, void 0, void 0, function* () {
      if (!(e.event.target instanceof HTMLElement)) {
        return;
      }
      const part = this._getInlayHintLabelPart(e);
      if (part) {
        yield this._instaService.invokeFunction(inlayHintsLocations.showGoToContextMenu, this._editor, e.event.target, part);
      }
    }));
  }
  _getInlayHintLabelPart(e) {
    var _a;
    if (e.target.type !== 6) {
      return void 0;
    }
    const options = (_a = e.target.detail.injectedText) === null || _a === void 0 ? void 0 : _a.options;
    if (options instanceof textModel.ModelDecorationInjectedTextOptions && (options === null || options === void 0 ? void 0 : options.attachedData) instanceof RenderedInlayHintLabelPart) {
      return options.attachedData;
    }
    return void 0;
  }
  _invokeCommand(command, item) {
    var _a;
    return __awaiter(this, void 0, void 0, function* () {
      try {
        yield this._commandService.executeCommand(command.id, ...(_a = command.arguments) !== null && _a !== void 0 ? _a : []);
      } catch (err) {
        this._notificationService.notify({
          severity: notification.Severity.Error,
          source: item.provider.displayName,
          message: err
        });
      }
    });
  }
  _cacheHintsForFastRestore(model2) {
    const items = /* @__PURE__ */ new Map();
    for (const [id, obj] of this._decorationsMetadata) {
      if (items.has(obj.item)) {
        continue;
      }
      let value = obj.item;
      const range2 = model2.getDecorationRange(id);
      if (range2) {
        const anchor = new inlayHints.InlayHintAnchor(range2, obj.item.anchor.direction);
        value = obj.item.with({ anchor });
      }
      items.set(obj.item, value);
    }
    this._inlayHintsCache.set(model2, Array.from(items.values()));
  }
  _getHintsRanges() {
    const extra = 30;
    const model2 = this._editor.getModel();
    const visibleRanges = this._editor.getVisibleRangesPlusViewportAboveBelow();
    const result = [];
    for (const range$1 of visibleRanges.sort(range.Range.compareRangesUsingStarts)) {
      const extendedRange = model2.validateRange(new range.Range(range$1.startLineNumber - extra, range$1.startColumn, range$1.endLineNumber + extra, range$1.endColumn));
      if (result.length === 0 || !range.Range.areIntersectingOrTouching(result[result.length - 1], extendedRange)) {
        result.push(extendedRange);
      } else {
        result[result.length - 1] = range.Range.plusRange(result[result.length - 1], extendedRange);
      }
    }
    return result;
  }
  _updateHintsDecorators(ranges, items) {
    var _a, _b;
    const newDecorationsData = [];
    const addInjectedText = (item, ref, content, cursorStops, attachedData) => {
      newDecorationsData.push({
        item,
        classNameRef: ref,
        decoration: {
          range: item.anchor.range,
          options: {
            // className: "rangeHighlight", // DEBUG highlight to see to what range a hint is attached
            description: "InlayHint",
            showIfCollapsed: item.anchor.range.isEmpty(),
            collapseOnReplaceEdit: !item.anchor.range.isEmpty(),
            stickiness: 0,
            [item.anchor.direction]: {
              content,
              inlineClassNameAffectsLetterSpacing: true,
              inlineClassName: ref.className,
              cursorStops,
              attachedData
            }
          }
        }
      });
    };
    const addInjectedWhitespace = (item, isLast) => {
      const marginRule = this._ruleFactory.createClassNameRef({
        width: `${fontSize / 3 | 0}px`,
        display: "inline-block"
      });
      addInjectedText(item, marginRule, " ", isLast ? model.InjectedTextCursorStops.Right : model.InjectedTextCursorStops.None);
    };
    const { fontSize, fontFamily } = this._getLayoutInfo();
    const fontFamilyVar = "--code-editorInlayHintsFontFamily";
    this._editor.getContainerDomNode().style.setProperty(fontFamilyVar, fontFamily);
    for (const item of items) {
      if (item.hint.paddingLeft) {
        addInjectedWhitespace(item, false);
      }
      const parts = typeof item.hint.label === "string" ? [{ label: item.hint.label }] : item.hint.label;
      for (let i = 0; i < parts.length; i++) {
        const part = parts[i];
        const isFirst = i === 0;
        const isLast = i === parts.length - 1;
        const cssProperties = {
          fontSize: `${fontSize}px`,
          fontFamily: `var(${fontFamilyVar}), ${editorOptions.EDITOR_FONT_DEFAULTS.fontFamily}`,
          verticalAlign: "middle"
        };
        if (item.hint.command) {
          cssProperties.cursor = "pointer";
        }
        this._fillInColors(cssProperties, item.hint);
        if ((part.command || part.location) && ((_a = this._activeInlayHintPart) === null || _a === void 0 ? void 0 : _a.part.item) === item && this._activeInlayHintPart.part.index === i) {
          cssProperties.textDecoration = "underline";
          if (this._activeInlayHintPart.hasTriggerModifier) {
            cssProperties.color = themeService.themeColorFromId(colorRegistry.editorActiveLinkForeground);
            cssProperties.cursor = "pointer";
          }
        }
        if (isFirst && isLast) {
          cssProperties.padding = `1px ${Math.max(1, fontSize / 4) | 0}px`;
          cssProperties.borderRadius = `${fontSize / 4 | 0}px`;
        } else if (isFirst) {
          cssProperties.padding = `1px 0 1px ${Math.max(1, fontSize / 4) | 0}px`;
          cssProperties.borderRadius = `${fontSize / 4 | 0}px 0 0 ${fontSize / 4 | 0}px`;
        } else if (isLast) {
          cssProperties.padding = `1px ${Math.max(1, fontSize / 4) | 0}px 1px 0`;
          cssProperties.borderRadius = `0 ${fontSize / 4 | 0}px ${fontSize / 4 | 0}px 0`;
        } else {
          cssProperties.padding = `1px 0 1px 0`;
        }
        addInjectedText(item, this._ruleFactory.createClassNameRef(cssProperties), fixSpace(part.label), isLast && !item.hint.paddingRight ? model.InjectedTextCursorStops.Right : model.InjectedTextCursorStops.None, new RenderedInlayHintLabelPart(item, i));
      }
      if (item.hint.paddingRight) {
        addInjectedWhitespace(item, true);
      }
      if (newDecorationsData.length > InlayHintsController._MAX_DECORATORS) {
        break;
      }
    }
    const decorationIdsToReplace = [];
    for (const range2 of ranges) {
      for (const { id } of (_b = this._editor.getDecorationsInRange(range2)) !== null && _b !== void 0 ? _b : []) {
        const metadata = this._decorationsMetadata.get(id);
        if (metadata) {
          decorationIdsToReplace.push(id);
          metadata.classNameRef.dispose();
          this._decorationsMetadata.delete(id);
        }
      }
    }
    const newDecorationIds = this._editor.deltaDecorations(decorationIdsToReplace, newDecorationsData.map((d) => d.decoration));
    for (let i = 0; i < newDecorationIds.length; i++) {
      const data = newDecorationsData[i];
      this._decorationsMetadata.set(newDecorationIds[i], { item: data.item, classNameRef: data.classNameRef });
    }
  }
  _fillInColors(props, hint) {
    if (hint.kind === languages.InlayHintKind.Parameter) {
      props.backgroundColor = themeService.themeColorFromId(colorRegistry.editorInlayHintParameterBackground);
      props.color = themeService.themeColorFromId(colorRegistry.editorInlayHintParameterForeground);
    } else if (hint.kind === languages.InlayHintKind.Type) {
      props.backgroundColor = themeService.themeColorFromId(colorRegistry.editorInlayHintTypeBackground);
      props.color = themeService.themeColorFromId(colorRegistry.editorInlayHintTypeForeground);
    } else {
      props.backgroundColor = themeService.themeColorFromId(colorRegistry.editorInlayHintBackground);
      props.color = themeService.themeColorFromId(colorRegistry.editorInlayHintForeground);
    }
  }
  _getLayoutInfo() {
    const options = this._editor.getOption(
      127
      /* inlayHints */
    );
    const editorFontSize = this._editor.getOption(
      46
      /* fontSize */
    );
    let fontSize = options.fontSize;
    if (!fontSize || fontSize < 5 || fontSize > editorFontSize) {
      fontSize = editorFontSize * 0.9 | 0;
    }
    const fontFamily = options.fontFamily || this._editor.getOption(
      43
      /* fontFamily */
    );
    return { fontSize, fontFamily };
  }
  _removeAllDecorations() {
    this._editor.deltaDecorations(Array.from(this._decorationsMetadata.keys()), []);
    for (let obj of this._decorationsMetadata.values()) {
      obj.classNameRef.dispose();
    }
    this._decorationsMetadata.clear();
  }
};
exports.InlayHintsController.ID = "editor.contrib.InlayHints";
exports.InlayHintsController._MAX_DECORATORS = 1500;
exports.InlayHintsController = __decorate([
  __param(1, languageFeatures.ILanguageFeaturesService),
  __param(2, languageFeatureDebounce.ILanguageFeatureDebounceService),
  __param(3, IInlayHintsCache),
  __param(4, commands.ICommandService),
  __param(5, notification.INotificationService),
  __param(6, instantiation.IInstantiationService)
], exports.InlayHintsController);
function fixSpace(str) {
  const noBreakWhitespace = " ";
  return str.replace(/[ \t]/g, noBreakWhitespace);
}
commands.CommandsRegistry.registerCommand("_executeInlayHintProvider", (accessor, ...args) => __awaiter(void 0, void 0, void 0, function* () {
  const [uri$1, range$1] = args;
  types.assertType(uri.URI.isUri(uri$1));
  types.assertType(range.Range.isIRange(range$1));
  const { inlayHintsProvider } = accessor.get(languageFeatures.ILanguageFeaturesService);
  const ref = yield accessor.get(resolverService.ITextModelService).createModelReference(uri$1);
  try {
    const model2 = yield inlayHints.InlayHintsFragments.create(inlayHintsProvider, ref.object.textEditorModel, [range.Range.lift(range$1)], cancellation.CancellationToken.None);
    const result = model2.items.map((i) => i.hint);
    setTimeout(() => model2.dispose(), 0);
    return result;
  } finally {
    ref.dispose();
  }
}));
exports.RenderedInlayHintLabelPart = RenderedInlayHintLabelPart;
