<script setup>
import { useNamespace, useLocale } from "@yh-ui/hooks";
import { computed, ref, useSlots, watch, onBeforeUnmount, nextTick, provide } from "vue";
import {
  aiMentionProps,
  aiMentionEmits
} from "./ai-mention";
import { YhMention } from "../../mention";
import { YhIcon } from "../../icon";
import { useComponentTheme } from "@yh-ui/theme";
import AiMentionNode from "./ai-mention-node.vue";
defineOptions({
  name: "YhAiMention"
});
const props = defineProps(aiMentionProps);
const emit = defineEmits(aiMentionEmits);
const slots = useSlots();
const forwardedSlotNames = computed(() => Object.keys(slots));
const ns = useNamespace("ai-mention");
const { t, lang } = useLocale();
const { themeStyle } = useComponentTheme("ai-mention", props.themeOverrides);
const mentionRef = ref(null);
const fileTreeData = ref([]);
const fileTreeLoading = ref(false);
const expandedFolders = ref(/* @__PURE__ */ new Set());
const currentFileType = ref(null);
let searchDebounceTimer = null;
const filteredOptions = computed(() => {
  if (!props.types || props.types.length === 0) return props.options;
  return props.options.filter((option) => !option.type || props.types.includes(option.type));
});
const handleUpdateValue = (val) => {
  emit("update:modelValue", val);
};
const handleSelect = (option, trigger) => {
  emit("select", option, trigger);
};
const getTypeIcon = (type) => {
  const iconMap = {
    document: "document",
    table: "grid",
    file: "folder",
    knowledge: "book"
  };
  return iconMap[type] || "document";
};
const getTypeLabel = (type) => {
  const labelMap = {
    document: t("ai.mention.document"),
    table: t("ai.mention.table"),
    file: t("ai.mention.file") === "ai.mention.file" ? t("common.close") === "\u5173\u95ED" ? "\u6587\u4EF6" : "File" : t("ai.mention.file"),
    knowledge: t("ai.mention.knowledge")
  };
  return labelMap[type] || type;
};
const getOptionIcon = (type) => {
  switch (type) {
    case "agent":
      return "robot";
    case "document":
      return "document";
    case "table":
      return "table";
    case "knowledge":
      return "book";
    case "file":
      return "folder";
    default:
      return "sparkles";
  }
};
const asAiMentionOption = (option) => option;
const mentionWrapperRef = ref(null);
const panelLeft = ref(0);
const panelTop = ref(0);
const updatePanelPosition = () => {
  if (!mentionWrapperRef.value) return;
  const rect = mentionWrapperRef.value.getBoundingClientRect();
  panelLeft.value = rect.left;
  panelTop.value = rect.bottom + 4;
};
const fileTreePanelStyle = computed(() => ({
  position: "fixed",
  top: `${panelTop.value}px`,
  left: `${panelLeft.value}px`,
  width: "320px",
  maxHeight: "400px",
  zIndex: 9999
}));
const loadFileTree = async (type, keyword = "") => {
  if (!props.enableFileTree) return;
  fileTreeLoading.value = true;
  currentFileType.value = type;
  try {
    let nodes = [];
    if (props.fileLoader) {
      nodes = await props.fileLoader(keyword, type, props.fileRoot);
    } else {
      nodes = generateMockFileTree(type, keyword);
    }
    if (props.fileRoot && props.fileRoot !== "/") {
      const prefix = props.fileRoot.endsWith("/") ? props.fileRoot.slice(0, -1) : props.fileRoot;
      const applyRoot = (list) => {
        return list.map((node) => {
          const newNode = { ...node };
          if (newNode.path) {
            newNode.path = newNode.path.startsWith("/") ? `${prefix}${newNode.path}` : `${prefix}/${newNode.path}`;
          }
          if (newNode.children) {
            newNode.children = applyRoot(newNode.children);
          }
          return newNode;
        });
      };
      nodes = applyRoot(nodes);
    }
    fileTreeData.value = nodes;
    emit("file-load", type, nodes);
    if (props.fileTreeExpandedLevel > 0) {
      nodes.forEach((node) => {
        if (node.isFolder && node.children?.length) {
          expandedFolders.value.add(node.key);
        }
      });
    }
  } catch (error) {
    console.error("Failed to load file tree:", error);
    fileTreeData.value = [];
  } finally {
    fileTreeLoading.value = false;
  }
};
const generateMockFileTree = (type, keyword) => {
  const mockData = {
    document: [
      {
        key: "docs",
        label: "\u6587\u6863",
        isFolder: true,
        path: "/docs",
        children: [
          {
            key: "docs/readme",
            label: "README.md",
            isFolder: false,
            path: "/docs/README.md",
            size: 2048,
            modifiedAt: Date.now() - 864e5
          },
          {
            key: "docs/guide",
            label: "\u4F7F\u7528\u6307\u5357.md",
            isFolder: false,
            path: "/docs/guide.md",
            size: 5120,
            modifiedAt: Date.now() - 1728e5
          },
          {
            key: "docs/api",
            label: "API \u6587\u6863.md",
            isFolder: false,
            path: "/docs/api.md",
            size: 10240,
            modifiedAt: Date.now() - 2592e5
          }
        ]
      },
      {
        key: "contracts",
        label: "\u5408\u540C",
        isFolder: true,
        path: "/contracts",
        children: [
          {
            key: "contracts/2024",
            label: "2024\u5408\u540C",
            isFolder: true,
            path: "/contracts/2024",
            children: [
              {
                key: "contracts/2024/a",
                label: "\u5408\u540CA.pdf",
                isFolder: false,
                path: "/contracts/2024/a.pdf",
                size: 1048576,
                modifiedAt: Date.now() - 3456e5
              }
            ]
          }
        ]
      },
      {
        key: "notes",
        label: "\u7B14\u8BB0.txt",
        isFolder: false,
        path: "/notes.txt",
        size: 1024,
        modifiedAt: Date.now()
      }
    ],
    table: [
      {
        key: "excel",
        label: "Excel \u6587\u4EF6",
        isFolder: true,
        path: "/excel",
        children: [
          {
            key: "excel/sales",
            label: "\u9500\u552E\u62A5\u8868.xlsx",
            isFolder: false,
            path: "/excel/sales.xlsx",
            size: 524288,
            modifiedAt: Date.now() - 864e5
          },
          {
            key: "excel/inventory",
            label: "\u5E93\u5B58\u8868.xlsx",
            isFolder: false,
            path: "/excel/inventory.xlsx",
            size: 262144,
            modifiedAt: Date.now() - 1728e5
          }
        ]
      },
      {
        key: "csv",
        label: "CSV \u6570\u636E",
        isFolder: true,
        path: "/csv",
        children: [
          {
            key: "csv/users",
            label: "\u7528\u6237\u6570\u636E.csv",
            isFolder: false,
            path: "/csv/users.csv",
            size: 4096,
            modifiedAt: Date.now()
          }
        ]
      }
    ],
    knowledge: [
      {
        key: "kb",
        label: "\u77E5\u8BC6\u5E93",
        isFolder: true,
        path: "/knowledge",
        children: [
          {
            key: "kb/faq",
            label: "\u5E38\u89C1\u95EE\u9898",
            isFolder: false,
            path: "/knowledge/faq",
            size: 8192,
            modifiedAt: Date.now() - 2592e5
          },
          {
            key: "kb/product",
            label: "\u4EA7\u54C1\u77E5\u8BC6",
            isFolder: false,
            path: "/knowledge/product",
            size: 12288,
            modifiedAt: Date.now() - 432e6
          }
        ]
      },
      {
        key: "wiki",
        label: "Wiki",
        isFolder: true,
        path: "/wiki",
        children: [
          {
            key: "wiki/tech",
            label: "\u6280\u672F\u6587\u6863",
            isFolder: false,
            path: "/wiki/tech",
            size: 20480,
            modifiedAt: Date.now() - 6048e5
          }
        ]
      }
    ],
    file: [
      {
        key: "root",
        label: "\u5168\u90E8\u6587\u4EF6",
        isFolder: true,
        path: "/",
        children: [
          {
            key: "images",
            label: "\u56FE\u7247",
            isFolder: true,
            path: "/images",
            children: [
              {
                key: "images/logo",
                label: "logo.png",
                isFolder: false,
                path: "/images/logo.png",
                size: 25600,
                modifiedAt: Date.now() - 864e5
              }
            ]
          },
          {
            key: "videos",
            label: "\u89C6\u9891",
            isFolder: true,
            path: "/videos",
            children: [
              {
                key: "videos/demo",
                label: "demo.mp4",
                isFolder: false,
                path: "/videos/demo.mp4",
                size: 104857600,
                modifiedAt: Date.now() - 1728e5
              }
            ]
          }
        ]
      }
    ]
  };
  let data = mockData[type] || [];
  if (keyword) {
    const filterNodes = (nodes) => {
      const result = [];
      for (const node of nodes) {
        if (node.label.toLowerCase().includes(keyword.toLowerCase())) {
          result.push(node);
        } else if (node.children) {
          const filteredChildren = filterNodes(node.children);
          if (filteredChildren.length > 0) {
            result.push({ ...node, children: filteredChildren });
          }
        }
      }
      return result;
    };
    data = filterNodes(data);
  }
  return data;
};
const handleSearch = (keyword, trigger) => {
  if (searchDebounceTimer) {
    clearTimeout(searchDebounceTimer);
  }
  if (trigger === "@" && props.enableFileTree) {
    const typeLabels = {
      document: [t("ai.mention.document"), "\u6587\u6863", "document", "Document"],
      file: [
        t("ai.mention.file") === "ai.mention.file" ? "\u6587\u4EF6" : t("ai.mention.file"),
        "\u6587\u4EF6",
        "file",
        "File"
      ],
      table: [t("ai.mention.table"), "\u8868\u683C", "table", "Table"],
      knowledge: [t("ai.mention.knowledge"), "\u77E5\u8BC6\u5E93", "knowledge", "Knowledge"]
    };
    const raw = (keyword || "").trimStart();
    for (const [type, labels] of Object.entries(typeLabels)) {
      const matchedLabel = labels.find(
        (label) => label && raw.toLowerCase().startsWith(label.toLowerCase())
      );
      if (matchedLabel) {
        const rest = raw.slice(matchedLabel.length).trim();
        searchDebounceTimer = setTimeout(() => {
          loadFileTree(type, rest);
        }, props.searchDebounce);
        emit("search", keyword, trigger);
        return;
      }
    }
    currentFileType.value = null;
    fileTreeData.value = [];
  }
  emit("search", keyword, trigger);
};
const toggleFolder = (key) => {
  if (expandedFolders.value.has(key)) {
    expandedFolders.value.delete(key);
  } else {
    expandedFolders.value.add(key);
  }
};
const handleFileSelect = (node) => {
  if (node.isFolder) {
    toggleFolder(node.key);
    return;
  }
  const option = {
    value: node.path,
    label: node.label,
    type: currentFileType.value,
    path: node.path,
    size: node.size,
    modifiedAt: node.modifiedAt,
    icon: getFileIcon(node)
  };
  emit("select", option, "@");
  emit("file-select", node, option);
};
const getFileIcon = (node) => {
  if (node.isFolder) return "folder";
  if (node.icon) return node.icon;
  const ext = node.path.split(".").pop()?.toLowerCase() || "";
  const iconMap = {
    pdf: "file-pdf",
    doc: "file-word",
    docx: "file-word",
    xlsx: "file-excel",
    xls: "file-excel",
    csv: "file-excel",
    txt: "document",
    md: "document",
    png: "picture",
    jpg: "picture",
    jpeg: "picture",
    gif: "picture",
    mp4: "video-play",
    mp3: "headset"
  };
  return iconMap[ext] || "document";
};
const formatFileSizeFn = (size) => {
  if (!size) return "";
  return props.formatFileSize(size);
};
const formatTimeFn = (timestamp) => {
  if (!timestamp) return "";
  const date = new Date(timestamp);
  const now = /* @__PURE__ */ new Date();
  const diff = now.getTime() - date.getTime();
  const isZh = lang.value.startsWith("zh");
  if (diff < 864e5) {
    return date.toLocaleTimeString(isZh ? "zh-CN" : "en-US", { hour: "2-digit", minute: "2-digit" });
  }
  if (diff < 6048e5) {
    const days = Math.floor(diff / 864e5);
    return isZh ? `${days}\u5929\u524D` : `${days} days ago`;
  }
  return date.toLocaleDateString(isZh ? "zh-CN" : "en-US");
};
const showFileTree = computed(() => {
  return props.enableFileTree && currentFileType.value !== null && fileTreeData.value.length > 0;
});
watch(showFileTree, (val) => {
  if (val) {
    nextTick(() => {
      updatePanelPosition();
    });
    window.addEventListener("scroll", updatePanelPosition, true);
    window.addEventListener("resize", updatePanelPosition, true);
  } else {
    window.removeEventListener("scroll", updatePanelPosition, true);
    window.removeEventListener("resize", updatePanelPosition, true);
  }
});
onBeforeUnmount(() => {
  window.removeEventListener("scroll", updatePanelPosition, true);
  window.removeEventListener("resize", updatePanelPosition, true);
  if (searchDebounceTimer) {
    clearTimeout(searchDebounceTimer);
    searchDebounceTimer = null;
  }
});
const refreshFileTree = () => {
  if (currentFileType.value) {
    loadFileTree(currentFileType.value);
  }
};
provide("isFolderExpanded", (key) => expandedFolders.value.has(key));
provide("toggleFolder", (key) => toggleFolder(key));
provide("handleFileSelect", (node) => handleFileSelect(node));
provide("getFileIcon", (node) => getFileIcon(node));
provide("formatFileSizeFn", (size) => formatFileSizeFn(size));
provide("formatTimeFn", (timestamp) => formatTimeFn(timestamp));
provide("showFileIcon", props.showFileIcon);
provide("showFileSize", props.showFileSize);
provide("showModifiedTime", props.showModifiedTime);
provide("ns", ns);
defineExpose({
  focus: () => mentionRef.value?.focus(),
  blur: () => mentionRef.value?.blur(),
  clear: () => mentionRef.value?.clear(),
  getRef: () => mentionRef.value?.ref,
  insertMention: (option, trigger) => mentionRef.value?.insertMention(option, trigger),
  refreshFileTree,
  toggleFolder: (key) => toggleFolder(key)
});
</script>

<template>
  <div ref="mentionWrapperRef" :class="ns.b()" :style="themeStyle">
    <YhMention
      ref="mentionRef"
      v-bind="$attrs"
      :model-value="modelValue"
      :options="filteredOptions"
      :triggers="triggers"
      :type="type"
      :placeholder="placeholder ?? t('ai.mention.placeholder')"
      :disabled="disabled"
      :size="size"
      :loading="loading || fileTreeLoading"
      :filter-option="filterOption"
      :maxlength="maxLength"
      :rows="rows"
      @update:model-value="handleUpdateValue"
      @select="handleSelect"
      @search="handleSearch"
      @focus="emit('focus', $event)"
      @blur="emit('blur', $event)"
      @input="emit('input', $event)"
      @keydown="emit('keydown', $event)"
      @change="emit('change', $event)"
    >
      <template #option="{ option }">
        <div :class="ns.e('option-item')">
          <div
            :class="[ns.e('option-icon'), asAiMentionOption(option).type ? ns.em('option-icon', asAiMentionOption(option).type) : '']"
          >
            <YhIcon
              :name="
                asAiMentionOption(option).icon || getOptionIcon(asAiMentionOption(option).type)
              "
            />
          </div>
          <div :class="ns.e('option-info')">
            <div :class="ns.e('option-label')">
              {{ asAiMentionOption(option).label || asAiMentionOption(option).value }}
            </div>
            <div v-if="asAiMentionOption(option).description" :class="ns.e('option-desc')">
              {{ asAiMentionOption(option).description }}
            </div>
          </div>
          <div
            v-if="asAiMentionOption(option).type"
            :class="[ns.e('option-tag'), ns.em('option-tag', asAiMentionOption(option).type)]"
          >
            {{
              t(`ai.mention.${asAiMentionOption(option).type}`) || asAiMentionOption(option).type
            }}
          </div>
        </div>
      </template>

      <template v-for="name in forwardedSlotNames" :key="name" #[name]="slotProps">
        <slot :name="name" v-bind="slotProps"></slot>
      </template>
    </YhMention>

    <!-- 文件树面板 -->
    <Teleport to="body">
      <Transition name="yh-fade-in-scale-up">
        <div v-if="showFileTree" :class="ns.e('file-tree-panel')" :style="fileTreePanelStyle">
          <div :class="ns.e('file-tree-header')">
            <YhIcon :name="getTypeIcon(currentFileType)" />
            <span>{{ getTypeLabel(currentFileType) }}</span>
            <span :class="ns.e('file-tree-count')">{{ fileTreeData.length }}</span>
          </div>
          <div :class="ns.e('file-tree-content')">
            <div :class="ns.e('file-tree-list')">
              <AiMentionNode v-for="node in fileTreeData" :key="node.key" :node="node" :depth="0" />

              <div
                v-if="fileTreeData.length === 0 && !fileTreeLoading"
                :class="ns.e('file-tree-empty')"
              >
                <YhIcon name="folder-opened" />
                <span>{{ t("ai.mention.noFiles") }}</span>
              </div>
            </div>
          </div>
        </div>
      </Transition>
    </Teleport>
  </div>
</template>

<style>
@charset "UTF-8";
/**
 * YH-UI Sass Variables
 * 原具体的 CSS 变量输出规则 (:root, html.dark 等) 已移至 root.scss，以防止各组件样式编译时产生重复的全局变量 CSS 冗余。
 */
.yh-ai-mention {
  width: 100%;
  position: relative;
}
.yh-ai-mention__option-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 8px 4px;
  width: 100%;
}

.yh-ai-mention__option-icon {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 32px;
  height: 32px;
  border-radius: 8px;
  background: var(--yh-color-primary-light-9);
  color: var(--yh-color-primary);
  font-size: 18px;
}
.yh-ai-mention__option-icon--agent {
  background: var(--yh-color-success-light-9);
  color: var(--yh-color-success);
}

.yh-ai-mention__option-icon--document {
  background: var(--yh-color-warning-light-9);
  color: var(--yh-color-warning);
}

.yh-ai-mention__option-icon--table {
  background: var(--yh-color-info-light-9);
  color: var(--yh-color-info);
}

.yh-ai-mention__option-icon--knowledge {
  background: var(--yh-color-danger-light-9);
  color: var(--yh-color-danger);
}

.yh-ai-mention__option-icon--file {
  background: var(--yh-fill-color-light);
  color: var(--yh-text-color-secondary);
}

.yh-ai-mention__option-info {
  flex: 1;
  min-width: 0;
}

.yh-ai-mention__option-label {
  font-size: 14px;
  font-weight: 500;
  color: var(--yh-text-color-primary);
  line-height: 1.4;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.yh-ai-mention__option-desc {
  font-size: 12px;
  color: var(--yh-text-color-secondary);
  line-height: 1.2;
  margin-top: 2px;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.yh-ai-mention__option-tag {
  font-size: 11px;
  padding: 2px 6px;
  border-radius: 4px;
  background: var(--yh-fill-color-light);
  color: var(--yh-text-color-placeholder);
  text-transform: capitalize;
}
.yh-ai-mention__option-tag--agent {
  background: var(--yh-color-success-light-9);
  color: var(--yh-color-success);
}

.yh-ai-mention__option-tag--document {
  background: var(--yh-color-warning-light-9);
  color: var(--yh-color-warning);
}

.yh-ai-mention__option-tag--table {
  background: var(--yh-color-info-light-9);
  color: var(--yh-color-info);
}

.yh-ai-mention__option-tag--knowledge {
  background: var(--yh-color-danger-light-9);
  color: var(--yh-color-danger);
}

.yh-ai-mention__option-tag--file {
  background: var(--yh-fill-color-light);
  color: var(--yh-text-color-secondary);
}

.yh-ai-mention__file-tree-panel {
  background: var(--yh-bg-color-overlay);
  border: 1px solid var(--yh-border-color-lighter);
  border-radius: 12px;
  box-shadow: var(--yh-shadow-lg);
  overflow: hidden;
}

.yh-ai-mention__file-tree-header {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 12px 16px;
  border-bottom: 1px solid var(--yh-border-color-lighter);
  background: var(--yh-fill-color-light);
  font-size: 14px;
  font-weight: 600;
  color: var(--yh-text-color-primary);
}

.yh-ai-mention__file-tree-count {
  margin-left: auto;
  font-size: 12px;
  color: var(--yh-text-color-secondary);
  background: var(--yh-fill-color);
  padding: 2px 8px;
  border-radius: 10px;
}

.yh-ai-mention__file-tree-content {
  max-height: 320px;
  overflow: auto;
}

.yh-ai-mention__file-tree-list {
  padding: 8px 0;
}

.yh-ai-mention__file-tree-node {
  display: flex;
  align-items: center;
  gap: 8px;
  padding: 8px 16px;
  cursor: pointer;
  transition: background-color 0.2s;
  font-size: 14px;
  color: var(--yh-text-color-primary);
}
.yh-ai-mention__file-tree-node:hover {
  background: var(--yh-fill-color-light);
}
.yh-ai-mention__file-tree-node--folder {
  font-weight: 500;
}

.yh-ai-mention__file-tree-node--expanded {
  background: var(--yh-fill-color-extra-light);
}

.yh-ai-mention__file-tree-child {
  padding-left: 32px;
}

.yh-ai-mention__folder-toggle {
  display: flex;
  align-items: center;
  justify-content: center;
  width: 16px;
  height: 16px;
  color: var(--yh-text-color-secondary);
  cursor: pointer;
}
.yh-ai-mention__folder-toggle:hover {
  color: var(--yh-color-primary);
}

.yh-ai-mention__file-tree-indent {
  width: 16px;
}

.yh-ai-mention__file-tree-icon {
  font-size: 16px;
  color: var(--yh-text-color-secondary);
}

.yh-ai-mention__file-tree-label {
  flex: 1;
  overflow: hidden;
  text-overflow: ellipsis;
  white-space: nowrap;
}

.yh-ai-mention__file-tree-size {
  font-size: 12px;
  color: var(--yh-text-color-secondary);
  white-space: nowrap;
}

.yh-ai-mention__file-tree-time {
  font-size: 12px;
  color: var(--yh-text-color-disabled);
  white-space: nowrap;
}

.yh-ai-mention__file-tree-empty {
  display: flex;
  flex-direction: column;
  align-items: center;
  gap: 8px;
  padding: 32px;
  color: var(--yh-text-color-placeholder);
  font-size: 14px;
}
</style>
