type LinkProps = AnchorHTMLAttributes<HTMLAnchorElement>;

const Link = createBlock<LinkProps>((props) => {
  const { onSelectionChange, selectBlockContents } = useEditorState(props);
  const popover = usePopoverState({ portal: true });
  const [href, setHref] = useState(props.href);

  useEffect(() => {
    setHref(props.href);
  }, [props.href]);

  useEffect(() => {
    return onSelectionChange((selection) => {
      if (selection.startContainerId === props.id || selection.endContainerId === props.id) {
        popover.show();
      }
    });
  }, [onSelect, props.id, popover.show]);

  return (
    <>
      <PopoverDisclosure
        as="a"
        state={popover}
        {...props}
        href={href}
        onClick={(event) => {
          props.onClick?.(event);
          if (event.defaultPrevented) return;
          event.preventDefault();
          popover.selectBlockConents();
        }}
      >
        {(disclosureProps) => (
          <Text as="a" {...disclosureProps} />
        )}
      </PopoverDisclosure>
      <Popover autoFocusOnShow={false} state={popover}>
        <a href={href} />
        <button>Edit</button>
      </Popover>
    </>
  );
});

const ListItem = createBlock((props) => {
  const { value, insertBlock } = useEditorState(props);
  useEnter((defaultAction) => {
    if (value.length) {
      insertBlock("ListItem");
    } else {
      defaultAction();
    }
  }, [value, insertBlock]);
  return <Text as="li" {...props} />;
});

const ListItem = createEditorComponent((props) => {
  const editor = useEditorState(props);
  useEditorEnter(() => {
    editor.insertElement("ListItem");
  }, [editor.insertElement]);
  return <Text as="li" {...props} />;
});

type ListProps = {
  inputs: {
    type: "ordered" | "unordered";
  };
};

const List = createEditorComponent<ListProps>((props) => {
  const editor = useEditorState({
    ...props,
    defaultValue: [{
      type: "ListItem",
      id: `${props.id}-1`,
    }],
  });
  const type = useEditorInput(
    ToggleGroup,
    {
      ...props,
      name: "type",
      options: [
        { value: "ordered", label: "Ordered", icon: "Ordered" },
        { value: "unordered", label: "Unordered", icon: "Unordered" },
      ],
    }
  );
  const component = type === "ordered" ? "ol" : "ul";
  
  // if defaultValue doesn't work
  useLayoutEffect(() => {
    if (!editor.value.length) {
      const selection = {
        startContainerId: props.id,
        startOffset: 0,
        endContainerId: props.id,
        endOffset: 0,
      };
      editor.insertElement("ListItem", null, selection);
    }
  }, [editor.insertElement, editor.value, props.id]);
  
  return <EditorComponent as={component} {...props} />;
});

type HeadingProps = TextProps & {
  inputs: {
    level?: number;
  };
};

const Heading = createEditorComponent<HeadingProps>((props) => {
  const level = useEditorInput(HeadingLevel, { ...props, name: "level", label: "Heading level" });
  const component = `h${level}`;
  
  useEditorReplace("Paragraph");
  
  return <Text as={component} {...props} />;
});

Heading.defaultProps = {
  inputs: {
    level: 1,
  },
};

Heading.triggers = {
  "#{1,6}": (text) => ({
    inputs: {
      level: text.length,
    },
  });
};

const HeadingLevel = createEditorInput<HeadingProps>((props) => {
  useEditorInputValue(props, "value");
  return <div />;
});
  
const defaultComponents = {
  Paragraph,
  Bold,
  Heading,
  ...
};
  
function App() {
  const editor = useEditorState({ defaultComponents });
  editor.getSelection(); // { startContainerId: "id", startOffset: 0, endContainerId: "id", endOffset: 3, collapsed: false }
  editor.insertElement("Paragraph", null, editor.getSelection());
  editor.insertElement(Heading, { inputs: { level: 2 } });
  editor.replaceElement(Paragraph);
  return <Editor state={editor} />;
}