// customization:
// - Element component to render heading tags correctly
import React from 'react';
import { renderToStaticMarkup } from 'react-dom/server';
import { useLocation } from 'react-router-dom';
import { toast } from 'react-toastify';
import { useIntl } from 'react-intl';
import { useSelector } from 'react-redux';
import { Node, Text } from 'slate';
import cx from 'classnames';
import { isEmpty, omit } from 'lodash';
import { UniversalLink, Toast } from '@plone/volto/components';
import { messages, addAppURL } from '@plone/volto/helpers';
import useClipboard from '@plone/volto/hooks/clipboard/useClipboard';
import config from '@plone/volto/registry';
import linkSVG from '@plone/volto/icons/link.svg';

import '@plone/volto-slate/editor/less/slate.less';

const OMITTED = ['editor', 'path'];

// TODO: read, see if relevant
// https://reactjs.org/docs/higher-order-components.html#dont-use-hocs-inside-the-render-method

// customization to
// fix: heading node does not receive Slate attributes
// solution:  Element replaces headings with native DOM node that receives {...attributes}
// todo: remove after update to correct version of volto
export const Element = ({
  element,
  attributes = {},
  extras,
  children,
  ...rest
}) => {
  const type = element?.type;

  // Render native heading tag so Slate attrs (ref, data-*, contentEditable...) land on the actual DOM node
  if (/^h[1-6]$/.test(type)) {
    const Tag = type;
    return <Tag {...attributes}>{children}</Tag>;
  }

  const { slate } = config.settings;
  const { elements } = slate;
  const El = elements[type] || elements['default'];

  // preserve attributes (and merge className with element.styleName if any)
  const attrs = {
    ...attributes,
    className:
      [attributes?.className, element?.styleName].filter(Boolean).join(' ') ||
      undefined,
  };
  if (attributes?.ref) attrs.ref = attributes.ref;

  return (
    <El
      element={element}
      {...omit(rest, OMITTED)}
      attributes={attrs}
      extras={extras}
    >
      {children}
    </El>
  );
};

export const Leaf = ({ children, ...rest }) => {
  const { attributes, leaf, mode } = rest;
  let { leafs } = config.settings.slate;

  children = Object.keys(leafs).reduce((acc, name) => {
    return Object.keys(leaf).includes(name)
      ? leafs[name]({ children: acc })
      : acc;
  }, children);

  const classNames = {
    [`highlight-${leaf.highlightType}`]: mode !== 'view' && leaf.highlightType,
    'highlight-selection': mode !== 'view' && leaf.isSelection,
  };

  // stylemenu support
  for (const prop in leaf) {
    if (prop.startsWith('style-')) {
      classNames[prop.substring(6)] = true;
    }
  }

  const klass = cx(classNames);

  return mode === 'view' ? (
    typeof children === 'string' ? (
      children.split('\n').map((t, i) => {
        // Softbreak support. Should do a plugin?
        return (
          <React.Fragment key={`${i}`}>
            {children.indexOf('\n') > -1 &&
            children.split('\n').length - 1 > i ? (
              <>
                {klass ? <span className={klass}>{t}</span> : t}
                <br />
              </>
            ) : klass ? (
              <span className={klass}>{t}</span>
            ) : (
              t
            )}
          </React.Fragment>
        );
      })
    ) : (
      <span className={klass}>{children}</span>
    )
  ) : (
    <span {...attributes} className={klass}>
      {children}
    </span>
  );
};

const serializeData = (node) => {
  return JSON.stringify({ type: node.type, data: node.data });
};

export const serializeNodes = (nodes, getAttributes, extras = {}) => {
  const editor = { children: nodes || [] };

  const _serializeNodes = (nodes) => {
    return (nodes || []).map(([node, path], i) => {
      return Text.isText(node) ? (
        <Leaf path={path} leaf={node} text={node} mode="view" key={path}>
          {node.text}
        </Leaf>
      ) : (
        <Element
          path={path}
          element={node}
          mode="view"
          key={path}
          data-slate-data={node.data ? serializeData(node) : null}
          attributes={getAttributes ? getAttributes(node, path) : null}
          extras={extras}
        >
          {_serializeNodes(Array.from(Node.children(editor, path)))}
        </Element>
      );
    });
  };

  return _serializeNodes(Array.from(Node.children(editor, [])));
};

/**
 * Get the concatenated text string of a node's content.
 *
 * Note that this WILL include spaces between block node leafs in contrary to
 * the original slate method. This function joins text of nodes with
 * separating spaces to produce a string for indexing purposes.
 *
 */
const ConcatenatedString = (node) => {
  if (Text.isText(node)) {
    return node.text.trim();
  } else {
    return node.children.map(ConcatenatedString).join(' ');
  }
};

/**
 * @function serializeNodesToText
 *
 * @param {Array[Node]} nodes
 *
 * @returns {string}
 */
export const serializeNodesToText = (nodes) => {
  return nodes.map(ConcatenatedString).join('\n');
};

export const serializeNodesToHtml = (nodes) =>
  renderToStaticMarkup(serializeNodes(nodes));

export const renderLinkElement = (tagName) => {
  function LinkElement({
    attributes,
    children,
    mode = 'edit',
    className = null,
  }) {
    const { slate = {} } = config.settings;
    const Tag = tagName;
    const slug = attributes.id || '';
    const location = useLocation();
    const token = useSelector((state) => state.userSession.token);
    const appPathname = addAppURL(location.pathname);
    // eslint-disable-next-line no-unused-vars
    const [copied, copy, setCopied] = useClipboard(
      appPathname.concat(`#${slug}`),
    );
    const intl = useIntl();
    return !token || slate.useLinkedHeadings === false ? (
      <Tag {...attributes} className={className} tabIndex={0}>
        {children}
      </Tag>
    ) : (
      <Tag {...attributes} className={className} tabIndex={0}>
        {children}
        {mode === 'view' && slug && (
          <UniversalLink
            className="anchor"
            aria-hidden="true"
            tabIndex={-1}
            href={`#${slug}`}
          >
            <style>
              {/* Prettify the unstyled flash of the link icon on development */}
              {`
              a.anchor svg {
                height: var(--anchor-svg-height, 24px);
              }
              `}
            </style>
            <svg
              {...linkSVG.attributes}
              dangerouslySetInnerHTML={{ __html: linkSVG.content }}
              height={null}
              onClick={() => {
                copy();

                toast.info(
                  <Toast
                    info
                    title={intl.formatMessage(messages.success)}
                    content={intl.formatMessage(messages.urlClipboardCopy)}
                  />,
                );
              }}
            ></svg>
          </UniversalLink>
        )}
      </Tag>
    );
  }
  LinkElement.displayName = `${tagName}LinkElement`;
  return LinkElement;
};
