import { computed, h, nextTick, reactive, ref, watch, watchEffect } from "vue";
import {
  installGlobalVar,
  useCan,
  useCursor,
  useDownKeys,
  useMode,
  useStage,
} from "./use-global-vars";
import {
  Area,
  InteractiveHook,
  InteractivePreset,
  useInteractiveAreas,
  useInteractiveDots,
  useInteractiveProps,
} from "./use-interactive";
import { Mode } from "@/constant/mode";
import { copy, mergeFuns } from "@/utils/shared";
import {
  Components,
  components,
  ComponentSnapInfo,
  ComponentValue,
  DrawItem,
  ShapeType,
  SnapPoint,
} from "../components";
import { useConversionPosition } from "./use-coversion-position";
import { eqPoint, lineInner, Pos } from "@/utils/math";
import { useCustomSnapInfos, useSnap } from "./use-snap";
import { generateSnapInfos } from "../components/util";
import { useStore, useStoreRenderProcessors } from "../store";
import DrawShape from "../renderer/draw-shape.vue";
import { useHistory, useHistoryAttach } from "./use-history";
import penA from "../assert/cursor/pic_pen_a.ico";
import penR from "../assert/cursor/pic_pen_r.ico";
import { useCurrentZIndex } from "./use-layer";

type PayData<T extends ShapeType> = ComponentValue<T, "addMode"> extends "area"
  ? Area
  : Pos;

export enum MessageAction {
  add,
  delete,
  update,
  replace,
}

export type AddMessage<T extends ShapeType> = {
  consumed: PayData<T>[];
  cur?: PayData<T>;
  ndx?: number;
  action: MessageAction;
};

export const useInteractiveDrawShapeAPI = installGlobalVar(() => {
  const mode = useMode();
  const can = useCan();
  const interactiveProps = useInteractiveProps();
  const conversion = useConversionPosition(true);
  const currentZIndex = useCurrentZIndex()

  let isEnter = false;
  const enter = () => {
    if (!isEnter) {
      isEnter = true;
      mode.push(Mode.draw);
    }
  };
  const leave = () => {
    if (isEnter) {
      isEnter = false;
      mode.pop();
    }
  };

  return {
    addShape: <T extends ShapeType>(
      shapeType: T,
      preset: Partial<DrawItem<T>> = {},
      data: PayData<T>,
      pixel = false
    ) => {
      if (!can.drawMode) {
        throw "当前状态不允许添加";
      }
      enter();
      if (pixel) {
        data = (
          Array.isArray(data) ? data.map(conversion) : conversion(data)
        ) as PayData<T>;
      }
      if (!preset.zIndex) {
        preset.zIndex = currentZIndex.max + 1 
      }
      interactiveProps.value = {
        type: shapeType,
        preset,
        callback: leave,
        operate: { single: true, immediate: true, data },
      };
    },
    enterDrawShape: async <T extends ShapeType>(
      shapeType: T,
      preset: InteractivePreset<T>["preset"] = {},
      single = false
    ) => {
      if (isEnter) {
        leave();
        await new Promise((resolve) => setTimeout(resolve, 16));
      }
      if (!can.drawMode || mode.include(Mode.draw)) {
        throw "当前状态不允许添加";
      }
      if (!preset.zIndex) {
        preset.zIndex = currentZIndex.max + 1 
      }
      interactiveProps.value = {
        type: shapeType,
        preset,
        operate: { single },
        callback: leave,
      };
      enter();
    },
    quitDrawShape: () => {
      leave();
      interactiveProps.value = void 0;
    },
  };
});

export const useDrawRunning = (shapeType?: ShapeType) => {
  const stage = useStage();
  const mode = useMode();
  const interactiveProps = useInteractiveProps();
  const isRunning = ref<boolean>(false);
  let currentPreset: any;
  const updateIsRunning = () => {
    const isRun = !!(
      stage.value &&
      mode.include(Mode.draw) &&
      shapeType === interactiveProps.value?.type
    );

    if (isRunning.value !== isRun) {
      isRunning.value = isRun;
    } else if (currentPreset !== interactiveProps.value?.preset) {
      isRunning.value = false;
      nextTick(() => {
        isRunning.value = isRun;
      });
    }
    currentPreset = interactiveProps.value?.preset;
  };
  watchEffect(updateIsRunning);
  return isRunning;
};

const usePointBeforeHandler = (enableTransform = false, enableSnap = false) => {
  const conversionPosition = useConversionPosition(enableTransform);
  const snap = enableSnap && useSnap();
  const infos = useCustomSnapInfos();
  const addedInfos: ComponentSnapInfo[] = [];

  return {
    transform: (p: SnapPoint, geo = [p], geoNeedConversion = true) => {
      snap && snap.clear();
      if (geoNeedConversion) {
        geo = geo.map(conversionPosition);
      }
      p = conversionPosition(p);
      const selfInfos = generateSnapInfos(geo, true, true);
      const transform = snap && snap.move(selfInfos);
      p = transform ? transform.point(p) : p;
      return p;
    },
    addRef(p: Pos | Pos[]) {
      const geo = Array.isArray(p) ? p : [p];
      const snapInfos = generateSnapInfos(geo, true, true);
      snapInfos.forEach((info) => {
        infos.add(info);
        addedInfos.push(info);
      });
    },
    clear: () => {
      snap && snap.clear();
    },
    clearRef: () => {
      addedInfos.forEach((info) => {
        infos.remove(info);
      });
      addedInfos.length = 0;
    },
  };
};

const useInteractiveDrawTemp = <T extends ShapeType>({
  type,
  useIA,
  refSelf,
  enter,
  quit,
  getSnapGeo,
}: {
  type: T;
  useIA: InteractiveHook;
  refSelf?: boolean;
  enter?: () => void;
  quit?: () => void;
  getSnapGeo?: (data: DrawItem<T>) => SnapPoint[];
}) => {
  const { quitDrawShape } = useInteractiveDrawShapeAPI();
  const isRuning = useDrawRunning(type);
  const items = reactive([]) as DrawItem<T>[];
  const obj = components[type] as Components[T];
  const beforeHandler = usePointBeforeHandler(true, true);
  const processors = useStoreRenderProcessors();
  const conversionPosition = useConversionPosition(true);
  const store = useStore();
  const processorIds = processors.register(() => DrawShape);
  const clear = () => {
    beforeHandler.clear();
    beforeHandler.clearRef();
  };

  const ia = useIA({
    shapeType: type,
    isRuning,
    quit: () => {
      items.length = 0;
      processorIds.length = 0;
      quitDrawShape();
      clear();
      quit && quit();
    },
    enter,
    beforeHandler: (p) => {
      beforeHandler.clear();
      let geo: SnapPoint[] | undefined;
      if (items.length && getSnapGeo) {
        const item = obj.interactiveFixData(
          {...items[0]} as any,
          {
            consumed: ia.consumedMessage,
            cur: conversionPosition(p),
            action: MessageAction.update,
          } as any
        );
        geo = getSnapGeo(item as any);
      }
      return beforeHandler.transform(p, geo, !geo);
    },
  });

  const addItem = (cur: PayData<T>) => {
    let item: any = obj.interactiveToData(
      {
        consumed: ia.consumedMessage,
        cur,
        action: MessageAction.add,
      } as any,
      ia.preset
    );
    if (!item) return;
    item = reactive(item);

    if (ia.singleDone.value) {
      store.addItem(type, item);
      return;
    }

    items.push(item);

    // 箭头参考自身位置
    if (refSelf && Array.isArray(cur)) {
      beforeHandler.addRef(cur[0]);
    }

    const stop = mergeFuns(
      // 监听位置变化
      watch(
        cur,
        () =>
          obj.interactiveFixData(item, {
            consumed: ia.consumedMessage,
            cur,
            action: MessageAction.update,
          } as any),
        { deep: true }
      ),
      // 监听是否消费完毕
      watch(ia.singleDone, () => {
        processorIds.push(item.id);
        store.addItem(type, item);
        const ndx = items.indexOf(item);
        items.splice(ndx, 1);
        clear();
        stop();
      })
    );
  };

  // 每次拽结束都加组件
  watch(
    () => ia.messages,
    (datas: any) => {
      datas.forEach(addItem);
      ia.consume(datas);
    },
    { immediate: true }
  );
  return items;
};

// 拖拽面积确定组件
export const useInteractiveDrawAreas = <T extends ShapeType>(type: T) => {
  const cursor = useCursor();
  let isEnter = false;
  return useInteractiveDrawTemp({
    type,
    useIA: useInteractiveAreas,
    refSelf: type === "arrow",
    enter() {
      isEnter = true;
      cursor.push("crosshair");
    },
    quit() {
      isEnter && cursor.pop();
    },
  });
};

export const useInteractiveDrawDots = <T extends ShapeType>(type: T) => {
  const cursor = useCursor();
  let isEnter = false;
  return useInteractiveDrawTemp({
    type,
    useIA: useInteractiveDots,
    enter() {
      isEnter = true;
      cursor.push(penA);
    },
    quit() {
      isEnter && cursor.pop();
    },
    getSnapGeo(item) {
      return components[type].getSnapPoints(item as any);
    },
  });
};

export const penUpdatePoints = <T extends Pos>(
  transfromPoints: T[],
  cur: T,
  needClose = false
) => {
  const points = [...transfromPoints];
  let oper: "del" | "add" | "set" | "no" = "add";
  const resetCur = () => {
    if (points.length) {
      return (cur = points.pop()!);
    } else {
      return cur;
    }
  };

  let repeatStart = false;
  for (let i = 0; i < points.length; i++) {
    if (eqPoint(points[i], cur)) {
      const isLast = i === points.length - 1;
      const isStart = needClose && i === 0;

      if (!isStart && !isLast) {
        points.splice(i--, 1);
        oper = "del";
        repeatStart = false;
      } else if ((oper !== "del" && isLast) || isStart) {
        oper = "no";
        if (isStart) {
          repeatStart = true;
        }
      }
    }
  }
  if (oper === "del" || oper === "no") {
    if (repeatStart) {
      const change = points.length > 2;
      return {
        points,
        oper,
        cur: change ? cur : resetCur(),
        unchanged: !change,
      };
    }
    return { points, oper, cur: repeatStart ? cur : resetCur() };
  }

  for (let i = 0, ndx = 0; i < transfromPoints.length - 1; i++, ndx++) {
    const line = [transfromPoints[i], transfromPoints[i + 1]];
    if (lineInner(line, cur)) {
      oper = "set";
      points.splice(++ndx, 0, cur);
      resetCur();
    }
  }

  return { points, oper, cur };
};

// 钢笔添加
export const useInteractiveDrawPen = <T extends ShapeType>(type: T) => {
  const { quitDrawShape } = useInteractiveDrawShapeAPI();
  const isRuning = useDrawRunning(type);
  const obj = components[type] as Components[T];
  const beforeHandler = usePointBeforeHandler(true, true);
  const history = useHistory();
  const processors = useStoreRenderProcessors();
  const store = useStore();
  const downKeys = useDownKeys();
  const free = computed(() => downKeys.has("Control"));
  const processorIds = processors.register(() => {
    return (props: any) => h(DrawShape, { ...props, show: false });
  });

  // 可能历史空间会撤销 重做更改到正在绘制的组件
  const currentCursor = ref(penA);
  const cursor = useCursor();
  let stopWatch: (() => void) | null = null;
  const ia = useInteractiveDots({
    shapeType: type,
    isRuning,
    enter() {
      cursor.push(currentCursor.value);
      watch(currentCursor, () => {
        cursor.value = currentCursor.value;
      });
    },
    quit: () => {
      items.length = 0;
      processorIds.length = 0;
      quitDrawShape();
      beforeHandler.clear();
      cursor.pop();
      stopWatch && stopWatch();
    },
    beforeHandler: (p) => {
      beforeHandler.clear();
      const pa = beforeHandler.transform(p, prev && [prev, p]);
      currentIsDel && beforeHandler.clear();
      return pa;
    },
  });

  const shape = computed(
    () =>
      ia.isRunning.value &&
      typeof ia.preset?.id === "string" &&
      ia.preset?.id &&
      ia.preset.getMessages &&
      store.getItemById(ia.preset.id)
  );
  const items = reactive([]) as DrawItem<T>[];
  const messages = useHistoryAttach<Pos[]>(
    `${type}-pen`,
    isRuning,
    shape.value ? (ia.preset!.getMessages! as any) : () => [],
    true
  );
  let prev: SnapPoint;
  let firstEntry = true;
  let currentIsDel = false;

  if (shape.value) {
    processorIds.push(shape.value.id);
    items[0] = copy(shape.value) as DrawItem<T>;
    firstEntry = false;
  }

  const getAddMessage = (cur: Pos) => {
    let consumed = messages.value;
    currentCursor.value = penA;
    let pen: null | ReturnType<typeof penUpdatePoints> = null;
    if (!free.value) {
      pen = penUpdatePoints(messages.value, cur, type === "line");
      consumed = pen.points;
      cur = pen.cur;
    }

    return {
      pen,
      consumed,
      cur,
      action: firstEntry ? MessageAction.add : MessageAction.replace,
    } as any;
  };
  const setMessage = (cur: Pos) => {
    const { pen, ...msg } = getAddMessage(cur);
    if ((currentIsDel = pen?.oper === "del")) {
      currentCursor.value = penR;
      beforeHandler.clear();
    }
    return msg;
  };
  const pushMessages = (cur: Pos) => {
    const { pen } = getAddMessage(cur);
    if (pen) {
      if (!pen.unchanged) {
        messages.value = pen.points;
        cur = pen.cur;
        messages.value.push(cur);
      }
    } else {
      messages.value.push(cur);
    }

    return !pen?.unchanged;
  };

  const addItem = (cur: PayData<T>) => {
    const dot = cur as Pos;
    if (messages.value.length === 0) {
      firstEntry = true;
      items.length = 0;
    }
    let item: any = items.length === 0 ? null : items[0];
    if (!item) {
      item = obj.interactiveToData(setMessage(dot), ia.preset);
      if (!item) return;
      items[0] = item = reactive(item);
    }

    if (ia.singleDone.value) {
      store.addItem(type, item);
      return;
    }

    const update = () => {
      obj.interactiveFixData(item, setMessage(dot));
      // console.log(JSON.parse(JSON.stringify(item)))
    };

    stopWatch = mergeFuns(
      watch(free, update),
      watch(dot, update, { immediate: true, deep: true }),
      watch(
        messages,
        () => {
          if (!messages.value) return;
          if (messages.value.length === 0) {
            quitDrawShape();
          } else {
            update();
          }
        },
        { deep: true }
      ),
      // 监听是否消费完毕
      watch(ia.singleDone, () => {
        prev = { ...dot, view: true };
        const cItem = JSON.parse(JSON.stringify(item));
        const isChange = pushMessages(dot);
        if (isChange) {
          if (firstEntry) {
            processorIds.push(item.id);
            history.preventTrack(() => store.addItem(type, cItem));
          } else {
            store.setItem(type, { id: item.id, value: cItem });
          }
        }
        beforeHandler.clear();
        stopWatch && stopWatch();
        stopWatch = null;
        firstEntry = false;
      })
    );
  };

  // 每次拽结束都加组件
  watch(
    () => ia.messages,
    (datas: any) => {
      datas.forEach(addItem);
      ia.consume(datas);
    },
    { immediate: true }
  );
  return items;
};

export const useInteractiveAdd = <T extends ShapeType>(type: T) => {
  const obj = components[type];
  if (obj.addMode === "dots") {
    return useInteractiveDrawPen(type);
  } else if (obj.addMode === "area") {
    return useInteractiveDrawAreas(type);
  } else {
    return useInteractiveDrawDots(type);
  }
};
