<script setup lang="ts">
  import {
    Dialog,
    DialogDescription,
    DialogPanel,
    TransitionChild,
    TransitionRoot,
  } from '@headlessui/vue';
  import type { OverlayScrollbarsComponentRef } from 'overlayscrollbars-vue';
  import { OverlayScrollbarsComponent } from 'overlayscrollbars-vue';
  import { cloneDeep } from 'lodash-es';
  import hotkeys from 'hotkeys-js';
  import Breadcrumb from '../Breadcrumb/index.vue';
  import BreadcrumbItem from '../Breadcrumb/item.vue';
  import { resolveRoutePath } from '@/utils';
  import eventBus from '@/utils/eventBus';
  import useSettingsStore from '@/store/modules/settings';
  import useMenuStore from '@/store/modules/menu';
  import type { Menu } from '@/types/global';

  defineOptions({
    name: 'Search',
  });

  const overlayTransitionClass = ref({
    enter: 'ease-in-out duration-500',
    enterFrom: 'opacity-0',
    enterTo: 'opacity-100',
    leave: 'ease-in-out duration-500',
    leaveFrom: 'opacity-100',
    leaveTo: 'opacity-0',
  });

  const transitionClass = computed(() => {
    return {
      enter: 'ease-out duration-300',
      enterFrom: 'opacity-0 translate-y-4 lg-translate-y-0 lg-scale-95',
      enterTo: 'opacity-100 translate-y-0 lg-scale-100',
      leave: 'ease-in duration-200',
      leaveFrom: 'opacity-100 translate-y-0 lg-scale-100',
      leaveTo: 'opacity-0 translate-y-4 lg-translate-y-0 lg-scale-95',
    };
  });

  const router = useRouter();
  const settingsStore = useSettingsStore();
  const menuStore = useMenuStore();

  interface listTypes {
    path: string;
    icon?: string;
    title?: string | (() => string);
    link?: string;
    breadcrumb: {
      title?: string | (() => string);
    }[];
  }

  const isShow = ref(false);
  const searchInput = ref('');
  const sourceList = ref<listTypes[]>([]);
  const actived = ref(-1);

  const searchInputRef = ref();
  const searchResultRef = ref<OverlayScrollbarsComponentRef>();
  const searchResultItemRef = ref<HTMLElement[]>([]);
  onBeforeUpdate(() => {
    searchResultItemRef.value = [];
  });

  const resultList = computed(() => {
    let result = [];
    result = sourceList.value.filter((item) => {
      let flag = false;
      if (item.title) {
        if (typeof item.title === 'function') {
          if (item.title().includes(searchInput.value)) {
            flag = true;
          }
        } else {
          if (item.title.includes(searchInput.value)) {
            flag = true;
          }
        }
      }
      if (item.path.includes(searchInput.value)) {
        flag = true;
      }
      if (
        item.breadcrumb.some((b) => {
          if (typeof b.title === 'function') {
            if (b.title().includes(searchInput.value)) {
              return true;
            }
          } else {
            if (b.title?.includes(searchInput.value)) {
              return true;
            }
          }
          return false;
        })
      ) {
        flag = true;
      }
      return flag;
    });
    return result;
  });

  watch(
    () => isShow.value,
    (val) => {
      if (val) {
        searchInput.value = '';
        actived.value = -1;
        // 当搜索显示的时候绑定上、下、回车快捷键，隐藏的时候再解绑。另外当 input 处于 focus 状态时，采用 vue 来绑定键盘事件
        hotkeys('up', keyUp);
        hotkeys('down', keyDown);
        hotkeys('enter', keyEnter);
      } else {
        hotkeys.unbind('up', keyUp);
        hotkeys.unbind('down', keyDown);
        hotkeys.unbind('enter', keyEnter);
      }
    },
  );
  watch(
    () => resultList.value,
    () => {
      actived.value = -1;
      handleScroll();
    },
  );

  onMounted(() => {
    eventBus.on('global-search-toggle', () => {
      if (!isShow.value) {
        initSourceList();
      }
      isShow.value = !isShow.value;
    });
    hotkeys('alt+s', (e) => {
      if (
        settingsStore.settings.toolbar.navSearch &&
        settingsStore.settings.navSearch.enableHotkeys
      ) {
        e.preventDefault();
        initSourceList();
        isShow.value = true;
      }
    });
    hotkeys('esc', (e) => {
      if (
        settingsStore.settings.toolbar.navSearch &&
        settingsStore.settings.navSearch.enableHotkeys
      ) {
        e.preventDefault();
        isShow.value = false;
      }
    });
    initSourceList();
  });

  function initSourceList() {
    sourceList.value = [];
    menuStore.allMenus.forEach((item) => {
      getSourceListByMenus(item.children);
    });
  }

  function hasChildren(item: Menu.recordRaw) {
    let flag = true;
    if (item.children?.every((i) => i.meta?.menu === false)) {
      flag = false;
    }
    return flag;
  }
  function getSourceListByMenus(
    arr: Menu.recordRaw[],
    basePath?: string,
    icon?: string,
    breadcrumb?: { title?: string | (() => string) }[],
  ) {
    arr.forEach((item) => {
      if (item.meta?.menu !== false) {
        const breadcrumbTemp = cloneDeep(breadcrumb) || [];
        if (item.children && hasChildren(item)) {
          breadcrumbTemp.push({
            title: item.meta?.title,
          });
          getSourceListByMenus(
            item.children,
            resolveRoutePath(basePath, item.path),
            item.meta?.icon ?? icon,
            breadcrumbTemp,
          );
        } else {
          breadcrumbTemp.push({
            title: item.meta?.title,
          });
          sourceList.value.push({
            path: resolveRoutePath(basePath, item.path),
            icon: item.meta?.icon ?? icon,
            title: item.meta?.title,
            link: item.meta?.link,
            breadcrumb: breadcrumbTemp,
          });
        }
      }
    });
  }

  function keyUp() {
    if (resultList.value.length) {
      actived.value -= 1;
      if (actived.value < 0) {
        actived.value = resultList.value.length - 1;
      }
      handleScroll();
    }
  }
  function keyDown() {
    if (resultList.value.length) {
      actived.value += 1;
      if (actived.value > resultList.value.length - 1) {
        actived.value = 0;
      }
      handleScroll();
    }
  }
  function keyEnter() {
    if (actived.value !== -1) {
      searchResultItemRef.value
        .find((item) => Number.parseInt(item.dataset.index!) === actived.value)
        ?.click();
    }
  }
  function handleScroll() {
    if (searchResultRef.value) {
      const contentDom = searchResultRef.value.osInstance()!.elements().content;
      let scrollTo = 0;
      if (actived.value !== -1) {
        scrollTo = contentDom.scrollTop;
        const activedOffsetTop =
          searchResultItemRef.value.find(
            (item) => Number.parseInt(item.dataset.index!) === actived.value,
          )?.offsetTop ?? 0;
        const activedClientHeight =
          searchResultItemRef.value.find(
            (item) => Number.parseInt(item.dataset.index!) === actived.value,
          )?.clientHeight ?? 0;
        const searchScrollTop = contentDom.scrollTop;
        const searchClientHeight = contentDom.clientHeight;
        if (activedOffsetTop + activedClientHeight > searchScrollTop + searchClientHeight) {
          scrollTo = activedOffsetTop + activedClientHeight - searchClientHeight;
        } else if (activedOffsetTop <= searchScrollTop) {
          scrollTo = activedOffsetTop;
        }
      }
      contentDom.scrollTo({
        top: scrollTo,
      });
    }
  }

  function pageJump(path: listTypes['path'], link: listTypes['link']) {
    if (link) {
      window.open(link, '_blank');
    } else {
      router.push(path);
    }
    isShow.value = false;
  }
</script>

<template>
  <TransitionRoot as="template" :show="isShow">
    <Dialog
      :initial-focus="searchInputRef"
      class="fixed inset-0 z-2000 flex"
      @close="isShow && eventBus.emit('global-search-toggle')"
    >
      <TransitionChild as="template" v-bind="overlayTransitionClass">
        <div
          class="fixed inset-0 bg-stone-200/75 backdrop-blur-sm transition-opacity dark-bg-stone-8/75"
        />
      </TransitionChild>
      <div class="fixed inset-0">
        <div class="h-full flex items-end justify-center p-4 text-center lg-items-center">
          <TransitionChild as="template" v-bind="transitionClass">
            <DialogPanel
              class="relative h-full max-h-4/5 w-full flex flex-col text-left lg-max-w-2xl"
            >
              <div
                class="flex flex-col overflow-y-auto rounded-xl bg-white shadow-xl dark-bg-stone-8"
              >
                <div class="flex items-center px-4 py-3" border-b="~ solid stone-2 dark-stone-7">
                  <SvgIcon name="i-ep:search" :size="18" class="text-stone-5" />
                  <input
                    ref="searchInputRef"
                    v-model="searchInput"
                    placeholder="搜索页面，支持标题、URL模糊查询"
                    class="w-full border-0 rounded-md bg-transparent px-3 text-base text-dark dark-text-white focus-outline-none placeholder-stone-4 dark-placeholder-stone-5"
                    @keydown.esc="eventBus.emit('global-search-toggle')"
                    @keydown.up.prevent="keyUp"
                    @keydown.down.prevent="keyDown"
                    @keydown.enter.prevent="keyEnter"
                  />
                </div>
                <DialogDescription class="relative m-0 of-y-hidden">
                  <OverlayScrollbarsComponent
                    ref="searchResultRef"
                    :options="{ scrollbars: { autoHide: 'leave', autoHideDelay: 300 } }"
                    defer
                    class="h-full"
                  >
                    <template v-if="resultList.length > 0">
                      <a
                        v-for="(item, index) in resultList"
                        ref="searchResultItemRef"
                        :key="item.path"
                        class="flex cursor-pointer items-center"
                        :class="{ 'bg-stone-2/40 dark-bg-stone-7/40': index === actived }"
                        :data-index="index"
                        @click="pageJump(item.path, item.link)"
                        @mouseover="actived = index"
                      >
                        <SvgIcon
                          v-if="item.icon"
                          :name="item.icon"
                          :size="20"
                          class="basis-16 transition"
                          :class="{ 'scale-120 text-ui-primary': index === actived }"
                        />
                        <div
                          class="flex flex-1 flex-col gap-1 truncate px-4 py-3"
                          border-l="~ solid stone-2 dark-stone-7"
                        >
                          <div class="truncate text-base font-bold">
                            {{
                              (typeof item.title === 'function' ? item.title() : item.title) ??
                              '[ 无标题 ]'
                            }}
                          </div>
                          <Breadcrumb v-if="item.breadcrumb.length" class="truncate">
                            <BreadcrumbItem
                              v-for="(bc, bcIndex) in item.breadcrumb"
                              :key="bcIndex"
                              class="text-xs"
                            >
                              {{
                                (typeof bc.title === 'function' ? bc.title() : bc.title) ??
                                '[ 无标题 ]'
                              }}
                            </BreadcrumbItem>
                          </Breadcrumb>
                        </div>
                      </a>
                    </template>
                    <template v-else>
                      <div flex="center col" py-6 text-stone-5>
                        <SvgIcon name="i-tabler:mood-empty" :size="40" />
                        <p m-2 text-base>没有找到你想要的</p>
                      </div>
                    </template>
                  </OverlayScrollbarsComponent>
                </DialogDescription>
                <div
                  v-if="settingsStore.mode === 'pc'"
                  class="flex justify-between px-4 py-3"
                  border-t="~ solid stone-2 dark-stone-7"
                >
                  <div class="flex gap-8">
                    <div class="inline-flex items-center gap-1 text-xs">
                      <HKbd>
                        <SvgIcon name="i-ion:md-return-left" :size="14" />
                      </HKbd>
                      <span>访问</span>
                    </div>
                    <div class="inline-flex items-center gap-1 text-xs">
                      <HKbd>
                        <SvgIcon name="i-ant-design:caret-up-filled" :size="14" />
                      </HKbd>
                      <HKbd>
                        <SvgIcon name="i-ant-design:caret-down-filled" :size="14" />
                      </HKbd>
                      <span>切换</span>
                    </div>
                  </div>
                  <div
                    v-if="settingsStore.settings.navSearch.enableHotkeys"
                    class="inline-flex items-center gap-1 text-xs"
                  >
                    <HKbd> ESC </HKbd>
                    <span>退出</span>
                  </div>
                </div>
              </div>
            </DialogPanel>
          </TransitionChild>
        </div>
      </div>
    </Dialog>
  </TransitionRoot>
</template>
