<template>
  <basicModal
    @register="register"
    :bodyStyle="getBodyStyle"
    ref="modalRef"
    wrapClassName="app-search"
  >
    <div class="app-search-card">
      <div class="app-search-card-input">
        <a-input
          ref="searchInputRef"
          v-model:value="searchKeyword"
          :loading="loading"
          allowClear
          placeholder="请输入关键词搜索"
          size="large"
          @input="handleSearch"
        >
          <template #prefix>
            <span v-if="loading"><LoadingOutlined /></span>
            <span v-else><SearchOutlined /></span>
          </template>
        </a-input>
      </div>

      <div class="app-search-card-result" :class="{ 'search-result-dark': settingStore.darkTheme }">
        <div v-if="!loading && !searchResult.length" class="no-result">
          <a-empty v-if="!loading" :image="simpleImage" />
          <a-spin v-else size="small" />
        </div>
        <div v-else-if="loading" class="no-result">
          <a-spin size="small" />
        </div>
        <ul v-else class="result-ul">
          <li
            v-for="(item, index) in searchResult"
            :key="item.key"
            :class="{ 'result-ul-li-on': index === activeIndex }"
            :data-index="index"
            class="result-ul-li"
            @click="handleEnter"
            @mouseenter="handleMouseenter"
          >
            <a href="javascript:;">
              <div class="result-ul-li-icon">
                <InteractionOutlined />
              </div>
              <div class="result-ul-li-content"> {{ item.title }}</div>
              <div class="result-ul-li-action">
                <EnterOutlined />
              </div>
            </a>
          </li>
        </ul>
      </div>
    </div>
    <template #footer>
      <div class="app-search-card-footer">
        <ul class="commands">
          <li>
            <span class="commands-icon">
              <EnterOutlined />
            </span>
            <span>确认</span>
          </li>
          <li>
            <span class="mr-2 commands-icon">
              <ArrowUpOutlined />
            </span>
            <span class="commands-icon">
              <ArrowDownOutlined />
            </span>
            <span>切换</span>
          </li>
          <li>
            <span class="commands-icon">
              <CloseOutlined />
            </span>
            <span>ESC关闭</span>
          </li>
        </ul>
      </div>
    </template>
  </basicModal>
</template>

<script lang="ts" setup>
  import type { Menu } from '@/router/types';
  import { ref, unref, onBeforeMount, nextTick, computed } from 'vue';
  import { basicModal, useModal } from '@/components/Modal';
  import {
    EnterOutlined,
    ArrowUpOutlined,
    ArrowDownOutlined,
    CloseOutlined,
    InteractionOutlined,
    LoadingOutlined,
    SearchOutlined,
  } from '@ant-design/icons-vue';
  import { onKeyStroke, useDebounceFn } from '@vueuse/core';
  import { getMenus } from '@/router/menus';
  import { filter } from '@/utils/helper/treeHelper';
  import { useGo } from '@/hooks/web/usePage';
  import { Empty } from 'ant-design-vue';
  import { useProjectSettingStore } from '@/store/modules/projectSetting';

  const loading = ref(false);
  const searchInputRef = ref();
  const searchKeyword = ref('');
  const searchResult = ref<SearchResult[]>([]);
  const activeIndex = ref(-1);
  let menuList: Menu[] = [];
  const go = useGo();
  const settingStore = useProjectSettingStore();
  const simpleImage = Empty.PRESENTED_IMAGE_SIMPLE;

  interface SearchResult {
    title: string;
    path: string;
    icon?: string;
    key: string;
  }

  const [register, { openModal, closeModal }] = useModal({
    title: '搜索页面',
    width: 608,
    wrapClassName: 'app-search',
    keyboard: false,
  });

  const getBodyStyle = computed(() => {
    return {
      background: settingStore.darkTheme ? 'rgb(31,31,31)' : 'rgb(239, 239, 245)',
    };
  });

  onBeforeMount(async () => {
    const list = await getMenus();
    menuList = list;
  });

  function show() {
    openModal();
    nextTick(() => {
      searchInputRef.value.focus();
    });
  }

  function hide() {
    closeModal();
  }

  const handleSearch = useDebounceFn(search, 200);

  function search() {
    loading.value = true;
    searchKeyword.value = searchKeyword.value.trim();
    if (!searchKeyword.value) {
      searchResult.value = [];
      loading.value = false;
      return;
    }
    const reg = createSearchReg(unref(searchKeyword));
    const filterMenu = filter(menuList, (item) => {
      return reg.test(item.title) && !item.meta?.hidden;
    });
    searchResult.value = handlerSearchResult(filterMenu, reg);
    activeIndex.value = 0;
    nextTick(() => {
      loading.value = false;
    });
  }

  function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
    const ret: SearchResult[] = [];
    filterMenu.forEach((item) => {
      const { title, path, key, icon, children, meta } = item;
      if (!meta?.hidden && reg.test(title) && !children?.length) {
        ret.push({
          title: parent?.title ? `${parent.title} > ${title}` : title,
          path:  path?path:parent?.path,
          icon,
          key,
        });
      }
      if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) {
        ret.push(...handlerSearchResult(children as any, reg, item));
      }
    });
    return ret;
  }

  // Translate special characters
  function transform(c: string) {
    const code: string[] = ['$', '(', ')', '*', '+', '.', '[', ']', '?', '\\', '^', '{', '}', '|'];
    return code.includes(c) ? `\\${c}` : c;
  }

  function createSearchReg(key: string) {
    const keys = [...key].map((item) => transform(item));
    const str = ['', ...keys, ''].join('.*');
    return new RegExp(str);
  }

  function handleMouseenter(e: any) {
    const index = e.target.dataset.index;
    activeIndex.value = Number(index);
  }

  function handleClose() {
    searchResult.value = [];
    searchKeyword.value = '';
    hide();
  }

  async function handleEnter() {
    if (!searchResult.value.length) {
      return;
    }
    const result = unref(searchResult);
    const index = unref(activeIndex);
    if (result.length === 0 || index < 0) {
      return;
    }
    const to = result[index];
    handleClose();
    await nextTick();
    go(to.path);
  }

  // 按方向上键
  function handleUp() {
    if (!searchResult.value.length) return;
    activeIndex.value--;
    if (activeIndex.value < 0) {
      activeIndex.value = searchResult.value.length - 1;
    }
  }

  // 按方向下键
  function handleDown() {
    if (!searchResult.value.length) return;
    activeIndex.value++;
    if (activeIndex.value > searchResult.value.length - 1) {
      activeIndex.value = 0;
    }
  }

  // 回车搜索
  onKeyStroke('Enter', handleEnter);

  // 按方向上键
  onKeyStroke('ArrowUp', handleUp);

  // 按方向下键
  onKeyStroke('ArrowDown', handleDown);

  // 键盘 esc 取消弹窗
  onKeyStroke('Escape', handleClose);

  defineExpose({
    show,
  });
</script>
<style lang="less">
  .app-search .ant-modal .ant-modal-content .ant-modal-footer {
    padding: 2px 0;
  }
</style>
<style lang="less" scoped>
  .app-search {
    position: fixed;
    top: 60px;
    left: 50%;
    margin-left: -235px;

    &-card {
      width: 100%;
      padding: 0;
      background: var(--color);
      box-shadow: var(--box-shadow);

      &-input {
        margin-top: 6px;
      }

      :deep(.n-card .n-card__footer) {
        padding: 0;
      }

      &-result {
        .no-result {
          font-size: 0.9em;
          margin: 0 auto;
          padding: 36px 0;
          text-align: center;
          width: 80%;

          p {
            color: #969faf;
          }
        }

        .result-ul {
          list-style: none;
          margin: 14px 0 0 0;
          padding: 0;

          :deep(.n-scrollbar .n-scrollbar-container .n-scrollbar-content) {
            max-height: 640px;
          }

          &-li {
            border-radius: 4px;
            display: flex;
            padding-bottom: 8px;
            position: relative;

            a {
              display: flex;
              align-items: center;
              background: #fff;
              border-radius: 4px;
              padding: 0 12px;
              width: 100%;
              color: rgb(51, 54, 57);
              border-bottom: 1px solid var(--border-color);

              .n-icon {
                color: #969faf;
              }
            }

            &-content {
              align-items: center;
              color: var(--text-color);
              display: flex;
              flex-direction: row;
              height: 56px;
              padding-left: 6px;
              padding-right: 12px;
              flex: 1;
              box-sizing: border-box;
            }

            &-icon {
              font-size: 18px;
            }

            &-on {
              a {
                background-color: #2d8cf0!important;
                color: #fff;

                .n-icon {
                  color: #fff;
                }

                .result-ul-li-content {
                  color: #fff;
                }
              }
            }
          }
        }
      }

      .search-result-dark {
        .result-ul {
          &-li {
            a {
              background: #282828;
              color: #b3b3b3;
            }
          }
        }
      }

      &-footer {
        align-items: center;
        background: var(--color);
        border-radius: 0 0 8px 8px;
        box-shadow: var(--box-shadow);
        display: flex;
        flex-shrink: 0;
        height: 44px;
        padding: 0 14px;
        position: relative;
        user-select: none;
        width: 100%;

        .commands {
          display: flex;
          align-items: center;

          li {
            display: flex;
            align-items: center;
            margin-right: 14px;
            padding-left: 1px;

            span {
              color: #969faf;
            }
          }

          &-icon {
            align-items: center;
            background: linear-gradient(-225deg, var(--color), var(--color));
            border-radius: 2px;
            box-shadow: inset 0 -2px 0 0 #cdcde6, inset 0 0 1px 1px #fff,
              0 1px 2px 1px rgba(30, 35, 90, 0.4);
            display: flex;
            height: 18px;
            justify-content: center;
            margin-right: 0.4em;
            padding-bottom: 2px;
            width: 20px;
          }
        }
      }
    }
  }

  .light-item-bg {
    background: rgb(239, 239, 245);
  }
</style>
