import React, {
  cloneElement,
  Fragment,
  useCallback,
  useEffect,
  useMemo,
  useRef,
  useState,
} from "react";
import classNames from "classnames";
import { InputNumber } from "easy-design";
import { debounce } from "lodash";
import { calcExpression, safeAdd, safeDiv, safeModulo, safeMul } from "../../_util/utils";
import styles from "./index.less";

export default function EasyNumber(props) {
  const { value, min, max, onChange } = props;
  const [state, setState] = useState(value);

  const onChangeRef = useMemo(() => debounce(onChange, 300), [onChange]);

  const debouncedChange = (newValue) => {
    if (newValue !== value) {
      if (newValue === "" || newValue == null) {
        if (min === undefined && max === undefined) {
          onChangeRef(null);
          setState(null);
        } else {
          setState(value);
        }
      } else {
        newValue = Number(newValue);
        if (!isNaN(newValue)) {
          newValue = newValue > max ? max : newValue < min ? min : newValue;
          if (newValue !== value) {
            onChangeRef(newValue);
          }
          setState(newValue);
        } else {
          setState(value);
        }
      }
    } else {
      setState(value);
    }
  };

  useEffect(() => setState(value), [value]);

  return (
    <NumberInput
      {...props}
      value={state}
      onChange={setState}
      onBlur={debouncedChange}
      onDragEnd={debouncedChange}
    />
  );
}

function round10(value, exp) {
  if (typeof exp !== "number" || exp < 0) return value;
  return Math.round(value * Math.pow(10, exp)) / Math.pow(10, exp);
}

export function NumberInput(props) {
  const {
    value,
    label,
    onChange = () => {},
    onBlur = () => {},
    onDragEnd = () => {},
    isEventMark,
    min,
    max,
    suffix,
    disabled,
    step = 1,
    validataFun,
    precision,
    ...rest
  } = props;
  const dragRef = useRef(null);

  const [drag, setDrag] = useState({ oldValue: value, value, dragging: false });
  // 记录开始拖拽时的值 每次拖拽以此为基础做偏移
  const [oldValue, setOldValue] = useState(value);

  const handleBlur = useCallback(
    (e) => {
      const newValue = typeof e === "number" ? e : e.target.value;
      const nextValue = newValue === "" || newValue == null ? null : newValue;
      let calcValue = calcExpression(nextValue);
      if (validataFun) {
        try {
          const fun = new Function("oldVal", "newVal", validataFun);
          let res = fun(value, calcValue);
          if (res && typeof res === "number") {
            calcValue = res;
          }
        } catch (e) {
          console.error(e);
        }
      }
      if (typeof precision === "number") {
        calcValue = round10(calcValue, precision);
      }
      onBlur(calcValue);
      setOldValue(calcValue);
    },
    [validataFun, onBlur, value, oldValue],
  );

  const handlePressEnter = (e) => {
    e.target.blur();
  };

  const getNewValue = ({ movementX }) => {
    // movementX返回鼠标按下到抬起头间的拖动距离 在开始拖拽的基础上做偏移 在onEnd的时候通知外部
    // 保持1000/100的移动距离和100/10的移动距离一致
    let newValue = safeAdd(oldValue, safeMul(Math.round(safeDiv(movementX, 5)), step));
    newValue = newValue > max ? max : newValue < min ? min : newValue;
    return newValue;
  };

  const handlePointerStart = () => {
    setOldValue(value);
  };
  const handlePointerMove = ({ movementX }) => {
    const newValue = getNewValue({ movementX });
    onChange(newValue);
    setDrag({ oldValue: value, value: newValue, dragging: true });
  };
  const handlePointerMoveEnd = ({ movementX }) => {
    const newValue = getNewValue({ movementX });
    onDragEnd(newValue);
    setDrag({ value: newValue, dragging: false });
  };

  return (
    <>
      <InputNumber
        value={drag.dragging ? drag.value : value}
        min={min}
        max={max}
        dragRef={suffix ? dragRef : undefined}
        suffix={suffix}
        disabled={disabled}
        onChange={onChange}
        onBlur={handleBlur}
        onPressEnter={handlePressEnter}
        step={step}
        {...rest}
      />
      <PointerDrag
        target={dragRef}
        onMoveStart={handlePointerStart}
        onMove={handlePointerMove}
        onMoveEnd={handlePointerMoveEnd}
      ></PointerDrag>
      {label && (
        <PointerDrag
          onMoveStart={handlePointerStart}
          onMove={handlePointerMove}
          onMoveEnd={handlePointerMoveEnd}
        >
          <div style={{ textAlign: "center", lineHeight: 1.1, marginTop: 4, color: "#999" }}>
            <span className={classNames({ [styles.eventMark]: isEventMark })}>{label}</span>
          </div>
        </PointerDrag>
      )}
    </>
  );
}

let moveDistance = 0;
export function PointerDrag({ children, target, onMoveStart, onMove, onMoveEnd }) {
  const [dragging, setDrag] = useState(false);
  const [position, setPosition] = useState({ x: 0, y: 0 });
  const isMouseDownOnTarget = useRef(false);
  const windowSize = useMemo(
    () => ({ width: document.body.clientWidth, height: document.body.clientHeight }),
    [],
  );

  const onMouseMove = (e) => {
    if (dragging) {
      setPosition((prev) => {
        let nextX = safeAdd(prev.x, e.movementX);
        nextX = safeModulo(safeAdd(nextX, 10 * windowSize.width), windowSize.width);
        return { x: nextX, y: prev.y };
      });
      // 记录移动距离
      moveDistance = safeAdd(moveDistance, e.movementX);
      onMove({ movementX: moveDistance });
    } else if (Math.abs(e.movementX) >= 1 && e.buttons && isMouseDownOnTarget.current) {
      onMouseDown(e);
    }
  };
  const onMouseUp = (e) => {
    isMouseDownOnTarget.current = false;
    if (dragging) {
      document.exitPointerLock();
      setDrag(false);
      onMoveEnd && onMoveEnd({ movementX: moveDistance });
    }
  };
  const onMouseDown = (e) => {
    e.target.requestPointerLock();
    setPosition({ x: e.clientX, y: e.clientY });
    setDrag(true);
    moveDistance = 0;
    onMoveStart && onMoveStart();
  };
  const handleMouseDown = () => {
    isMouseDownOnTarget.current = true;
  };

  useEffect(() => {
    if (!target || !target.current) return;

    const el = target.current;
    el.addEventListener("mousedown", handleMouseDown);
    el.addEventListener("mousemove", onMouseMove);
    el.addEventListener("mouseup", onMouseUp);
    return () => {
      el.removeEventListener("mousedown", handleMouseDown);
      el.removeEventListener("mousemove", onMouseMove);
      el.removeEventListener("mouseup", onMouseUp);
    };
  }, [target, onMove, dragging, windowSize]);

  const arrow = (
    <svg
      width={46}
      height={15}
      style={{
        position: "fixed",
        left: position.x,
        top: position.y,
        width: 46,
        height: 15,
        zIndex: 1,
        transform: "translate(-50%, -50%)",
      }}
    >
      <g transform="translate(2 3)">
        <path
          fillRule="evenodd"
          d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z"
          stroke="#fafafa"
          style={{ strokeWidth: 2 }}
        />
        <path
          fillRule="evenodd"
          d="M 15 4.5L 15 2L 11.5 5.5L 15 9L 15 6.5L 31 6.5L 31 9L 34.5 5.5L 31 2L 31 4.5Z"
        />
      </g>
    </svg>
  );

  return (
    <Fragment>
      {children &&
        cloneElement(children, {
          onMouseMove: (e) => {
            e.persist();
            onMouseMove(e);
          },
          onMouseUp,
        })}
      {dragging && arrow}
    </Fragment>
  );
}
