// packages/editor/src/components/collab-sidebar/comments.js
import clsx from "clsx";
import {
  useState,
  RawHTML,
  useEffect,
  useCallback,
  useMemo,
  useRef
} from "@wordpress/element";
import {
  __experimentalText as Text,
  __experimentalHStack as HStack,
  __experimentalVStack as VStack,
  __experimentalConfirmDialog as ConfirmDialog,
  Button,
  FlexItem,
  privateApis as componentsPrivateApis
} from "@wordpress/components";
import { useDebounce } from "@wordpress/compose";
import { published, moreVertical } from "@wordpress/icons";
import { __, _x, sprintf, _n } from "@wordpress/i18n";
import { useSelect, useDispatch } from "@wordpress/data";
import { __unstableStripHTML as stripHTML } from "@wordpress/dom";
import {
  store as blockEditorStore,
  privateApis as blockEditorPrivateApis
} from "@wordpress/block-editor";
import { unlock } from "../../lock-unlock";
import CommentAuthorInfo from "./comment-author-info";
import CommentForm from "./comment-form";
import { focusCommentThread, getCommentExcerpt } from "./utils";
import { useFloatingThread } from "./hooks";
import { AddComment } from "./add-comment";
import { store as editorStore } from "../../store";
import { Fragment, jsx, jsxs } from "react/jsx-runtime";
var { useBlockElement } = unlock(blockEditorPrivateApis);
var { Menu } = unlock(componentsPrivateApis);
function Comments({
  threads: noteThreads,
  onEditComment,
  onAddReply,
  onCommentDelete,
  newNoteFormState,
  setNewNoteFormState,
  commentSidebarRef,
  reflowComments,
  isFloating = false,
  commentLastUpdated
}) {
  const [heights, setHeights] = useState({});
  const [selectedThread, setSelectedThread] = useState(null);
  const [boardOffsets, setBoardOffsets] = useState({});
  const [blockRefs, setBlockRefs] = useState({});
  const { setCanvasMinHeight } = unlock(useDispatch(editorStore));
  const { selectBlock, toggleBlockSpotlight } = unlock(
    useDispatch(blockEditorStore)
  );
  const { blockCommentId, selectedBlockClientId, orderedBlockIds } = useSelect((select) => {
    const {
      getBlockAttributes,
      getSelectedBlockClientId,
      getClientIdsWithDescendants
    } = select(blockEditorStore);
    const clientId = getSelectedBlockClientId();
    return {
      blockCommentId: clientId ? getBlockAttributes(clientId)?.metadata?.noteId : null,
      selectedBlockClientId: clientId,
      orderedBlockIds: getClientIdsWithDescendants()
    };
  }, []);
  const relatedBlockElement = useBlockElement(selectedBlockClientId);
  const threads = useMemo(() => {
    const t = [...noteThreads];
    const orderedThreads = [];
    if (isFloating && newNoteFormState === "open") {
      const newNoteThread = {
        id: "new-note-thread",
        blockClientId: selectedBlockClientId,
        content: { rendered: "" }
      };
      orderedBlockIds.forEach((blockId) => {
        if (blockId === selectedBlockClientId) {
          orderedThreads.push(newNoteThread);
        } else {
          const threadForBlock = t.find(
            (thread) => thread.blockClientId === blockId
          );
          if (threadForBlock) {
            orderedThreads.push(threadForBlock);
          }
        }
      });
      return orderedThreads;
    }
    return t;
  }, [
    noteThreads,
    isFloating,
    newNoteFormState,
    selectedBlockClientId,
    orderedBlockIds
  ]);
  const handleDelete = async (comment) => {
    const currentIndex = threads.findIndex((t) => t.id === comment.id);
    const nextThread = threads[currentIndex + 1];
    const prevThread = threads[currentIndex - 1];
    await onCommentDelete(comment);
    if (comment.parent !== 0) {
      setSelectedThread(comment.parent);
      focusCommentThread(comment.parent, commentSidebarRef.current);
      return;
    }
    if (nextThread) {
      setSelectedThread(nextThread.id);
      focusCommentThread(nextThread.id, commentSidebarRef.current);
    } else if (prevThread) {
      setSelectedThread(prevThread.id);
      focusCommentThread(prevThread.id, commentSidebarRef.current);
    } else {
      setSelectedThread(null);
      setNewNoteFormState("closed");
      relatedBlockElement?.focus();
    }
  };
  useEffect(() => {
    setSelectedThread(
      newNoteFormState === "open" ? "new-note-thread" : blockCommentId
    );
  }, [blockCommentId, newNoteFormState]);
  const setBlockRef = useCallback((id, blockRef) => {
    setBlockRefs((prev) => ({ ...prev, [id]: blockRef }));
  }, []);
  useEffect(() => {
    const calculateAllOffsets = () => {
      const offsets = {};
      if (!isFloating) {
        return { offsets, minHeight: 0 };
      }
      const selectedThreadIndex = threads.findIndex(
        (t) => t.id === selectedThread
      );
      const breakIndex = selectedThreadIndex === -1 ? 0 : selectedThreadIndex;
      const selectedThreadData = threads[breakIndex];
      if (!selectedThreadData || !blockRefs[selectedThreadData.id]) {
        return { offsets, minHeight: 0 };
      }
      let blockElement = blockRefs[selectedThreadData.id];
      let blockRect = blockElement?.getBoundingClientRect();
      const selectedThreadTop = blockRect?.top || 0;
      const selectedThreadHeight = heights[selectedThreadData.id] || 0;
      offsets[selectedThreadData.id] = -16;
      let previousThreadData = {
        threadTop: selectedThreadTop - 16,
        threadHeight: selectedThreadHeight
      };
      for (let i = breakIndex + 1; i < threads.length; i++) {
        const thread = threads[i];
        if (!blockRefs[thread.id]) {
          continue;
        }
        blockElement = blockRefs[thread.id];
        blockRect = blockElement?.getBoundingClientRect();
        const threadTop = blockRect?.top || 0;
        const threadHeight = heights[thread.id] || 0;
        let additionalOffset = -16;
        const previousBottom = previousThreadData.threadTop + previousThreadData.threadHeight;
        if (threadTop < previousBottom + 16) {
          additionalOffset = previousBottom - threadTop + 20;
        }
        offsets[thread.id] = additionalOffset;
        previousThreadData = {
          threadTop: threadTop + additionalOffset,
          threadHeight
        };
      }
      let nextThreadData = {
        threadTop: selectedThreadTop - 16
      };
      for (let i = selectedThreadIndex - 1; i >= 0; i--) {
        const thread = threads[i];
        if (!blockRefs[thread.id]) {
          continue;
        }
        blockElement = blockRefs[thread.id];
        blockRect = blockElement?.getBoundingClientRect();
        const threadTop = blockRect?.top || 0;
        const threadHeight = heights[thread.id] || 0;
        let additionalOffset = -16;
        const threadBottom = threadTop + threadHeight;
        if (threadBottom > nextThreadData.threadTop) {
          additionalOffset = nextThreadData.threadTop - threadTop - threadHeight - 20;
        }
        offsets[thread.id] = additionalOffset;
        nextThreadData = {
          threadTop: threadTop + additionalOffset
        };
      }
      let editorMinHeight = 0;
      const lastThread = threads[threads.length - 1];
      if (blockRefs[lastThread.id]) {
        const lastBlockElement = blockRefs[lastThread.id];
        const lastBlockRect = lastBlockElement?.getBoundingClientRect();
        const lastThreadTop = lastBlockRect?.top || 0;
        const lastThreadHeight = heights[lastThread.id] || 0;
        const lastThreadOffset = offsets[lastThread.id] || 0;
        editorMinHeight = lastThreadTop + lastThreadHeight + lastThreadOffset + 32;
      }
      return { offsets, minHeight: editorMinHeight };
    };
    const { offsets: newOffsets, minHeight } = calculateAllOffsets();
    if (Object.keys(newOffsets).length > 0) {
      setBoardOffsets(newOffsets);
    }
    setCanvasMinHeight(minHeight);
  }, [
    heights,
    blockRefs,
    isFloating,
    threads,
    selectedThread,
    setCanvasMinHeight
  ]);
  const handleThreadNavigation = (event, thread, isSelected) => {
    if (event.defaultPrevented) {
      return;
    }
    const currentIndex = threads.findIndex((t) => t.id === thread.id);
    if ((event.key === "Enter" || event.key === "ArrowRight") && event.currentTarget === event.target && !isSelected) {
      setNewNoteFormState("closed");
      setSelectedThread(thread.id);
      if (!!thread.blockClientId) {
        selectBlock(thread.blockClientId, null);
        toggleBlockSpotlight(thread.blockClientId, true);
      }
    } else if ((event.key === "Enter" || event.key === "ArrowLeft") && event.currentTarget === event.target && isSelected || event.key === "Escape") {
      setSelectedThread(null);
      setNewNoteFormState("closed");
      if (thread.blockClientId) {
        toggleBlockSpotlight(thread.blockClientId, false);
      }
      focusCommentThread(thread.id, commentSidebarRef.current);
    } else if (event.key === "ArrowDown" && currentIndex < threads.length - 1 && event.currentTarget === event.target) {
      const nextThread = threads[currentIndex + 1];
      focusCommentThread(nextThread.id, commentSidebarRef.current);
    } else if (event.key === "ArrowUp" && currentIndex > 0 && event.currentTarget === event.target) {
      const prevThread = threads[currentIndex - 1];
      focusCommentThread(prevThread.id, commentSidebarRef.current);
    } else if (event.key === "Home" && event.currentTarget === event.target) {
      focusCommentThread(threads[0].id, commentSidebarRef.current);
    } else if (event.key === "End" && event.currentTarget === event.target) {
      focusCommentThread(
        threads[threads.length - 1].id,
        commentSidebarRef.current
      );
    }
  };
  const hasThreads = Array.isArray(threads) && threads.length > 0;
  if (!hasThreads && !isFloating) {
    return /* @__PURE__ */ jsx(
      AddComment,
      {
        onSubmit: onAddReply,
        newNoteFormState,
        setNewNoteFormState,
        commentSidebarRef
      }
    );
  }
  return /* @__PURE__ */ jsxs(Fragment, { children: [
    !isFloating && newNoteFormState === "open" && /* @__PURE__ */ jsx(
      AddComment,
      {
        onSubmit: onAddReply,
        newNoteFormState,
        setNewNoteFormState,
        commentSidebarRef
      }
    ),
    threads.map((thread) => /* @__PURE__ */ jsx(
      Thread,
      {
        thread,
        onAddReply,
        onCommentDelete: handleDelete,
        onEditComment,
        isSelected: selectedThread === thread.id,
        setSelectedThread,
        setNewNoteFormState,
        commentSidebarRef,
        reflowComments,
        isFloating,
        calculatedOffset: boardOffsets[thread.id] ?? 0,
        setHeights,
        setBlockRef,
        selectedThread,
        commentLastUpdated,
        newNoteFormState,
        onKeyDown: (event) => handleThreadNavigation(
          event,
          thread,
          selectedThread === thread.id
        )
      },
      thread.id
    ))
  ] });
}
function Thread({
  thread,
  onEditComment,
  onAddReply,
  onCommentDelete,
  isSelected,
  setNewNoteFormState,
  commentSidebarRef,
  reflowComments,
  isFloating,
  calculatedOffset,
  setHeights,
  setBlockRef,
  setSelectedThread,
  selectedThread,
  commentLastUpdated,
  newNoteFormState,
  onKeyDown
}) {
  const { toggleBlockHighlight, selectBlock, toggleBlockSpotlight } = unlock(
    useDispatch(blockEditorStore)
  );
  const relatedBlockElement = useBlockElement(thread.blockClientId);
  const debouncedToggleBlockHighlight = useDebounce(
    toggleBlockHighlight,
    50
  );
  const { y, refs } = useFloatingThread({
    thread,
    calculatedOffset,
    setHeights,
    setBlockRef,
    selectedThread,
    commentLastUpdated
  });
  const onMouseEnter = () => {
    debouncedToggleBlockHighlight(thread.blockClientId, true);
  };
  const onMouseLeave = () => {
    debouncedToggleBlockHighlight(thread.blockClientId, false);
  };
  const handleCommentSelect = () => {
    setNewNoteFormState("closed");
    setSelectedThread(thread.id);
    if (!!thread.blockClientId) {
      selectBlock(thread.blockClientId, null);
      toggleBlockSpotlight(thread.blockClientId, true);
    }
  };
  const unselectThread = () => {
    setSelectedThread(null);
    setNewNoteFormState("closed");
    toggleBlockSpotlight(thread.blockClientId, false);
  };
  const allReplies = thread?.reply || [];
  const lastReply = allReplies.length > 0 ? allReplies[allReplies.length - 1] : void 0;
  const restReplies = allReplies.length > 0 ? allReplies.slice(0, -1) : [];
  const commentExcerpt = getCommentExcerpt(
    stripHTML(thread.content?.rendered),
    10
  );
  const ariaLabel = !!thread.blockClientId ? sprintf(
    // translators: %s: note excerpt
    __("Note: %s"),
    commentExcerpt
  ) : sprintf(
    // translators: %s: note excerpt
    __("Original block deleted. Note: %s"),
    commentExcerpt
  );
  if (thread.id === "new-note-thread" && newNoteFormState === "open" && isFloating) {
    return /* @__PURE__ */ jsx(
      AddComment,
      {
        onSubmit: onAddReply,
        newNoteFormState,
        setNewNoteFormState,
        commentSidebarRef,
        reflowComments,
        isFloating,
        y,
        refs
      }
    );
  }
  return /* @__PURE__ */ jsxs(
    VStack,
    {
      className: clsx("editor-collab-sidebar-panel__thread", {
        "is-selected": isSelected,
        "is-floating": isFloating
      }),
      id: `comment-thread-${thread.id}`,
      spacing: "3",
      onClick: handleCommentSelect,
      onMouseEnter,
      onMouseLeave,
      onFocus: onMouseEnter,
      onBlur: onMouseLeave,
      onKeyDown,
      tabIndex: 0,
      role: "treeitem",
      "aria-label": ariaLabel,
      "aria-expanded": isSelected,
      ref: isFloating ? refs.setFloating : void 0,
      style: isFloating ? { top: y } : void 0,
      children: [
        /* @__PURE__ */ jsx(
          Button,
          {
            className: "editor-collab-sidebar-panel__skip-to-comment",
            variant: "secondary",
            size: "compact",
            onClick: () => {
              focusCommentThread(
                thread.id,
                commentSidebarRef.current,
                "textarea"
              );
            },
            children: __("Add new reply")
          }
        ),
        !thread.blockClientId && /* @__PURE__ */ jsx(Text, { as: "p", weight: 500, variant: "muted", children: __("Original block deleted.") }),
        /* @__PURE__ */ jsx(
          CommentBoard,
          {
            thread,
            isExpanded: isSelected,
            onEdit: (params = {}) => {
              onEditComment(params);
              if (params.status === "approved") {
                unselectThread();
                if (isFloating) {
                  relatedBlockElement?.focus();
                } else {
                  focusCommentThread(
                    thread.id,
                    commentSidebarRef.current
                  );
                }
              }
            },
            onDelete: onCommentDelete,
            reflowComments
          }
        ),
        isSelected && allReplies.map((reply) => /* @__PURE__ */ jsx(
          CommentBoard,
          {
            thread: reply,
            parent: thread,
            isExpanded: isSelected,
            onEdit: onEditComment,
            onDelete: onCommentDelete,
            reflowComments
          },
          reply.id
        )),
        !isSelected && restReplies.length > 0 && /* @__PURE__ */ jsx(HStack, { className: "editor-collab-sidebar-panel__more-reply-separator", children: /* @__PURE__ */ jsx(
          Button,
          {
            size: "compact",
            variant: "tertiary",
            className: "editor-collab-sidebar-panel__more-reply-button",
            onClick: () => {
              setSelectedThread(thread.id);
              focusCommentThread(
                thread.id,
                commentSidebarRef.current
              );
            },
            children: sprintf(
              // translators: %s: number of replies.
              _n(
                "%s more reply",
                "%s more replies",
                restReplies.length
              ),
              restReplies.length
            )
          }
        ) }),
        !isSelected && lastReply && /* @__PURE__ */ jsx(
          CommentBoard,
          {
            thread: lastReply,
            parent: thread,
            isExpanded: isSelected,
            onEdit: onEditComment,
            onDelete: onCommentDelete,
            reflowComments
          }
        ),
        isSelected && /* @__PURE__ */ jsxs(VStack, { spacing: "2", role: "treeitem", children: [
          /* @__PURE__ */ jsx(HStack, { alignment: "left", spacing: "3", justify: "flex-start", children: /* @__PURE__ */ jsx(CommentAuthorInfo, {}) }),
          /* @__PURE__ */ jsx(VStack, { spacing: "2", children: /* @__PURE__ */ jsx(
            CommentForm,
            {
              onSubmit: (inputComment) => {
                if ("approved" === thread.status) {
                  onEditComment({
                    id: thread.id,
                    status: "hold",
                    content: inputComment
                  });
                } else {
                  onAddReply({
                    content: inputComment,
                    parent: thread.id
                  });
                }
              },
              onCancel: (event) => {
                event.stopPropagation();
                unselectThread();
                focusCommentThread(
                  thread.id,
                  commentSidebarRef.current
                );
              },
              submitButtonText: "approved" === thread.status ? __("Reopen & Reply") : __("Reply"),
              rows: "approved" === thread.status ? 2 : 4,
              labelText: sprintf(
                // translators: %1$s: note identifier, %2$s: author name
                __("Reply to note %1$s by %2$s"),
                thread.id,
                thread.author_name
              ),
              reflowComments
            }
          ) })
        ] }),
        !!thread.blockClientId && /* @__PURE__ */ jsx(
          Button,
          {
            className: "editor-collab-sidebar-panel__skip-to-block",
            variant: "secondary",
            size: "compact",
            onClick: (event) => {
              event.stopPropagation();
              relatedBlockElement?.focus();
            },
            children: __("Back to block")
          }
        )
      ]
    }
  );
}
var CommentBoard = ({
  thread,
  parent,
  isExpanded,
  onEdit,
  onDelete,
  reflowComments
}) => {
  const [actionState, setActionState] = useState(false);
  const [showConfirmDialog, setShowConfirmDialog] = useState(false);
  const actionButtonRef = useRef(null);
  const handleConfirmDelete = () => {
    onDelete(thread);
    setActionState(false);
    setShowConfirmDialog(false);
  };
  const handleCancel = () => {
    setActionState(false);
    setShowConfirmDialog(false);
    actionButtonRef.current?.focus();
  };
  const isResolutionComment = thread.type === "note" && thread.meta && (thread.meta._wp_note_status === "resolved" || thread.meta._wp_note_status === "reopen");
  const actions = [
    {
      id: "edit",
      title: __("Edit"),
      isEligible: ({ status }) => status !== "approved",
      onClick: () => {
        setActionState("edit");
      }
    },
    {
      id: "reopen",
      title: _x("Reopen", "Reopen note"),
      isEligible: ({ status }) => status === "approved",
      onClick: () => {
        onEdit({ id: thread.id, status: "hold" });
      }
    },
    {
      id: "delete",
      title: __("Delete"),
      isEligible: () => true,
      onClick: () => {
        setActionState("delete");
        setShowConfirmDialog(true);
      }
    }
  ];
  const canResolve = thread.parent === 0;
  const moreActions = parent?.status !== "approved" ? actions.filter((item) => item.isEligible(thread)) : [];
  return /* @__PURE__ */ jsxs(
    VStack,
    {
      spacing: "2",
      role: thread.parent !== 0 ? "treeitem" : void 0,
      children: [
        /* @__PURE__ */ jsxs(HStack, { alignment: "left", spacing: "3", justify: "flex-start", children: [
          /* @__PURE__ */ jsx(
            CommentAuthorInfo,
            {
              avatar: thread?.author_avatar_urls?.[48],
              name: thread?.author_name,
              date: thread?.date,
              userId: thread?.author
            }
          ),
          isExpanded && /* @__PURE__ */ jsx(
            FlexItem,
            {
              className: "editor-collab-sidebar-panel__comment-status",
              onClick: (event) => {
                event.stopPropagation();
              },
              children: /* @__PURE__ */ jsxs(HStack, { spacing: "0", children: [
                canResolve && /* @__PURE__ */ jsx(
                  Button,
                  {
                    label: _x(
                      "Resolve",
                      "Mark note as resolved"
                    ),
                    size: "small",
                    icon: published,
                    disabled: thread.status === "approved",
                    accessibleWhenDisabled: thread.status === "approved",
                    onClick: () => {
                      onEdit({
                        id: thread.id,
                        status: "approved"
                      });
                    }
                  }
                ),
                /* @__PURE__ */ jsxs(Menu, { placement: "bottom-end", children: [
                  /* @__PURE__ */ jsx(
                    Menu.TriggerButton,
                    {
                      render: /* @__PURE__ */ jsx(
                        Button,
                        {
                          ref: actionButtonRef,
                          size: "small",
                          icon: moreVertical,
                          label: __("Actions"),
                          disabled: !moreActions.length,
                          accessibleWhenDisabled: true
                        }
                      )
                    }
                  ),
                  /* @__PURE__ */ jsx(Menu.Popover, { children: moreActions.map((action) => /* @__PURE__ */ jsx(
                    Menu.Item,
                    {
                      onClick: () => action.onClick(),
                      children: /* @__PURE__ */ jsx(Menu.ItemLabel, { children: action.title })
                    },
                    action.id
                  )) })
                ] })
              ] })
            }
          )
        ] }),
        "edit" === actionState ? /* @__PURE__ */ jsx(
          CommentForm,
          {
            onSubmit: (value) => {
              onEdit({
                id: thread.id,
                content: value
              });
              setActionState(false);
              actionButtonRef.current?.focus();
            },
            onCancel: () => handleCancel(),
            thread,
            submitButtonText: _x("Update", "verb"),
            labelText: sprintf(
              // translators: %1$s: note identifier, %2$s: author name.
              __("Edit note %1$s by %2$s"),
              thread.id,
              thread.author_name
            ),
            reflowComments
          }
        ) : /* @__PURE__ */ jsx(
          RawHTML,
          {
            className: clsx(
              "editor-collab-sidebar-panel__user-comment",
              {
                "editor-collab-sidebar-panel__resolution-text": isResolutionComment
              }
            ),
            children: isResolutionComment ? (() => {
              const actionText = thread.meta._wp_note_status === "resolved" ? __("Marked as resolved") : __("Reopened");
              const content = thread?.content?.raw;
              if (content && typeof content === "string" && content.trim() !== "") {
                return sprintf(
                  // translators: %1$s: action label ("Marked as resolved" or "Reopened"); %2$s: note text.
                  __("%1$s: %2$s"),
                  actionText,
                  content
                );
              }
              return actionText;
            })() : thread?.content?.rendered
          }
        ),
        "delete" === actionState && /* @__PURE__ */ jsx(
          ConfirmDialog,
          {
            isOpen: showConfirmDialog,
            onConfirm: handleConfirmDelete,
            onCancel: handleCancel,
            confirmButtonText: __("Delete"),
            children: __(
              "Are you sure you want to delete this note? This will also delete all of this note's replies."
            )
          }
        )
      ]
    }
  );
};
var comments_default = Comments;
export {
  Comments,
  comments_default as default
};
//# sourceMappingURL=comments.js.map
