import { BubbleMenu, BubbleMenuProps } from "@tiptap/react/menus";
import { isNodeSelection, useEditorState } from "@tiptap/react";
import type { Editor } from "@tiptap/react";
import {
  ComponentType,
  CSSProperties,
  FC,
  useEffect,
  useRef,
  useState,
} from "react";
import {
  IconBold,
  IconCode,
  IconItalic,
  IconStrikethrough,
  IconUnderline,
  IconMessage,
  IconEyeOff,
  IconClearFormatting,
} from "@tabler/icons-react";
import clsx from "clsx";
import classes from "./bubble-menu.module.css";
import { ActionIcon, rem, Tooltip } from "@mantine/core";
import { ColorSelector } from "./color-selector";
import { NodeSelector } from "./node-selector";
import { TextAlignmentSelector } from "./text-alignment-selector";
import {
  draftCommentIdAtom,
  showCommentPopupAtom,
} from "@/features/comment/atoms/comment-atom";
import { useAtom, useAtomValue } from "jotai";
import { v7 as uuid7 } from "uuid";
import { isCellSelection, isTextSelected } from "@docmost/editor-ext";
import { LinkSelector } from "@/features/editor/components/bubble-menu/link-selector.tsx";
import { useTranslation } from "react-i18next";
import { showLinkMenuAtom } from "@/features/editor/atoms/editor-atoms";
import { userAtom } from "@/features/user/atoms/current-user-atom";
import {
  hasStressAfterSelection,
  toggleStressAccent,
} from "./stress-accent";

// Tabler has no acute-accent glyph (IconGrave is a tombstone), so we ship a
// tiny local icon that mirrors the Tabler icon API ({ style, stroke }).
function IconStress({
  style,
  stroke = 2,
}: {
  style?: React.CSSProperties;
  stroke?: string | number;
}) {
  return (
    <svg
      xmlns="http://www.w3.org/2000/svg"
      viewBox="0 0 24 24"
      fill="none"
      stroke="currentColor"
      strokeWidth={stroke}
      strokeLinecap="round"
      strokeLinejoin="round"
      style={style}
    >
      <path d="M5 19l5 -12l5 12" />
      <path d="M7.5 14h5" />
      <path d="M13 5l4 -3" />
    </svg>
  );
}

export interface BubbleMenuItem {
  name: string;
  isActive: () => boolean;
  command: () => void;
  // Rendered as <item.icon style={...} stroke={2} />, so the real contract is
  // just { style?, stroke? }. stroke is string|number to match Tabler's own prop
  // type; Tabler icons and the local IconStress both satisfy it (no cast needed).
  icon: ComponentType<{ style?: CSSProperties; stroke?: string | number }>;
}

type EditorBubbleMenuProps = Omit<BubbleMenuProps, "children" | "editor"> & {
  editor: Editor | null;
  templateMode?: boolean;
};

export const EditorBubbleMenu: FC<EditorBubbleMenuProps> = (props) => {
  const { templateMode = false } = props;
  const { t } = useTranslation();
  const [showCommentPopup, setShowCommentPopup] = useAtom(showCommentPopupAtom);
  const user = useAtomValue(userAtom);
  const editorToolbarEnabled =
    user?.settings?.preferences?.editorToolbar ?? false;
  const [, setDraftCommentId] = useAtom(draftCommentIdAtom);
  const showCommentPopupRef = useRef(showCommentPopup);
  const [showLinkMenu] = useAtom(showLinkMenuAtom);
  const showLinkMenuRef = useRef(showLinkMenu);

  useEffect(() => {
    showCommentPopupRef.current = showCommentPopup;
  }, [showCommentPopup]);

  useEffect(() => {
    showLinkMenuRef.current = showLinkMenu;
  }, [showLinkMenu]);

  const editorState = useEditorState({
    editor: props.editor,
    selector: (ctx) => {
      if (!props.editor) {
        return null;
      }

      return {
        isBold: ctx.editor.isActive("bold"),
        isItalic: ctx.editor.isActive("italic"),
        isUnderline: ctx.editor.isActive("underline"),
        isStrike: ctx.editor.isActive("strike"),
        isCode: ctx.editor.isActive("code"),
        isComment: ctx.editor.isActive("comment"),
        isSpoiler: ctx.editor.isActive("spoiler"),
        // A stress accent already sits right after the selection end.
        isStress: hasStressAfterSelection(ctx.editor.state),
      };
    },
  });

  const items: BubbleMenuItem[] = [
    {
      name: "Bold",
      isActive: () => editorState?.isBold,
      command: () => props.editor.chain().focus().toggleBold().run(),
      icon: IconBold,
    },
    {
      name: "Italic",
      isActive: () => editorState?.isItalic,
      command: () => props.editor.chain().focus().toggleItalic().run(),
      icon: IconItalic,
    },
    {
      name: "Underline",
      isActive: () => editorState?.isUnderline,
      command: () => props.editor.chain().focus().toggleUnderline().run(),
      icon: IconUnderline,
    },
    {
      name: "Strike",
      isActive: () => editorState?.isStrike,
      command: () => props.editor.chain().focus().toggleStrike().run(),
      icon: IconStrikethrough,
    },
    {
      name: "Code",
      isActive: () => editorState?.isCode,
      command: () => props.editor.chain().focus().toggleCode().run(),
      icon: IconCode,
    },
    {
      name: "Spoiler",
      isActive: () => editorState?.isSpoiler,
      command: () => props.editor.chain().focus().toggleSpoiler().run(),
      icon: IconEyeOff,
    },
    {
      name: "Stress",
      isActive: () => editorState?.isStress,
      // Toggle the U+0301 combining accent right after the selected letter.
      // The whole toggle is a single transaction, so one Ctrl+Z reverts it.
      command: () => {
        const editor = props.editor;
        editor.view.dispatch(toggleStressAccent(editor.state));
        editor.view.focus();
      },
      icon: IconStress,
    },
    {
      name: "Clear formatting",
      // Action, not a toggle — never show an active/highlighted state.
      isActive: () => false,
      // Mirror the fixed-toolbar behavior: strip all inline marks from the selection.
      command: () => props.editor.chain().focus().unsetAllMarks().run(),
      icon: IconClearFormatting,
    },
  ];

  const commentItem: BubbleMenuItem = {
    name: "Comment",
    isActive: () => editorState?.isComment,
    command: () => {
      const commentId = uuid7();

      props.editor.chain().focus().setCommentDecoration().run();
      setDraftCommentId(commentId);
      setShowCommentPopup(true);
    },
    icon: IconMessage,
  };

  const bubbleMenuProps: EditorBubbleMenuProps = {
    ...props,
    shouldShow: ({ state, editor }) => {
      const { selection } = state;
      const { empty } = selection;

      if (
        !editor.isEditable ||
        editor.isActive("image") ||
        empty ||
        isNodeSelection(selection) ||
        isCellSelection(selection) ||
        showLinkMenuRef.current ||
        showCommentPopupRef?.current
      ) {
        return false;
      }
      return isTextSelected(editor);
    },
    options: {
      placement: editorToolbarEnabled ? "bottom" : "top",
      offset: 8,
      onHide: () => {
        setIsNodeSelectorOpen(false);
        setIsTextAlignmentOpen(false);
        setIsColorSelectorOpen(false);
      },
    },
  };

  const [isNodeSelectorOpen, setIsNodeSelectorOpen] = useState(false);
  const [isTextAlignmentSelectorOpen, setIsTextAlignmentOpen] = useState(false);
  const [isColorSelectorOpen, setIsColorSelectorOpen] = useState(false);

  // Hide the bubble menu immediately when the link menu is shown
  if (showLinkMenu) return;

  return (
    <BubbleMenu
      {...bubbleMenuProps}
      style={{ zIndex: 199, position: "relative" }}
    >
      <div className={classes.bubbleMenu}>
        {!editorToolbarEnabled && (
          <>
            <NodeSelector
              editor={props.editor}
              isOpen={isNodeSelectorOpen}
              setIsOpen={() => {
                setIsNodeSelectorOpen(!isNodeSelectorOpen);
                setIsTextAlignmentOpen(false);
                setIsColorSelectorOpen(false);
              }}
            />

            <TextAlignmentSelector
              editor={props.editor}
              isOpen={isTextAlignmentSelectorOpen}
              setIsOpen={() => {
                setIsTextAlignmentOpen(!isTextAlignmentSelectorOpen);
                setIsNodeSelectorOpen(false);
                setIsColorSelectorOpen(false);
              }}
            />

            <ActionIcon.Group>
              {items.map((item, index) => (
                <Tooltip key={index} label={t(item.name)} withArrow>
                  <ActionIcon
                    key={index}
                    variant="default"
                    size="lg"
                    radius="0"
                    aria-label={t(item.name)}
                    className={clsx({ [classes.active]: item.isActive() })}
                    style={{ border: "none" }}
                    onClick={item.command}
                  >
                    <item.icon style={{ width: rem(16) }} stroke={2} />
                  </ActionIcon>
                </Tooltip>
              ))}
            </ActionIcon.Group>

            <ColorSelector
              editor={props.editor}
              isOpen={isColorSelectorOpen}
              setIsOpen={() => {
                setIsColorSelectorOpen(!isColorSelectorOpen);
                setIsNodeSelectorOpen(false);
                setIsTextAlignmentOpen(false);
              }}
            />
          </>
        )}

        <LinkSelector />

        {!templateMode && (
          <Tooltip label={t(commentItem.name)} withArrow withinPortal={false}>
            <ActionIcon
              variant="default"
              size="lg"
              radius="6px"
              aria-label={t(commentItem.name)}
              style={{ border: "none" }}
              onClick={commentItem.command}
            >
              <IconMessage size={16} stroke={2} />
            </ActionIcon>
          </Tooltip>
        )}
      </div>
    </BubbleMenu>
  );
};
