import { Button, Tabs, Typography } from 'antd';
import { createStyles } from 'antd-style';
import { LiveContext } from 'dumi';
import toReactElement from 'jsonml-to-react-element';
import JsonML from 'jsonml.js/lib/utils';
import Prism from 'prismjs';
import React, { useContext, useEffect, useMemo } from 'react';
import LiveCode from './LiveCode';

const useStyle = createStyles(({ token, css }) => {
  const { colorIcon, colorBgTextHover, antCls } = token;

  return {
    code: css`
      position: relative;
      margin-top: -16px;
    `,

    copyButton: css`
      color: ${colorIcon};
      position: absolute;
      top: 16px;
      inset-inline-end: 16px;
      width: 32px;
      text-align: center;
      background: ${colorBgTextHover};
      padding: 0;
    `,

    copyIcon: css`
      ${antCls}-typography-copy {
        margin-inline-start: 0;
      }
      ${antCls}-typography-copy:not(${antCls}-typography-copy-success) {
        color: ${colorIcon};

        &:hover {
          color: ${colorIcon};
        }
      }
    `,
  };
});

const LANGS = {
  tsx: 'TypeScript',
  jsx: 'JavaScript',
  style: 'CSS',
};

interface CodePreviewProps {
  sourceCode?: string;
  jsxCode?: string;
  styleCode?: string;
  onCodeTypeChange?: (activeKey: string) => void;
}

function toReactComponent(jsonML: any[]) {
  return toReactElement(jsonML, [
    [
      (node: any) => JsonML.isElement(node) && JsonML.getTagName(node) === 'pre',
      (node: any, index: number) => {
        const attr = JsonML.getAttributes(node);
        return (
          <pre key={index} className={`language-${attr.lang}`}>
            <code dangerouslySetInnerHTML={{ __html: attr.highlighted }} />
          </pre>
        );
      },
    ],
  ]);
}

const CodePreview: React.FC<CodePreviewProps> = ({
  sourceCode = '',
  jsxCode = '',
  styleCode = '',
  onCodeTypeChange,
}) => {
  // 避免 Tabs 数量不稳定的闪动问题
  const initialCodes = {} as Record<'tsx' | 'jsx' | 'style', string>;
  if (sourceCode) {
    initialCodes.tsx = '';
  }
  if (jsxCode) {
    initialCodes.jsx = '';
  }
  if (styleCode) {
    initialCodes.style = '';
  }
  const [highlightedCodes, setHighlightedCodes] = React.useState(initialCodes);
  const sourceCodes = {
    tsx: sourceCode,
    jsx: jsxCode,
    style: styleCode,
  } as Record<'tsx' | 'jsx' | 'style', string>;
  useEffect(() => {
    const codes = {
      tsx: Prism.highlight(sourceCode, Prism.languages.javascript, 'jsx'),
      jsx: Prism.highlight(jsxCode, Prism.languages.javascript, 'jsx'),
      style: Prism.highlight(styleCode, Prism.languages.css, 'css'),
    };
    // 去掉空的代码类型
    Object.keys(codes).forEach((key: keyof typeof codes) => {
      if (!codes[key]) {
        delete codes[key];
      }
    });
    setHighlightedCodes(codes);
  }, [jsxCode, sourceCode, styleCode]);

  const langList = Object.keys(highlightedCodes);

  const { styles } = useStyle();

  const { enabled: liveEnabled } = useContext(LiveContext);

  const items = useMemo(
    () =>
      langList.map((lang: keyof typeof LANGS) => ({
        label: LANGS[lang],
        key: lang,
        children: (
          <div className={styles.code}>
            {lang === 'tsx' && liveEnabled ? (
              <LiveCode />
            ) : (
              toReactComponent(['pre', { lang, highlighted: highlightedCodes[lang] }])
            )}
            <Button type="text" className={styles.copyButton}>
              <Typography.Text className={styles.copyIcon} copyable={{ text: sourceCodes[lang] }} />
            </Button>
          </div>
        ),
      })),
    [JSON.stringify(highlightedCodes)],
  );

  if (!langList.length) {
    return null;
  }

  if (langList.length === 1) {
    return liveEnabled ? (
      <LiveCode />
    ) : (
      toReactComponent([
        'pre',
        {
          lang: langList[0],
          highlighted: highlightedCodes[langList[0] as keyof typeof LANGS],
          className: 'highlight',
        },
      ])
    );
  }

  return <Tabs centered className="highlight" onChange={onCodeTypeChange} items={items} />;
};

export default CodePreview;