import { DC, EntityShape } from "@/deconstruction";
import {
  computed,
  EmitFn,
  isRef,
  reactive,
  Ref,
  ref,
  shallowReactive,
  watchEffect,
} from "vue";
import { useAutomaticData } from "./use-automatic-data";
import { useCurrentZIndex, useZIndex } from "./use-layer";
import { useAnimationMouseStyle } from "./use-mouse-status";
import { components, DrawItem, ShapeType } from "../components";
import { useMatCompTransformer, useLineTransformer } from "./use-transformer";
import { useGetShapeCopyTransform } from "./use-copy";
import {
  Bottom,
  Delete,
  DocumentCopy,
  Location,
  Lock,
  Top,
  Unlock,
} from "@element-plus/icons-vue";
import { mergeFuns, onlyId } from "@/utils/shared";
import { Shape } from "konva/lib/Shape";
import { Transform } from "konva/lib/Util";
import { mergeDescribes, PropertyKeys } from "../propertys";
import { useStore } from "../store";
import { globalWatch } from "./use-global-vars";
import { useAlignmentShape } from "./use-alignment";

type Emit<T> = EmitFn<{
  updateShape: (value: T) => void;
  addShape: (value: T) => void;
  delShape: () => void;
}>;

export const useComponentMenus = <T extends DrawItem>(
  shape: Ref<DC<EntityShape> | undefined>,
  data: Ref<T>,
  emit: Emit<T>,
  alignment?: (data: T, mat: Transform) => void,
  copyHandler?: (transform: Transform, data: T) => T
) => {
  const operateMenus: Array<{
    icon?: any;
    label?: string;
    handler: () => void;
  }> = shallowReactive([]);

  // 锁定 解锁
  operateMenus.push(
    reactive({
      label: computed(() => (data.value.lock ? "解锁" : "锁定")) as any,
      icon: computed(() => (data.value.lock ? Unlock : Lock)),
      handler() {
        data.value.lock = !data.value.lock;
        emit("updateShape", { ...data.value });
      },
    })
  );

  // 置顶 置底
  const currentZIndex = useCurrentZIndex();
  operateMenus.push(
    {
      label: `置顶`,
      icon: Top,
      handler() {
        data.value.zIndex = currentZIndex.max + 1;
        emit("updateShape", { ...data.value });
      },
    },
    {
      label: `置底`,
      icon: Bottom,
      handler() {
        data.value.zIndex = currentZIndex.min - 1;
        emit("updateShape", { ...data.value });
      },
    }
  );

  if (alignment) {
    const [alignmentShape] = useAlignmentShape(shape);
    operateMenus.push({
      label: "对齐",
      async handler() {
        const mat = await alignmentShape();
        alignment(data.value, mat);
        emit("updateShape", { ...data.value });
      },
      icon: Location,
    });
  }

  if (copyHandler) {
    const getCopyTransform = useGetShapeCopyTransform(shape);
    operateMenus.push({
      label: `复制`,
      icon: DocumentCopy,
      handler() {
        const transform = getCopyTransform();
        const copyData = copyHandler(
          transform,
          JSON.parse(JSON.stringify(data.value)) as T
        );
        copyData.id = onlyId();
        emit("addShape", copyData);
      },
    });
  }

  operateMenus.push({
    label: `删除`,
    icon: Delete,
    handler() {
      emit("delShape");
    },
  });

  return operateMenus;
};

export type UseComponentStatusProps<
  T extends DrawItem,
  S extends EntityShape
> = {
  emit: Emit<T>;
  type?: ShapeType;
  props: { data: T };
  alignment?: (data: T, mat: Transform) => void;
  getMouseStyle: any;
  defaultStyle: any;
  propertys: PropertyKeys;
  transformType?: "line" | "mat" | "custom";
  customTransform?: (
    callback: () => void,
    shape: Ref<DC<S> | undefined>,
    data: Ref<T>
  ) => void;
  getRepShape?: () => Shape;
  copyHandler: (transform: Transform, data: T) => T;
};

export const useComponentStatus = <S extends EntityShape, T extends DrawItem>(
  args: UseComponentStatusProps<T, S>
) => {
  const shape = ref<DC<S>>();
  const data = useAutomaticData(() => args.props.data);
  const [style] = useAnimationMouseStyle({
    data: data,
    shape,
    getMouseStyle: args.getMouseStyle,
  }) as any;

  if (args.transformType === "line") {
    useLineTransformer(
      shape as any,
      data as any,
      (newData) => args.emit("updateShape", newData as T),
      args.getRepShape as any
    );
  } else if (args.transformType === "mat") {
    useMatCompTransformer(shape, data as any, (nData) =>
      args.emit("updateShape", nData as any)
    );
  } else if (args.transformType === "custom" && args.customTransform) {
    args.customTransform(
      () => args.emit("updateShape", data.value as any),
      shape,
      data
    );
  }
  useZIndex(shape, data);

  return {
    data,
    style,
    tData: computed(() => {
      const tData = { ...args.defaultStyle, ...data.value };
      if (style) {
        Object.assign(tData, style.value);
      }
      return tData;
    }),
    shape,
    operateMenus: useComponentMenus(
      shape,
      data,
      args.emit,
      args.alignment,
      args.copyHandler
    ),
    describes: mergeDescribes(
      data,
      args.defaultStyle,
      args.propertys || []
    )
  };
};

export const useGetComponentData = <D extends DrawItem>() => {
  const store = useStore();

  return (shape: Ref<EntityShape | undefined> | EntityShape | undefined) =>
    computed(() => {
      shape = isRef(shape) ? shape.value : shape;
      if (!shape?.id()) return;
      return store.getItemById(shape.id()) as D;
    });
};

export const useComponentsAttach = <T>(
  getter: <K extends ShapeType>(type: K, data: DrawItem<K>) => T,
  types = Object.keys(components) as ShapeType[]
) => {
  const store = useStore();
  const attachs = reactive([]) as T[];
  const cleanups = [] as Array<() => void>;

  for (const type of types) {
    cleanups.push(
      globalWatch(
        () => store.data[type]?.map((item) => item),
        (items, _, onCleanup) => {
          if (!items) return;
          for (const item of items) {
            const attachWatchStop = watchEffect((onCleanup) => {
              const attach = getter(type, item);
              attachs.push(attach);
              onCleanup(() => {
                const ndx = attachs.indexOf(attach);
                ~ndx && attachs.splice(ndx, 1);
              });
            });
            const existsWatchStop = watchEffect(() => {
              if (!items.includes(item)) {
                attachWatchStop();
                existsWatchStop();
              }
            });
            onCleanup(() => {
              attachWatchStop();
              existsWatchStop();
            });
          }
        },
        { immediate: true }
      )
    );
  }

  return {
    attachs,
    cleanup: mergeFuns(cleanups),
  };
};
