import { useMouseShapeStatus } from "./use-mouse-status.ts";
import { Ref, ref, watch } from "vue";
import { DC, EntityShape } from "../../deconstruction";
import {
  installGlobalVar,
  useCan,
  useMode,
  useStage,
  useTransformIngShapes,
} from "./use-global-vars.ts";
import { Mode } from "../../constant/mode.ts";
import { Transform, Util } from "konva/lib/Util";
import { Pos, vector } from "@/utils/math.ts";
import { useConversionPosition } from "./use-coversion-position.ts";
import { getOffset, listener } from "@/utils/event.ts";
import { flatPositions, mergeFuns, round } from "@/utils/shared.ts";
import { Line } from "konva/lib/shapes/Line";
import { setShapeTransform } from "@/utils/shape.ts";
import { Transformer } from "../transformer.ts";
import { TransformerConfig } from "konva/lib/shapes/Transformer";
import { themeColor, themeMouseColors } from "@/constant/help-style.ts";
import { useComponentSnap } from "./use-snap.ts";
import { useViewerInvertTransform, useViewerTransform } from "./use-viewer.ts";
import { Rect } from "konva/lib/shapes/Rect";
import { Text } from "konva/lib/shapes/Text";
import { Group } from "konva/lib/Group";
import { BaseItem } from "../components/util.ts";
import { useGetComponentData } from "./use-component.ts";

export type TransformerExtends = Transformer & {
  queueShapes: Ref<EntityShape[]>;
};
export const useTransformer = installGlobalVar(() => {
  const anchorCornerRadius = 5;
  const transformer = new Transformer({
    borderStrokeWidth: 2,
    borderStroke: themeMouseColors.pub,
    anchorCornerRadius,
    anchorSize: anchorCornerRadius * 2,
    rotationSnaps: [0, 30, 60, 90, 120, 150, 180, 210, 240, 270, 300, 330, 360],
    rotationSnapTolerance: 3,
    anchorStrokeWidth: 2,
    anchorStroke: themeMouseColors.pub,
    anchorFill: themeMouseColors.press,
    flipEnabled: false,
    padding: 10,
    useSingleNodeRotation: true,
  }) as TransformerExtends;
  transformer.queueShapes = ref([]);

  transformer.on("transformstart.attachText", () => {
    const operateType = transformer.getActiveAnchor() as TransformerVectorType;
    if (operateType !== "rotater") return;
    const $node = transformer.findOne<Rect>(".rotater")!;
    const $g = new Group();
    const $text = new Text({
      listening: false,
      fill: themeColor,
      fontSize: 12,
      width: 100,
      align: "center",
      offset: { x: 50, y: 0 },
    });
    $g.add($text);
    $node.parent!.add($g);
    setShapeTransform($g, $node.getTransform());
    $g.y($g.y() - 2 * $text.fontSize());

    const updateText = () => {
      const rotation = transformer.rotation();
      $text.rotation(-rotation).text(` ${round(rotation, 1)}°`);
    };

    updateText();
    transformer.on("transform.attachText", updateText);
    transformer.on("transformend.attachText", () => {
      transformer.off("transform.attachText transformend.attachText");
      $g.destroy();
    });
  });
  return transformer;
}, Symbol("transformer"));

export const usePointerIsTransformerInner = () => {
  const transformer = useTransformer();
  const stage = useStage();
  return () => {
    const $stage = stage.value!.getStage();
    const tfRect = transformer.getClientRect();
    const padding = transformer.padding();
    tfRect.x -= padding;
    tfRect.y -= padding;
    tfRect.width += padding;
    tfRect.height += padding;
    const pointRect = { ...$stage.pointerPos!, width: 1, height: 1 };

    return Util.haveIntersection(tfRect, pointRect);
  };
};

export type ScaleVectorType =
  | "middle-left"
  | "middle-right"
  | "top-center"
  | "bottom-center"
  | "top-right"
  | "top-left"
  | "bottom-right"
  | "bottom-left";
export type TransformerVectorType = ScaleVectorType | "rotater";

export const useGetTransformerOperType = () => {
  const transformer = useTransformer();
  return () => {
    if (!transformer.nodes().length) return null;
    return transformer.getActiveAnchor() as TransformerVectorType;
  };
};

export const useGetTransformerVectors = () => {
  const viewerInvertTransform = useViewerInvertTransform();
  const transformer = useTransformer();

  return (type: TransformerVectorType): Pos | null => {
    if (!transformer.nodes().length) return null;
    const merTransform = viewerInvertTransform.value
      .copy()
      .multiply(transformer.getTransform());

    const getVector = (operateType: TransformerVectorType): Pos => {
      if (operateType === "rotater") {
        return vector(getVector("bottom-left"))
          .add(getVector("bottom-right"))
          .add(getVector("top-left"))
          .add(getVector("top-right"))
          .divideScalar(4);
      } else {
        const centerNode = transformer.findOne(`.${operateType}`)!;
        return {
          x: centerNode.x(),
          y: centerNode.y(),
        };
      }
    };
    return merTransform.point(getVector(type));
  };
};

export const useGetTransformerOperDirection = () => {
  const getTransformerOperType = useGetTransformerOperType();
  const getTransformerVectors = useGetTransformerVectors();
  const originTypeMap = {
    "middle-left": "middle-right",
    "middle-right": "middle-left",
    "top-center": "bottom-center",
    "bottom-center": "top-center",
    "top-right": "bottom-left",
    "top-left": "bottom-right",
    "bottom-right": "top-left",
    "bottom-left": "top-right",
    rotater: "rotater",
  } as const;

  return () => {
    const operateType = getTransformerOperType();
    if (!operateType || !originTypeMap[operateType]) return null;
    const origin = getTransformerVectors(originTypeMap[operateType]);
    const operTarget = getTransformerVectors(operateType);
    return origin && operTarget && ([origin, operTarget] as const);
  };
};

export const useShapeDrag = (shape: Ref<DC<EntityShape> | undefined>) => {
  const offset = ref<Pos>();
  const mode = useMode();
  const can = useCan();
  const conversion = useConversionPosition(true);
  const transformIngShapes = useTransformIngShapes();

  const init = (shape: EntityShape) => {
    const dom = shape.getStage()!.container();

    let start: Pos | undefined;
    const enter = (position: Pos) => {
      mode.push(Mode.update);
      if (!start) {
        start = position;
        if (!can.dragMode) return;
        mode.add(Mode.draging);
        transformIngShapes.value.push(shape);
      }
    };
    const leave = () => {
      if (start) {
        offset.value = void 0;
        mode.pop();
        start = void 0;
        const ndx = transformIngShapes.value.indexOf(shape);
        ~ndx && transformIngShapes.value.splice(ndx, 1);
      }
    };

    shape.draggable(true);
    shape.dragBoundFunc((_, ev) => {
      if (!start) {
        enter(ev);
      } else if (can.dragMode) {
        const end = conversion(getOffset(ev, dom));
        offset.value = {
          x: end.x - start.x,
          y: end.y - start.y,
        };
      }
      return shape.absolutePosition();
    });

    shape.on("pointerdown.mouse-drag", (ev) => {
      enter(conversion(getOffset(ev.evt)));
    });

    return mergeFuns([
      () => {
        shape.draggable(false);
        shape.off("pointerdown.mouse-drag");
        start && leave();
      },
      listener(document.documentElement, "pointerup", () => {
        start && leave();
      }),
    ]);
  };

  watch(
    () => (can.editMode || mode.include(Mode.update)) && shape.value?.getNode(),
    (canEdit, _, onCleanup) => {
      canEdit && onCleanup(init(shape.value!.getNode()));
    }
  );
  return offset;
};

type Rep<T> = {
  tempShape: T;
  init?: () => void;
  update?: () => void;
  destory: () => void;
};
const emptyFn = () => {};
export const useShapeTransformer = <T extends EntityShape>(
  shape: Ref<DC<T> | undefined>,
  transformerConfig: TransformerConfig = {},
  replaceShape?: (shape: T) => Rep<T>,
  handlerTransform?: (transform: Transform) => Transform
) => {
  const offset = useShapeDrag(shape);
  const transform = ref<Transform>();
  const status = useMouseShapeStatus(shape);
  const mode = useMode();
  const transformer = useTransformer();
  const transformIngShapes = useTransformIngShapes();
  const viewTransform = useViewerTransform();
  const getComponentData = useGetComponentData();
  const can = useCan();

  const init = ($shape: T) => {
    let rep: Rep<T>;
    if (replaceShape) {
      rep = replaceShape($shape);
    } else {
      rep = {
        tempShape: $shape,
        destory: emptyFn,
        update: emptyFn,
      };
    }

    const updateTransform = () => {
      if (!can.dragMode) return;
      let appleTransform = rep.tempShape.getTransform().copy();
      if (handlerTransform) {
        appleTransform = handlerTransform(appleTransform);
        setShapeTransform(rep.tempShape, appleTransform);
      }
      transform.value = appleTransform;
    };

    rep.tempShape.on("transform.shapemer", updateTransform);

    const boundHandler = () => rep.update && rep.update();
    $shape.on("bound-change", boundHandler);

    // 拖拽时要更新矩阵
    let prevMoveTf: Transform | null = null;
    const stopDragWatch = watch(
      offset,
      (translate, oldTranslate) => {
        if (translate) {
          if (!oldTranslate) {
            rep.update && rep.update();
          }
          const moveTf = new Transform().translate(translate.x, translate.y);
          const finalTf = moveTf.copy();
          prevMoveTf && finalTf.multiply(prevMoveTf.invert());
          finalTf.multiply(rep.tempShape.getTransform());
          prevMoveTf = moveTf;

          setShapeTransform(rep.tempShape, finalTf);
          rep.tempShape.fire("transform");
        } else {
          prevMoveTf = null;
          transform.value = void 0;
        }
      },
      { immediate: true }
    );

    const stopTransformerWatch = watch(
      () => status.value.active,
      (active, _, onCleanup) => {
        const parent = $shape.parent;
        if (!(active && parent)) return;
        const oldConfig: TransformerConfig = {};

        for (const key in transformerConfig) {
          oldConfig[key] = (transformer as any)[key]();
          (transformer as any)[key](transformerConfig[key]);
        }

        transformer.nodes([rep.tempShape]);
        transformer.queueShapes.value = [$shape];
        parent.add(transformer);

        let isEnter = false;
        const downHandler = () => {
          if (isEnter) {
            mode.pop();
          }
          mode.push(Mode.update);
          isEnter = true;
          if (!can.dragMode) return;

          rep.update && rep.update();
          mode.add(Mode.draging);
          transformIngShapes.value.push($shape);
        };
        transformer.on("pointerdown.shapemer", downHandler);
        const stopPointupListener = listener(
          $shape.getStage()!.container(),
          "pointerup",
          () => {
            if (isEnter) {
              mode.pop();
              transform.value = void 0;
              isEnter = false;
              const ndx = transformIngShapes.value.indexOf($shape);
              ~ndx && transformIngShapes.value.splice(ndx, 1);
            }
          }
        );

        const stopTransformerForceUpdate = watch(
          viewTransform,
          () => transformer.forceUpdate(),
          { flush: "post" }
        );

        const stopLeaveUpdate = watch(
          () => getComponentData($shape).value,
          (val) => {
            rep.update && rep.update();
          },
          { flush: "post", deep: true }
        );

        onCleanup(() => {
          for (const key in oldConfig) {
            (transformer as any)[key](oldConfig[key]);
          }
          stopTransformerForceUpdate();
          stopPointupListener();
          stopLeaveUpdate();
          // TODO: 有可能transformer已经转移
          if (transformer.queueShapes.value.includes($shape)) {
            transformer.nodes([]);
            transformer.queueShapes.value = [];
          }
          transform.value = void 0;

          if (isEnter) {
            mode.pop();
            const ndx = transformIngShapes.value.indexOf($shape);
            ~ndx && transformIngShapes.value.splice(ndx, 1);
          }
          transformer.off("pointerdown.shapemer", downHandler);
        });
      }
    );

    return () => {
      $shape.off("bound-change", boundHandler);
      rep.tempShape.off("transform.shapemer", updateTransform);
      stopDragWatch();
      stopTransformerWatch();
      rep.destory();
    };
  };

  watch(
    () => shape.value,
    (shape, _) => {
      if (!shape) return;
      watch(
        () =>
          (can.editMode || mode.include(Mode.update)) &&
          (status.value.active || status.value.hover),
        (canEdit, _, onCleanup) => {
          if (canEdit) {
            const stop = init(shape.getStage());
            onCleanup(stop);
          } else {
            onCleanup(() => {});
          }
        },
        { immediate: true }
      );
    }
  );
  return transform;
};

export const cloneRepShape = <T extends EntityShape>(
  shape: T
): ReturnType<GetRepShape<T, any>> => {
  shape =
    ((shape as Group)?.findOne &&
      ((shape as Group)?.findOne(".repShape") as T)) ||
    shape;
  return {
    shape: shape.clone({
      fill: "rgb(0, 255, 0)",
      visible: false,
      strokeWidth: 0,
    }),
    update: (_, rep) => {
      setShapeTransform(rep, shape.getTransform());
    },
  };
};

export const transformerRepShapeHandler = <T extends EntityShape>(
  shape: T,
  repShape: T
) => {
  if (import.meta.env.DEV) {
    repShape.visible(true);
    repShape.opacity(0.1);
  }
  shape.parent!.add(repShape);
  repShape.zIndex(shape.getZIndex());
  repShape.listening(false);
  shape.repShape = repShape;

  return [
    repShape,
    () => {
      shape.repShape = undefined;
      repShape.remove();
    },
  ] as const;
};

type GetRepShape<T extends EntityShape, K extends object> = (shape: T) => {
  shape: T;
  update?: (data: K, shape: T) => void;
};
export type CustomTransformerProps<
  T extends BaseItem,
  S extends EntityShape
> = {
  openSnap?: boolean;
  getRepShape?: GetRepShape<S, T>;
  beforeHandler?: (data: T, mat: Transform) => T;
  handler?: (data: T, mat: Transform) => Transform | void | true;
  callback?: (data: T, mat: Transform) => void;
  transformerConfig?: TransformerConfig;
};

export const useCustomTransformer = <T extends BaseItem, S extends EntityShape>(
  shape: Ref<DC<S> | undefined>,
  data: Ref<T>,
  props: CustomTransformerProps<T, S>
) => {
  const { getRepShape, handler, callback, openSnap, transformerConfig } = props;
  const needSnap = openSnap && useComponentSnap(data.value.id);
  const transformer = useTransformer();
  let repResult: ReturnType<GetRepShape<S, T>>;
  const transform = useShapeTransformer(
    shape,
    transformerConfig,
    getRepShape &&
      ((shape) => {
        repResult = getRepShape(shape);
        const [_, destory] = transformerRepShapeHandler(shape, repResult.shape);
        return {
          tempShape: repResult.shape,
          update: () => {
            repResult.update && repResult.update(data.value, repResult.shape);
          },
          destory,
        };
      })
  );
  let callMat: Transform;
  watch(transform, (current, oldTransform) => {
    if (current) {
      if (!handler) return;
      const snapData = props.beforeHandler
        ? props.beforeHandler(data.value, current)
        : data.value;

      let nTransform;
      if (needSnap && (nTransform = needSnap[0](snapData))) {
        current = nTransform.multiply(current);
      }
      callMat = current;
      const mat = handler(data.value, current);
      if (mat) {
        if (repResult.update) {
          repResult.update(data.value, repResult.shape);
        } else if (mat !== true) {
          setShapeTransform(repResult.shape, mat);
          callMat = mat;
        }
        transformer.forceUpdate();
      }
    } else if (oldTransform) {
      needSnap && needSnap[1]();
      callback && callback(data.value, callMat);
    }
  });
  return transform;
};

export type LineTransformerData = BaseItem & {
  points: Pos[];
  attitude: number[];
};

export const useLineTransformer = <T extends LineTransformerData>(
  shape: Ref<DC<Line> | undefined>,
  data: Ref<T>,
  callback: (data: T) => void,
  genRepShape?: ($shape: Line) => Line
) => {
  let tempShape: Line;
  let inverAttitude: Transform;
  let stableVs = data.value.points;
  let tempVs = data.value.points;

  useCustomTransformer(shape, data, {
    openSnap: true,
    beforeHandler(data, mat) {
      const transfrom = mat.copy().multiply(inverAttitude);
      return {
        ...data,
        points: stableVs.map((v) => transfrom.point(v)),
      };
    },
    handler(data, mat) {
      // 顶点更新
      const transfrom = mat.copy().multiply(inverAttitude);
      data.points = tempVs = stableVs.map((v) => transfrom.point(v));
    },
    callback(data, mat) {
      data.attitude = mat.m;
      data.points = stableVs = tempVs;
      callback(data);
    },
    getRepShape($shape) {
      let repShape: Line;
      if (genRepShape) {
        repShape = genRepShape($shape);
      } else {
        repShape = cloneRepShape($shape).shape;
      }

      repShape = (repShape as any).points
        ? repShape
        : (repShape as unknown as Group).findOne<Line>(".line")!;
      tempShape = repShape;
      const update = (data: T) => {
        const attitude = new Transform(data.attitude);
        const inverMat = attitude.copy().invert();
        setShapeTransform(repShape, attitude);
        const initVs = data.points.map((v) => inverMat.point(v));
        stableVs = tempVs = data.points;
        repShape.points(flatPositions(initVs));
        repShape.closed(true);
        inverAttitude = inverMat;
      };
      update(data.value);

      return {
        update,
        shape: repShape,
      };
    },
  });
};

export const useMatCompTransformer = <T extends BaseItem & { mat: number[] }>(
  shape: Ref<DC<EntityShape> | undefined>,
  data: Ref<T>,
  callback: (data: T) => void
) => {
  return useCustomTransformer(shape, data, {
    beforeHandler(data, mat) {
      return { ...data, mat: mat.m };
    },
    handler(data, mat) {
      data.mat = mat.m;
    },
    getRepShape: cloneRepShape,
    callback,
    openSnap: true,
  });
};
