<!-- 分段设置组件 —— 对应源代码 SegmentSetting.tsx -->
<template>
  <div :class="[styles.content, styles.segmentContent]">
    <h3 :class="styles.segmentTitle">分段设置</h3>
    <div :class="styles.segmentLayout">
      <div :class="styles.segmentLeft">
        <SegmenteInfo
          :indexing-estimate="indexingEstimate"
          :set-indexing-estimate="setIndexingEstimate"
          :preview-indexing-estimate="previewIndexingEstimate"
          :file-list="fileList"
          :selected-file-uid="selectedFileUid"
          :set-selected-file-uid="setSelectedFileUid"
          :set-current-file-index="setCurrentFileIndex"
        />

        <ElFormItem label="Embedding 模型" :class="styles.embeddingItem">
          <ElSelect
            :class="styles.embeddingSelect"
            placeholder="搜索模型"
            filterable
            :model-value="toModelSelectValue(embeddingModel)"
            :loading="loadingTextEmbedding"
            @change="(value: any) => setEmbeddingModel(parseModelSelectValue(String(value ?? '')))"
          >
            <ElOptionGroup
              v-for="group in embeddingOptions"
              :key="group.label"
              :label="group.label"
            >
              <ElOption
                v-for="opt in group.options"
                :key="opt.value"
                :value="opt.value"
                :label="opt.label"
              >
                <div :class="styles.embeddingOption">
                  <img
                    v-if="opt.icon"
                    :class="styles.embeddingOptionIcon"
                    :src="opt.icon"
                    alt=""
                  />
                  <span :class="styles.embeddingOptionLabel">{{ opt.label }}</span>
                </div>
              </ElOption>
            </ElOptionGroup>
          </ElSelect>
        </ElFormItem>

        <div :class="styles.retrievalSection">
          <h3 :class="styles.segmentTitle">检索设置</h3>

          <div :class="styles.settingCard">
            <div :class="styles.settingHeader">
              <div :class="styles.settingIcon">
                <ElIcon><Search /></ElIcon>
              </div>
              <div>
                <div :class="styles.settingName">混合检索</div>
                <div :class="styles.settingDesc">
                  同时执行全文检索和向量检索，并应用重排序步骤，从两类查询结果中选择匹配用户问题的最佳结果，用户可以选择设置权重或配置重新排序模型。
                </div>
              </div>
            </div>

            <div :class="styles.retrievalForm">
              <div :class="styles.retrievalRerankRow">
                <div :class="styles.retrievalLabel">
                  <span>
                    Rerank 模型
                    <ElIcon :class="styles.infoIcon"><InfoFilled /></ElIcon>
                  </span>
                </div>
                <ElSwitch :model-value="rerankEnabled" @update:model-value="(v: any) => setRerankEnabled(Boolean(v))" />
              </div>

              <ElSelect
                v-if="rerankEnabled"
                :class="styles.retrievalModelSelect"
                placeholder="搜索模型"
                filterable
                :model-value="toModelSelectValue(rerankModel)"
                :loading="loadingRerank"
                @change="(value: any) => setRerankModel(parseModelSelectValue(String(value ?? '')))"
              >
                <ElOptionGroup
                  v-for="group in rerankOptions"
                  :key="group.label"
                  :label="group.label"
                >
                  <ElOption
                    v-for="opt in group.options"
                    :key="opt.value"
                    :value="opt.value"
                    :label="opt.label"
                  >
                    <div :class="styles.embeddingOption">
                      <img
                        v-if="opt.icon"
                        :class="styles.embeddingOptionIcon"
                        :src="opt.icon"
                        alt=""
                      />
                      <span :class="styles.embeddingOptionLabel">{{ opt.label }}</span>
                    </div>
                  </ElOption>
                </ElOptionGroup>
              </ElSelect>

              <div :class="styles.retrievalRow">
                <div :class="styles.retrievalControl">
                  <div :class="styles.retrievalControlHeader">
                    <span>
                      Top K
                      <ElIcon :class="styles.infoIcon"><InfoFilled /></ElIcon>
                    </span>
                  </div>
                  <div :class="styles.retrievalControlBody">
                    <ElInputNumber
                      :class="styles.retrievalNumberInput"
                      :min="1"
                      :max="20"
                      :model-value="topK"
                      @update:model-value="(v: any) => setTopK(Number(v ?? 1))"
                    />
                    <ElSlider
                      :class="styles.retrievalSlider"
                      :min="1"
                      :max="20"
                      :model-value="topK"
                      @update:model-value="(v: any) => setTopK(Number(v))"
                    />
                  </div>
                </div>

                <div :class="styles.retrievalControl">
                  <div :class="styles.retrievalControlHeader">
                    <span>
                      Score 阈值
                      <ElIcon :class="styles.infoIcon"><InfoFilled /></ElIcon>
                    </span>
                    <ElSwitch
                      size="small"
                      :model-value="scoreThresholdEnabled"
                      @update:model-value="(v: any) => setScoreThresholdEnabled(Boolean(v))"
                    />
                  </div>
                  <div :class="styles.retrievalControlBody">
                    <ElInputNumber
                      :class="styles.retrievalNumberInput"
                      :min="0"
                      :max="1"
                      :step="0.01"
                      :model-value="scoreThreshold"
                      :disabled="!scoreThresholdEnabled"
                      @update:model-value="(v: any) => setScoreThreshold(Number(v ?? 0))"
                    />
                    <ElSlider
                      :class="styles.retrievalSlider"
                      :min="0"
                      :max="1"
                      :step="0.01"
                      :model-value="scoreThreshold"
                      :disabled="!scoreThresholdEnabled"
                      @update:model-value="(v: any) => setScoreThreshold(Number(v))"
                    />
                  </div>
                </div>
              </div>
            </div>
          </div>
        </div>
      </div>

      <SegmentedPreview
        :selected-filename="selectedFilename"
        :selected-ext="selectedExt"
        :selected-icon-style="selectedIconStyle"
        :loading-indexing-estimate="loadingIndexingEstimate"
        :indexing-estimate-preview-chunks="indexingEstimatePreviewChunks"
      />
    </div>
  </div>
</template>

<script setup lang="ts">
defineOptions({ name: "SegmentSetting" });

import { watch } from "vue";
import { Search, InfoFilled } from "@element-plus/icons-vue";
import SegmenteInfo from "./SegmenteInfo.vue";
import SegmentedPreview from "./SegmentedPreview.vue";
import { getFileIconStyle, type FileIconStyle } from "./fileStyles";
import { useDifyGet, useDifyPost } from "../useDifyApis";
import { knowledgeBase } from "../api";
import {
  decodeInputValue,
  getQueryIndexingEstimateJson,
  type IndexingEstimate,
  type ModelType,
  type UploadFile,
} from "../types";
import styles from "../style/createKnowledge.module.scss";

type IndexingEstimatePreviewChunk = {
  content?: string;
  summary?: string | null;
  child_chunks?: unknown;
};

type LocalizedText = {
  zh_Hans?: string;
  en_US?: string;
  [key: string]: string | undefined;
};

type TextEmbeddingModel = {
  model: string;
  label?: LocalizedText;
  model_type?: string;
  status?: string;
  deprecated?: boolean;
};

type TextEmbeddingProvider = {
  provider: string;
  label?: LocalizedText;
  icon_small?: LocalizedText;
  status?: string;
  models?: TextEmbeddingModel[];
};

type ModelOption = {
  key: string;
  value: string;
  label: string;
  icon?: string;
};

type ModelOptionGroup = {
  label: string;
  options: ModelOption[];
};

interface Props {
  fileList: UploadFile[];
  fileIds: string[];
  rerankModel: ModelType;
  embeddingModel: ModelType;
  indexingEstimate: IndexingEstimate;
  rerankEnabled: boolean;
  topK: number;
  scoreThreshold: number;
  scoreThresholdEnabled: boolean;
  setRerankEnabled: (value: boolean) => void;
  setTopK: (value: number) => void;
  setScoreThreshold: (value: number) => void;
  setScoreThresholdEnabled: (value: boolean) => void;
  setRerankModel: (model: ModelType) => void;
  setEmbeddingModel: (model: ModelType) => void;
  setIndexingEstimate: (estimate: IndexingEstimate) => void;
}

const props = defineProps<Props>();

const selectedFileUid = ref<string>(props.fileList[0]?.uid ?? "");
const currentFileIndex = ref(0);

const selectedFile = computed(() =>
  props.fileList.find((file) => file.uid === selectedFileUid.value)
);

const selectedFilename = computed(() => selectedFile.value?.name);
const selectedExt = computed(() => {
  const name = selectedFilename.value;
  if (!name || !name.includes(".")) return "";
  return name.split(".").pop()?.toLowerCase() || "";
});
const selectedIconStyle = computed<FileIconStyle>(() =>
  getFileIconStyle(selectedExt.value)
);

// 预览块
const {
  result: indexingEstimateResult,
  doPost: postIndexingEstimate,
  loading: loadingIndexingEstimate,
} = useDifyPost({
  url: knowledgeBase.indexingEstimate,
});

// textEmbedding 模型
const { result: textModeldingResult, loading: loadingTextEmbedding } = useDifyGet({
  url: knowledgeBase.textEmbedding,
  moreOperations: {
    immediate: true,
  },
});

// rerank 模型
const { result: rerankResult, loading: loadingRerank } = useDifyGet({
  url: knowledgeBase.rerank,
  moreOperations: {
    immediate: true,
  },
});

watch(
  () => props.fileList,
  (fileList) => {
    if (!fileList?.length) {
      selectedFileUid.value = "";
      return;
    }
    if (selectedFileUid.value && fileList.some((f) => f.uid === selectedFileUid.value)) {
      return;
    }
    selectedFileUid.value = fileList[0]?.uid ?? "";
  },
  { immediate: true, deep: true }
);

watch(
  [currentFileIndex, selectedFileUid],
  () => {
    if (selectedFileUid.value) {
      previewIndexingEstimate();
    }
  },
  { immediate: true }
);

const indexingEstimatePreviewChunks = computed<IndexingEstimatePreviewChunk[]>(() => {
  const resp = indexingEstimateResult.value as any;
  const payload = resp?.data ?? resp;
  return Array.isArray(payload?.preview) ? payload.preview : [];
});

function pickText(text?: LocalizedText): string {
  return text?.zh_Hans ?? text?.en_US ?? Object.values(text ?? {})[0] ?? "";
}

function formatIconUrl(icon?: LocalizedText): string {
  const url = pickText(icon);
  return url || "";
}

function toModelSelectValue(model: ModelType): string | undefined {
  if (!model?.currentProvider || !model?.currentModel) return undefined;
  return `${model.currentProvider}::${model.currentModel}`;
}

function parseModelSelectValue(value: string): ModelType {
  const [currentProvider, ...rest] = String(value ?? "").split("::");
  const currentModel = rest.join("::");
  return {
    currentProvider: currentProvider ?? "",
    currentModel: currentModel ?? "",
  };
}

// 点击预览分段
function previewIndexingEstimate() {
  const fileId = props.fileIds[currentFileIndex.value];
  if (!fileId) return;
  postIndexingEstimate({
    data: getQueryIndexingEstimateJson(
      [fileId],
      [
        {
          id: "remove_extra_spaces",
          enabled: props.indexingEstimate.pre_processing_rules.includes(
            "remove_extra_spaces"
          ),
        },
        {
          id: "remove_urls_emails",
          enabled: props.indexingEstimate.pre_processing_rules.includes(
            "remove_urls_emails"
          ),
        },
      ],
      {
        separator: decodeInputValue(props.indexingEstimate.segmentIdentifier),
        max_tokens: props.indexingEstimate.maximumSegmentLength,
        chunk_overlap: props.indexingEstimate.segmentOverlapLength,
      }
    ),
  });
}

function setCurrentFileIndex(index: number) {
  currentFileIndex.value = index;
}

function setSelectedFileUid(uid: string) {
  selectedFileUid.value = uid;
}

// textEmbedding 选择
const embeddingOptions = computed<ModelOptionGroup[]>(() => {
  const resp = textModeldingResult.value as any;
  const embeddingProviders: TextEmbeddingProvider[] = Array.isArray(resp?.data)
    ? resp.data
    : [];

  return embeddingProviders
    .filter((p) => (p?.status ?? "active") === "active")
    .map((provider) => {
      const providerLabel = pickText(provider.label) || provider.provider;
      const iconPath = formatIconUrl(provider.icon_small);
      const providerIcon = iconPath ? "/p30" + iconPath : "";
      const models = (provider.models ?? [])
        .filter((m) => (m?.status ?? "active") === "active")
        .filter((m) => !m?.deprecated)
        .filter((m) => (m?.model_type ?? "text-embedding") === "text-embedding")
        .map((model) => {
          const modelLabel = pickText(model.label) || model.model;
          return {
            key: `${provider.provider}::${model.model}`,
            value: `${provider.provider}::${model.model}`,
            label: modelLabel,
            icon: providerIcon,
          };
        });
      return {
        label: providerLabel,
        options: models,
      };
    })
    .filter((group) => group.options.length > 0);
});

// rerank 选择
const rerankOptions = computed<ModelOptionGroup[]>(() => {
  const resp = rerankResult.value as any;
  const rerankProviders: TextEmbeddingProvider[] = Array.isArray(resp?.data)
    ? resp.data
    : [];

  return rerankProviders
    .filter((p) => (p?.status ?? "active") === "active")
    .map((provider) => {
      const providerLabel = pickText(provider.label) || provider.provider;
      const iconPath = formatIconUrl(provider.icon_small);
      const providerIcon = iconPath ? "/p30" + iconPath : "";
      const models = (provider.models ?? [])
        .filter((m) => (m?.status ?? "active") === "active")
        .filter((m) => !m?.deprecated)
        .filter((m) => (m?.model_type ?? "rerank") === "rerank")
        .map((model) => {
          const modelLabel = pickText(model.label) || model.model;
          return {
            key: `${provider.provider}::${model.model}`,
            value: `${provider.provider}::${model.model}`,
            label: modelLabel,
            icon: providerIcon,
          };
        });
      return {
        label: providerLabel,
        options: models,
      };
    })
    .filter((group) => group.options.length > 0);
});

// 默认选择第一个模型
watch(
  [embeddingOptions, () => props.embeddingModel],
  () => {
    const firstValue = embeddingOptions.value?.[0]?.options?.[0]?.value;
    if (!firstValue) return;
    let exists = false;
    for (const group of embeddingOptions.value) {
      for (const opt of group.options) {
        if (opt.value === toModelSelectValue(props.embeddingModel)) {
          exists = true;
        }
      }
    }
    if (
      !props.embeddingModel?.currentProvider ||
      !props.embeddingModel?.currentModel ||
      !exists
    ) {
      props.setEmbeddingModel(parseModelSelectValue(firstValue));
    }
  },
  { deep: true }
);

watch(
  [rerankOptions, () => props.rerankModel],
  () => {
    const firstValue = rerankOptions.value?.[0]?.options?.[0]?.value;
    if (!firstValue) return;
    let exists = false;
    for (const group of rerankOptions.value) {
      for (const opt of group.options) {
        if (opt.value === toModelSelectValue(props.rerankModel)) {
          exists = true;
        }
      }
    }
    if (
      !props.rerankModel?.currentProvider ||
      !props.rerankModel?.currentModel ||
      !exists
    ) {
      props.setRerankModel(parseModelSelectValue(firstValue));
    }
  },
  { deep: true }
);
</script>
