<script lang="ts" setup>
import { usePluginModuleStore } from "@/stores/plugin";
import { formatDatetime } from "@/utils/date";
import { usePermission } from "@/utils/permission";
import type {
  Extension,
  ListedComment,
  ListedReply,
  Post,
  SinglePage,
} from "@halo-dev/api-client";
import { consoleApiClient, coreApiClient } from "@halo-dev/api-client";
import {
  Dialog,
  IconAddCircle,
  IconExternalLinkLine,
  Toast,
  VAvatar,
  VButton,
  VDropdownItem,
  VEmpty,
  VEntity,
  VEntityField,
  VLoading,
  VSpace,
  VStatusDot,
  VTag,
} from "@halo-dev/components";
import type {
  CommentSubjectRefProvider,
  CommentSubjectRefResult,
  OperationItem,
} from "@halo-dev/console-shared";
import { useMutation, useQuery, useQueryClient } from "@tanstack/vue-query";
import {
  computed,
  onMounted,
  provide,
  ref,
  type Ref,
  toRefs,
  markRaw,
} from "vue";
import { useI18n } from "vue-i18n";
import ReplyCreationModal from "./ReplyCreationModal.vue";
import ReplyListItem from "./ReplyListItem.vue";
import { useOperationItemExtensionPoint } from "@console/composables/use-operation-extension-points";
import EntityDropdownItems from "@/components/entity/EntityDropdownItems.vue";

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

const props = withDefaults(
  defineProps<{
    comment: ListedComment;
    isSelected?: boolean;
  }>(),
  {
    isSelected: false,
  }
);

const { comment } = toRefs(props);

const hoveredReply = ref<ListedReply>();
const showReplies = ref(false);
const replyModal = ref(false);

provide<Ref<ListedReply | undefined>>("hoveredReply", hoveredReply);

const handleDelete = async () => {
  Dialog.warning({
    title: t("core.comment.operations.delete_comment.title"),
    description: t("core.comment.operations.delete_comment.description"),
    confirmType: "danger",
    confirmText: t("core.common.buttons.confirm"),
    cancelText: t("core.common.buttons.cancel"),
    onConfirm: async () => {
      try {
        await coreApiClient.content.comment.deleteComment({
          name: props.comment?.comment?.metadata.name as string,
        });

        Toast.success(t("core.common.toast.delete_success"));
      } catch (error) {
        console.error("Failed to delete comment", error);
      } finally {
        queryClient.invalidateQueries({ queryKey: ["comments"] });
      }
    },
  });
};

const handleApproveReplyInBatch = async () => {
  Dialog.warning({
    title: t("core.comment.operations.approve_applies_in_batch.title"),
    confirmText: t("core.common.buttons.confirm"),
    cancelText: t("core.common.buttons.cancel"),
    onConfirm: async () => {
      try {
        const repliesToUpdate = replies.value?.filter((reply) => {
          return !reply.reply.spec.approved;
        });
        const promises = repliesToUpdate?.map((reply) => {
          return coreApiClient.content.reply.patchReply({
            name: reply.reply.metadata.name,
            jsonPatchInner: [
              {
                op: "add",
                path: "/spec/approved",
                value: true,
              },
              {
                op: "add",
                path: "/spec/approvedTime",
                // TODO: 暂时由前端设置发布时间。see https://github.com/halo-dev/halo/pull/2746
                value: new Date().toISOString(),
              },
            ],
          });
        });
        await Promise.all(promises || []);

        Toast.success(t("core.common.toast.operation_success"));
      } catch (e) {
        console.error("Failed to approve comment replies in batch", e);
      } finally {
        await refetch();
      }
    },
  });
};

const handleApprove = async () => {
  try {
    await coreApiClient.content.comment.patchComment({
      name: props.comment.comment.metadata.name,
      jsonPatchInner: [
        {
          op: "add",
          path: "/spec/approved",
          value: true,
        },
        {
          op: "add",
          path: "/spec/approvedTime",
          // TODO: 暂时由前端设置发布时间。see https://github.com/halo-dev/halo/pull/2746
          value: new Date().toISOString(),
        },
      ],
    });

    Toast.success(t("core.common.toast.operation_success"));
  } catch (error) {
    console.error("Failed to approve comment", error);
  } finally {
    queryClient.invalidateQueries({ queryKey: ["comments"] });
  }
};

const {
  data: replies,
  isLoading,
  refetch,
} = useQuery<ListedReply[]>({
  queryKey: [
    "comment-replies",
    props.comment.comment.metadata.name,
    showReplies,
  ],
  queryFn: async () => {
    const { data } = await consoleApiClient.content.reply.listReplies({
      commentName: props.comment.comment.metadata.name,
      page: 0,
      size: 0,
    });
    return data.items;
  },
  refetchInterval(data) {
    const deletingReplies = data?.filter(
      (reply) => !!reply.reply.metadata.deletionTimestamp
    );
    return deletingReplies?.length ? 1000 : false;
  },
  enabled: computed(() => showReplies.value),
});

const { mutateAsync: updateCommentLastReadTimeMutate } = useMutation({
  mutationKey: ["update-comment-last-read-time"],
  mutationFn: async () => {
    return coreApiClient.content.comment.patchComment(
      {
        name: props.comment.comment.metadata.name,
        jsonPatchInner: [
          {
            op: "add",
            path: "/spec/lastReadTime",
            value: new Date().toISOString(),
          },
        ],
      },
      {
        mute: true,
      }
    );
  },
  retry: 3,
});

const handleToggleShowReplies = async () => {
  showReplies.value = !showReplies.value;

  if (props.comment.comment.status?.unreadReplyCount) {
    await updateCommentLastReadTimeMutate();
  }

  queryClient.invalidateQueries({ queryKey: ["comments"] });
};

const onReplyCreationModalClose = () => {
  queryClient.invalidateQueries({ queryKey: ["comments"] });

  if (showReplies.value) {
    refetch();
  }

  replyModal.value = false;
};

// Subject ref processing
const SubjectRefProviders = ref<CommentSubjectRefProvider[]>([
  {
    kind: "Post",
    group: "content.halo.run",
    resolve: (subject: Extension): CommentSubjectRefResult => {
      const post = subject as Post;
      return {
        label: t("core.comment.subject_refs.post"),
        title: post.spec.title,
        externalUrl: post.status?.permalink,
        route: {
          name: "PostEditor",
          query: {
            name: post.metadata.name,
          },
        },
      };
    },
  },
  {
    kind: "SinglePage",
    group: "content.halo.run",
    resolve: (subject: Extension): CommentSubjectRefResult => {
      const singlePage = subject as SinglePage;
      return {
        label: t("core.comment.subject_refs.page"),
        title: singlePage.spec.title,
        externalUrl: singlePage.status?.permalink,
        route: {
          name: "SinglePageEditor",
          query: {
            name: singlePage.metadata.name,
          },
        },
      };
    },
  },
]);

const { pluginModules } = usePluginModuleStore();

onMounted(() => {
  for (const pluginModule of pluginModules) {
    const callbackFunction =
      pluginModule?.extensionPoints?.["comment:subject-ref:create"];

    if (typeof callbackFunction !== "function") {
      continue;
    }

    const providers = callbackFunction();

    SubjectRefProviders.value.push(...providers);
  }
});

const subjectRefResult = computed(() => {
  const { subject } = props.comment;
  if (!subject) {
    return {
      label: t("core.comment.subject_refs.unknown"),
      title: t("core.comment.subject_refs.unknown"),
    };
  }
  const subjectRef = SubjectRefProviders.value.find(
    (provider) =>
      provider.kind === subject.kind &&
      subject.apiVersion.startsWith(provider.group)
  );
  if (!subjectRef) {
    return {
      label: t("core.comment.subject_refs.unknown"),
      title: t("core.comment.subject_refs.unknown"),
    };
  }
  return subjectRef.resolve(subject);
});

const { operationItems } = useOperationItemExtensionPoint<ListedComment>(
  "comment:list-item:operation:create",
  comment,
  computed((): OperationItem<ListedComment>[] => [
    {
      priority: 0,
      component: markRaw(VDropdownItem),
      label: t("core.comment.operations.approve_comment_in_batch.button"),
      action: handleApprove,
      hidden: props.comment?.comment.spec.approved,
    },
    {
      priority: 10,
      component: markRaw(VDropdownItem),
      label: t("core.comment.operations.approve_applies_in_batch.button"),
      action: handleApproveReplyInBatch,
    },
    {
      priority: 20,
      component: markRaw(VDropdownItem),
      props: {
        type: "danger",
      },
      label: t("core.common.buttons.delete"),
      action: handleDelete,
    },
  ])
);
</script>

<template>
  <ReplyCreationModal
    v-if="replyModal"
    :comment="comment"
    @close="onReplyCreationModalClose"
  />
  <VEntity :is-selected="isSelected" :class="{ 'hover:bg-white': showReplies }">
    <template v-if="showReplies" #prepend>
      <div class="absolute inset-y-0 left-0 w-[1px] bg-black/50"></div>
      <div class="absolute inset-y-0 right-0 w-[1px] bg-black/50"></div>
      <div class="absolute inset-x-0 top-0 h-[1px] bg-black/50"></div>
      <div class="absolute inset-x-0 bottom-0 h-[1px] bg-black/50"></div>
    </template>
    <template
      v-if="currentUserHasPermission(['system:comments:manage'])"
      #checkbox
    >
      <slot name="checkbox" />
    </template>
    <template #start>
      <VEntityField>
        <template #description>
          <VAvatar
            circle
            :src="comment?.owner.avatar"
            :alt="comment?.owner.displayName"
            size="md"
          ></VAvatar>
        </template>
      </VEntityField>
      <VEntityField
        class="w-28 min-w-[7rem]"
        :title="comment?.owner?.displayName"
        :description="comment?.owner?.email"
      ></VEntityField>
      <VEntityField width="100%">
        <template #description>
          <div class="flex flex-col gap-2">
            <div class="mb-1 flex items-center gap-2">
              <VTag>{{ subjectRefResult.label }}</VTag>
              <RouterLink
                :to="subjectRefResult.route || $route"
                class="line-clamp-2 inline-block text-sm font-medium text-gray-900 hover:text-gray-600"
              >
                {{ subjectRefResult.title }}
              </RouterLink>
              <a
                v-if="subjectRefResult.externalUrl"
                :href="subjectRefResult.externalUrl"
                target="_blank"
                class="hidden text-gray-600 hover:text-gray-900 group-hover:block"
              >
                <IconExternalLinkLine class="h-3.5 w-3.5" />
              </a>
            </div>
            <div class="break-all text-sm text-gray-900">
              {{ comment?.comment?.spec.content }}
            </div>
            <div class="flex items-center gap-3 text-xs">
              <span
                class="select-none text-gray-700 hover:text-gray-900"
                @click="handleToggleShowReplies"
              >
                {{
                  $t("core.comment.list.fields.reply_count", {
                    count: comment?.comment?.status?.replyCount || 0,
                  })
                }}
              </span>
              <VStatusDot
                v-show="(comment?.comment?.status?.unreadReplyCount || 0) > 0"
                v-tooltip="$t('core.comment.list.fields.has_new_replies')"
                state="success"
                animate
              />
              <span
                class="select-none text-gray-700 hover:text-gray-900"
                @click="replyModal = true"
              >
                {{ $t("core.comment.operations.reply.button") }}
              </span>
            </div>
          </div>
        </template>
      </VEntityField>
    </template>
    <template #end>
      <VEntityField v-if="!comment?.comment.spec.approved">
        <template #description>
          <VStatusDot state="success">
            <template #text>
              <span class="text-xs text-gray-500">
                {{ $t("core.comment.list.fields.pending_review") }}
              </span>
            </template>
          </VStatusDot>
        </template>
      </VEntityField>
      <VEntityField v-if="comment?.comment?.metadata.deletionTimestamp">
        <template #description>
          <VStatusDot
            v-tooltip="$t('core.common.status.deleting')"
            state="warning"
            animate
          />
        </template>
      </VEntityField>
      <VEntityField>
        <template #description>
          <span class="truncate text-xs tabular-nums text-gray-500">
            {{
              formatDatetime(
                comment?.comment.spec.creationTime ||
                  comment?.comment.metadata.creationTimestamp
              )
            }}
          </span>
        </template>
      </VEntityField>
    </template>
    <template
      v-if="currentUserHasPermission(['system:comments:manage'])"
      #dropdownItems
    >
      <EntityDropdownItems :dropdown-items="operationItems" :item="comment" />
    </template>

    <template v-if="showReplies" #footer>
      <!-- Replies -->
      <div
        class="ml-8 mt-3 divide-y divide-gray-100 rounded-base border-t border-gray-100 pt-3"
      >
        <VLoading v-if="isLoading" />
        <Transition v-else-if="!replies?.length" appear name="fade">
          <VEmpty
            :message="$t('core.comment.reply_empty.message')"
            :title="$t('core.comment.reply_empty.title')"
          >
            <template #actions>
              <VSpace>
                <VButton @click="refetch()">
                  {{ $t("core.common.buttons.refresh") }}
                </VButton>
                <VButton type="secondary" @click="replyModal = true">
                  <template #icon>
                    <IconAddCircle class="h-full w-full" />
                  </template>
                  {{ $t("core.comment.reply_empty.new") }}
                </VButton>
              </VSpace>
            </template>
          </VEmpty>
        </Transition>
        <Transition v-else appear name="fade">
          <div>
            <ReplyListItem
              v-for="reply in replies"
              :key="reply.reply.metadata.name"
              :class="{ 'hover:bg-white': showReplies }"
              :reply="reply"
              :comment="comment"
              :replies="replies"
            ></ReplyListItem>
          </div>
        </Transition>
      </div>
    </template>
  </VEntity>
</template>
