<script setup lang="ts">
import { onBeforeUnmount, onMounted, ref, watch } from "vue";

type ToastUiEditor = {
  destroy: () => void;
  getMarkdown: () => string;
  getHTML: () => string;
  setMarkdown: (markdown: string, cursorToEnd?: boolean) => void;
  setHTML: (html: string, cursorToEnd?: boolean) => void;
};

const props = defineProps<{
  modelValue: string;
  placeholder?: string;
}>();

const emit = defineEmits<{
  (event: "update:modelValue", value: string): void;
}>();

const mountEl = ref<HTMLDivElement | null>(null);
const editor = ref<ToastUiEditor | null>(null);
const isSyncing = ref(false);

function looksLikeHtml(value: string) {
  return /<([a-z][\w-]*)\b[^>]*>/i.test(value);
}

onMounted(async () => {
  if (!mountEl.value) return;
  const [{ default: Editor }] = await Promise.all([
    import("@toast-ui/editor"),
    import("@toast-ui/editor/dist/toastui-editor.css"),
  ]);

  const initialValue = String(props.modelValue ?? "");
  const instance = new Editor({
    el: mountEl.value,
    initialEditType: "markdown",
    previewStyle: "vertical",
    initialValue: looksLikeHtml(initialValue) ? "" : initialValue,
    placeholder: props.placeholder ?? "Write with Markdown...",
    height: "520px",
    usageStatistics: false,
    events: {
      change: () => {
        if (isSyncing.value) return;
        emit("update:modelValue", instance.getMarkdown());
      },
    },
  });

  if (looksLikeHtml(initialValue)) {
    isSyncing.value = true;
    instance.setHTML(initialValue, false);
    emit("update:modelValue", instance.getMarkdown());
    isSyncing.value = false;
  }

  editor.value = instance as unknown as ToastUiEditor;
});

watch(
  () => props.modelValue,
  (incoming) => {
    const instance = editor.value;
    if (!instance || isSyncing.value) return;
    const next = String(incoming ?? "");
    const currentMarkdown = instance.getMarkdown();
    const currentHtml = instance.getHTML();
    if (next === currentMarkdown || next === currentHtml) return;

    isSyncing.value = true;
    if (looksLikeHtml(next)) {
      instance.setHTML(next, false);
      emit("update:modelValue", instance.getMarkdown());
    } else {
      instance.setMarkdown(next, false);
    }
    isSyncing.value = false;
  },
);

onBeforeUnmount(() => {
  editor.value?.destroy();
  editor.value = null;
});
</script>

<template>
  <div class="markdown-rich-editor min-h-[420px]">
    <div ref="mountEl" />
  </div>
</template>

<style scoped>
.markdown-rich-editor :deep(.toastui-editor-defaultUI) {
  border-radius: 0.75rem;
  border: 1px solid color-mix(in oklab, var(--color-base-content) 14%, transparent);
}

.markdown-rich-editor :deep(.toastui-editor-mode-switch) {
  border-top: 1px solid color-mix(in oklab, var(--color-base-content) 12%, transparent);
}

.markdown-rich-editor :deep(.toastui-editor-main-container) {
  min-height: 360px;
}
</style>
