<script setup lang="ts">
import { computed, onMounted, reactive, ref, watch } from 'vue';

import { $t } from '@easyflow/locales';

import { InfoFilled } from '@element-plus/icons-vue';
import {
  ElButton,
  ElInput,
  ElInputNumber,
  ElMessage,
  ElTooltip,
} from 'element-plus';

import { api } from '#/api/request';
import PreviewSearchKnowledge from '#/views/ai/documentCollection/PreviewSearchKnowledge.vue';
import { buildKnowledgePath } from '#/views/ai/documentCollection/share-path';

type RetrievalMode = 'HYBRID' | 'KEYWORD' | 'VECTOR';

interface SearchResultItem {
  sorting: number;
  content: string;
  renderMarkdown?: string;
  sourceFileName?: string;
  score?: number;
  hitSource?: 'BOTH' | 'KEYWORD' | 'VECTOR';
  vectorScore?: number;
  keywordScore?: number;
}

const props = defineProps({
  knowledgeId: {
    type: String,
    required: true,
  },
  requestClient: {
    type: Object as any,
    default: () => api,
  },
  endpointPrefix: {
    type: String,
    default: '',
  },
  showConfig: {
    type: Boolean,
    default: false,
  },
});

const searchDataList = ref<SearchResultItem[]>([]);
const keyword = ref('');
const retrievalMode = ref<RetrievalMode>('HYBRID');
const isSearching = ref(false);
const hasSearched = ref(false);
const previewSearchKnowledgeRef = ref();
const searchConfig = reactive({
  docRecallMaxNum: 5,
  simThreshold: 0.6,
});

const retrievalModeDescriptions = computed(() => [
  {
    key: 'HYBRID' as RetrievalMode,
    label: $t('bot.retrievalModes.hybrid'),
    description: $t(
      'documentCollectionSearch.retrievalModeDescriptions.hybrid',
    ),
  },
  {
    key: 'VECTOR' as RetrievalMode,
    label: $t('bot.retrievalModes.vector'),
    description: $t(
      'documentCollectionSearch.retrievalModeDescriptions.vector',
    ),
  },
  {
    key: 'KEYWORD' as RetrievalMode,
    label: $t('bot.retrievalModes.keyword'),
    description: $t(
      'documentCollectionSearch.retrievalModeDescriptions.keyword',
    ),
  },
]);

const applySearchConfig = (options?: Record<string, any>) => {
  const rawRecallMax = Number(options?.docRecallMaxNum);
  const rawSimilarity = Number(options?.simThreshold);
  searchConfig.docRecallMaxNum =
    Number.isFinite(rawRecallMax) && rawRecallMax > 0 ? rawRecallMax : 5;
  searchConfig.simThreshold =
    Number.isFinite(rawSimilarity) && rawSimilarity >= 0 && rawSimilarity <= 1
      ? rawSimilarity
      : 0.6;
};

const loadSearchConfig = async () => {
  if (!props.showConfig || !props.knowledgeId) {
    return;
  }
  try {
    const res = await props.requestClient.get(
      buildKnowledgePath(
        props.endpointPrefix,
        '/api/v1/documentCollection/detail',
      ),
      {
        params: {
          id: props.knowledgeId,
        },
      },
    );
    applySearchConfig(res.data?.options);
  } catch {
    applySearchConfig();
  }
};

onMounted(() => {
  loadSearchConfig();
});

watch(
  () => props.knowledgeId,
  () => {
    loadSearchConfig();
  },
);

const handleSearch = async () => {
  const normalizedKeyword = keyword.value.trim();
  if (!normalizedKeyword) {
    ElMessage.error($t('message.pleaseInputContent'));
    return;
  }
  if (isSearching.value) {
    return;
  }
  keyword.value = normalizedKeyword;
  isSearching.value = true;
  hasSearched.value = true;
  previewSearchKnowledgeRef.value?.loadingContent(true);
  try {
    const res = await props.requestClient.get(
      buildKnowledgePath(
        props.endpointPrefix,
        '/api/v1/documentCollection/search',
      ),
      {
        params: {
          knowledgeId: props.knowledgeId,
          keyword: normalizedKeyword,
          retrievalMode: retrievalMode.value,
          docRecallMaxNum: searchConfig.docRecallMaxNum,
          simThreshold: searchConfig.simThreshold,
        },
      },
    );
    searchDataList.value = res.data || [];
  } catch {
    ElMessage.error($t('documentCollection.searchFailed'));
    searchDataList.value = [];
  } finally {
    isSearching.value = false;
    previewSearchKnowledgeRef.value?.loadingContent(false);
  }
};

const handleRetrievalModeChange = (mode: RetrievalMode) => {
  retrievalMode.value = mode;
};
</script>

<template>
  <div class="knowledge-search-shell">
    <div class="knowledge-search-sidebar">
      <div class="search-controls">
        <div class="search-controls__search">
          <div class="search-input">
            <ElInput
              v-model="keyword"
              clearable
              :placeholder="$t('common.searchPlaceholder')"
              @keyup.enter="handleSearch"
            />
            <ElButton
              type="primary"
              :loading="isSearching"
              @click="handleSearch"
            >
              {{ $t('button.query') }}
            </ElButton>
          </div>
        </div>

        <div v-if="showConfig" class="search-controls__header">
          <div class="config-grid">
            <div class="config-item">
              <div class="config-item__label">
                <span>{{
                  $t('documentCollectionSearch.docRecallMaxNum.label')
                }}</span>
                <ElTooltip
                  :content="
                    $t('documentCollectionSearch.docRecallMaxNum.tooltip')
                  "
                  placement="top"
                  effect="dark"
                >
                  <InfoFilled class="info-icon" />
                </ElTooltip>
              </div>
              <ElInputNumber
                v-model="searchConfig.docRecallMaxNum"
                :min="1"
                :max="50"
                :step="1"
                class="config-item__control"
              />
            </div>

            <div class="config-item">
              <div class="config-item__label">
                <span>{{
                  $t('documentCollectionSearch.simThreshold.label')
                }}</span>
                <ElTooltip
                  :content="$t('documentCollectionSearch.simThreshold.tooltip')"
                  placement="top"
                  effect="dark"
                >
                  <InfoFilled class="info-icon" />
                </ElTooltip>
              </div>
              <ElInputNumber
                v-model="searchConfig.simThreshold"
                :min="0"
                :max="1"
                :step="0.01"
                :precision="2"
                controls-position="right"
                class="config-item__control"
              />
            </div>
          </div>
        </div>

        <div class="search-controls__mode">
          <div class="search-hint__list">
            <button
              v-for="item in retrievalModeDescriptions"
              :key="item.key"
              type="button"
              class="search-hint__item"
              :class="{ 'is-active': retrievalMode === item.key }"
              @click="handleRetrievalModeChange(item.key)"
            >
              <div class="search-hint__label">{{ item.label }}</div>
              <div class="search-hint__desc">{{ item.description }}</div>
            </button>
          </div>
        </div>
      </div>
    </div>

    <div class="knowledge-search-preview">
      <PreviewSearchKnowledge
        ref="previewSearchKnowledgeRef"
        :data="searchDataList"
        :is-searching="hasSearched"
        :retrieval-mode="retrievalMode"
      />
    </div>
  </div>
</template>

<style scoped>
.knowledge-search-shell {
  display: grid;
  grid-template-columns: minmax(300px, 332px) minmax(0, 1fr);
  gap: 20px;
  align-items: start;
  width: 100%;
  height: 100%;
  min-height: 0;
}

.knowledge-search-sidebar {
  display: flex;
  flex-direction: column;
  position: sticky;
  top: 0;
  align-self: start;
  min-height: 0;
}

.search-controls {
  display: grid;
  gap: 16px;
  padding: 18px;
  background: var(--el-fill-color-blank);
  border: 1px solid rgb(15 23 42 / 6%);
  border-radius: 18px;
}

.search-controls__header {
  display: grid;
  gap: 12px;
}

.search-controls__search {
  padding-bottom: 4px;
}

.search-controls__mode {
  display: grid;
  gap: 10px;
}

.config-grid {
  display: grid;
  grid-template-columns: repeat(2, minmax(0, 1fr));
  gap: 14px;
}

.config-item {
  display: grid;
  gap: 6px;
}

.config-item__label {
  display: flex;
  gap: 4px;
  align-items: center;
  font-size: 14px;
  color: var(--el-text-color-regular);
}

.config-item__control {
  width: 100%;
}

.config-item__control :deep(.el-input-number) {
  width: 100%;
}

.info-icon {
  width: 16px;
  height: 16px;
  font-size: 14px;
  color: var(--el-text-color-secondary);
  cursor: help;
}

.search-input {
  display: flex;
  gap: 12px;
  align-items: center;
}

.search-input :deep(.el-input) {
  flex: 1;
}

.search-hint__list {
  display: grid;
  gap: 10px;
}

.search-hint__item {
  display: grid;
  gap: 6px;
  min-width: 0;
  padding: 12px 14px;
  text-align: left;
  cursor: pointer;
  background: rgb(248 250 252 / 88%);
  border: 1px solid rgb(15 23 42 / 6%);
  border-radius: 14px;
  transition:
    border-color 0.2s ease,
    background-color 0.2s ease;
}

.search-hint__item:hover {
  border-color: rgb(59 130 246 / 22%);
  background: rgb(248 250 252 / 100%);
}

.search-hint__item.is-active {
  background: rgb(239 246 255 / 92%);
  border-color: rgb(96 165 250 / 45%);
}

.search-hint__item:focus-visible {
  outline: 2px solid var(--el-color-primary-light-5);
  outline-offset: 2px;
}

.search-hint__label {
  font-size: 14px;
  font-weight: 600;
  color: var(--el-text-color-primary);
}

.search-hint__desc {
  font-size: 13px;
  line-height: 1.6;
  color: var(--el-text-color-secondary);
}

.knowledge-search-preview {
  min-width: 0;
  height: 100%;
  min-height: 0;
}

@media (max-width: 1024px) {
  .knowledge-search-shell {
    grid-template-columns: 1fr;
  }

  .knowledge-search-preview {
    min-height: 420px;
  }

  .search-input {
    flex-wrap: wrap;
  }

  .search-input :deep(.el-button) {
    width: 100%;
  }

  .config-grid {
    grid-template-columns: 1fr;
  }
}
</style>
