// Copyright 2000-2024 JetBrains s.r.o. and contributors. Use of this source code is governed by the Apache 2.0 license.
package com.intellij.codeInspection.i18n.folding;

import com.intellij.codeInsight.hint.HintManagerImpl;
import com.intellij.codeInsight.hint.HintUtil;
import com.intellij.openapi.actionSystem.ActionManager;
import com.intellij.openapi.actionSystem.ActionPlaces;
import com.intellij.openapi.actionSystem.AnAction;
import com.intellij.openapi.actionSystem.ex.ActionUtil;
import com.intellij.openapi.application.ApplicationManager;
import com.intellij.openapi.application.ReadAction;
import com.intellij.openapi.editor.Document;
import com.intellij.openapi.editor.Editor;
import com.intellij.openapi.editor.EditorFactory;
import com.intellij.openapi.editor.FoldRegion;
import com.intellij.openapi.editor.event.*;
import com.intellij.openapi.editor.impl.EditorImpl;
import com.intellij.openapi.editor.impl.FoldingPopupManager;
import com.intellij.openapi.keymap.KeymapUtil;
import com.intellij.openapi.util.Key;
import com.intellij.openapi.util.text.HtmlBuilder;
import com.intellij.openapi.util.text.HtmlChunk;
import com.intellij.openapi.util.text.StringUtil;
import com.intellij.ui.ColorUtil;
import com.intellij.ui.HintHint;
import com.intellij.ui.LightweightHint;
import com.intellij.util.Alarm;
import com.intellij.util.ui.UIUtil;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;

import javax.swing.*;
import javax.swing.event.HyperlinkEvent;
import java.awt.*;
import java.util.Arrays;

import static com.intellij.codeInsight.hint.HintManager.*;
import static com.intellij.openapi.actionSystem.IdeActions.ACTION_EDIT_PROPERTY_VALUE;
import static com.intellij.openapi.actionSystem.IdeActions.ACTION_EXPAND_REGION;

public final class EditPropertyValueTooltipManager implements EditorMouseListener, CaretListener {
  private static final Key<Boolean> INITIALIZED = Key.create("PropertyEditTooltipManager");
  private static final long TOOLTIP_DELAY_MS = 500;

  private final Alarm myCalculateShowTooltipAlarm;

  static void initializeForDocument(@NotNull Document document) {
    EditorFactory.getInstance().editors(document).forEach(EditPropertyValueTooltipManager::initializeForEditor);
  }

  private static void initializeForEditor(@NotNull Editor editor) {
    if (editor.getUserData(INITIALIZED) != null) return;
    editor.putUserData(INITIALIZED, Boolean.TRUE);
    new EditPropertyValueTooltipManager(editor);
  }

  private EditPropertyValueTooltipManager(@NotNull Editor editor) {
    editor.getCaretModel().addCaretListener(this);
    editor.addEditorMouseListener(this);
    myCalculateShowTooltipAlarm = new Alarm(Alarm.ThreadToUse.POOLED_THREAD, ((EditorImpl)editor).getDisposable());
  }

  @Override
  public void mouseReleased(@NotNull EditorMouseEvent event) {
    if (event.getArea() == EditorMouseEventArea.EDITING_AREA) {
      submitCalculateShowingTooltipInBGT(event.getEditor());
    }
  }

  @Override
  public void caretPositionChanged(@NotNull CaretEvent event) {
    submitCalculateShowingTooltipInBGT(event.getEditor());
  }

  private void submitCalculateShowingTooltipInBGT(@NotNull Editor editor) {
    myCalculateShowTooltipAlarm.cancelAllRequests();
    myCalculateShowTooltipAlarm.addRequest(() -> {
      if (ReadAction.compute(() -> shouldShowTooltip(editor))) {
        ApplicationManager.getApplication().invokeLater(() -> showTooltip(editor));
      }
    }, TOOLTIP_DELAY_MS);
  }

  private static boolean shouldShowTooltip(@NotNull Editor editor) {
    return !editor.isDisposed() && EditPropertyValueAction.isEnabled(editor);
  }

  private static void showTooltip(@NotNull Editor editor) {
    String hintText = StringUtil.join(Arrays.asList(createActionText(ACTION_EXPAND_REGION, "expand"),
                                                    createActionText(ACTION_EDIT_PROPERTY_VALUE, "edit")),
                                      "&nbsp;&nbsp;&nbsp;&nbsp;");
    if (hintText.isEmpty()) return;
    JComponent component = HintUtil.createInformationLabel("<html>" + hintText, e -> {
      if (e.getEventType() != HyperlinkEvent.EventType.ACTIVATED) return;
      String actionId = switch (e.getDescription()) {
        case "expand" -> ACTION_EXPAND_REGION;
        case "edit" -> ACTION_EDIT_PROPERTY_VALUE;
        default -> null;
      };
      if (actionId != null) {
        AnAction action = ActionManager.getInstance().getAction(actionId);
        if (action != null) {
          ActionUtil.invokeAction(action, editor.getContentComponent(), ActionPlaces.UNKNOWN, e.getInputEvent(), null);
        }
      }
    }, null, null);
    showTooltip(editor, component, false);
  }

  private static @Nullable String createActionText(@NotNull String actionId, @NotNull String href) {
    AnAction action = ActionManager.getInstance().getAction(actionId);
    if (action == null) return null;
    String text = action.getTemplateText();
    if (text == null) return null;
    HtmlChunk.Element shortcut = HtmlChunk.span("color:" + ColorUtil.toHtmlColor(UIUtil.getContextHelpForeground()))
      .addText(KeymapUtil.getFirstKeyboardShortcutText(action));
    return new HtmlBuilder().appendLink(href, text).append(" ").append(shortcut).toString();
  }

  public static LightweightHint showTooltip(@NotNull Editor editor, @NotNull JComponent component, boolean tenacious) {
    if (editor.isDisposed()) return null;
    FoldRegion foldRegion = editor.getFoldingModel().getCollapsedRegionAtOffset(editor.getCaretModel().getOffset());
    if (foldRegion == null) return null;
    JComponent editorComponent = editor.getContentComponent();
    JRootPane rootPane = editorComponent.getRootPane();
    if (rootPane == null) return null;
    Point start = editor.offsetToXY(foldRegion.getStartOffset(), true, false);
    Point end = editor.offsetToXY(foldRegion.getEndOffset(), false, true);
    Point relativePoint = new Point((start.x + end.x) / 2, start.y);
    Point point = SwingUtilities.convertPoint(editorComponent, relativePoint, rootPane.getLayeredPane());
    LightweightHint hint = new LightweightHint(component);
    HintHint hintHint = HintManagerImpl.createHintHint(editor, point, hint, ABOVE).setShowImmediately(true).setStatus(HintHint.Status.Info);
    int flags = HIDE_BY_TEXT_CHANGE | HIDE_BY_SCROLLING;
    if (tenacious) {
      hintHint.setExplicitClose(true);
    }
    else {
      flags |= HIDE_BY_ANY_KEY;
    }
    FoldingPopupManager.disableForEditor(editor);
    hint.addHintListener(e -> FoldingPopupManager.enableForEditor(editor));
    HintManagerImpl.getInstanceImpl().showEditorHint(hint, editor, point, flags, 0, false, hintHint);
    return hint;
  }
}
