import React, { useEffect, useState } from "react";
import scrollIntoView, {
  CustomBehaviorOptions,
  Options,
} from "scroll-into-view-if-needed";
import classNames from "classnames";
import { Button } from "../button";
import { Overlay } from "../overlay";
import { MediaObject } from "../mediaobject";
import { BubbleProps, BubbleContent } from "../bubble";
import { useConfig } from "../util";
import { injectValue } from "../_util/inject-value";
import { useDefault } from "../_util/use-default";
import { useTranslation } from "../i18n";

export interface GuideStep {
  /**
   * 需要高亮的元素
   */
  element: Element | (() => Element);

  /**
   * 气泡位置
   */
  placement?: BubbleProps["placement"];

  /**
   * 标题
   */
  title?: React.ReactNode;

  /**
   * 描述
   */
  description?: React.ReactNode;

  /**
   * 配图 URL
   */
  image?: string;

  /**
   * 弹出位置偏离参考位置的位移
   *
   * 如： `10`、`"50%"`、`"10 + 10%"`、`[10, 10]`
   *
   * @since 2.6.3
   */
  placementOffset?: BubbleProps["placementOffset"];

  /**
   * 气泡箭头是否指向目标元素中心
   * @since 2.6.3
   */
  arrowPointAtCenter?: BubbleProps["arrowPointAtCenter"];

  /**
   * 步骤气泡自定义类名
   * @since 2.5.0
   */
  bubbleClassName?: string;

  /**
   * 步骤气泡自定义样式
   * @since 2.5.0
   */
  bubbleStyle?: React.CSSProperties;

  /**
   * 滚动元素至视野范围的配置
   *
   * **详见：https://www.npmjs.com/package/scroll-into-view-if-needed**
   *
   * @default { behavior: "smooth", block: "center", inline: "center" }
   * @since 2.6.18
   */
  scrollIntoViewOptions?: CustomBehaviorOptions<any> | Options | boolean;
}

export interface GuideProps {
  /**
   * 引导步骤
   */
  steps?: GuideStep[];

  /**
   * 开始引导内容
   */
  startContent?: GuideStep;

  /**
   * 包含 `children` 时，在 `children` 渲染完成后展示引导
   */
  children?: JSX.Element;

  /**
   * 当前步骤. 如果有引导内容（非第一步），current值设为 -1
   */
  current?: number;

  /**
   * 步骤变化回调
   *
   * @since 2.6.10 支持 `context`
   */
  onCurrentChange?: (
    current: number,
    context: { from: "start" | "back" | "next" | "finish" | "cancel" }
  ) => void;

  /**
   * 受控展示
   * @default true
   */
  visible?: boolean;

  /**
   * 隐藏取消按钮
   * @default false
   */
  hideCancelButton?: boolean;

  /**
   * 隐藏计数
   * @default false
   * @since 2.6.4
   */
  hideCount?: boolean;

  /**
   * 隐藏上一步按钮
   * @default false
   */
  showBackButton?: boolean;

  /**
   * 取消按钮文字
   * @default "跳过"
   */
  cancelText?: string;

  /**
   * 上一步按钮文字
   * @default "上一步"
   */
  backText?: string;

  /**
   * 下一步按钮文字
   * @default "下一步"
   */
  nextText?: string;

  /**
   * 完成按钮文字
   * @default "完成"
   */
  finishText?: string;

  /**
   * 是否自动滚动元素至视野范围
   * @default false
   */
  autoScrollIntoView?: boolean;

  /**
   * 下一步（及完成）按钮样式
   * @default "link"
   * @since 2.6.18
   */
  nextButtonTheme?: "link" | "primary";

  /**
   * 是否显示遮罩
   * @default true
   * @since 2.7.4
   */
  showMask?: boolean;

  /**
   * 是否显示圆点
   * @default false
   * @since 2.7.4
   */
  showDot?: boolean;

  /**
   * 引导开始按钮文字
   * @default "确定"
   * @since 2.7.4
   */
  startFinishText?: string;

  /**
   * 是否显示椭圆动画
   * @default false
   * @since 2.7.4
   */
  visibleEllipseAnimation?: boolean;

  /**
   * 是否显示三角箭头
   * @default true
   * @since 2.7.4
   */
  visibleTriangleArrow?: boolean;

  /**
   * 是否显示关闭按钮
   * @default false
   * @since 2.7.4
   */
  visibleCloseBtn?: boolean;
}

const borderWidth = 1;

function getOffset(element: HTMLElement) {
  if (!element) {
    return {};
  }
  const { body } = document;
  const docEl = document.documentElement;
  const scrollTop = window.pageYOffset || docEl.scrollTop || body.scrollTop;
  const scrollLeft = window.pageXOffset || docEl.scrollLeft || body.scrollLeft;
  try {
    const x = element.getBoundingClientRect();
    return {
      top: x.top + scrollTop,
      width: x.width,
      height: x.height,
      left: x.left + scrollLeft,
    };
  } catch (_) {
    return {};
  }
}

// 抽取body公共函数
const renderBody = (step, classPrefix) => {
  const { title, description, image } = step;

  return (
    <div className={`${classPrefix}-guide__body`}>
      <MediaObject
        media={
          image && (
            <img className={`${classPrefix}-guide__image`} src={image} alt="" />
          )
        }
      >
        {title && (
          <h3 className={`${classPrefix}-guide__messagetitle`}>{title}</h3>
        )}
        {description && (
          <div className={`${classPrefix}-guide__messagetext`}>
            {description}
          </div>
        )}
      </MediaObject>
    </div>
  );
};

// 抽取圆点函数
const renderDot = (count, current, classPrefix) => {
  return (
    <div className={`${classPrefix}-guide__dot`}>
      {[...Array(count)].map((items, index) => (
        <span
          key={index}
          className={classNames(`${classPrefix}-guide__dot-item`, {
            "is-active": index === current,
          })}
        ></span>
      ))}
    </div>
  );
};

// 获取overlay公共属性
const getOverlayCommonProps = (zIndex, key, placement, placementOffset) => {
  return {
    key,
    placement,
    placementOffset,
    modifiers: {
      flip: { enabled: false },
      preventOverflow: { enabled: false },
      hide: { enabled: false },
    },
    overlayProps: { style: { zIndex } },
  };
};

export function Guide({
  steps = [],
  startContent,
  children = null,
  visible = true,
  autoScrollIntoView,
  current,
  onCurrentChange,
  showBackButton,
  hideCount,
  hideCancelButton,
  cancelText,
  backText,
  nextText,
  finishText,
  nextButtonTheme = "link",
  showDot = false,
  showMask = true,
  startFinishText,
  visibleEllipseAnimation = false,
  visibleTriangleArrow = true,
  visibleCloseBtn = false,
}: GuideProps) {
  const t = useTranslation();
  const { classPrefix } = useConfig();
  const [currentIndex, setCurrentIndex] = useDefault(
    current,
    -1,
    onCurrentChange
  );

  const [isStartStep, setIsStartStep] = useState(false);

  // 保证子级结点存在
  useEffect(() => {
    if (startContent && (typeof current === "undefined" || current < 0)) {
      setIsStartStep(true);
    } else {
      setIsStartStep(false);
    }

    if (current <= -2) {
      setIsStartStep(false);
    }

    if (typeof current === "undefined") {
      setCurrentIndex(0, { from: "start" });
    }
  }, [current, startContent, setCurrentIndex]);

  let step = startContent;
  let count = 0;

  if (steps && !isStartStep) {
    step = steps[currentIndex];
    count = steps.length;
  }

  useEffect(() => {
    if (step) {
      const element = injectValue(step.element)();
      if (element) {
        scrollIntoView(
          element,
          step.scrollIntoViewOptions || {
            behavior: "smooth",
            block: "center",
            inline: "center",
          }
        );
      }
    }
  }, [autoScrollIntoView, step]);

  if (!visible || !step) {
    return null;
  }

  let offset = step.placementOffset || 10;
  if (step.arrowPointAtCenter) {
    offset = Array.isArray(offset) ? offset[1] : offset;
    const [, placementModifier] = step.placement.split("-");
    if (placementModifier === "start") {
      offset = ["50%-26", offset];
    } else if (placementModifier === "end") {
      offset = ["26-50%", offset];
    }
  }

  const element = injectValue(step.element)();
  const { placement } = step;
  const { height, width } = getOffset(element as HTMLElement);

  if (!element) {
    return null;
  }

  const maskLayerProps = getOverlayCommonProps(
    9999,
    "highlight",
    "top-start",
    0
  );
  const layerProps = getOverlayCommonProps(10000, "bubble", placement, offset);

  return (
    <Overlay
      layers={[
        <Overlay.Layer
          visible
          content={({ style }) => {
            const left = +style.left;
            const top = +style.top;
            const p = `M ${left - borderWidth} ${top - borderWidth} H ${left +
              width +
              borderWidth} V ${top + height + borderWidth} H ${left -
              borderWidth} L ${left - borderWidth} ${top - borderWidth} Z`;
            return (
              <svg
                style={{
                  top: 0,
                  left: 0,
                  overflow: "hidden",
                  position: "fixed",
                  width: "100vw",
                  height: "100vh",
                  fillRule: "evenodd",
                }}
              >
                <path
                  d={`${p} M 0 0 H 9999 V 9999 H 0 L 0 0 Z`}
                  fill="#000000"
                  opacity={!showMask ? 0 : 0.8}
                />
                <path
                  d={`M ${left} ${top} H ${left + width} V ${top +
                    height} H ${left} L ${left} 0 Z ${p}`}
                  fill="#cccccc"
                  opacity={showMask ? 0 : 1}
                />
              </svg>
            );
          }}
          {...maskLayerProps}
        />,
        <Overlay.Layer
          visible
          {...layerProps}
          content={({ placement }) => (
            <BubbleContent
              placement={placement}
              className={classNames(
                `${classPrefix}-guide`,
                step.bubbleClassName,
                {
                  [`${classPrefix}-guide--start`]: !showDot,
                  "is-show-triangle-arrow": visibleTriangleArrow,
                }
              )}
              style={step.bubbleStyle}
            >
              {visibleEllipseAnimation && (
                <div className={`${classPrefix}-guide__flashing-light`}>
                  <span
                    className={`${classPrefix}-guide__flashing-light-dot`}
                  ></span>
                </div>
              )}
              {visibleCloseBtn && (
                <Button
                  type="icon"
                  icon="close"
                  htmlType="button"
                  className={`${classPrefix}-guide__close-icon`}
                  onClick={() => {
                    setCurrentIndex(-2, { from: "cancel" });
                  }}
                  aria-label={t.closeText}
                />
              )}
              {renderBody(step, classPrefix)}
              <div className={`${classPrefix}-guide__footer`}>
                {showDot &&
                  count &&
                  count > 0 &&
                  renderDot(count, current, classPrefix)}
                <div className={`${classPrefix}-guide__btn-group`}>
                  {!hideCancelButton && (current < count - 1 || isStartStep) && (
                    <Button
                      type="text"
                      htmlType="button"
                      onClick={() => {
                        setCurrentIndex(-2, { from: "cancel" });
                      }}
                    >
                      {cancelText || t.guideCancel}
                    </Button>
                  )}
                  {showBackButton &&
                    ((startContent && current >= 0) ||
                      (!startContent && current > 0)) && (
                      <Button
                        type="text"
                        htmlType="button"
                        className={`${classPrefix}-guide__btn-back`}
                        onClick={() => {
                          setCurrentIndex(current - 1, { from: "back" });
                        }}
                      >
                        {backText || t.guideBack}
                      </Button>
                    )}
                  {current < count - 1 ? (
                    <Button
                      type={nextButtonTheme}
                      htmlType="button"
                      onClick={() => {
                        setCurrentIndex(current + 1, { from: "next" });
                      }}
                    >
                      {nextText || t.guideNext}
                      {!hideCount && count > 0
                        ? ` ${current + 1}/${count}`
                        : ""}
                    </Button>
                  ) : (
                    <Button
                      type={nextButtonTheme}
                      htmlType="button"
                      className={`${classPrefix}-guide__btn-finish`}
                      onClick={() => {
                        if (isStartStep) {
                          setCurrentIndex(0, { from: "start" });
                        } else {
                          setCurrentIndex(-2, { from: "finish" });
                        }
                      }}
                    >
                      {isStartStep
                        ? startFinishText || t.okText
                        : finishText || t.guideFinish}
                      {!hideCount && count > 0
                        ? ` ${current + 1}/${count}`
                        : ""}
                    </Button>
                  )}
                </div>
              </div>
            </BubbleContent>
          )}
        />,
      ]}
    >
      {ref => {
        return (
          <div
            ref={() => {
              if (typeof ref === "function") {
                ref(element as HTMLElement);
              }
            }}
          >
            {children}
          </div>
        );
      }}
    </Overlay>
  );
}

Guide.displayName = "Guide";
