/* eslint-disable @typescript-eslint/consistent-type-definitions */
import { Extension } from '@tiptap/core';
import '@tiptap/extension-text-style';

export type FontSizeOptions = {
  /**
   * Mark types where the `fontSize` attribute is applied (via inline `<span>` styles).
   */
  types: string[];
  /**
   * Node types where the `fontSize` attribute is applied so that the rendered
   * element itself receives the inline `font-size` style. This is required for
   * list items, whose `::marker` (the bullet/number) inherits font-size from
   * the `<li>` element and not from the inner `<span>` produced by `textStyle`.
   */
  nodeTypes: string[];
};

declare module '@tiptap/core' {
  interface Commands<ReturnType> {
    fontSize: {
      /**
       * Set the font size
       */
      setFontSize: (fontSize: string) => ReturnType;
      /**
       * Unset the font size
       */
      unsetFontSize: () => ReturnType;
    };
  }
}

export default Extension.create<FontSizeOptions>({
  name: 'fontSize',

  addOptions() {
    return {
      types: ['textStyle'],
      nodeTypes: ['listItem'],
    };
  },

  addGlobalAttributes() {
    const fontSizeAttribute = {
      default: null as string | null,
      parseHTML: (element: HTMLElement) =>
        element.style.fontSize.replace(/['"]+/g, '') || null,
      renderHTML: (attributes: { fontSize?: string | null }) => {
        if (!attributes.fontSize) {
          return {};
        }

        return {
          style: `font-size: ${attributes.fontSize}`,
        };
      },
    };

    return [
      {
        types: this.options.types,
        attributes: {
          fontSize: fontSizeAttribute,
        },
      },
      {
        types: this.options.nodeTypes,
        attributes: {
          fontSize: fontSizeAttribute,
        },
      },
    ];
  },

  addCommands() {
    const { nodeTypes } = this.options;

    return {
      setFontSize:
        fontSize =>
        ({ chain }) => {
          return chain()
            .setMark('textStyle', { fontSize })
            .command(({ tr, state }) => {
              const { from, to } = state.selection;
              if (from !== to) {
                state.doc.nodesBetween(from, to, (node, pos) => {
                  if (nodeTypes.includes(node.type.name)) {
                    tr.setNodeAttribute(pos, 'fontSize', fontSize);
                  }
                });
              }
              return true;
            })
            .run();
        },
      unsetFontSize:
        () =>
        ({ chain }) => {
          return chain()
            .setMark('textStyle', { fontSize: null })
            .removeEmptyTextStyle()
            .command(({ tr, state }) => {
              const { from, to } = state.selection;
              if (from !== to) {
                state.doc.nodesBetween(from, to, (node, pos) => {
                  if (nodeTypes.includes(node.type.name)) {
                    tr.setNodeAttribute(pos, 'fontSize', null);
                  }
                });
              }
              return true;
            })
            .run();
        },
    };
  },
});
