<template>
  <div class="main">
    <van-cell-group inset>
      <div class="editor-toolbar">
        <span class="toolbar-hint">图片经服务端压缩后上传，并记入资源库便于排查</span>
        <van-uploader
          :max-count="1"
          :max-size="orgHomepageImageMaxBytes"
          accept="image/*"
          :after-read="onImageAfterRead"
          :before-read="beforeImageRead"
          @oversize="onImageOversize"
        >
          <van-button
            type="primary"
            size="small"
            icon="photo-o"
            :loading="imageUploading"
          >
            插入图片
          </van-button>
        </van-uploader>
      </div>
      <van-field
        ref="markdownFieldRef"
        v-model="markdownText"
        rows="10"
        autosize
        type="textarea"
        label="机构首页 Markdown"
        placeholder="请输入机构首页内容，或插入图片生成 Markdown"
      />
    </van-cell-group>

    <div class="actions">
      <van-button type="primary" block @click="onSave">保存</van-button>
    </div>

    <div class="preview">
      <h5>预览</h5>
      <div class="preview-md" v-html="previewHtml" />
    </div>
  </div>
</template>

<script lang="ts">
import { computedAsync } from "@vueuse/core";
import { defineComponent, nextTick, onMounted, ref } from "vue";
import {
  Button,
  CellGroup,
  Field,
  showFailToast,
  showSuccessToast,
  Uploader,
} from "vant";
import { marked } from "marked";
import useOrg from "~/store/org";
import { ImageHelper } from "~/utils";
import { UpyunAccess } from "~/utils/upyun";

const ORG_HOMEPAGE_IMAGE_MAX_BYTES = 10 * 1024 * 1024;

marked.setOptions({ breaks: true, gfm: true });

/** 历史失败上传留在正文里的占位，避免继续污染预览 */
function stripBrokenCompressMarkdown(md: string): string {
  return md
    .replace(/!\[[^\]]*\]\(\s*failed to open file[^)]*\)/g, "")
    .replace(/\n{3,}/g, "\n\n");
}

/** 预览区 img：又拍云需带 _upt，否则浏览器无法加载 */
async function signUpyunImagesInMarkedHtml(html: string): Promise<string> {
  if (typeof document === "undefined" || !html.includes("<img")) {
    return html;
  }
  const wrap = document.createElement("div");
  wrap.innerHTML = html;
  const imgs = wrap.querySelectorAll("img[src]");
  await Promise.all(
    Array.from(imgs).map(async (el) => {
      const src = el.getAttribute("src");
      if (!src?.includes("upyun")) return;
      const signed = await UpyunAccess.getSign(src);
      if (signed) el.setAttribute("src", signed);
    }),
  );
  return wrap.innerHTML;
}

export default defineComponent({
  name: "org-homepage",
  components: {
    [Button.name]: Button,
    [CellGroup.name]: CellGroup,
    [Field.name]: Field,
    [Uploader.name]: Uploader,
  },
  setup() {
    const { store, getHomepage, saveHomepage } = useOrg();
    const markdownText = ref("");
    const markdownFieldRef = ref<InstanceType<typeof Field> | null>(null);
    const imageUploading = ref(false);

    const previewHtml = computedAsync(
      async () => {
        const raw = (markdownText.value || "").trim();
        if (!raw) return '<p class="preview-empty">暂无内容</p>';
        const html = marked.parse(raw) as string;
        return signUpyunImagesInMarkedHtml(html);
      },
      '<p class="preview-empty">暂无内容</p>',
    );

    const getMarkdownTextarea = (): HTMLTextAreaElement | null => {
      const root = markdownFieldRef.value as unknown as { $el?: HTMLElement };
      return root?.$el?.querySelector?.("textarea") ?? null;
    };

    const insertMarkdownAtCaret = async (snippet: string) => {
      const textarea = getMarkdownTextarea();
      const text = markdownText.value;
      if (!textarea) {
        markdownText.value = text + snippet;
        return;
      }
      const start = textarea.selectionStart ?? text.length;
      const end = textarea.selectionEnd ?? text.length;
      markdownText.value = text.slice(0, start) + snippet + text.slice(end);
      await nextTick();
      const ta = getMarkdownTextarea();
      if (ta) {
        const pos = start + snippet.length;
        ta.focus();
        ta.setSelectionRange(pos, pos);
      }
    };

    const beforeImageRead = (file: File) => {
      if (!file.type.startsWith("image/")) {
        showFailToast("请选择图片文件");
        return false;
      }
      return true;
    };

    const onImageOversize = () => {
      showFailToast("文件大小不能超过 10MB");
    };

    const onImageAfterRead = async (item: { file: File } | { file: File }[]) => {
      const first = Array.isArray(item) ? item[0] : item;
      const file = first?.file;
      if (!file) {
        showFailToast("读取文件失败");
        return;
      }
      imageUploading.value = true;
      try {
        console.info("[org-homepage] image upload start", {
          name: file.name,
          size: file.size,
          type: file.type,
        });
        const urlRaw = await ImageHelper.compress_by_form(file);
        if (!urlRaw) return;
        const url = urlRaw.split("?")[0];
        console.info("[org-homepage] compress ok, create_resource", { url });
        const rid = await ImageHelper.create_resource(url, file.name || "image.jpg", {
          res_type: "org_homepage",
        });
        if (!rid) {
          showFailToast("资源登记失败，图片可能已上传但未绑定账号");
          return;
        }
        console.info("[org-homepage] create_resource ok", { rid });
        await insertMarkdownAtCaret(`\n![](${url})\n`);
        showSuccessToast("已插入图片");
      } finally {
        imageUploading.value = false;
      }
    };

    onMounted(async () => {
      if (!store.currentOrgId) {
        showFailToast("请先选择机构");
        return;
      }
      const loaded = (await getHomepage(store.currentOrgId)) || "";
      markdownText.value = stripBrokenCompressMarkdown(loaded);
    });

    const onSave = async () => {
      if (!store.currentOrgId) {
        showFailToast("请先选择机构");
        return;
      }
      const cleaned = stripBrokenCompressMarkdown(markdownText.value);
      markdownText.value = cleaned;
      const ok = await saveHomepage(store.currentOrgId, cleaned);
      if (ok) {
        showSuccessToast("保存成功");
      }
    };

    return {
      markdownText,
      markdownFieldRef,
      onSave,
      previewHtml,
      beforeImageRead,
      onImageAfterRead,
      onImageOversize,
      imageUploading,
      orgHomepageImageMaxBytes: ORG_HOMEPAGE_IMAGE_MAX_BYTES,
    };
  },
});
</script>

<style scoped lang="less">
.main {
  padding: 0.5rem;
}
.editor-toolbar {
  display: flex;
  flex-wrap: wrap;
  align-items: center;
  justify-content: space-between;
  gap: 0.24rem;
  padding: 0.32rem 0.32rem 0;
}
.toolbar-hint {
  font-size: 0.24rem;
  color: #969799;
  flex: 1;
  min-width: 4rem;
}
.actions {
  margin: 0.8rem 0;
}
.preview h5 {
  margin: 0 0 0.32rem;
  font-size: 0.28rem;
  color: #323233;
}
.preview-md {
  word-break: break-word;
  background: #f7f8fa;
  border-radius: 8px;
  padding: 0.5rem;
  font-size: 0.28rem;
  line-height: 1.6;
}
.preview-md :deep(img) {
  max-width: 100%;
  height: auto;
  vertical-align: middle;
}
.preview-md :deep(.preview-empty) {
  color: #969799;
  margin: 0;
}
.preview-md :deep(pre) {
  white-space: pre-wrap;
  word-break: break-word;
}
</style>
