import { createElement, Children, cloneElement, useEffect, useRef, useState } from 'react';
import { type SxProps } from '@mui/material';
import isUndefined from 'lodash/isUndefined';

import { mergeProps } from '../Util';
import { styled } from '../Theme';
import BaseScreenshot from './BaseScreenshot';

import './devices.css';

type DeviceStyles = {
  borderRadius: number;
  width: number;
  scale: number;
  zIndex?: number;
};

const types = {
  'iphone-x': {
    borderRadius: 32,
    width: 342,
    scale: 0.78,
  },
  'iphone-8': {
    borderRadius: 0,
    width: 336,
    scale: 0.78,
  },
  'ipad-pro': {
    borderRadius: 0,
    width: 560,
    scale: 0.62,
  },
  'imac-pro': {
    borderRadius: 0,
    width: 624,
    scale: 0.56,
  },
  macbook: {
    borderRadius: 0,
    width: 740,
    scale: 0.47,
  },
  'macbook-pro': {
    borderRadius: 0,
    width: 740,
    scale: 0.47,
  },
  'surface-pro': {
    borderRadius: 0,
    width: 561,
    scale: 0.62,
  },
  'surface-book': {
    borderRadius: 0,
    width: 728,
    scale: 0.47,
  },
  'surface-studio': {
    borderRadius: 0,
    width: 640,
    scale: 0.55,
  },
  'galaxy-s8': {
    borderRadius: 0,
    width: 321,
    scale: 0.8,
  },
  'google-pixel': {
    borderRadius: 0,
    width: 360,
    scale: 0.72,
  },
  'google-pixel-2-xl': {
    borderRadius: 0,
    width: 360,
    scale: 0.72,
  },
  'apple-watch': {
    borderRadius: 0,
    width: 200,
    scale: 1,
  },
};

const childProps = {
  img: ['alt', 'title', 'src', 'srcSet', 'sizes', 'loading', 'key', 'children'],
  video: ['alt', 'title', 'muted', 'autoplay', 'loop', 'key', 'children'],
};

const createChild = (child: React.ReactElement<any>) => [
  createElement(
    child.type,
    childProps[child.type as keyof typeof childProps].reduce((acc: Record<string, any>, x: string) => {
      acc[x] = child.props[x];
      return acc;
    }, {})
  ),
];

const findChildren = (
  children: React.ReactElement<any>[],
  returnArgWhenNotFound = true
): React.ReactElement<any>[] | null => {
  for (let i = 0; i < children.length; i++) {
    const child = children[i];

    if (['img', 'video'].includes(child.type as string)) {
      return createChild(child);
    }

    if (child.props && child.props.children) {
      const subChildren = Children.toArray(child.props.children) as React.ReactElement<any>[];
      const result = findChildren(subChildren, false);
      if (result) {
        return result;
      }
    }
  }

  if (returnArgWhenNotFound) {
    return children;
  }

  return null;
};

export interface OldScreenshotProps extends React.ComponentProps<'div'> {
  type: keyof typeof types;
  className?: string;
  style?: React.CSSProperties;
  width?: number;
  height?: number;
  sx?: SxProps;
}

// This component is built upon the awesome device.css lib
// By default it will find and only render img/video tags in the children
// If neither of them are found, the whole child tree is rendered
function OldScreenshot(rawProps: OldScreenshotProps) {
  const props = Object.assign({}, rawProps);
  // eslint-disable-next-line react/prop-types
  if (isUndefined(props.type)) {
    props.type = 'iphone-x';
  }
  // eslint-disable-next-line react/prop-types
  if (isUndefined(props.className)) {
    props.className = '';
  }
  // eslint-disable-next-line react/prop-types
  if (isUndefined(props.style)) {
    props.style = {};
  }
  // eslint-disable-next-line react/prop-types
  if (isUndefined(props.width)) {
    props.width = 0;
  }
  // eslint-disable-next-line react/prop-types
  if (isUndefined(props.height)) {
    props.height = 0;
  }
  const newProps = mergeProps(props, OldScreenshot, ['style', 'width', 'height']) as OldScreenshotProps;
  const { type, children, style, className, width, height, ...rest } = newProps;
  const { zIndex = 0, borderRadius = 0 } = (types[type!] || {}) as DeviceStyles;

  return (
    <Div
      {...rest}
      type={type!}
      className={`device device-${type} ${className}`.trim()}
      style={style}
      contentRadius={borderRadius}
      contentZIndex={zIndex}>
      <div className="device-frame">
        <div className="device-content">
          {/* eslint-disable-next-line react/no-array-index-key */}
          {findChildren(Children.toArray(children) as React.ReactElement<any>[])?.map((item: any, index: number) => {
            // eslint-disable-next-line react/no-array-index-key
            return cloneElement(item, { key: index });
          })}
        </div>
      </div>
      <div className="device-stripe" />
      <div className="device-header" />
      <div className="device-sensors" />
      <div className="device-btns" />
      <div className="device-power" />
    </Div>
  );
}

type DivProps = {
  type: keyof typeof types;
  contentRadius: number;
  contentZIndex: number;
};

const Div = styled('div')<DivProps>`
  @media (max-width: ${(props) => types[props.type].width}px) {
    transform-origin: 0 0;
    transform: scale(${(props) => types[props.type].scale});
  }
  .device-content {
    overflow: hidden;
  }
  .device-content video,
  .device-content img {
    border-radius: ${(props) => props.contentRadius}px;
    background-color: #fff;
    background-position: center center;
    background-size: cover;
    object-fit: cover;
    width: 100.1%;
    height: 100.1%;
  }
`;

export interface ScreenFixerProps extends OldScreenshotProps {}

/**
 * 用于修正旧版 Screenshot 无法匹配尺寸的问题
 * @param {*} props params to OldScreenshot
 * @returns <OldScreenshot />
 */
function ScreenFixer({ ...props }: ScreenFixerProps) {
  const screenEl = useRef<HTMLDivElement>(null);
  const [height, setHeight] = useState<number>();
  const [scale, setScale] = useState<number | null>(null);

  useEffect(() => {
    let resizeObs: ResizeObserver | null = null;

    const fixSize = () => {
      const { clientWidth } = screenEl.current!;

      // 获取内部元素的宽高
      const { clientWidth: targetWidth, clientHeight: targetHeight } = screenEl.current!.children[0];

      const realScale = clientWidth / targetWidth;
      if (realScale < 1) {
        setScale(realScale);
        setHeight(realScale * targetHeight);
      } else {
        setScale(null);
        setHeight(undefined);
      }
    };

    if (screenEl.current) {
      resizeObs = new ResizeObserver(fixSize);
      resizeObs.observe(screenEl.current);
    }

    return () => {
      if (resizeObs) {
        resizeObs.disconnect();
      }
    };
    // eslint-disable-next-line react-hooks/exhaustive-deps
  }, [screenEl.current]);

  return (
    <ReScreen ref={screenEl} style={{ height }}>
      <OldScreenshot {...props} style={{ transform: scale ? `scale(${scale})` : undefined }} />
    </ReScreen>
  );
}

const ReScreen = styled('div')`
  div[type] {
    transform: scale(1);
    transform-origin: 0 0;
  }
`;

export interface ScreenShotProps extends ScreenFixerProps {
  type: keyof typeof types;
  src?: string;
}

function Screenshot({ type, src = '', children, sx = {}, ...rest }: ScreenShotProps) {
  const _type = type.toLowerCase();

  // 新版采用容器采用BaseScreenshot，svg集成，更容易拓展，响应式更好
  if (['phone', 'macbook'].includes(_type)) {
    return (
      <BaseScreenshot type={_type} sx={sx} {...rest}>
        {children || (src ? <img src={src} alt="screenshot" /> : null)}
      </BaseScreenshot>
    );
  }

  // 旧版采用纯css制作，定制性欠缺，暂时保留使用
  return (
    <ScreenFixer type={type} sx={{ ...sx, margin: 'auto' }} {...rest}>
      {children || (src ? <img src={src} alt="screenshot" /> : null)}
    </ScreenFixer>
  );
}
export default Screenshot;
