import React, { cloneElement, isValidElement } from 'react';
import { BugOutlined } from '@ant-design/icons';
import { Button, Drawer, Flex, Grid, Popover, Tag, Timeline, Typography } from 'antd';
import type { TimelineItemProps } from 'antd';
import { createStyles } from 'antd-style';
import semver from 'semver';

import deprecatedVersions from '../../../../BUG_VERSIONS.json';
import useFetch from '../../../hooks/useFetch';
import useLocale from '../../../hooks/useLocale';
import useLocation from '../../../hooks/useLocation';
import Link from '../Link';

interface MatchDeprecatedResult {
  match?: string;
  reason: string[];
}

interface ChangelogInfo {
  version: string;
  changelog: string;
  refs: string[];
  releaseDate: string;
}

function matchDeprecated(v: string): MatchDeprecatedResult {
  const match = Object.keys(deprecatedVersions).find((depreciated) =>
    semver.satisfies(v, depreciated),
  );
  const reason = deprecatedVersions[match as keyof typeof deprecatedVersions] || [];
  return {
    match,
    reason: Array.isArray(reason) ? reason : [reason],
  };
}

const useStyle = createStyles(({ token, css }) => ({
  listWrap: css`
    > li {
      line-height: 2;
    }
  `,
  linkRef: css`
    margin-inline-start: ${token.marginXS}px;
  `,
  bug: css`
    font-size: ${token.fontSize}px;
    color: #aaa;
    margin-inline-start: ${token.marginXS}px;
    display: inline-block;
    vertical-align: inherit;
    cursor: pointer;
    &:hover {
      color: #333;
    }
  `,
  bugReasonTitle: css`
    padding: ${token.paddingXXS}px ${token.paddingXS}px;
  `,
  bugReasonList: css`
    width: 100%;
    max-width: 100%;
    li {
      padding: ${token.paddingXXS}px ${token.paddingXS}px;
      a {
        display: flex;
        align-items: center;
        gap: ${token.marginXXS}px;
      }
    }
  `,
  extraLink: css`
    font-size: ${token.fontSize}px;
  `,
  drawerContent: {
    position: 'relative',
    [`> ${token.antCls}-drawer-body`]: {
      scrollbarWidth: 'thin',
      scrollbarGutter: 'stable',
    },
  },
  versionWrap: css`
    margin-bottom: 1em;
  `,
  versionTitle: css`
    height: 28px;
    line-height: 28px;
    font-weight: 600;
    font-size: 20px;
    margin: 0 !important;
  `,
  versionTag: css`
    user-select: none;
    display: inline-flex;
    align-items: center;
    justify-content: center;
    &:last-child {
      margin-inline-end: 0;
    }
  `,
}));

const locales = {
  cn: {
    full: '查看完整日志',
    changelog: '更新日志',
    loading: '加载中...',
    empty: '暂无更新',
    bugList: 'Bug 版本',
  },
  en: {
    full: 'Full Changelog',
    changelog: 'Changelog',
    loading: 'loading...',
    empty: 'Nothing update',
    bugList: 'Bug Versions',
  },
};

const ParseChangelog: React.FC<{ changelog: string }> = (props) => {
  const { changelog = '' } = props;

  const parsedChangelog = React.useMemo(() => {
    const nodes: React.ReactNode[] = [];

    let isQuota = false;
    let isBold = false;
    let lastStr = '';

    for (let i = 0; i < changelog.length; i += 1) {
      const char = changelog[i];
      const isDoubleAsterisk = char === '*' && changelog[i + 1] === '*';

      if (char !== '`' && !isDoubleAsterisk) {
        lastStr += char;
      } else {
        let node: React.ReactNode = lastStr;
        if (isQuota) {
          node = <code>{node}</code>;
        } else if (isBold) {
          node = <strong>{node}</strong>;
        }

        nodes.push(node);
        lastStr = '';
        if (char === '`') {
          isQuota = !isQuota;
        } else if (isDoubleAsterisk) {
          isBold = !isBold;
          i += 1; // Skip the next '*'
        }
      }
    }

    nodes.push(lastStr);

    return nodes;
  }, [changelog]);

  return <span>{parsedChangelog}</span>;
};

const RefLinks: React.FC<{ refs: string[] }> = ({ refs }) => {
  const { styles } = useStyle();
  return (
    <>
      {refs?.map((ref) => (
        <a className={styles.linkRef} key={ref} href={ref} target="_blank" rel="noreferrer">
          #{ref.match(/^.*\/(\d+)$/)?.[1]}
        </a>
      ))}
    </>
  );
};

const RenderChangelogList: React.FC<{ changelogList: ChangelogInfo[] }> = ({ changelogList }) => {
  const elements: React.ReactNode[] = [];
  const { styles } = useStyle();
  for (let i = 0; i < changelogList.length; i += 1) {
    const { refs, changelog } = changelogList[i];
    // Check if the next line is an image link and append it to the current line
    if (i + 1 < changelogList.length && changelogList[i + 1].changelog.trim().startsWith('<img')) {
      const imgDom = new DOMParser().parseFromString(changelogList[i + 1].changelog, 'text/html');
      const imgElement = imgDom.querySelector('img');
      elements.push(
        <li key={i}>
          <ParseChangelog changelog={changelog} />
          <RefLinks refs={refs} />
          <br />
          <img
            src={imgElement?.getAttribute('src') || ''}
            alt={imgElement?.getAttribute('alt') || ''}
            width={imgElement?.getAttribute('width') || ''}
          />
        </li>,
      );
      i += 1; // Skip the next line
    } else {
      elements.push(
        <li key={i}>
          <ParseChangelog changelog={changelog} />
          <RefLinks refs={refs} />
        </li>,
      );
    }
  }
  return <ul className={styles.listWrap}>{elements}</ul>;
};

const useChangelog = (componentPath: string, lang: 'cn' | 'en'): ChangelogInfo[] => {
  const logFileName = `components-changelog-${lang}.json`;

  const data = useFetch({
    key: `component-changelog-${lang}`,
    request: () => import(`../../../preset/${logFileName}`),
  });
  return React.useMemo(() => {
    const component = componentPath.replace(/-/g, '');
    const componentName = Object.keys(data).find(
      (name) => name.toLowerCase() === component.toLowerCase(),
    );
    return data[componentName as keyof typeof data] as ChangelogInfo[];
  }, [data, componentPath]);
};

const ComponentChangelog: React.FC<Readonly<React.PropsWithChildren>> = (props) => {
  const { children } = props;
  const [locale, lang] = useLocale(locales);
  const [show, setShow] = React.useState(false);
  const { pathname } = useLocation();

  const { styles } = useStyle();

  const componentPath = pathname.match(/\/components\/([^/]+)/)?.[1] || '';

  const list = useChangelog(componentPath, lang);

  const timelineItems = React.useMemo<TimelineItemProps[]>(() => {
    const changelogMap: Record<string, ChangelogInfo[]> = {};

    list?.forEach((info) => {
      changelogMap[info.version] = changelogMap[info.version] || [];
      changelogMap[info.version].push(info);
    });

    return Object.keys(changelogMap).map((version) => {
      const changelogList = changelogMap[version];
      const bugVersionInfo = matchDeprecated(version);
      return {
        children: (
          <Typography>
            <Flex className={styles.versionWrap} justify="flex-start" align="center" gap="middle">
              <Button
                color="default"
                className={styles.versionTitle}
                variant="link"
                href={`/changelog${lang === 'cn' ? '-cn' : ''}/#${version.replace(/\./g, '').replace(/\s.*/g, '-')}`}
              >
                {version}
                {bugVersionInfo.match && (
                  <Popover
                    destroyTooltipOnHide
                    placement="right"
                    title={<span className={styles.bugReasonTitle}>{locale.bugList}</span>}
                    content={
                      <ul className={styles.bugReasonList}>
                        {bugVersionInfo.reason.map<React.ReactNode>((reason, index) => (
                          <li key={`reason-${index}`}>
                            <a type="link" target="_blank" rel="noreferrer" href={reason}>
                              <BugOutlined />
                              {reason
                                ?.replace(/#.*$/, '')
                                ?.replace(
                                  /^https:\/\/github\.com\/ant-design\/ant-design\/(issues|pull)\//,
                                  '#',
                                )}
                            </a>
                          </li>
                        ))}
                      </ul>
                    }
                  >
                    <BugOutlined className={styles.bug} />
                  </Popover>
                )}
              </Button>
              <Tag className={styles.versionTag} bordered={false} color="blue">
                {changelogList[0]?.releaseDate}
              </Tag>
            </Flex>
            <RenderChangelogList changelogList={changelogList} />
          </Typography>
        ),
      };
    });
  }, [list]);

  const screens = Grid.useBreakpoint();
  const width = screens.md ? '48vw' : '90vw';

  if (!pathname.startsWith('/components/') || !list || !list.length) {
    return null;
  }

  return (
    <>
      {isValidElement(children) &&
        cloneElement(children as React.ReactElement, {
          onClick: () => setShow(true),
        })}
      <Drawer
        destroyOnClose
        className={styles.drawerContent}
        title={locale.changelog}
        extra={
          <Link className={styles.extraLink} to={`/changelog${lang === 'cn' ? '-cn' : ''}`}>
            {locale.full}
          </Link>
        }
        open={show}
        width={width}
        onClose={() => setShow(false)}
      >
        <Timeline items={timelineItems} />
      </Drawer>
    </>
  );
};

export default ComponentChangelog;
