import { computed, reactive, ref, Ref, toRaw, watch, watchEffect } from "vue";
import { DC, EntityShape } from "../../deconstruction";
import { Shape } from "konva/lib/Shape";
import {
  globalWatch,
  installGlobalVar,
  useCan,
  useDownKeys,
  useStage,
  useTransformIngShapes,
} from "./use-global-vars.ts";
import { Stage } from "konva/lib/Stage";
import { listener } from "../../utils/event.ts";
import { inRevise, mergeFuns } from "../../utils/shared.ts";
import { ComponentValue, DrawItem, ShapeType } from "../components";
import { shapeTreeContain } from "../../utils/shape.ts";
import {
  usePointerIsTransformerInner,
  useTransformer,
} from "./use-transformer.ts";
import { useAniamtion } from "./use-animation.ts";
import { KonvaEventObject } from "konva/lib/Node";
import { useFormalLayer } from "./use-layer.ts";
import { Layer } from "konva/lib/Layer";
import { useStore } from "../store/index.ts";
import { Group } from "konva/lib/Group";

export const getHoverShape = (stage: Stage, layer: Layer) => {
  const hover = ref<EntityShape>();

  const enterHandler = (ev: KonvaEventObject<any, Stage>) => {
    const target = ev.target;
    hover.value = target;
    target.off("pointerleave", leaveHandler);
    target.on("pointerleave", leaveHandler as any);
  };

  const leaveHandler = () => {
    if (hover.value) {
      hover.value.off("pointerleave", leaveHandler);
      hover.value = undefined;
    }
  };

  stage.on("pointerenter", enterHandler);
  return [
    hover,
    () => {
      stage.off("pointerenter", enterHandler);
      leaveHandler();
    },
  ] as const;
};

export const useShapeIsTransformerInner = () => {
  const transformer = useTransformer();
  const pointerIsTransformerInner = usePointerIsTransformerInner();
  const stage = useStage();

  return (shape: EntityShape) => {
    const inner = ref(true);
    const $stage = stage.value!.getNode();
    const updateInner = () => {
      inner.value =
        transformer.isTransforming() ||
        (transformer.queueShapes.value.includes(shape) &&
          pointerIsTransformerInner());
    };

    const stop = watch(
      transformer.queueShapes,
      (_a, _, onCleanup) => {
        updateInner();
        if (inner.value) {
          $stage.on("pointermove", updateInner);
          onCleanup(() => {
            $stage.off("pointermove", updateInner);
          });
        }
      },
      { immediate: true, flush: "sync" }
    );

    return [inner, stop] as const;
  };
};

export const useMouseShapesStatus = installGlobalVar(() => {
  const can = useCan();
  const stage = useStage();
  const formatLayer = useFormalLayer();
  const listeners = ref([]) as Ref<EntityShape[]>;
  const hovers = ref([]) as Ref<EntityShape[]>;
  const press = ref([]) as Ref<EntityShape[]>;
  const selects = ref([]) as Ref<EntityShape[]>;
  const actives = ref([]) as Ref<EntityShape[]>;
  const keys = useDownKeys();
  const shapeIsTransformerInner = useShapeIsTransformerInner();

  const init = (stage: Stage) => {
    let downTime: number;
    let downTarget: EntityShape | null;

    const prevent = computed(() => keys.has(" "));
    const [hover, hoverDestory] = getHoverShape(stage, formatLayer.value!);
    const hoverChange = (onCleanup: any) => {
      if (prevent.value) {
        return;
      }

      const pHover =
        hover.value && shapeTreeContain(listeners.value, hover.value);
      // TODO首先确定之前的有没有离开
      if (hovers.value.length && hovers.value[0] !== pHover) {
        const check = hovers.value[0];
        const [inner] = shapeIsTransformerInner(check);
        onCleanup(
          watchEffect(() => {
            if (!inner.value && !prevent.value) {
              hovers.value.pop();
            }
          })
        );
      } else if (pHover) {
        hovers.value[0] = pHover;
      } else if (hovers.value.length) {
        hovers.value.pop();
      }
    };
    const stopHoverCheck = watch(
      () => [hover.value, prevent.value, hovers.value[0]],
      (_a, _b, onCleanup) => hoverChange(onCleanup)
    );

    stage.on("pointerdown.mouse-status", (ev) => {
      if (prevent.value) return;
      downTime = Date.now();
      const target = shapeTreeContain(listeners.value, ev.target);
      if (target && !press.value.includes(target)) {
        press.value.push(target);
      }
      downTarget = target;
    });

    return mergeFuns(
      stopHoverCheck,
      hoverDestory,
      listener(stage.container(), "pointerup", () => {
        if (prevent.value) return;
        press.value = [];
        if (Date.now() - downTime >= 300) return;
        if (downTarget) {
          const ndx = selects.value.indexOf(downTarget);
          if (~ndx) {
            selects.value.splice(ndx, 1);
          } else {
            selects.value.push(downTarget);
          }
          actives.value = [downTarget];
        } else {
          actives.value = [];
        }
      }),
      () => {
        listeners.value.forEach((shape) => {
          shape.off("pointerleave.mouse-status");
        });
        stage.off(
          "pointerenter.mouse-status pointerdown.mouse-status pointereup.mouse-status pointermove.mouse-status"
        );
        hovers.value = [];
        actives.value = [];
        press.value = [];
        selects.value = [];
      }
    );
  };

  let cleanup: () => void;
  const stopStatusWatch = globalWatch(
    () => can.mouseReact,
    (current, prev) => {
      if (inRevise(prev, current)) {
        cleanup! && cleanup();
        if (current) {
          cleanup = init(stage.value!.getNode());
        }
      }
    },
    { immediate: true }
  );

  return {
    var: reactive({ hovers, actives, selects, press, listeners }),
    onDestroy: () => {
      stopStatusWatch();
      cleanup && cleanup();
    },
  };
}, Symbol("mouseStatus"));

export const useMouseShapeStatus = (
  shape: Ref<DC<EntityShape> | undefined>
) => {
  const status = useMouseShapesStatus();
  watch(
    () => shape.value?.getStage(),
    (shape, _, onCleanup) => {
      if (shape) {
        if (status.listeners.includes(shape)) return;
        status.listeners.push(shape);
        onCleanup(() => {
          for (const key in status) {
            const k = key as keyof typeof status;
            const ndx = status[k].indexOf(shape);
            ~ndx && status[k].splice(ndx, 1);
          }
        });
      }
    }
  );

  return computed(() => {
    const $shape = shape.value?.getStage() as Shape;
    return {
      hover: status.hovers.includes($shape),
      active: status.actives.includes($shape),
      press: status.press.includes($shape),
      select: status.selects.includes($shape),
    };
  });
};

export const useActiveItem = <T extends EntityShape>() => {
  const status = useMouseShapesStatus();
  const store = useStore();
  return computed(() => {
    if (!status.actives[0]) return;
    let shape = status.actives[0] as T;
    shape =
      ((shape as Group)?.findOne &&
        ((shape as Group)?.findOne(".repShape") as T)) ||
      shape;

    return {
      shape,
      item: store.getItemById(status.actives[0].id()),
    };
  });
};

type MouseStyleProps<T extends ShapeType> = {
  shape?: Ref<DC<EntityShape> | undefined>;
  getMouseStyle: ComponentValue<T, "getMouseStyle">;
  data: Ref<DrawItem<T>>;
};
export const useMouseStyle = <T extends ShapeType>(
  props: MouseStyleProps<T>
) => {
  const shape = props.shape || ref();
  const status = useMouseShapeStatus(shape);
  const transformIngShapes = useTransformIngShapes();
  const mouseStyle = computed(() => {
    return props.getMouseStyle(props.data.value as any) as any;
  });
  const getStyle = () => {
    const styleMap = new Map([[mouseStyle.value.default, true]]);
    if ("hover" in mouseStyle.value) {
      styleMap.set(mouseStyle.value.hover, status.value.hover);
    }
    if ("press" in mouseStyle.value) {
      styleMap.set(mouseStyle.value.press, status.value.press);
    }
    if ("focus" in mouseStyle.value) {
      styleMap.set(mouseStyle.value.focus, status.value.active);
    }
    if (
      "drag" in mouseStyle.value &&
      transformIngShapes.value.includes(shape.value?.getNode()!)
    ) {
      styleMap.set(mouseStyle.value.drag, true);
    }
    // if ('select' in props.style) {
    // 	styleMap.set(props.style.select, status.value.select)
    // }

    const finalStyle = {};
    for (const [style, use] of styleMap.entries()) {
      use && Object.assign(finalStyle as any, style);
    }
    return finalStyle;
  };
  const style = ref();
  watchEffect(() => {
    const newStyle = getStyle();
    if (inRevise(newStyle, style.value)) {
      style.value = newStyle;
    }
  });
  return { currentStyle: style, status, shape };
};

export const useAnimationMouseStyle = <T extends ShapeType>(
  props: MouseStyleProps<T>
) => {
  const { currentStyle } = useMouseStyle(props);
  return useAniamtion(currentStyle as any);
};
