<script setup lang="ts">
import { LeftOutlined } from "@ant-design/icons-vue";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import { message } from "ant-design-vue";
import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue";
import { useI18n } from "vue-i18n";
import { useRoute, useRouter } from "vue-router";

import ArticleEditorCanvas from "@/components/ArticleEditorCanvas.vue";
import PublishArticleModal from "@/components/PublishArticleModal.vue";
import { articlesApi } from "@/lib/api";
import { formatError } from "@/lib/errors";

const route = useRoute();
const router = useRouter();
const queryClient = useQueryClient();
const { t } = useI18n();

const articleId = computed(() => Number(route.params.id));
const title = ref("");
const markdown = ref("");
const initialTitle = ref("");
const initialMarkdown = ref("");
const leaveModalOpen = ref(false);
const publishModalOpen = ref(false);

const detailQuery = useQuery({
  queryKey: computed(() => ["articles", "detail", articleId.value]),
  enabled: computed(() => Number.isInteger(articleId.value) && articleId.value > 0),
  queryFn: () => articlesApi.detail(articleId.value),
});

watch(
  () => detailQuery.data.value,
  (detail) => {
    if (!detail) {
      return;
    }

    const nextTitle = detail.title ?? "";
    const nextMarkdown = stripLeadingTitleHeading(nextTitle, detail.markdown_content ?? "");

    title.value = nextTitle;
    markdown.value = nextMarkdown;
    initialTitle.value = nextTitle;
    initialMarkdown.value = nextMarkdown;
  },
  { immediate: true },
);

const detail = computed(() => detailQuery.data.value);
const editorLocked = computed(() => detail.value?.generate_status !== "completed");
const referencedImageAssetIds = computed(() => extractReferencedImageAssetIds(markdown.value));
const hasChanges = computed(
  () =>
    title.value.trim() !== initialTitle.value.trim() ||
    markdown.value !== initialMarkdown.value,
);

const saveMutation = useMutation({
  mutationFn: () =>
    articlesApi.update(articleId.value, {
      title: title.value.trim(),
      markdown_content: markdown.value,
      referenced_image_asset_ids: referencedImageAssetIds.value,
    }),
  onSuccess: async (data) => {
    message.success(t("article.editor.messages.saved"));
    title.value = data.title ?? "";
    markdown.value = data.markdown_content ?? "";
    initialTitle.value = title.value;
    initialMarkdown.value = markdown.value;
    await Promise.all([
      queryClient.invalidateQueries({ queryKey: ["articles"] }),
      queryClient.invalidateQueries({ queryKey: ["workspace"] }),
      queryClient.invalidateQueries({ queryKey: ["templates"] }),
      queryClient.invalidateQueries({ queryKey: ["articles", "detail", articleId.value] }),
      queryClient.invalidateQueries({ queryKey: ["articles", "detail", articleId.value, "publish-modal"] }),
    ]);
  },
  onError: (error) => {
    message.error(formatError(error));
  },
});

const saveDisabled = computed(
  () => editorLocked.value || saveMutation.isPending.value || title.value.trim() === "" || !hasChanges.value,
);
const leaveSaveDisabled = computed(
  () => editorLocked.value || saveMutation.isPending.value || title.value.trim() === "",
);

async function saveArticle(): Promise<boolean> {
  if (leaveSaveDisabled.value || !hasChanges.value) {
    return false;
  }

  try {
    await saveMutation.mutateAsync();
    return true;
  } catch {
    return false;
  }
}

async function handleSave(): Promise<void> {
  if (saveDisabled.value) {
    return;
  }

  await saveArticle();
}

function handleSaveShortcut(event: KeyboardEvent): void {
  if (!(event.ctrlKey || event.metaKey) || event.key.toLowerCase() !== "s") {
    return;
  }

  event.preventDefault();
  event.stopPropagation();
  if (event.repeat || saveDisabled.value) {
    return;
  }

  void handleSave();
}

async function handlePublish(): Promise<void> {
  if (editorLocked.value) {
    return;
  }

  if (hasChanges.value && !saveMutation.isPending.value) {
    try {
      await saveMutation.mutateAsync();
    } catch {
      return;
    }
  }

  publishModalOpen.value = true;
}

async function uploadEditorImage(file: File): Promise<string> {
  const result = await articlesApi.uploadImage(articleId.value, file);
  return result.url;
}

async function handlePublished(): Promise<void> {
  await Promise.all([
    detailQuery.refetch(),
    queryClient.invalidateQueries({ queryKey: ["articles"] }),
    queryClient.invalidateQueries({ queryKey: ["workspace"] }),
  ]);
}

const isFreeCreateEmpty = computed(
  () =>
    detail.value?.source_type === "free_create" &&
    title.value.trim() === "" &&
    markdown.value.trim() === "",
);

const isFreeCreateInitiallyEmpty = computed(
  () =>
    detail.value?.source_type === "free_create" &&
    initialTitle.value.trim() === "" &&
    initialMarkdown.value.trim() === "",
);

function handleBack(): void {
  if (saveMutation.isPending.value) {
    return;
  }

  if (isFreeCreateEmpty.value) {
    void cleanupEmptyFreeCreate();
    return;
  }

  if (hasChanges.value) {
    leaveModalOpen.value = true;
    return;
  }

  navigateBack();
}

async function cleanupEmptyFreeCreate(): Promise<void> {
  try {
    await articlesApi.remove(articleId.value);
    await Promise.all([
      queryClient.invalidateQueries({ queryKey: ["articles"] }),
      queryClient.invalidateQueries({ queryKey: ["workspace"] }),
    ]);
  } catch {
    // ignore cleanup errors
  }
  navigateBack();
}

function navigateBack(): void {
  if (window.history.length > 1) {
    void router.back();
    return;
  }

  void router.push("/articles/templates");
}

function handleStay(): void {
  leaveModalOpen.value = false;
}

function handleDiscardLeave(): void {
  leaveModalOpen.value = false;
  if (isFreeCreateInitiallyEmpty.value) {
    void cleanupEmptyFreeCreate();
    return;
  }
  navigateBack();
}

async function handleSaveAndLeave(): Promise<void> {
  const saved = await saveArticle();
  if (!saved) {
    return;
  }

  leaveModalOpen.value = false;
  navigateBack();
}

function stripLeadingTitleHeading(titleValue: string, markdownValue: string): string {
  const normalizedTitle = titleValue.trim();
  if (!normalizedTitle) {
    return markdownValue;
  }

  const normalizedMarkdown = markdownValue.replace(/^\uFEFF/, "");
  const match = normalizedMarkdown.match(/^#\s+(.+?)\s*(\r?\n|$)/);
  if (!match) {
    return markdownValue;
  }

  if (match[1].trim() !== normalizedTitle) {
    return markdownValue;
  }

  return normalizedMarkdown.slice(match[0].length).replace(/^\s*\r?\n/, "");
}

function extractReferencedImageAssetIds(markdownValue: string): number[] {
  const ids = new Set<number>();
  const matcher = /data-asset-id=(?:"|')(\d+)(?:"|')/g;

  for (const match of markdownValue.matchAll(matcher)) {
    const id = Number.parseInt(match[1] ?? "", 10);
    if (Number.isInteger(id) && id > 0) {
      ids.add(id);
    }
  }

  return [...ids];
}

onMounted(() => {
  window.addEventListener("keydown", handleSaveShortcut);
});

onBeforeUnmount(() => {
  window.removeEventListener("keydown", handleSaveShortcut);
});
</script>

<template>
  <div class="article-editor-view">
    <header class="article-editor-view__topbar">
      <button class="article-editor-view__back" type="button" @click="handleBack">
        <LeftOutlined />
        <span>{{ t("common.back") }}</span>
      </button>

      <div class="article-editor-view__actions">
        <a-button
          :disabled="saveDisabled"
          :loading="saveMutation.isPending.value"
          title="Ctrl/Cmd + S"
          aria-keyshortcuts="Control+S Meta+S"
          @click="handleSave"
        >
          {{ t("common.save") }}
        </a-button>
        <a-button type="primary" :disabled="editorLocked" @click="handlePublish">
          {{ t("article.editor.publish") }}
        </a-button>
      </div>
    </header>

    <div v-if="detailQuery.isPending.value" class="article-editor-view__loading">
      <a-skeleton active :paragraph="{ rows: 14 }" />
    </div>

    <template v-else-if="detail">
      <a-alert
        v-if="editorLocked"
        class="article-editor-view__alert"
        type="info"
        show-icon
        :message="t('article.editor.messages.locked')"
      />
      <div class="article-editor-view__layout">
        <section class="article-editor-view__main">
          <ArticleEditorCanvas
            :key="String(articleId)"
            :article-id="articleId"
            v-model:title="title"
            v-model="markdown"
            :disabled="editorLocked"
            :upload-image="uploadEditorImage"
          />
        </section>
      </div>
    </template>

    <a-empty v-else :description="t('common.noData')" />

    <a-modal
      :open="leaveModalOpen"
      :title="t('article.editor.leaveConfirm.title')"
      :closable="false"
      :mask-closable="false"
      @cancel="handleStay"
    >
      <p class="article-editor-view__leave-copy">
        {{ t("article.editor.leaveConfirm.description") }}
      </p>

      <template #footer>
        <a-button @click="handleStay">
          {{ t("common.cancel") }}
        </a-button>
        <a-button @click="handleDiscardLeave">
          {{ t("article.editor.leaveConfirm.discard") }}
        </a-button>
        <a-button
          type="primary"
          :loading="saveMutation.isPending.value"
          :disabled="leaveSaveDisabled"
          @click="handleSaveAndLeave"
        >
          {{ t("article.editor.leaveConfirm.saveAndLeave") }}
        </a-button>
      </template>
    </a-modal>

    <PublishArticleModal
      v-model:open="publishModalOpen"
      :article-id="detail?.id ?? null"
      @published="handlePublished"
    />
  </div>
</template>

<style scoped>
.article-editor-view {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

.article-editor-view__topbar {
  display: flex;
  align-items: center;
  justify-content: space-between;
  gap: 16px;
}

.article-editor-view__back {
  display: inline-flex;
  align-items: center;
  gap: 10px;
  padding: 0;
  border: 0;
  background: transparent;
  color: #111827;
  font-size: 16px;
  font-weight: 700;
  cursor: pointer;
}

.article-editor-view__back :deep(.anticon) {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 36px;
  height: 36px;
  border-radius: 999px;
  background: rgba(255, 255, 255, 0.82);
  border: 1px solid rgba(144, 157, 181, 0.24);
}

.article-editor-view__actions {
  display: flex;
  gap: 12px;
}

.article-editor-view__main {
  height: calc(100vh - 180px);
  overflow: hidden;
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid rgba(216, 225, 237, 0.9);
  border-radius: 28px;
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.05);
  backdrop-filter: blur(14px);
}

.article-editor-view__loading {
  padding: 24px;
  background: rgba(255, 255, 255, 0.88);
  border: 1px solid rgba(216, 225, 237, 0.9);
  border-radius: 28px;
  box-shadow: 0 18px 40px rgba(15, 23, 42, 0.05);
  backdrop-filter: blur(14px);
}

.article-editor-view__alert {
  border-radius: 20px;
}

.article-editor-view__leave-copy {
  margin: 0;
  color: #475467;
  line-height: 1.75;
}

.article-editor-view__layout {
  min-width: 0;
}

.article-editor-view__main {
  display: flex;
  flex-direction: column;
  gap: 18px;
}

@media (max-width: 768px) {
  .article-editor-view__topbar {
    flex-direction: column;
    align-items: stretch;
  }

  .article-editor-view__actions {
    width: 100%;
  }

  .article-editor-view__actions :deep(.ant-btn) {
    flex: 1;
  }
}
</style>
