<script lang="ts">
export default {
  name: "Search",
};
</script>
<script setup lang="ts">
import { ref, computed, watch, shallowRef, markRaw, nextTick, onMounted } from "vue";
import Mark from 'mark.js/src/vanilla.js'
import localSearchIndex from '@localSearchIndex'
import MiniSearch, { type SearchResult } from 'minisearch'
import { Search } from "@element-plus/icons-vue";
import { watchDebounced, useFocus, computedAsync } from "@vueuse/core";
import { useI18n } from "vue-i18n";
import { useData } from 'vitepress'
import { escapeRegExp } from 'vitepress/dist/client/shared'
import _ from "lodash";

const { t } = useI18n();

/* Search */

const searchIndexData = shallowRef(localSearchIndex)

// hmr
if (import.meta.hot) {
  import.meta.hot.accept('/@localSearchIndex', (m) => {
    if (m) {
      searchIndexData.value = m.default
    }
  })
}

interface Result {
  title: string
  titles: string[]
  text?: string
}

const vitePressData = useData()
const { localeIndex, theme } = vitePressData
const searchIndex = computedAsync(async () =>
  markRaw(
    MiniSearch.loadJSON<Result>(
      (await searchIndexData.value[localeIndex.value]?.())?.default,
      {
        fields: ['title', 'titles', 'text'],
        storeFields: ['title', 'titles'],
        searchOptions: {
          fuzzy: 0.2,
          prefix: true,
          boost: { title: 4, text: 2, titles: 1 },
          ...(theme.value.search?.provider === 'local' &&
            theme.value.search.options?.miniSearch?.searchOptions)
        },
        ...(theme.value.search?.provider === 'local' &&
          theme.value.search.options?.miniSearch?.options)
      }
    )
  )
)

const filterText = ref("");
const input$ = ref();
const { focused } = useFocus(computed(() => input$.value?.input));
onMounted(() => {
  focused.value = true
})
const results = shallowRef<(SearchResult & Result)[]>([])
const loading = ref(false);

const suggestionVisible = computed(() => {
  // TEST
  // return true
  const isValidData = results.value.length > 0;
  return !!( focused.value && (isValidData || loading.value || filterText.value) );
});


const resultsEl = shallowRef<HTMLElement>()
const mark = computedAsync(async () => {
  if (!resultsEl.value) return
  return markRaw(new Mark(resultsEl.value))
}, null)

function formMarkRegex(terms: Set<string>) {
  return new RegExp(
    [...terms]
      .sort((a, b) => b.length - a.length)
      .map((term) => `(${escapeRegExp(term)})`)
      .join('|'),
    'gi'
  )
}
watch(() => filterText.value, () => {
  if (!results.value.length) {
    loading.value = true
  }
})
watchDebounced(
  () => [searchIndex.value, filterText.value] as const,
  async ([index, filterTextValue], old, onCleanup) => {
    let canceled = false
    onCleanup(() => {
      canceled = true
    })

    if (!index) return

    // Search
    results.value = index
      .search(filterTextValue)
      .slice(0, 16) as (SearchResult & Result)[]

    const terms = new Set<string>()

    results.value = results.value.map((r) => {
      for (const term in r.match) {
        terms.add(term)
      }
      return r
    })
    loading.value = false

    await nextTick()
    if (canceled) return

    await new Promise((r) => {
      mark.value?.unmark({
        done: () => {
          mark.value?.markRegExp(formMarkRegex(terms), { done: r })
        }
      })
    })

    // const excerpts = el.value?.querySelectorAll('.result .excerpt') ?? []
    // for (const excerpt of excerpts) {
    //   excerpt
    //     .querySelector('mark[data-markjs="true"]')
    //     ?.scrollIntoView({ block: 'center' })
    // }
    // FIXME: without this whole page scrolls to the bottom
    // resultsEl.value?.firstElementChild?.scrollIntoView({ block: 'start' })
  },
  { debounce: 200, immediate: true }
);
const searchRecommend = (e) => {
  filterText.value = e.target.innerText
  focused.value = true
}
</script>
<template>
  <div class="search-container">
    <el-popover
      :visible="suggestionVisible"
      :show-arrow="false"
      :offset="0"
      :teleported="false"
      width="100%"
    >
      <template #reference>
        <div class="search-trigger" :class="{ 'has-content': suggestionVisible }">
          <el-input
            :ref="(el) => (input$ = el)"
            v-model="filterText"
            clearable
            :prefix-icon="Search"
            :placeholder="t('请输入关键词，如：课程、协同、AI')"
          ></el-input>
        </div>
      </template>
      <div class="search-content">
        <template v-if="loading">
          <el-skeleton animated />
        </template>
        <template v-else-if="results.length">
          <ul ref="resultsEl" class="results">
            <li v-for="(p, index) in results" :key="index">
              <a
                :href="p.id"
                class="result"
                :aria-label="[...p.titles, p.title].join(' > ')"
              >
                <div>
                  <div class="titles">
                    <span class="title-icon">#</span>
                    <span v-for="(t, index) in p.titles" :key="index" class="title">
                      <span class="text" v-html="t" />
                      <span class="vpi-chevron-right local-search-icon" />
                    </span>
                    <span class="title main">
                      <span class="text" v-html="p.title" />
                    </span>
                  </div>
                </div>
              </a>
            </li>
          </ul>
        </template>
        <template v-else-if="filterText">
          <el-empty :image-size="80">
            <template #description>
              <span
                >无法找到相关结果 <strong>"{{ filterText }}"</strong>
              </span>
            </template>
          </el-empty>
        </template>
      </div>
    </el-popover>
    <div class="search-recommend">
      <span> 搜索推荐: </span>
      <span class="recommend" @click="searchRecommend">课程</span>
      <span class="recommend" @click="searchRecommend">协同</span>
      <span class="recommend" @click="searchRecommend">项目</span>
      <span class="recommend" @click="searchRecommend">登录</span>
      <span class="recommend" @click="searchRecommend">AI助手</span>
    </div>
  </div>
</template>
<i18n locale="zh-HK">
{
  "请输入关键词，如：课程、协同、AI": "TODO",
}
</i18n>
<style lang="scss" scoped>
.search-container {
  width: 514px;
  margin: auto;
  position: relative;
  .search-trigger {
    border: 1px solid #aeccfe;
    padding: 1px;
    width: 100%;
    height: 52px;
    border-radius: 26px;
    display: flex;
    align-items: center;
    padding: 0 10px;
    overflow: hidden;
    transition: all 0.2s;
    :deep(.el-input) {
      .el-input__wrapper {
        box-shadow: none;
      }
    }
    &:has(input:focus) {
      border: none;
      box-shadow: var(--el-box-shadow-light);
    }
    &.has-content {
      border-bottom: 1px solid #e2eeff;
      border-radius: 26px 26px 0 0;
    }
  }
  :deep(.el-popover) {
    border-radius: 0 0 26px 26px;
    border: none;
    clip-path: inset(0px -10px -10px -10px);
    padding: 0;
    overflow: hidden;
    max-height: 300px;
    overflow-y: scroll;
  }
  .search-recommend {
    display: flex;
    align-items: center;
    gap: 8px;
    padding: 0 20px;
    margin-top: 8px;
    span {
      color: #41506dcc;
      font-size: 12px;
      font-weight: 600;
      line-height: 20px;
    }
    .recommend {
      cursor: pointer;
    }
  }
  .search-content {
    .el-skeleton {
      padding: 10px 20px;
    }
    .results {
      display: flex;
      flex-direction: column;
      overflow-x: hidden;
      overflow-y: auto;
      overscroll-behavior: contain;

      list-style-type: none;
      padding: 0 0 5px;
      margin: 0;

      li {
        margin: 0;
      }

      .result {
        display: flex;
        align-items: center;
        gap: 8px;
        border-radius: 4px;
        transition: none;
        line-height: 1rem;
        outline: none;

        min-height: 40px;
        padding: 5px 20px;
        color: #333;

        :deep(mark) {
          color: #3681fc;
          background: transparent;
        }

        &:hover {
          background-color: #ecf5ff;
        }

        .titles {
          display: flex;
          flex-wrap: wrap;
          gap: 4px;
          position: relative;
          z-index: 1001;
          padding: 2px 0;
        }

        .title {
          display: flex;
          align-items: center;
          gap: 4px;
        }

        .title.main {
          font-weight: 500;
        }

        .title-icon {
          opacity: 0.5;
          font-weight: 500;
          color: var(--vp-c-brand-1);
        }

        .title svg {
          opacity: 0.5;
        }
      }
    }
  }
}
</style>
