import { DC, EntityShape } from "@/deconstruction";
import { ElMessage, MessageHandler } from "element-plus";
import { nextTick, onUnmounted, ref, Ref, watchEffect } from "vue";
import {
  useHelperLayer,
  useMouseMigrateTempLayer,
  useTempLayer,
} from "./use-layer";
import { clickListener, listener } from "@/utils/event";
import { useCursor, useDownKeys, useMode, useStage } from "./use-global-vars";
import { mergeFuns } from "@/utils/shared";
import { getLineRelationMat, Pos } from "@/utils/math";
import { Circle } from "konva/lib/shapes/Circle";
import { themeMouseColors } from "@/constant/help-style";
import { Line } from "konva/lib/shapes/Line";
import { Shape } from "konva/lib/Shape";
import { shapeTreeContain } from "@/utils/shape";
import { useViewerInvertTransform, useViewerTransform } from "./use-viewer";
import { Mode } from "@/constant/mode";
import { useMouseShapesStatus, useMouseStyle } from "./use-mouse-status";

export const useJoinShapes = (
  shapes: (Ref<DC<EntityShape> | undefined> | undefined)[]
) => {
  if (shapes.length < 2) throw "连线至少需要两个对象!";
  const stage = useStage();
  const tempLayer = useTempLayer();
  const getAttri = (shape?: Ref<DC<EntityShape> | undefined>) => {
    let sticky: null | Ref<boolean> = null;
    if (shape) {
      sticky = ref(false);
      useMouseMigrateTempLayer(shape, sticky);
    }
    return {
      sticky,
      canAdd: () => {
        const pos = stage.value?.getNode().pointerPos;
        if (!pos) return false;
        if (!shape) return true;
        const $shape = shape.value?.getNode();
        if (!$shape) return false;
        const interShape = tempLayer.value!.getIntersection(pos);
        return interShape && shapeTreeContain($shape, interShape) === $shape;
      },
    };
  };

  const invertTransform = useViewerInvertTransform();
  const viewTransform = useViewerTransform();
  const attribs = shapes.map(getAttri);
  const quitHooks: (() => void)[] = [];
  const helperLayer = useHelperLayer();
  const drawLines = (points: Ref<(Pos | null)[]>) => {
    const size = 10;
    const shapes: Shape[] = [];
    const stopWatchs: Array<() => void> = [];

    for (let i = 1; i < points.value.length; i++) {
      const line = new Line({
        dash: [5, 10],
        stroke: themeMouseColors.pub,
        strokeWidth: size / 4,
        visible: false,
      });
      helperLayer.value?.add(line);
      shapes.push(line);
      stopWatchs.push(
        watchEffect(() => {
          let cur = points.value[i];
          let pre = points.value[i - 1];
          const isShow = !!(pre && cur);
          line.visible(isShow);
          if (isShow) {
            pre = viewTransform.value.point(pre!);
            cur = viewTransform.value.point(cur!);
            line.points([pre.x, pre.y, cur.x, cur.y]);
          }
        })
      );
    }

    const circleConfig = {
      radius: size / 2,
      fill: themeMouseColors.disable,
      stroke: themeMouseColors.press,
      strokeWidth: size / 4,
      visible: false,
    };
    for (let i = 0; i < points.value.length; i++) {
      const circle = new Circle(circleConfig);
      helperLayer.value?.add(circle);
      shapes.push(circle);
      stopWatchs.push(
        watchEffect(() => {
          let p = points.value[i];
          circle.visible(!!p);
          if (p) {
            p = viewTransform.value.point(p);
            circle.x(p.x);
            circle.y(p.y);
          }
        })
      );
    }

    quitHooks.push(() => {
      mergeFuns(stopWatchs)();
      shapes.forEach((shape) => shape.destroy());
    });
    return points;
  };

  const mode = useMode()
  const cursor = useCursor();
  const keys = useDownKeys();
  const status = useMouseShapesStatus()
  const select = async (sure: (ndx: number) => void) => {
    const $stage = stage.value!.getNode();
    const dom = $stage.container();
    const points = drawLines(ref<(Pos | null)[]>(attribs.map(() => null)));

    cursor.push("crosshair");
    mode.push(Mode.readonly)
    
    const cleanup = () => {
      cursor.pop()
      mode.pop()
    }

    for (let i = 0; i < attribs.length; i++) {
      const attrib = attribs[i];
      attrib.sticky && (attrib.sticky.value = true);
      const stopMove = listener(dom, "mousemove", () => {
        if (attrib.canAdd() && !keys.has(" ")) {
          points.value[i] = invertTransform.value.point($stage.pointerPos!);
        }
      });
      const $shape = shapes[i]?.value?.getNode()
      await nextTick()
      $shape && status.actives.push($shape)
      try {
        await new Promise<void>((resolve, reject) => {
          let isSure = false;
          const stopClick = clickListener(dom, () => {
            if (!points.value[i] || keys.has(" ")) return;
            stopMove();
            stopClick();
            sure(i);
            isSure = true;
            resolve();
          });
          quitHooks.push(() => {
            if (!isSure) {
              stopMove();
              stopClick();
              reject();
            }
          });
        });
      } catch (e) {
        cleanup()
        throw e;
      } finally {
        attrib.sticky && (attrib.sticky.value = false);
        if ($shape) {
          const ndx = status.actives.indexOf($shape)
          status.actives.splice(ndx, 1)
        }
      }
    }
    cleanup();
    return points.value as Pos[];
  };

  const quit = () => {
    mergeFuns(quitHooks)();
    quitHooks.length = 0;
  };
  onUnmounted(quit);
  return [select, quit] as const;
};

export const useAlignmentShape = (shape: Ref<DC<EntityShape> | undefined>) => {
  const quitHooks: (() => void)[] = [];
  const quit = () => mergeFuns(quitHooks)();
  onUnmounted(quit);

  const joins = new Array(2)
    .fill(0)
    .map(() => useJoinShapes([shape, undefined]));
  quitHooks.push(...joins.map(([_, clear]) => clear));

  let msgHandler: MessageHandler | null = null;
  const info = (msg: string) => {
    msgClocse();
    msgHandler = ElMessage.info({ message: msg, duration: 0 });
  };
  const msgClocse = () => {
    msgHandler?.close();
    msgHandler = null;
  };
  quitHooks.push(msgClocse);

  const keydownHandler = (ev: KeyboardEvent) => {
    if (ev.key === "Escape") {
      quit();
    }
  };

  const alignment = async () => {
    const $shape = shape.value?.getNode()!;
    const initOpacity = $shape.opacity();
    const alignMaps: Pos[][] = [];

    window.addEventListener("keydown", keydownHandler);
    quitHooks.push(() => window.removeEventListener("keydown", keydownHandler));

    for (const [join] of joins) {
      info("请在当前图例选择位置（按esc退出对齐）");
      try {
        const map = await join((ndx) => {
          ndx === 0 && info("请在画板中与图例对应的位置（按esc退出对齐）");
          $shape.opacity(0.3);
        });
        alignMaps.push(map);
      } finally {
        $shape.opacity(initOpacity);
      }
    }

    const mat = getLineRelationMat(
      [alignMaps[0][0], alignMaps[1][0]],
      [alignMaps[0][1], alignMaps[1][1]]
    );
    quit();
    return mat;
  };

  return [alignment, quit] as const;
};
