<script setup lang="ts">
import * as PDFJS from "pdfjs-dist";
import { inject, onMounted, provide, Ref, useTemplateRef, watch } from "vue";

import MinimalUiManager from "../utils/manager";
import {
  COMMENT_EDITOR_KEY,
  CONTAINER_OBJ_KEY,
  EDITOR_ANNOTATION_LAYER_OBJ_KEY,
  EDITOR_TEXT_LAYER_OBJ_KEY,
  FREE_TEXT_EDITOR_KEY,
  HIGHLIGHT_EDITOR_KEY,
  INK_EDITOR_KEY,
  STAMP_EDITOR_KEY,
} from "../utils/symbols";

import type { IL10n } from "pdfjs-dist/types/web/interfaces";
import type { PDFPageProxy, PageViewport } from "pdfjs-dist";
import type {
  EditorEmitters,
  EditorEventPayload,
  EditorFn,
  EditorParams,
  EditorRequest,
  HighlightEditorColors,
} from "../types";

const DEFAULT_HIGHLIGHT_COLORS: HighlightEditorColors = {
  yellow: ["#FFEB3B", "#FFFFCC"],
  green: ["#8BC34A", "#53FFBC"],
  pink: ["#FFCBE6", "#F6B8FF"],
  red: ["#F44336", "#FF4F5F"],
  blue: ["#2196F3", "#80EBFF"],
};

const props = defineProps<{
  page?: PDFPageProxy;
  viewport?: PageViewport;
  document?: PDFJS.PDFDocumentProxy;
  intent: string;
  editorType: number;
  editorLayer: boolean;
}>();

const emits = defineEmits<{
  (e: "editorAdded", payload: EditorEventPayload): void;
  (e: "editorRemoved", payload: EditorEventPayload): void;
  (e: "editorSelected", payload: EditorEventPayload): void;
  (e: "editorLoaded"): void;
}>();

const layer = useTemplateRef<HTMLDivElement>("layer");

let editor: PDFJS.AnnotationEditorLayer | null = null;

const commentEditor: EditorFn & EditorRequest = { fn: null, request: null };
const freeTextEditor: EditorParams & EditorEmitters = {
  params: null,
  emit: null,
};
const highlightEditor: EditorFn & EditorParams & EditorEmitters = {
  fn: null,
  params: null,
  emit: null,
};
const inkEditor: EditorParams & EditorEmitters = { params: null, emit: null };
const stampEditor: EditorFn & EditorRequest & EditorEmitters = {
  fn: (source: File | string | null) => {
    if (props.editorType !== PDFJS.AnnotationEditorType.STAMP) {
      console.warn(`[vue-pdf] Cannot add stamp when editor type is not STAMP.`);
      return;
    }

    if (typeof source === "string") {
      editor?.addNewEditor({ bitmapUrl: source });
    } else if (source) {
      editor?.addNewEditor({ bitmapFile: source });
    } else {
      editor?.addNewEditor();
    }
  },
  request: null,
  emit: null,
};

provide(FREE_TEXT_EDITOR_KEY, freeTextEditor);
provide(HIGHLIGHT_EDITOR_KEY, highlightEditor);
provide(COMMENT_EDITOR_KEY, commentEditor);
provide(STAMP_EDITOR_KEY, stampEditor);
provide(INK_EDITOR_KEY, inkEditor);

const textLayerProvider = inject(EDITOR_TEXT_LAYER_OBJ_KEY)! as {
  container: Ref<HTMLDivElement | undefined>;
  promise: Promise<HTMLDivElement | undefined>;
  resolve: (value: HTMLDivElement | undefined) => void;
};
const annotationLayerProvider = inject(EDITOR_ANNOTATION_LAYER_OBJ_KEY)! as {
  instance: Ref<PDFJS.AnnotationLayer | undefined>;
  promise: Promise<PDFJS.AnnotationLayer | undefined>;
  resolve: (value: PDFJS.AnnotationLayer | undefined) => void;
};
const containerObj = inject(CONTAINER_OBJ_KEY)! as {
  wrapper: Ref<HTMLDivElement | undefined>;
  container: Ref<HTMLDivElement | undefined>;
  uiManager: MinimalUiManager | null;
};

async function render() {
  if (!containerObj.uiManager && !props.editorLayer) return;

  const page = props.page!;
  const viewport = props.viewport!;

  if (!containerObj.uiManager) {
    const editorsParams = [
      freeTextEditor.params,
      highlightEditor.params,
      inkEditor.params,
    ].filter((e) => e !== null) as Function[];

    const emitters = {
      [PDFJS.AnnotationEditorType.FREETEXT]: freeTextEditor.emit,
      [PDFJS.AnnotationEditorType.INK]: inkEditor.emit,
      [PDFJS.AnnotationEditorType.STAMP]: stampEditor.emit,
      [PDFJS.AnnotationEditorType.HIGHLIGHT]: highlightEditor.emit,
      [MinimalUiManager.LAYER_EMITTER_ID]: emits,
    };

    containerObj.uiManager = new MinimalUiManager(
      props.document,
      commentEditor,
      stampEditor,
      highlightEditor.fn?.() ?? DEFAULT_HIGHLIGHT_COLORS,
      containerObj.wrapper.value!,
      emitters,
      editorsParams,
    );
  }

  // Wait for both text layer and annotation layer dependencies to resolve
  const [textLayerElement, annotationLayerInstance] = await Promise.all([
    textLayerProvider.promise,
    annotationLayerProvider.promise,
  ]);

  if (editor && editor.pageIndex !== page._pageIndex) {
    editor.destroy();
    editor = null;
  }

  const clonedViewport = viewport.clone({ dontFlip: true });
  if (editor) {
    editor.pause(true);
    editor.update({ viewport: clonedViewport });
    containerObj.uiManager?.onRotationChanging({
      pagesRotation: clonedViewport.rotation,
    });
    containerObj.uiManager?.onScaleChanging({ scale: clonedViewport.scale });
    containerObj.uiManager?.onPageChanging({ pageNumber: page.pageNumber });
    editor.pause(false);
    containerObj.uiManager?.updateMode(props.editorType);
    return;
  }

  const drawLayer = new PDFJS.DrawLayer();
  drawLayer.setParent(containerObj.wrapper.value);

  editor = new PDFJS.AnnotationEditorLayer({
    uiManager: containerObj.uiManager!,
    div: layer.value!,
    viewport: viewport!.clone({ dontFlip: true }),
    enabled: true,
    pageIndex: page!.pageNumber - 1,
    l10n: null as any as IL10n,
    textLayer: textLayerElement
      ? ({ div: textLayerElement } as any as HTMLDivElement)
      : undefined, // <-- Type casting to satisfy typescript bllsht :)
    drawLayer: drawLayer,
    mode: props.editorType,
    structTreeLayer: null,
    accessibilityManager: undefined,
    annotationLayer: annotationLayerInstance,
  });
  editor.toggleDrawing = (enabled: boolean) => {
    layer.value?.classList.toggle("drawing", !enabled);
  };
  editor.hasTextLayer = (textlayer: HTMLDivElement) => {
    const pageAttr = textlayer.getAttribute("data-main-page");
    if (!pageAttr) return false;
    return parseInt(pageAttr) === page._pageIndex;
  };
  editor.render({ viewport: viewport!.clone({ dontFlip: true }) });
  editor.enable();

  containerObj.uiManager?.onRotationChanging({
    pagesRotation: clonedViewport.rotation,
  });
  containerObj.uiManager?.onScaleChanging({ scale: clonedViewport.scale });
  containerObj.uiManager?.onPageChanging({ pageNumber: page.pageNumber });
  containerObj.uiManager?.updateMode(props.editorType);

  emits("editorLoaded");
}

function checkEditorType() {
  const whiteList = [
    PDFJS.AnnotationEditorType.FREETEXT,
    PDFJS.AnnotationEditorType.HIGHLIGHT,
    PDFJS.AnnotationEditorType.INK,
    PDFJS.AnnotationEditorType.STAMP,
    PDFJS.AnnotationEditorType.NONE,
    PDFJS.AnnotationEditorType.DISABLE,
  ];

  if (!whiteList.includes(props.editorType)) {
    console.warn(`[vue-pdf] Unsupported editor type: ${props.editorType}.`);
  } else {
    render();
  }
}

function unselectAll() {
  containerObj.uiManager?.unselectAll();
}

watch(
  () => [props.editorLayer],
  ([newVal]) => {
    if (newVal && !containerObj.uiManager) {
      if (props.page && props.viewport && layer.value) checkEditorType();
    } else {
      const svgsPath = containerObj.wrapper.value?.querySelectorAll("svg");
      svgsPath?.forEach((svg) => {
        if (newVal) svg.classList.remove("svg-editor-hidden");
        else svg.classList.add("svg-editor-hidden");
      });
    }
  },
);

watch(
  () => [props.viewport, props.editorType],
  () => {
    if (props.page && props.viewport && layer.value) checkEditorType();
  },
);

onMounted(() => {
  if (props.page && props.viewport && layer.value) checkEditorType();
});

defineExpose({
  unselectAll,
});
</script>

<template>
  <div
    v-show="editorLayer"
    ref="layer"
    class="annotationEditorLayer"
    id="annotationEditorLayer"
  />
  <slot></slot>
</template>
