import { RunOnceScheduler, createCancelablePromise } from "../../../../base/common/async.mjs";
import { CancellationToken } from "../../../../base/common/cancellation.mjs";
import { onUnexpectedError } from "../../../../base/common/errors.mjs";
import { MarkdownString } from "../../../../base/common/htmlContent.mjs";
import { Disposable } from "../../../../base/common/lifecycle.mjs";
import { Schemas } from "../../../../base/common/network.mjs";
import { isMacintosh } from "../../../../base/common/platform.mjs";
import { originalFSPath, joinPath } from "../../../../base/common/resources.mjs";
import { StopWatch } from "../../../../base/common/stopwatch.mjs";
import { URI } from "../../../../base/common/uri.mjs";
import "./links.css.mjs";
import { registerEditorContribution, registerEditorAction, EditorAction } from "../../../browser/editorExtensions.mjs";
import { ModelDecorationOptions } from "../../../common/model/textModel.mjs";
import { ILanguageFeatureDebounceService } from "../../../common/services/languageFeatureDebounce.mjs";
import { ILanguageFeaturesService } from "../../../common/services/languageFeatures.mjs";
import { ClickLinkGesture } from "../../gotoSymbol/browser/link/clickLinkGesture.mjs";
import { getLinks } from "./getLinks.mjs";
import { localize } from "../../../../nls.mjs";
import { INotificationService } from "../../../../platform/notification/common/notification.mjs";
import { IOpenerService } from "../../../../platform/opener/common/opener.mjs";
import { editorActiveLinkForeground } from "../../../../platform/theme/common/colorRegistry.mjs";
import { registerThemingParticipant } from "../../../../platform/theme/common/themeService.mjs";
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());
  });
};
let LinkDetector = class LinkDetector2 extends Disposable {
  constructor(editor, openerService, notificationService, languageFeaturesService, languageFeatureDebounceService) {
    super();
    this.editor = editor;
    this.openerService = openerService;
    this.notificationService = notificationService;
    this.languageFeaturesService = languageFeaturesService;
    this.providers = this.languageFeaturesService.linkProvider;
    this.debounceInformation = languageFeatureDebounceService.for(this.providers, "Links", { min: 1e3, max: 4e3 });
    this.computeLinks = this._register(new RunOnceScheduler(() => this.computeLinksNow(), 1e3));
    this.computePromise = null;
    this.activeLinksList = null;
    this.currentOccurrences = {};
    this.activeLinkDecorationId = null;
    const clickLinkGesture = this._register(new ClickLinkGesture(editor));
    this._register(clickLinkGesture.onMouseMoveOrRelevantKeyDown(([mouseEvent, keyboardEvent]) => {
      this._onEditorMouseMove(mouseEvent, keyboardEvent);
    }));
    this._register(clickLinkGesture.onExecute((e) => {
      this.onEditorMouseUp(e);
    }));
    this._register(clickLinkGesture.onCancel((e) => {
      this.cleanUpActiveLinkDecoration();
    }));
    this._register(editor.onDidChangeConfiguration((e) => {
      if (!e.hasChanged(
        63
        /* links */
      )) {
        return;
      }
      this.updateDecorations([]);
      this.stop();
      this.computeLinks.schedule(0);
    }));
    this._register(editor.onDidChangeModelContent((e) => {
      if (!this.editor.hasModel()) {
        return;
      }
      this.computeLinks.schedule(this.debounceInformation.get(this.editor.getModel()));
    }));
    this._register(editor.onDidChangeModel((e) => {
      this.currentOccurrences = {};
      this.activeLinkDecorationId = null;
      this.stop();
      this.computeLinks.schedule(0);
    }));
    this._register(editor.onDidChangeModelLanguage((e) => {
      this.stop();
      this.computeLinks.schedule(0);
    }));
    this._register(this.providers.onDidChange((e) => {
      this.stop();
      this.computeLinks.schedule(0);
    }));
    this.computeLinks.schedule(0);
  }
  static get(editor) {
    return editor.getContribution(LinkDetector2.ID);
  }
  computeLinksNow() {
    return __awaiter(this, void 0, void 0, function* () {
      if (!this.editor.hasModel() || !this.editor.getOption(
        63
        /* links */
      )) {
        return;
      }
      const model = this.editor.getModel();
      if (!this.providers.has(model)) {
        return;
      }
      if (this.activeLinksList) {
        this.activeLinksList.dispose();
        this.activeLinksList = null;
      }
      this.computePromise = createCancelablePromise((token) => getLinks(this.providers, model, token));
      try {
        const sw = new StopWatch(false);
        this.activeLinksList = yield this.computePromise;
        this.debounceInformation.update(model, sw.elapsed());
        if (model.isDisposed()) {
          return;
        }
        this.updateDecorations(this.activeLinksList.links);
      } catch (err) {
        onUnexpectedError(err);
      } finally {
        this.computePromise = null;
      }
    });
  }
  updateDecorations(links) {
    const useMetaKey = this.editor.getOption(
      70
      /* multiCursorModifier */
    ) === "altKey";
    const oldDecorations = [];
    const keys = Object.keys(this.currentOccurrences);
    for (const decorationId of keys) {
      const occurence = this.currentOccurrences[decorationId];
      oldDecorations.push(occurence.decorationId);
    }
    const newDecorations = [];
    if (links) {
      for (const link of links) {
        newDecorations.push(LinkOccurrence.decoration(link, useMetaKey));
      }
    }
    const decorations = this.editor.deltaDecorations(oldDecorations, newDecorations);
    this.currentOccurrences = {};
    this.activeLinkDecorationId = null;
    for (let i = 0, len = decorations.length; i < len; i++) {
      const occurence = new LinkOccurrence(links[i], decorations[i]);
      this.currentOccurrences[occurence.decorationId] = occurence;
    }
  }
  _onEditorMouseMove(mouseEvent, withKey) {
    const useMetaKey = this.editor.getOption(
      70
      /* multiCursorModifier */
    ) === "altKey";
    if (this.isEnabled(mouseEvent, withKey)) {
      this.cleanUpActiveLinkDecoration();
      const occurrence = this.getLinkOccurrence(mouseEvent.target.position);
      if (occurrence) {
        this.editor.changeDecorations((changeAccessor) => {
          occurrence.activate(changeAccessor, useMetaKey);
          this.activeLinkDecorationId = occurrence.decorationId;
        });
      }
    } else {
      this.cleanUpActiveLinkDecoration();
    }
  }
  cleanUpActiveLinkDecoration() {
    const useMetaKey = this.editor.getOption(
      70
      /* multiCursorModifier */
    ) === "altKey";
    if (this.activeLinkDecorationId) {
      const occurrence = this.currentOccurrences[this.activeLinkDecorationId];
      if (occurrence) {
        this.editor.changeDecorations((changeAccessor) => {
          occurrence.deactivate(changeAccessor, useMetaKey);
        });
      }
      this.activeLinkDecorationId = null;
    }
  }
  onEditorMouseUp(mouseEvent) {
    if (!this.isEnabled(mouseEvent)) {
      return;
    }
    const occurrence = this.getLinkOccurrence(mouseEvent.target.position);
    if (!occurrence) {
      return;
    }
    this.openLinkOccurrence(
      occurrence,
      mouseEvent.hasSideBySideModifier,
      true
      /* from user gesture */
    );
  }
  openLinkOccurrence(occurrence, openToSide, fromUserGesture = false) {
    if (!this.openerService) {
      return;
    }
    const { link } = occurrence;
    link.resolve(CancellationToken.None).then((uri) => {
      if (typeof uri === "string" && this.editor.hasModel()) {
        const modelUri = this.editor.getModel().uri;
        if (modelUri.scheme === Schemas.file && uri.startsWith(`${Schemas.file}:`)) {
          const parsedUri = URI.parse(uri);
          if (parsedUri.scheme === Schemas.file) {
            const fsPath = originalFSPath(parsedUri);
            let relativePath = null;
            if (fsPath.startsWith("/./")) {
              relativePath = `.${fsPath.substr(1)}`;
            } else if (fsPath.startsWith("//./")) {
              relativePath = `.${fsPath.substr(2)}`;
            }
            if (relativePath) {
              uri = joinPath(modelUri, relativePath);
            }
          }
        }
      }
      return this.openerService.open(uri, { openToSide, fromUserGesture, allowContributedOpeners: true, allowCommands: true });
    }, (err) => {
      const messageOrError = err instanceof Error ? err.message : err;
      if (messageOrError === "invalid") {
        this.notificationService.warn(localize("invalid.url", "Failed to open this link because it is not well-formed: {0}", link.url.toString()));
      } else if (messageOrError === "missing") {
        this.notificationService.warn(localize("missing.url", "Failed to open this link because its target is missing."));
      } else {
        onUnexpectedError(err);
      }
    });
  }
  getLinkOccurrence(position) {
    if (!this.editor.hasModel() || !position) {
      return null;
    }
    const decorations = this.editor.getModel().getDecorationsInRange({
      startLineNumber: position.lineNumber,
      startColumn: position.column,
      endLineNumber: position.lineNumber,
      endColumn: position.column
    }, 0, true);
    for (const decoration2 of decorations) {
      const currentOccurrence = this.currentOccurrences[decoration2.id];
      if (currentOccurrence) {
        return currentOccurrence;
      }
    }
    return null;
  }
  isEnabled(mouseEvent, withKey) {
    return Boolean(mouseEvent.target.type === 6 && (mouseEvent.hasTriggerModifier || withKey && withKey.keyCodeIsTriggerKey));
  }
  stop() {
    var _a;
    this.computeLinks.cancel();
    if (this.activeLinksList) {
      (_a = this.activeLinksList) === null || _a === void 0 ? void 0 : _a.dispose();
      this.activeLinksList = null;
    }
    if (this.computePromise) {
      this.computePromise.cancel();
      this.computePromise = null;
    }
  }
  dispose() {
    super.dispose();
    this.stop();
  }
};
LinkDetector.ID = "editor.linkDetector";
LinkDetector = __decorate([
  __param(1, IOpenerService),
  __param(2, INotificationService),
  __param(3, ILanguageFeaturesService),
  __param(4, ILanguageFeatureDebounceService)
], LinkDetector);
const decoration = {
  general: ModelDecorationOptions.register({
    description: "detected-link",
    stickiness: 1,
    collapseOnReplaceEdit: true,
    inlineClassName: "detected-link"
  }),
  active: ModelDecorationOptions.register({
    description: "detected-link-active",
    stickiness: 1,
    collapseOnReplaceEdit: true,
    inlineClassName: "detected-link-active"
  })
};
class LinkOccurrence {
  constructor(link, decorationId) {
    this.link = link;
    this.decorationId = decorationId;
  }
  static decoration(link, useMetaKey) {
    return {
      range: link.range,
      options: LinkOccurrence._getOptions(link, useMetaKey, false)
    };
  }
  static _getOptions(link, useMetaKey, isActive) {
    const options = Object.assign({}, isActive ? decoration.active : decoration.general);
    options.hoverMessage = getHoverMessage(link, useMetaKey);
    return options;
  }
  activate(changeAccessor, useMetaKey) {
    changeAccessor.changeDecorationOptions(this.decorationId, LinkOccurrence._getOptions(this.link, useMetaKey, true));
  }
  deactivate(changeAccessor, useMetaKey) {
    changeAccessor.changeDecorationOptions(this.decorationId, LinkOccurrence._getOptions(this.link, useMetaKey, false));
  }
}
function getHoverMessage(link, useMetaKey) {
  const executeCmd = link.url && /^command:/i.test(link.url.toString());
  const label = link.tooltip ? link.tooltip : executeCmd ? localize("links.navigate.executeCmd", "Execute command") : localize("links.navigate.follow", "Follow link");
  const kb = useMetaKey ? isMacintosh ? localize("links.navigate.kb.meta.mac", "cmd + click") : localize("links.navigate.kb.meta", "ctrl + click") : isMacintosh ? localize("links.navigate.kb.alt.mac", "option + click") : localize("links.navigate.kb.alt", "alt + click");
  if (link.url) {
    let nativeLabel = "";
    if (/^command:/i.test(link.url.toString())) {
      const match = link.url.toString().match(/^command:([^?#]+)/);
      if (match) {
        const commandId = match[1];
        const nativeLabelText = localize("tooltip.explanation", "Execute command {0}", commandId);
        nativeLabel = ` "${nativeLabelText}"`;
      }
    }
    const hoverMessage = new MarkdownString("", true).appendMarkdown(`[${label}](${link.url.toString(true).replace(/ /g, "%20")}${nativeLabel}) (${kb})`);
    return hoverMessage;
  } else {
    return new MarkdownString().appendText(`${label} (${kb})`);
  }
}
class OpenLinkAction extends EditorAction {
  constructor() {
    super({
      id: "editor.action.openLink",
      label: localize("label", "Open Link"),
      alias: "Open Link",
      precondition: void 0
    });
  }
  run(accessor, editor) {
    const linkDetector = LinkDetector.get(editor);
    if (!linkDetector) {
      return;
    }
    if (!editor.hasModel()) {
      return;
    }
    const selections = editor.getSelections();
    for (const sel of selections) {
      const link = linkDetector.getLinkOccurrence(sel.getEndPosition());
      if (link) {
        linkDetector.openLinkOccurrence(link, false);
      }
    }
  }
}
registerEditorContribution(LinkDetector.ID, LinkDetector);
registerEditorAction(OpenLinkAction);
registerThemingParticipant((theme, collector) => {
  const activeLinkForeground = theme.getColor(editorActiveLinkForeground);
  if (activeLinkForeground) {
    collector.addRule(`.monaco-editor .detected-link-active { color: ${activeLinkForeground} !important; }`);
  }
});
export {
  LinkDetector
};
