<script setup lang="ts">
import { useQuery, useQueryClient } from "@tanstack/vue-query";
import type { TableColumnsType } from "ant-design-vue";
import type { ArticleVersion } from "@geo/shared-types";
import { computed, ref, watch } from "vue";
import { useI18n } from "vue-i18n";

import {
  articlesApi,
  isGenerationStreamEnabled,
  subscribeArticleGeneration,
  type ArticleGenerationStreamEvent,
} from "@/lib/api";
import {
  formatDateTime,
  getGenerateStatusMeta,
  getPublishStatusMeta,
  getSourceTypeLabel,
} from "@/lib/display";
import { formatError } from "@/lib/errors";

const props = defineProps<{
  open: boolean;
  articleId: number | null;
}>();

const emit = defineEmits<{
  close: [];
}>();

const queryClient = useQueryClient();
const { t } = useI18n();
const activeTab = ref("content");
const enabled = computed(() => props.open && Boolean(props.articleId));
const streamTitle = ref("");
const streamMarkdown = ref("");
const streamStatus = ref<string | null>(null);
const streamError = ref("");
const streamFeatureEnabled = isGenerationStreamEnabled();

const detailQuery = useQuery({
  queryKey: computed(() => ["articles", "detail", props.articleId]),
  enabled,
  queryFn: () => articlesApi.detail(props.articleId as number),
});

const versionsQuery = useQuery({
  queryKey: computed(() => ["articles", "versions", props.articleId]),
  enabled,
  queryFn: () => articlesApi.versions(props.articleId as number),
});

const versionColumns = computed<TableColumnsType<ArticleVersion>>(() => [
  {
    title: t("common.versions"),
    dataIndex: "version_no",
    key: "version_no",
    width: 88,
  },
  {
    title: t("common.title"),
    dataIndex: "title",
    key: "title",
  },
  {
    title: t("common.source"),
    dataIndex: "source_label",
    key: "source_label",
    width: 120,
  },
  {
    title: t("common.wordCount"),
    dataIndex: "word_count",
    key: "word_count",
    width: 92,
  },
  {
    title: t("common.createdAt"),
    dataIndex: "created_at",
    key: "created_at",
    width: 168,
  },
]);

const detail = computed(() => detailQuery.data.value);
const displayTitle = computed(() => streamTitle.value || detail.value?.title || t("article.untitled"));
const displayMarkdown = computed(() => streamMarkdown.value || detail.value?.markdown_content || "");
const displayError = computed(() => streamError.value || detail.value?.generation_error_message || "");
const generateMeta = computed(() =>
  getGenerateStatusMeta(streamStatus.value || detail.value?.generate_status),
);
const publishMeta = computed(() => getPublishStatusMeta(detail.value?.publish_status));

function handleClose(): void {
  activeTab.value = "content";
  emit("close");
}

watch(
  [() => props.open, () => props.articleId],
  ([open, articleId], _prev, onCleanup) => {
    resetStreamState();

    if (!open || !articleId || !streamFeatureEnabled) {
      return;
    }

    const controller = new AbortController();
    void subscribeArticleGeneration(
      articleId,
      {
        onEvent: handleStreamEvent,
        onClose: () => {
          if (!controller.signal.aborted) {
            void refreshArticleData();
          }
        },
      },
      controller.signal,
    ).catch((error) => {
      if (controller.signal.aborted) {
        return;
      }
      streamError.value = formatError(error);
    });

    onCleanup(() => {
      controller.abort();
    });
  },
  { immediate: true },
);

watch(
  [() => props.open, () => props.articleId, () => detail.value?.generate_status],
  ([open, articleId, generateStatus], _prev, onCleanup) => {
    if (!open || !articleId || streamFeatureEnabled || generateStatus !== "generating") {
      return;
    }

    const timer = window.setInterval(() => {
      void refreshArticleData();
    }, 2000);

    onCleanup(() => {
      window.clearInterval(timer);
    });
  },
  { immediate: true },
);

function handleStreamEvent(event: ArticleGenerationStreamEvent): void {
  if (event.type === "ping") {
    return;
  }

  if (event.title) {
    streamTitle.value = event.title;
  }
  if (event.status) {
    streamStatus.value = event.status;
  }
  if (event.error) {
    streamError.value = event.error;
  }

  if (event.type === "snapshot" || event.type === "completed") {
    if (event.content) {
      streamMarkdown.value = event.content;
    }
  } else if (event.type === "delta") {
    if (event.content) {
      streamMarkdown.value = event.content;
    } else if (event.delta) {
      streamMarkdown.value += event.delta;
    }
  }

  if (event.type === "completed" || event.type === "error") {
    void refreshArticleData();
  }
}

function resetStreamState(): void {
  streamTitle.value = "";
  streamMarkdown.value = "";
  streamStatus.value = null;
  streamError.value = "";
}

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

<template>
  <a-drawer
    :open="open"
    width="840"
    :title="t('article.drawerTitle')"
    class="article-drawer"
    @close="handleClose"
  >
    <div v-if="detailQuery.isPending.value" class="article-drawer__loading">
      <a-skeleton active :paragraph="{ rows: 10 }" />
    </div>

    <template v-else-if="detail">
      <section class="article-drawer__hero">
        <div>
          <p class="eyebrow">{{ t("article.preview") }}</p>
          <h2>{{ displayTitle }}</h2>
          <p>
            {{ getSourceTypeLabel(detail.source_type) }} ·
            {{ formatDateTime(detail.created_at) }}
          </p>
        </div>

        <div class="article-drawer__tags">
          <a-tag :color="generateMeta.color">{{ generateMeta.label }}</a-tag>
          <a-tag :color="publishMeta.color">{{ publishMeta.label }}</a-tag>
        </div>
      </section>

      <section class="article-drawer__meta">
        <a-descriptions :column="2" size="small" bordered>
          <a-descriptions-item :label="t('article.meta.templateType')">
            {{ detail.template_name || "--" }}
          </a-descriptions-item>
          <a-descriptions-item :label="t('article.meta.currentVersion')">
            {{ detail.version_no ?? "--" }}
          </a-descriptions-item>
          <a-descriptions-item :label="t('article.meta.source')">
            {{ detail.source_label || "--" }}
          </a-descriptions-item>
          <a-descriptions-item :label="t('article.meta.wordCount')">
            {{ detail.word_count || "--" }}
          </a-descriptions-item>
        </a-descriptions>
      </section>

      <a-alert
        v-if="displayError"
        class="article-drawer__alert"
        type="error"
        show-icon
        :message="t('common.generateStatus')"
        :description="displayError"
      />

      <a-tabs v-model:activeKey="activeTab" class="article-drawer__tabs">
        <a-tab-pane key="content" :tab="t('article.preview')">
          <div
            v-if="detail.html_content"
            class="article-drawer__content"
            v-html="detail.html_content"
          />
          <pre v-else-if="displayMarkdown" class="article-drawer__markdown">{{
            displayMarkdown
          }}</pre>
          <a-empty v-else :description="t('article.noContent')" />
        </a-tab-pane>

        <a-tab-pane key="versions" :tab="t('article.versionHistory')">
          <a-table
            :columns="versionColumns"
            :data-source="versionsQuery.data.value || []"
            :loading="versionsQuery.isPending.value"
            :pagination="false"
            row-key="id"
            size="small"
          >
            <template #bodyCell="{ column, record }">
              <template v-if="column.key === 'title'">
                <div class="article-drawer__version-title">
                  <strong>{{ record.title || t("article.versionUntitled") }}</strong>
                  <span>{{ formatDateTime(record.created_at) }}</span>
                </div>
              </template>
              <template v-else-if="column.key === 'source_label'">
                {{ record.source_label || "--" }}
              </template>
              <template v-else-if="column.key === 'created_at'">
                {{ formatDateTime(record.created_at) }}
              </template>
            </template>
          </a-table>
        </a-tab-pane>
      </a-tabs>
    </template>

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

<style scoped>
.article-drawer :deep(.ant-drawer-body) {
  padding: 0 24px 24px;
}

.article-drawer__loading {
  padding-top: 16px;
}

.article-drawer__hero {
  display: flex;
  align-items: flex-start;
  justify-content: space-between;
  gap: 24px;
  padding-top: 12px;
}

.article-drawer__hero h2 {
  margin: 0 0 8px;
  font-size: 24px;
  line-height: 1.2;
}

.article-drawer__hero p:last-child {
  margin: 0;
  color: var(--muted);
}

.article-drawer__tags {
  display: flex;
  flex-wrap: wrap;
  justify-content: flex-end;
  gap: 8px;
}

.article-drawer__meta {
  margin-top: 24px;
}

.article-drawer__alert {
  margin-top: 24px;
}

.article-drawer__tabs {
  margin-top: 24px;
}

.article-drawer__content,
.article-drawer__markdown {
  padding: 20px;
  background: #f8fafc;
  border: 1px solid #e5ecf5;
  border-radius: 18px;
}

.article-drawer__content :deep(h1),
.article-drawer__content :deep(h2),
.article-drawer__content :deep(h3) {
  margin-top: 0;
}

.article-drawer__markdown {
  margin: 0;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
  line-height: 1.6;
  white-space: pre-wrap;
}

.article-drawer__version-title {
  display: flex;
  flex-direction: column;
  gap: 4px;
}

.article-drawer__version-title span {
  color: var(--muted);
  font-size: 12px;
}

@media (max-width: 960px) {
  .article-drawer__hero {
    flex-direction: column;
  }

  .article-drawer__tags {
    justify-content: flex-start;
  }
}
</style>
