<script setup lang="ts">
import { ReloadOutlined } from "@ant-design/icons-vue";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import { message, notification } from "ant-design-vue";
import type {
  MediaPlatform,
  PlatformAccount,
  PublisherLocalPlatformState,
  PublisherPublishResponse,
  PublisherPublishTaskResult,
} from "@geo/shared-types";
import { computed, h, ref, watch, watchEffect } from "vue";
import { useI18n } from "vue-i18n";

import CoverPickerModal from "@/components/CoverPickerModal.vue";
import { articlesApi, getApiBaseURL, mediaApi, resolveApiURL } from "@/lib/api";
import {
  coverUploadRequired,
  deriveCoverFileName,
} from "@/lib/cover-requirements";
import { formatError } from "@/lib/errors";
import {
  normalizePublishPlatformId,
  normalizePublishPlatformIds,
} from "@/lib/publish-platforms";
import { publishWithPublisherPlugin } from "@/lib/publisher-plugin";
import { loadPublisherRuntimeState } from "@/lib/publisher-runtime";

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

const emit = defineEmits<{
  "update:open": [value: boolean];
  published: [];
}>();

const { t } = useI18n();
const queryClient = useQueryClient();

const selectedAccountIds = ref<number[]>([]);
const coverEnabled = ref(false);
const coverAssetUrl = ref("");
const coverFileName = ref("");
const runtimeLoading = ref(false);
const pluginInstalled = ref(false);
const pluginVersion = ref<string | undefined>();
const pluginInstallationId = ref<number | null>(null);
const localPlatforms = ref<PublisherLocalPlatformState[]>([]);
const coverPickerOpen = ref(false);
const selectionHydrated = ref(false);
const coverHydrated = ref(false);

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

const accountsQuery = useQuery({
  queryKey: ["media", "platform-accounts", "publish-modal"],
  enabled: computed(() => props.open),
  queryFn: () => mediaApi.accounts(),
});

const platformsQuery = useQuery({
  queryKey: ["media", "platforms", "publish-modal"],
  enabled: computed(() => props.open),
  queryFn: () => mediaApi.platforms(),
});

watch(
  () => props.open,
  async (open) => {
    if (!open) {
      selectedAccountIds.value = [];
      coverAssetUrl.value = "";
      coverFileName.value = "";
      coverEnabled.value = false;
      pluginInstallationId.value = null;
      localPlatforms.value = [];
      selectionHydrated.value = false;
      coverHydrated.value = false;
      return;
    }
    selectionHydrated.value = false;
    coverHydrated.value = false;
    await refreshRuntime();
  },
  { immediate: true },
);

watchEffect(() => {
  if (!props.open) {
    return;
  }

  if (!coverHydrated.value && !detailQuery.isPending.value) {
    const initialUrl = resolveApiURL(detailQuery.data.value?.cover_asset_url);
    coverAssetUrl.value = initialUrl;
    coverFileName.value = deriveCoverFileName(initialUrl);
    coverEnabled.value = Boolean(initialUrl);
    coverHydrated.value = true;
  }

  if (selectionHydrated.value) {
    return;
  }

  if (
    detailQuery.isPending.value ||
    accountsQuery.isPending.value ||
    platformsQuery.isPending.value ||
    runtimeLoading.value
  ) {
    return;
  }

  const selectedPlatforms = new Set(normalizePublishPlatformIds(detailQuery.data.value?.platforms ?? []));
  selectedAccountIds.value = accountCards.value
    .filter((account) => account.accountId && account.selectable && selectedPlatforms.has(account.platformId))
    .map((account) => account.accountId as number);
  selectionHydrated.value = true;
});

const localPlatformMap = computed(() => {
  return new Map(localPlatforms.value.map((item) => [normalizePublishPlatformId(item.platform_id), item]));
});

const accountGroups = computed(() => {
  const groups = new Map<string, PlatformAccount[]>();
  for (const account of accountsQuery.data.value ?? []) {
    const platformId = normalizePublishPlatformId(account.platform_id);
    const list = groups.get(platformId) ?? [];
    list.push(account);
    groups.set(platformId, list);
  }
  return groups;
});

const platformNameMap = computed(() => {
  return new Map(
    (platformsQuery.data.value ?? []).map((platform) => [normalizePublishPlatformId(platform.platform_id), platform.name]),
  );
});

const accountCards = computed(() => {
  return (platformsQuery.data.value ?? []).map((platform) => {
    const platformId = normalizePublishPlatformId(platform.platform_id);
    const accounts = accountGroups.value.get(platformId) ?? [];
    const account = accounts[0] ?? null;
    const local = localPlatformMap.value.get(platformId);
    const localConnected = Boolean(local?.connected);
    const uidMatches = account
      ? localConnected && (!local?.platform_uid || local.platform_uid === account.platform_uid)
      : false;
    const selectable = Boolean(account && account.status === "active" && uidMatches && pluginInstalled.value);

    return {
      key: account?.id ?? platformId,
      accountId: account?.id ?? null,
      platformId,
      platformName: platform.name,
      platformShortName: platform.short_name,
      platformAccentColor: platform.accent_color,
      platformCategory: platform.category,
      platformUid: account?.platform_uid ?? local?.platform_uid ?? "--",
      nickname: account?.nickname ?? local?.nickname ?? t("media.card.unbound"),
      status: account?.status ?? "unbound",
      local,
      selectable,
      statusText: resolveAccountStatusText(platform, account, local),
    };
  });
});

const modalTitle = computed(() => detailQuery.data.value?.title || t("article.untitled"));
const selectedPlatformIds = computed(() =>
  accountCards.value
    .filter((account) => account.accountId && isSelected(account.accountId))
    .map((account) => account.platformId),
);
const publishModalVisible = computed(() => props.open && !coverPickerOpen.value);
const coverRequired = computed(() => coverUploadRequired(selectedPlatformIds.value));
const effectiveCoverEnabled = computed(() => coverRequired.value || coverEnabled.value);
const normalizedCoverValue = computed(() => (effectiveCoverEnabled.value ? coverAssetUrl.value.trim() : ""));

watch(
  coverRequired,
  (required) => {
    if (required) {
      coverEnabled.value = true;
    }
  },
  { immediate: true },
);

const publishMutation = useMutation({
  mutationFn: async () => {
    if (!props.articleId || !detailQuery.data.value) {
      throw new Error("missing_article");
    }
    if (!selectedAccountIds.value.length) {
      throw new Error("no_accounts_selected");
    }
    if (coverRequired.value && !normalizedCoverValue.value) {
      throw new Error("cover_required_for_selected_platforms");
    }

    const batch = await articlesApi.publishBatch(props.articleId, {
      platform_account_ids: selectedAccountIds.value,
      plugin_installation_id: pluginInstallationId.value,
      publish_type: "publish",
      cover_asset_url: normalizedCoverValue.value || null,
    });

    return publishWithPublisherPlugin({
      article_id: detailQuery.data.value.id,
      title: detailQuery.data.value.title ?? t("article.untitled"),
      markdown_content: detailQuery.data.value.markdown_content ?? "",
      html_content: detailQuery.data.value.html_content ?? null,
      cover_asset_url: normalizedCoverValue.value || null,
      callback_base_url: getApiBaseURL(),
      publish_type: "publish",
      tasks: batch.tasks,
    });
  },
  onSuccess: async (result) => {
    const successCount = result.results.filter((item) => item.success).length;
    const failedCount = result.results.length - successCount;
    if (failedCount === 0) {
      notification.success({
        message: t("media.publish.messages.successTitle"),
        description: t("media.publish.messages.success", { count: successCount }),
        placement: "topRight",
        duration: 4.5,
      });
    } else {
      showPublishFailures(result);
    }
    await Promise.all([
      queryClient.invalidateQueries({ queryKey: ["articles"] }),
      queryClient.invalidateQueries({ queryKey: ["workspace"] }),
      queryClient.invalidateQueries({ queryKey: ["media"] }),
      queryClient.invalidateQueries({ queryKey: ["articles", "detail", props.articleId] }),
      queryClient.invalidateQueries({ queryKey: ["articles", "publish-records", props.articleId] }),
    ]);
    emit("published");
    emit("update:open", false);
  },
  onError: (error) => {
    const normalized = error instanceof Error ? error.message : "";
    if (normalized === "no_accounts_selected") {
      message.warning(t("media.publish.messages.selectPlatform"));
      return;
    }
    if (normalized === "cover_required_for_selected_platforms") {
      message.warning(t("media.publish.messages.coverRequired"));
      return;
    }
    message.error(formatError(error));
  },
});

async function refreshRuntime(): Promise<void> {
  runtimeLoading.value = true;
  try {
    const runtime = await loadPublisherRuntimeState();
    pluginInstalled.value = runtime.ping.installed;
    pluginVersion.value = runtime.ping.version;
    pluginInstallationId.value = runtime.pluginInstallationId;
    localPlatforms.value = runtime.localPlatforms;
  } catch (error) {
    pluginInstalled.value = false;
    localPlatforms.value = [];
    message.error(formatError(error));
  } finally {
    runtimeLoading.value = false;
  }
}

function toggleAccount(accountId: number, selectable: boolean): void {
  if (!selectable || publishMutation.isPending.value) {
    return;
  }
  if (selectedAccountIds.value.includes(accountId)) {
    selectedAccountIds.value = selectedAccountIds.value.filter((id) => id !== accountId);
    return;
  }
  selectedAccountIds.value = [...selectedAccountIds.value, accountId];
}

function isSelected(accountId: number): boolean {
  return selectedAccountIds.value.includes(accountId);
}

function handleCoverToggle(checked: boolean): void {
  if (coverRequired.value) {
    coverEnabled.value = true;
    return;
  }
  coverEnabled.value = checked;
}

function handleCoverPicked(payload: { url: string; fileName: string }): void {
  coverAssetUrl.value = payload.url;
  coverFileName.value = payload.fileName;
  coverEnabled.value = true;
}

function handleRemoveCover(): void {
  coverAssetUrl.value = "";
  coverFileName.value = "";
}

function resolveAccountStatusText(
  platform: MediaPlatform,
  account: PlatformAccount | null,
  local?: PublisherLocalPlatformState,
): string {
  if (!account) {
    return t("media.card.unboundHint", { platform: platform.name });
  }
  if (!pluginInstalled.value) {
    return t("media.plugin.notInstalled");
  }
  if (!local?.connected) {
    return t("media.account.localMissing");
  }
  if (local.platform_uid && local.platform_uid !== account.platform_uid) {
    return t("media.account.localMismatch");
  }
  return t("media.account.ready");
}

function platformNameForResult(result: PublisherPublishTaskResult): string {
  return platformNameMap.value.get(result.platform_id) ?? result.platform_id;
}

function publishFailureText(result: PublisherPublishTaskResult): string {
  return t("media.publish.messages.failureItem", {
    platform: platformNameForResult(result),
    reason: result.message?.trim() || t("media.publish.messages.unknownFailure"),
  });
}

function showPublishFailures(result: PublisherPublishResponse): void {
  const failedResults = result.results.filter((item) => !item.success);
  if (!failedResults.length) {
    return;
  }

  const successCount = result.results.filter((item) => item.success).length;
  const failedCount = failedResults.length;
  const summary = t("media.publish.messages.partial", {
    success: successCount,
    failed: failedCount,
  });

  notification.error({
    message: t("media.publish.messages.failureTitle"),
    description: h(
      "div",
      [
        h("div", { style: "line-height:1.75;font-weight:500;margin-bottom:8px;" }, summary),
        ...failedResults.map((item) =>
          h("div", { style: "line-height:1.75;" }, publishFailureText(item)),
        ),
      ],
    ),
    placement: "topRight",
    duration: 8,
  });
}
</script>

<template>
  <a-modal
    :open="publishModalVisible"
    :title="t('media.publish.title')"
    :width="920"
    :confirm-loading="publishMutation.isPending.value"
    @ok="publishMutation.mutate()"
    @cancel="emit('update:open', false)"
  >
    <div class="publish-modal">
      <section class="publish-modal__hero">
        <div class="publish-modal__hero-top">
          <span class="eyebrow">标题</span>
          <div class="publish-modal__hero-actions">
            <a-tag :color="pluginInstalled ? 'success' : 'error'" class="hero-tag">
              {{ pluginInstalled ? t("media.plugin.ready") : t("media.plugin.notInstalled") }}
            </a-tag>
            <a-button :loading="runtimeLoading" @click="refreshRuntime" size="small" class="hero-btn">
              <template #icon><ReloadOutlined /></template>
              {{ t("media.actions.redetect") }}
            </a-button>
          </div>
        </div>
        <h3>{{ modalTitle }}</h3>
      </section>

      <a-alert
        v-if="!pluginInstalled"
        type="warning"
        show-icon
        :message="t('media.plugin.installTitle')"
        :description="t('media.plugin.installDesc')"
      />

      <section class="publish-modal__section">
        <div class="publish-modal__section-header">
          <h3><span class="required-star">*</span> {{ t("media.publish.platformsTitle") }}：</h3>
          <p class="muted">{{ t("media.publish.platformsHint") }}</p>
        </div>

        <div v-if="accountsQuery.isPending.value || platformsQuery.isPending.value" class="publish-modal__loading">
          <a-skeleton active :paragraph="{ rows: 5 }" />
        </div>

        <div v-else-if="accountCards.length" class="publish-modal__grid">
          <button
            v-for="account in accountCards"
            :key="account.key"
            type="button"
            class="publish-modal__card"
            :class="{
              'publish-modal__card--active': account.accountId ? isSelected(account.accountId) : false,
              'publish-modal__card--disabled': !account.selectable,
            }"
            @click="account.accountId ? toggleAccount(account.accountId, account.selectable) : undefined"
          >
            <div class="publish-modal__card-left">
              <span class="publish-modal__check">
                <span
                  v-if="account.accountId && isSelected(account.accountId)"
                  class="publish-modal__check-inner"
                ></span>
              </span>
              <span class="publish-modal__card-badge" :style="{ color: account.platformAccentColor }">
                {{ account.platformShortName }}
              </span>
              <div class="publish-modal__card-text">
                <span class="publish-modal__card-name">{{ account.platformName }}</span>
                <span class="publish-modal__card-sub" v-if="account.accountId">
                  {{ account.nickname }}
                </span>
                <span class="publish-modal__card-sub" v-else>
                  {{ t("media.card.unbound") }}
                </span>
              </div>
            </div>
            <div class="publish-modal__card-right">
              <span class="publish-modal__card-status" :class="account.selectable ? 'status-selectable' : 'status-disabled'">
                {{ account.selectable ? t("media.account.selectable") : t("media.account.unavailable") }}
              </span>
            </div>
          </button>
        </div>

        <a-empty v-else :description="t('media.empty.accounts')" />
      </section>

      <section class="publish-modal__section">
        <div class="publish-modal__section-header inline-header">
          <h3>{{ t("media.publish.coverTitle") }}：</h3>
          <a-switch
            :checked="effectiveCoverEnabled"
            :disabled="coverRequired"
            @change="handleCoverToggle"
          />
        </div>
        <p class="muted">
          {{ coverRequired ? t("media.publish.messages.coverRequired") : t("media.publish.coverHint") }}
        </p>

        <div class="publish-modal__cover-body" v-if="effectiveCoverEnabled">
          <button
            type="button"
            class="publish-modal__cover-preview"
            @click="coverPickerOpen = true"
          >
            <template v-if="coverAssetUrl">
              <img :src="coverAssetUrl" alt="cover preview" />
            </template>
            <template v-else>
              <span class="publish-modal__cover-plus">+</span>
              <span>{{ t("media.publish.coverUpload") }}</span>
            </template>
          </button>

          <div class="publish-modal__cover-side">
            <div class="publish-modal__cover-actions">
              <a-button type="primary" ghost @click="coverPickerOpen = true">
                {{ coverAssetUrl ? t("article.editor.coverReplace") : t("media.publish.coverUpload") }}
              </a-button>
              <a-button v-if="coverAssetUrl" @click="handleRemoveCover">
                {{ t("article.editor.coverRemove") }}
              </a-button>
            </div>

            <div v-if="coverAssetUrl" class="publish-modal__cover-file">
              {{ coverFileName || t("article.editor.coverSaved") }}
            </div>
          </div>
        </div>
      </section>
    </div>

  </a-modal>

  <CoverPickerModal
    v-model:open="coverPickerOpen"
    :article-id="detailQuery.data.value?.id ?? null"
    :platform-ids="selectedPlatformIds"
    :current-url="coverAssetUrl"
    :current-file-name="coverFileName"
    @confirmed="handleCoverPicked"
  />
</template>

<style scoped>
.publish-modal {
  display: flex;
  flex-direction: column;
  gap: 24px;
}

.publish-modal__hero {
  display: flex;
  flex-direction: column;
  gap: 12px;
  padding: 16px 20px;
  border: 1px solid #e5e5e5;
  border-radius: 12px;
  background: #f7f7f7;
}

.publish-modal__hero-top {
  display: flex;
  align-items: center;
  gap: 16px;
  justify-content: space-between;
  margin-bottom: 12px;
}

.publish-modal__hero h3 {
  margin: 0;
  font-size: 16px;
  font-weight: 600;
  color: #262626;
  line-height: 1.5;
}

.eyebrow {
  font-size: 16px;
  text-transform: uppercase;
  color: #000000;
  margin: 0;
}

.publish-modal__hero-actions {
  display: flex;
  align-items: center;
  gap: 10px;
}

.hero-tag {
  margin: 0;
}

.publish-modal__section {
  display: flex;
  flex-direction: column;
}

.publish-modal__section-header {
  margin-bottom: 12px;
}

.publish-modal__section-header.inline-header {
  display: flex;
  align-items: center;
  gap: 12px;
  margin-bottom: 8px;
}

.publish-modal__section-header h3 {
  margin: 0 0 6px 0;
  font-size: 15px;
  font-weight: 600;
  color: #262626;
}

.publish-modal__section-header.inline-header h3 {
  margin: 0;
}

.required-star {
  color: #ff4d4f;
  margin-right: 4px;
  font-family: SimSun, sans-serif;
}

.muted {
  font-size: 13px;
  color: #8c8c8c;
  margin: 0;
}

.publish-modal__grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 12px;
  margin-top: 4px;
}

.publish-modal__card {
  display: flex;
  align-items: center;
  justify-content: space-between;
  width: 100%;
  padding: 12px 16px;
  border: 1px solid #d9d9d9;
  border-radius: 8px;
  background: #fff;
  text-align: left;
  cursor: pointer;
  transition: all 0.2s ease;
}

.publish-modal__card:hover:not(.publish-modal__card--disabled) {
  border-color: #1677ff;
}

.publish-modal__card--active {
  border-color: #1677ff;
}

.publish-modal__card--disabled {
  opacity: 0.6;
  cursor: not-allowed;
  background-color: #f7f7f7;
}

.publish-modal__card-left {
  display: flex;
  align-items: center;
  gap: 12px;
}

.publish-modal__check {
  width: 16px;
  height: 16px;
  border: 1px solid #d9d9d9;
  border-radius: 4px;
  display: flex;
  align-items: center;
  justify-content: center;
  background: #fff;
  transition: all 0.2s;
}

.publish-modal__card--active .publish-modal__check {
  background-color: #1677ff;
  border-color: #1677ff;
}

.publish-modal__check-inner {
  width: 8px;
  height: 8px;
  background-color: transparent;
  border: 2px solid #fff;
  border-top: 0;
  border-left: 0;
  transform: rotate(45deg) scale(1) translate(-1px, -1px);
  display: block;
}

.publish-modal__card-badge {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 24px;
  height: 24px;
  border-radius: 6px;
  background: #f7f7f7;
  border: 1px solid #e5e5e5;
  font-size: 12px;
  font-weight: 700;
}

.publish-modal__card-text {
  display: flex;
  flex-direction: column;
}

.publish-modal__card-name {
  font-size: 14px;
  font-weight: 500;
  color: #262626;
  line-height: 1.3;
}

.publish-modal__card-sub {
  font-size: 12px;
  font-weight: 400;
  color: #8c8c8c;
  margin-top: 2px;
}

.publish-modal__card-status {
  font-size: 13px;
}

.status-selectable {
  color: #52c41a;
}

.status-disabled {
  color: #1677ff;
}

.publish-modal__loading {
  padding: 12px 0;
}

.publish-modal__cover-body {
  display: grid;
  grid-template-columns: 176px minmax(0, 1fr);
  gap: 16px;
  margin-top: 14px;
}

.publish-modal__cover-preview {
  display: flex;
  align-items: center;
  justify-content: center;
  min-height: 124px;
  padding: 0;
  border: 1px dashed #d3dceb;
  border-radius: 20px;
  background:
    radial-gradient(circle at top, rgba(70, 102, 255, 0.08), transparent 45%),
    #fbfcff;
  color: #475467;
  cursor: pointer;
  overflow: hidden;
}

.publish-modal__cover-preview img {
  width: 100%;
  height: 124px;
  object-fit: cover;
}

.publish-modal__cover-plus {
  display: inline-flex;
  align-items: center;
  justify-content: center;
  width: 44px;
  height: 44px;
  margin-right: 8px;
  border-radius: 999px;
  background: #eef4ff;
  color: #355dff;
  font-size: 28px;
  line-height: 1;
}

.publish-modal__cover-side {
  display: flex;
  flex-direction: column;
  gap: 12px;
}

.publish-modal__cover-actions {
  display: flex;
  flex-wrap: wrap;
  gap: 10px;
}

.publish-modal__cover-file {
  color: #667085;
  font-size: 13px;
  line-height: 1.7;
}

@media (max-width: 860px) {
  .publish-modal__hero {
    flex-direction: column;
  }

  .publish-modal__grid {
    grid-template-columns: 1fr;
  }

  .publish-modal__cover-body {
    grid-template-columns: 1fr;
  }
}
</style>
