import {
  installGlobalVar,
  useCan,
  useMode,
  useStage,
} from "./use-global-vars.ts";
import { DrawItem, ShapeType } from "../components";
import { reactive, Ref, ref, watch, watchEffect } from "vue";
import { Pos } from "../../utils/math.ts";
import { clickListener, getOffset, listener } from "../../utils/event.ts";
import { mergeFuns } from "../../utils/shared.ts";
import { Mode } from "@/constant/mode.ts";

export type InteractivePreset<T extends ShapeType = ShapeType> = {
  type: T;
  callback?: () => void;
  preset?: Partial<DrawItem<T>> & { getMessages?: () => Pos[] | Area[] };
  operate?: {
    immediate?: boolean;
    single?: boolean;
    data?: any;
  };
};
export const useInteractiveProps = installGlobalVar(
  () => ref<InteractivePreset | undefined>(),
  Symbol("interactiveProps")
);

export type Area = [Pos, Pos];
export type InteractiveHook = typeof useInteractiveAreas | typeof useInteractiveDots
export type InteractiveAreas = ReturnType<typeof useInteractiveAreas>;
export type InteractiveDots = ReturnType<typeof useInteractiveDots>;
export type Interactive = InteractiveAreas | InteractiveDots;

const useInteractiveExpose = <T extends object>(
  messages: Ref<T[]>,
  init: (dom: HTMLDivElement) => () => void,
  singleDone: Ref<boolean>,
  isRunning: Ref<boolean>,
  quit: () => void,
  autoConsumed?: boolean
) => {
  const consumedMessages = reactive(new WeakSet<T>()) as WeakSet<T>;
  const stage = useStage();
  const interactiveProps = useInteractiveProps();

  watch(
    isRunning,
    (can, _, onCleanup) => {
      if (can) {
        const props = interactiveProps.value!;
        const cleanups = [] as Array<() => void>;
        if (props.operate?.single) {
          // 如果指定单次则消息中有信息，并且确定完成则马上退出
          cleanups.push(
            watchEffect(
              () => {
                if (messages.value.length > 0 && singleDone.value) {
                  quit();
                  props.callback && props.callback();
                }
              },
              { flush: "post" }
            )
          );
        }

        // 单纯添加
        if (props.operate?.immediate) {
          messages.value.push(props.operate.data as T);
          singleDone.value = true;
        } else {
          const $stage = stage.value!.getStage();
          const dom = $stage.container();
          cleanups.push(init(dom));
          cleanups.push(() => {
            quit();
            props.callback && props.callback();
          });
        }
        onCleanup(mergeFuns(cleanups));
      } else {
        messages.value = [];
      }
    },
    { immediate: true }
  );

  return {
    isRunning,
    get preset() {
      return interactiveProps.value?.preset;
    },
    get messages() {
      const items = messages.value;
      const result = items.filter((item) => !consumedMessages.has(item));
      autoConsumed && result.forEach((item) => consumedMessages.add(item));
      return result as T[];
    },
    getNdx(item: T) {
      return messages.value.indexOf(item);
    },
    get consumedMessage() {
      const items = messages.value;
      return items.filter((item) => consumedMessages.has(item)) as T[];
    },
    consume(items: T[]) {
      items.forEach((item) => consumedMessages.add(item));
    },
    singleDone,
  };
};

type UseInteractiveProps = {
  isRuning: Ref<boolean>;
  quit: () => void;
  beforeHandler?: (p: Pos) => Pos;
  enter?: () => void
  shapeType?: ShapeType;
  autoConsumed?: boolean;
};

export const useInteractiveAreas = ({
  isRuning,
  autoConsumed,
  beforeHandler,
  quit,
  enter
}: UseInteractiveProps) => {
  const mode = useMode();
  const can = useCan();
  const singleDone = ref(true);
  const messages = ref<Area[]>([]);

  const init = (dom: HTMLDivElement) => {
    let pushed = false;
    let pushNdx = -1;
    let downed = false;
    let tempArea: Area;
    let dragging = false;
    enter && enter()

    return mergeFuns(
      listener(dom, "pointerdown", (ev) => {
        if (!can.dragMode) return;
        const position = getOffset(ev, dom);
        if (ev.button === 0) {
          tempArea = [
            beforeHandler ? beforeHandler(position) : position,
          ] as unknown as Area;
          downed = true;
          singleDone.value = false;
          dragging = false;
          mode.add(Mode.draging);
        }
      }),
      listener(document.documentElement, "pointermove", (ev) => {
        if (!can.dragMode) return;
        const end = getOffset(ev, dom);
        const point = beforeHandler ? beforeHandler(end) : end;

        if (downed) {
          if (pushed) {
            messages.value[pushNdx]![1] = point;
          } else {
            tempArea[1] = point;
            pushed = true;
            pushNdx = messages.value.length;
            messages.value[pushNdx] = tempArea;
          }
          dragging = true;
        } else {
          tempArea = [point] as unknown as Area;
        }
      }),
      listener(dom, "pointerup", (ev) => {
        if (downed) {
          mode.del(Mode.draging);
        } 
        downed = false
        if (!dragging) return;

        if (can.dragMode) {
          const position = getOffset(ev, dom);
          messages.value[pushNdx]![1] = beforeHandler
            ? beforeHandler(position)
            : position;
        }

        pushNdx = -1;
        pushed = false;
        downed = false;
        dragging = false;
        singleDone.value = true;
      })
    );
  };

  return useInteractiveExpose(
    messages,
    init,
    singleDone,
    isRuning,
    quit,
    autoConsumed
  );
};

export const useInteractiveDots = ({
  autoConsumed,
  isRuning,
  quit,
  enter,
  beforeHandler,
}: UseInteractiveProps) => {
  if (autoConsumed === void 0) autoConsumed = false;

  const mode = useMode();
  const can = useCan();
  const singleDone = ref(true);
  const messages = ref<Pos[]>([]);

  const init = (dom: HTMLDivElement) => {
    if (!can.dragMode) return () => {};
    let moveIng = false;
    let pushed = false;
    const empty = { x: -9999, y: -9999 };
    const pointer = ref(empty);
    enter && enter()
    mode.add(Mode.draging);

    return mergeFuns(
      () => {
        mode.del(Mode.draging);
      },
      clickListener(dom, (_) => {
        if (!moveIng || !can.dragMode) return;
        pointer.value = { ...empty };
        singleDone.value = true;
        moveIng = false;
        pushed = false;
      }),
      listener(dom, "pointermove", (ev) => {
        if (!can.dragMode) return;
        if (!pushed) {
          messages.value.push(pointer.value);
          singleDone.value = false;
          pushed = true;
        }

        moveIng = true;
        const position = getOffset(ev);
        const current = beforeHandler ? beforeHandler(position) : position;
        pointer.value.x = current.x;
        pointer.value.y = current.y;
      })
    );
  };
  return useInteractiveExpose(
    messages,
    init,
    singleDone,
    isRuning,
    quit,
    autoConsumed
  );
};
