import {
  Block,
  BlockConfig,
  blockHasType,
  defaultToggledState,
  UnreachableCaseError,
} from "@blocknote/core";
import { ReactNode, useReducer } from "react";

import { useEditorState } from "../../hooks/useEditorState.js";
import { ReactCustomBlockRenderProps } from "../../schema/ReactBlockSpec.js";

const showChildrenReducer = (
  showChildren: boolean,
  action:
    | {
        type: "toggled";
      }
    | {
        type: "childAdded";
      }
    | {
        type: "lastChildRemoved";
      },
) => {
  if (action.type === "toggled") {
    return !showChildren;
  }

  if (action.type === "childAdded") {
    return true;
  }

  if (action.type === "lastChildRemoved") {
    return false;
  }

  throw new UnreachableCaseError(action);
};

export const ToggleWrapper = (
  props: Omit<
    ReactCustomBlockRenderProps<BlockConfig<any, any, any>>,
    "contentRef"
  > & {
    children: ReactNode;
    toggledState?: {
      set: (block: Block<any, any, any>, isToggled: boolean) => void;
      get: (block: Block<any, any, any>) => boolean;
    };
  },
) => {
  const { block, editor, children, toggledState } = props;

  const [showChildren, dispatch] = useReducer(
    showChildrenReducer,
    (toggledState || defaultToggledState).get(block),
  );

  const handleToggle = (block: Block<any, any, any>) => {
    (toggledState || defaultToggledState).set(
      editor.getBlock(block)!,
      !showChildren,
    );
    dispatch({
      type: "toggled",
    });
  };

  const handleChildAdded = (block: Block<any, any, any>) => {
    (toggledState || defaultToggledState).set(block, true);
    dispatch({
      type: "childAdded",
    });
  };

  const handleLastChildRemoved = (block: Block<any, any, any>) => {
    (toggledState || defaultToggledState).set(block, false);
    dispatch({
      type: "lastChildRemoved",
    });
  };

  const childCount = useEditorState({
    editor,
    selector: ({ editor }) => {
      if (
        !blockHasType(block, editor, block.type, { isToggleable: "boolean" }) &&
        !block.props.isToggleable
      ) {
        return 0;
      }

      const newBlock = editor.getBlock(block)!;
      const newChildCount = newBlock.children.length || 0;

      if (newChildCount > childCount) {
        // If a child block is added while children are hidden, show children.
        if (!showChildren) {
          handleChildAdded(newBlock);
        }
      } else if (newChildCount === 0 && newChildCount < childCount) {
        // If the last child block is removed while children are shown, hide
        // children.
        if (showChildren) {
          handleLastChildRemoved(newBlock);
        }
      }

      return newChildCount;
    },
  });

  if ("isToggleable" in block.props && !block.props.isToggleable) {
    return children;
  }

  return (
    <div>
      <div className="bn-toggle-wrapper" data-show-children={showChildren}>
        <button
          className="bn-toggle-button"
          type="button"
          onMouseDown={(event) => event.preventDefault()}
          onClick={() => handleToggle(editor.getBlock(block)!)}
        >
          <svg
            xmlns="http://www.w3.org/2000/svg"
            height="1em"
            viewBox="0 -960 960 960"
            width="1em"
            fill="currentcolor"
          >
            {/* https://fonts.google.com/icons?selected=Material+Symbols+Rounded:chevron_right:FILL@0;wght@700;GRAD@0;opsz@24&icon.query=chevron&icon.style=Rounded&icon.size=24&icon.color=%23e8eaed */}
            <path d="M472-480 332-620q-18-18-18-44t18-44q18-18 44-18t44 18l183 183q9 9 14 21t5 24q0 12-5 24t-14 21L420-252q-18 18-44 18t-44-18q-18-18-18-44t18-44l140-140Z" />
          </svg>
        </button>
        {children}
      </div>
      {editor.isEditable && showChildren && childCount === 0 && (
        <button
          className="bn-toggle-add-block-button"
          type="button"
          onClick={() => {
            const updatedBlock = editor.updateBlock(block, {
              // Single empty block with default type.
              children: [{}],
            });
            editor.setTextCursorPosition(updatedBlock.children[0].id, "end");
            editor.focus();
          }}
        >
          {editor.dictionary.toggle_blocks.add_block_button}
        </button>
      )}
    </div>
  );
};
