<script setup>
import { ref, watch, computed, onUnmounted } from "vue";
import MarkdownIt from "markdown-it";
import hljs from "highlight.js";
import axios from "axios";
import DOMPurify from "dompurify";
import "highlight.js/styles/github-dark.css";
import { useI18n } from "@/i18n/composables";

// 1. 在 setup 作用域创建 MarkdownIt 实例
const md = new MarkdownIt({
  html: true,
  linkify: true,
  typographer: true,
  breaks: false,
});

md.enable(["table", "strikethrough"]);
md.renderer.rules.table_open = () => '<div class="table-container"><table>';
md.renderer.rules.table_close = () => "</table></div>";

// 2. 复制按钮的 SVG 图标常量
const ICONS = {
  SUCCESS:
    '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><polyline points="20,6 9,17 4,12"></polyline></svg>',
  ERROR:
    '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><circle cx="12" cy="12" r="10"></circle><line x1="12" y1="8" x2="12" y2="12"></line><line x1="12" y1="16" x2="12.01" y2="16"></line></svg>',
  COPY: '<svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>',
};

const props = defineProps({
  show: { type: Boolean, default: false },
  pluginName: { type: String, default: "" },
  repoUrl: { type: String, default: null },
  mode: {
    type: String,
    default: "readme",
    validator: (value) => ["readme", "changelog", "first-notice"].includes(value),
  },
});

const emit = defineEmits(["update:show"]);
const { t, locale } = useI18n();

const content = ref(null);
const error = ref(null);
const loading = ref(false);
const isEmpty = ref(false);
const copyFeedbackTimer = ref(null);
const lastRequestId = ref(0);
const scrollContainer = ref(null);

function slugifyHeading(text, slugCounts) {
  const base = (text || "")
    .trim()
    .toLowerCase()
    .normalize("NFKD")
    .replace(/[\u0300-\u036f]/g, "")
    .replace(/[^\p{Letter}\p{Number}\s-]/gu, "")
    .replace(/\s+/g, "-")
    .replace(/-+/g, "-");

  if (!base) return "";

  const count = slugCounts.get(base) || 0;
  slugCounts.set(base, count + 1);
  return count === 0 ? base : `${base}-${count}`;
}

onUnmounted(() => {
  if (copyFeedbackTimer.value) clearTimeout(copyFeedbackTimer.value);
});

// 渲染后的 HTML
const renderedHtml = computed(() => {
  // 强制依赖 locale，确保语言切换时重新渲染
  const _ = locale?.value;
  if (!content.value) return "";

  // 设置 fence 规则，直接使用当前作用域的 t 函数
  md.renderer.rules.fence = (tokens, idx) => {
    const token = tokens[idx];
    const lang = token.info.trim() || "";
    const code = token.content;

    const highlighted =
      lang && hljs.getLanguage(lang)
        ? hljs.highlight(code, { language: lang }).value
        : md.utils.escapeHtml(code);

    return `<div class="code-block-wrapper">
      ${lang ? `<span class="code-lang-label">${lang}</span>` : ""}
      <button class="copy-code-btn" title="${t("core.common.copy")}">
        <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" stroke-width="2"><rect x="9" y="9" width="13" height="13" rx="2" ry="2"></rect><path d="M5 15H4a2 2 0 0 1-2-2V4a2 2 0 0 1 2-2h9a2 2 0 0 1 2 2v1"></path></svg>
      </button>
      <pre class="hljs"><code class="language-${lang}">${highlighted}</code></pre>
    </div>`;
  };

  const rawHtml = md.render(content.value);

  const cleanHtml = DOMPurify.sanitize(rawHtml, {
    ALLOWED_TAGS: [
      "h1",
      "h2",
      "h3",
      "h4",
      "h5",
      "h6",
      "p",
      "br",
      "hr",
      "ul",
      "ol",
      "li",
      "blockquote",
      "pre",
      "code",
      "a",
      "img",
      "table",
      "thead",
      "tbody",
      "tr",
      "th",
      "td",
      "strong",
      "em",
      "del",
      "s",
      "details",
      "summary",
      "div",
      "span",
      "input",
      "button",
      "svg",
      "rect",
      "path",
      "polyline",
    ],
    ALLOWED_ATTR: [
      "href",
      "src",
      "alt",
      "title",
      "class",
      "id",
      "target",
      "rel",
      "type",
      "checked",
      "disabled",
      "open",
      "align",
      "width",
      "height",
      "viewBox",
      "fill",
      "stroke",
      "stroke-width",
      "points",
      "d",
      "x",
      "y",
      "rx",
      "ry",
    ],
  });

  // 3. 后处理方案：完全隔离，安全性最高
  const tempDiv = document.createElement("div");
  tempDiv.innerHTML = cleanHtml;

  const slugCounts = new Map();
  tempDiv.querySelectorAll("h1, h2, h3, h4, h5, h6").forEach((heading) => {
    if (heading.id) {
      slugCounts.set(heading.id, (slugCounts.get(heading.id) || 0) + 1);
      return;
    }

    const slug = slugifyHeading(heading.textContent, slugCounts);
    if (slug) heading.id = slug;
  });

  tempDiv.querySelectorAll("a").forEach((link) => {
    const href = link.getAttribute("href");
    // 强制所有外部链接使用安全的 _blank 策略
    if (href && (href.startsWith("http") || href.startsWith("//"))) {
      link.setAttribute("target", "_blank");
      link.setAttribute("rel", "noopener noreferrer");
    }
  });

  return tempDiv.innerHTML;
});

const modeConfig = computed(() => {
  if (props.mode === "changelog") {
    return {
      title: t("core.common.changelog.title"),
      loading: t("core.common.changelog.loading"),
      emptyTitle: t("core.common.changelog.empty.title"),
      emptySubtitle: t("core.common.changelog.empty.subtitle"),
      apiPath: "/api/plugin/changelog",
      showGithubButton: false,
      showRefreshButton: true,
      refreshLabel: t("core.common.readme.buttons.refresh"),
    };
  }

  if (props.mode === "first-notice") {
    return {
      title: t("core.common.firstNotice.title"),
      loading: t("core.common.firstNotice.loading"),
      emptyTitle: t("core.common.firstNotice.empty.title"),
      emptySubtitle: t("core.common.firstNotice.empty.subtitle"),
      apiPath: "/api/stat/first-notice",
      showGithubButton: false,
      showRefreshButton: false,
      refreshLabel: "",
    };
  }

  return {
    title: t("core.common.readme.title"),
    loading: t("core.common.readme.loading"),
    emptyTitle: t("core.common.readme.empty.title"),
    emptySubtitle: t("core.common.readme.empty.subtitle"),
    apiPath: "/api/plugin/readme",
    showGithubButton: true,
    showRefreshButton: true,
    refreshLabel: t("core.common.readme.buttons.refresh"),
  };
});

const requiresPluginName = computed(
  () => props.mode === "readme" || props.mode === "changelog",
);

async function fetchContent() {
  if (requiresPluginName.value && !props.pluginName) return;
  const requestId = ++lastRequestId.value;
  loading.value = true;
  content.value = null;
  error.value = null;
  isEmpty.value = false;

  try {
    let params;
    if (requiresPluginName.value) {
      params = { name: props.pluginName };
    } else if (props.mode === "first-notice") {
      params = { locale: locale.value };
    }
    const res = await axios.get(modeConfig.value.apiPath, { params });
    if (requestId !== lastRequestId.value) return;

    if (res.data.status === "ok") {
      if (res.data.data.content) content.value = res.data.data.content;
      else isEmpty.value = true;
    } else {
      error.value = res.data.message;
    }
  } catch (err) {
    if (requestId === lastRequestId.value) error.value = err.message;
  } finally {
    if (requestId === lastRequestId.value) loading.value = false;
  }
}

watch(
  [() => props.show, () => props.pluginName, () => props.mode],
  ([show, name]) => {
    if (!show) return;
    if (requiresPluginName.value && !name) return;
    fetchContent();
  },
  { immediate: true },
);

function handleContainerClick(event) {
  const btn = event.target.closest(".copy-code-btn");
  if (btn) {
    const code = btn.closest(".code-block-wrapper")?.querySelector("code");
    if (code) {
      if (navigator.clipboard?.writeText) {
        navigator.clipboard
          .writeText(code.textContent)
          .then(() => showCopyFeedback(btn, true))
          .catch(() => tryFallbackCopy(code.textContent, btn));
      } else {
        tryFallbackCopy(code.textContent, btn);
      }
    }
    return;
  }

  const anchor = event.target.closest('a[href^="#"]');
  if (!anchor) return;

  const rawHref = anchor.getAttribute("href");
  const targetId = rawHref ? decodeURIComponent(rawHref.slice(1)) : "";
  if (!targetId) return;

  const target = scrollContainer.value?.querySelector(
    `#${CSS.escape(targetId)}`,
  );
  if (!target) return;

  event.preventDefault();
  target.scrollIntoView({ behavior: "smooth", block: "start" });
}

function tryFallbackCopy(text, btn) {
  try {
    const textArea = document.createElement("textarea");
    textArea.value = text;
    Object.assign(textArea.style, {
      position: "absolute",
      opacity: "0",
      zIndex: "-1",
    });
    btn.parentNode.appendChild(textArea);
    textArea.select();
    const success = document.execCommand("copy");
    btn.parentNode.removeChild(textArea);
    showCopyFeedback(btn, success);
  } catch (err) {
    showCopyFeedback(btn, false);
  }
}

function showCopyFeedback(btn, success) {
  if (copyFeedbackTimer.value) clearTimeout(copyFeedbackTimer.value);
  btn.setAttribute("title", t(`core.common.${success ? "copied" : "error"}`));
  btn.innerHTML = success ? ICONS.SUCCESS : ICONS.ERROR;
  btn.style.color = success ? "var(--v-theme-success)" : "var(--v-theme-error)";

  copyFeedbackTimer.value = setTimeout(() => {
    if (document.body.contains(btn)) {
      btn.innerHTML = ICONS.COPY;
      btn.style.color = "";
      btn.setAttribute("title", t("core.common.copy"));
    }
    copyFeedbackTimer.value = null;
  }, 2000);
}

const _show = computed({
  get: () => props.show,
  set: (val) => emit("update:show", val),
});

// 安全打开外部链接
function openExternalLink(url) {
  if (!url) return;
  window.open(url, "_blank", "noopener,noreferrer");
}

const showActionArea = computed(() => {
  const hasGithub = modeConfig.value.showGithubButton && !!props.repoUrl;
  return hasGithub || modeConfig.value.showRefreshButton;
});
</script>

<template>
  <v-dialog v-model="_show" width="800">
    <v-card>
      <v-card-title class="d-flex justify-space-between align-center">
        <span class="text-h2 pa-2">{{ modeConfig.title }}</span>
        <v-btn icon @click="_show = false" variant="text">
          <v-icon>mdi-close</v-icon>
        </v-btn>
      </v-card-title>
      <v-card-text ref="scrollContainer" style="overflow-y: auto">
        <div v-if="showActionArea" class="d-flex justify-space-between mb-4">
          <v-btn
            v-if="modeConfig.showGithubButton && repoUrl"
            color="primary"
            prepend-icon="mdi-github"
            @click="openExternalLink(repoUrl)"
          >
            {{ t("core.common.readme.buttons.viewOnGithub") }}
          </v-btn>
          <v-btn
            v-if="modeConfig.showRefreshButton"
            color="secondary"
            prepend-icon="mdi-refresh"
            @click="fetchContent"
          >
            {{ modeConfig.refreshLabel }}
          </v-btn>
        </div>

        <div
          v-if="loading"
          class="d-flex flex-column align-center justify-center"
          style="height: 100%"
        >
          <v-progress-circular
            indeterminate
            color="primary"
            size="64"
            class="mb-4"
          ></v-progress-circular>
          <p class="text-body-1 text-center">{{ modeConfig.loading }}</p>
        </div>

        <div
          v-else-if="renderedHtml"
          class="markdown-body"
          v-html="renderedHtml"
          @click="handleContainerClick"
        ></div>

        <div
          v-else-if="error"
          class="d-flex flex-column align-center justify-center"
          style="height: 100%"
        >
          <v-icon size="64" color="error" class="mb-4"
            >mdi-alert-circle-outline</v-icon
          >
          <p class="text-body-1 text-center mb-2">
            {{ t("core.common.error") }}
          </p>
          <p class="text-body-2 text-center text-medium-emphasis">
            {{ error }}
          </p>
        </div>

        <div
          v-else-if="isEmpty"
          class="d-flex flex-column align-center justify-center"
          style="height: 100%"
        >
          <v-icon size="64" color="warning" class="mb-4"
            >mdi-file-question-outline</v-icon
          >
          <p class="text-body-1 text-center mb-2">
            {{ modeConfig.emptyTitle }}
          </p>
          <p class="text-body-2 text-center text-medium-emphasis">
            {{ modeConfig.emptySubtitle }}
          </p>
        </div>
      </v-card-text>
      <v-card-actions>
        <v-spacer></v-spacer>
        <v-btn color="primary" variant="tonal" @click="_show = false">
          {{ t("core.common.close") }}
        </v-btn>
      </v-card-actions>
    </v-card>
  </v-dialog>
</template>

<style scoped>
:deep(.markdown-body) {
  --markdown-border: rgba(128, 128, 128, 0.3);
  font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial,
    sans-serif;
  line-height: 1.6;
  padding: 8px 0;
  color: var(--v-theme-secondaryText);
}

:deep(.markdown-body [align="center"]) {
  text-align: center;
}
:deep(.markdown-body [align="right"]) {
  text-align: right;
}

:deep(.markdown-body h1),
:deep(.markdown-body h2),
:deep(.markdown-body h3),
:deep(.markdown-body h4),
:deep(.markdown-body h5),
:deep(.markdown-body h6) {
  margin-top: 24px;
  margin-bottom: 16px;
  font-weight: 600;
  line-height: 1.25;
  scroll-margin-top: 12px;
}

:deep(.markdown-body h1) {
  font-size: 2em;
  border-bottom: 1px solid var(--v-theme-border);
  padding-bottom: 0.3em;
}
:deep(.markdown-body h2) {
  font-size: 1.5em;
  border-bottom: 1px solid var(--v-theme-border);
  padding-bottom: 0.3em;
}
:deep(.markdown-body p) {
  margin-top: 0;
  margin-bottom: 16px;
}

:deep(.markdown-body .code-block-wrapper) {
  position: relative;
  margin-bottom: 16px;
}
:deep(.markdown-body .code-lang-label) {
  position: absolute;
  top: 8px;
  left: 12px;
  font-size: 12px;
  color: #8b949e;
  text-transform: uppercase;
  font-weight: 500;
  z-index: 1;
}

:deep(.markdown-body .copy-code-btn) {
  position: absolute;
  top: 8px;
  right: 8px;
  background: rgba(110, 118, 129, 0.4);
  border: none;
  border-radius: 6px;
  padding: 6px;
  cursor: pointer;
  color: #c9d1d9;
  display: flex;
  align-items: center;
  justify-content: center;
  transition:
    background-color 0.2s,
    color 0.2s;
  z-index: 1;
}

:deep(.markdown-body .copy-code-btn:hover) {
  background: rgba(110, 118, 129, 0.6);
  color: #fff;
}

:deep(.markdown-body code) {
  padding: 0.2em 0.4em;
  margin: 0;
  background-color: rgba(110, 118, 129, 0.2);
  border-radius: 6px;
  font-size: 85%;
  font-family: "SFMono-Regular", Consolas, "Liberation Mono", Menlo, monospace;
}

:deep(.markdown-body pre.hljs) {
  padding: 16px;
  padding-top: 32px;
  overflow: auto;
  font-size: 85%;
  line-height: 1.45;
  background-color: #0d1117;
  border-radius: 6px;
  margin: 0;
}

:deep(.markdown-body pre.hljs code) {
  background-color: transparent;
  padding: 0;
  border-radius: 0;
  color: #c9d1d9;
}
:deep(.markdown-body ul),
:deep(.markdown-body ol) {
  padding-left: 2em;
  margin-bottom: 16px;
}

:deep(.markdown-body img) {
  max-width: 100%;
  margin: 8px 0;
  box-sizing: border-box;
  background-color: var(--v-theme-background);
  border-radius: 3px;
}

:deep(.markdown-body img[src*="shields.io"]),
:deep(.markdown-body img[src*="badge"]) {
  display: inline-block;
  vertical-align: middle;
  height: auto;
  margin: 2px 4px;
  background-color: transparent;
}

:deep(.markdown-body blockquote) {
  padding: 0 1em;
  color: var(--v-theme-secondaryText);
  border-left: 0.25em solid var(--v-theme-border);
  margin-bottom: 16px;
}

:deep(.markdown-body a) {
  color: var(--v-theme-primary);
  text-decoration: none;
}
:deep(.markdown-body a:hover) {
  text-decoration: underline;
}

:deep(.markdown-body table) {
  border-spacing: 0;
  border-collapse: collapse;
  width: 100%;
  margin-bottom: 0;
  border: 1px solid var(--markdown-border);
}
:deep(.markdown-body .table-container) {
  width: 100%;
  overflow-x: auto;
  margin-bottom: 16px;
  border: 1px solid var(--markdown-border);
  border-radius: 6px;
}

:deep(.markdown-body table th),
:deep(.markdown-body table td) {
  padding: 6px 13px;
  border: 1px solid var(--markdown-border);
}
:deep(.markdown-body table th) {
  font-weight: 600;
  background-color: rgba(128, 128, 128, 0.1);
}
:deep(.markdown-body table tr) {
  background-color: transparent;
}
:deep(.markdown-body table tr:nth-child(2n)) {
  background-color: rgba(128, 128, 128, 0.05);
}

:deep(.markdown-body hr) {
  height: 0.25em;
  padding: 0;
  margin: 24px 0;
  background-color: var(--v-theme-containerBg);
  border: 0;
}

:deep(.markdown-body details) {
  margin-bottom: 16px;
  border: 1px solid var(--v-theme-border);
  border-radius: 6px;
  padding: 8px 12px;
  background-color: var(--v-theme-surface);
}

:deep(.markdown-body details[open]) {
  padding-bottom: 12px;
}
:deep(.markdown-body summary) {
  cursor: pointer;
  font-weight: 600;
  padding: 4px 0;
  list-style: none;
  display: flex;
  align-items: center;
  gap: 6px;
}

:deep(.markdown-body summary::before) {
  content: "▶";
  font-size: 0.75em;
  transition: transform 0.2s ease;
}
:deep(.markdown-body details[open] summary::before) {
  transform: rotate(90deg);
}
:deep(.markdown-body summary::-webkit-details-marker) {
  display: none;
}
:deep(.markdown-body details > *:not(summary)) {
  margin-top: 12px;
}

:deep(.markdown-body .hljs-keyword),
:deep(.markdown-body .hljs-selector-tag),
:deep(.markdown-body .hljs-title),
:deep(.markdown-body .hljs-section),
:deep(.markdown-body .hljs-doctag),
:deep(.markdown-body .hljs-name),
:deep(.markdown-body .hljs-strong) {
  font-weight: bold;
}
</style>
