<template>
  <el-dialog title="搜索页面" v-model="isModal" class="app-search" :width="598" draggable>
    <!-- :class="{ 'light-item-bg': !getDarkTheme }" -->
    <div class="app-search-card">
      <div class="app-search-card-input">
        <el-input
          ref="searchInput"
          v-model="searchKeyword"
          :loading="loading"
          clearable
          placeholder="请输入关键词搜索"
          size="large"
          @input="handleSearch"
        >
          <template #prefix>
            <el-icon class="el-input__icon" v-if="loading">
              <LoadingOutlined />
            </el-icon>
            <el-icon class="el-input__icon" v-else>
              <SearchOutline />
            </el-icon>
          </template>
        </el-input>
      </div>
      <div class="app-search-card-result">
        <div v-if="!loading && !searchResult.length" class="no-result">
          <p v-if="!loading">暂无搜索结果</p>
        </div>
        <div v-else-if="loading" class="no-result" v-loading="loading"> </div>
        <ul v-else class="result-ul">
          <el-scrollbar>
            <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">
                  <el-icon class="el-input__icon">
                    <InteractionOutlined />
                  </el-icon>
                </div>
                <div class="result-ul-li-content"> {{ item.name }}</div>
                <div class="result-ul-li-action">
                  <el-icon class="el-input__icon">
                    <EnterOutlined />
                  </el-icon>
                </div>
              </a>
            </li>
          </el-scrollbar>
        </ul>
      </div>
    </div>
    <template #footer>
      <div class="app-search-card-footer">
        <ul class="commands">
          <li>
            <el-icon class="el-input__icon commands-icon">
              <EnterOutlined />
            </el-icon>
            <span>确认</span>
          </li>
          <li>
            <el-icon class="mr-2 el-input__icon commands-icon">
              <ArrowUpOutlined />
            </el-icon>
            <el-icon class="el-input__icon commands-icon">
              <ArrowDownOutlined />
            </el-icon>
            <span>切换</span>
          </li>
          <li>
            <el-icon class="el-input__icon commands-icon">
              <CloseOutlined />
            </el-icon>
            <span>ESC关闭</span>
          </li>
        </ul>
      </div>
    </template>
  </el-dialog>
</template>

<script lang="ts" setup>
  import type { Menu } from '@/router/types';
  import { ref, unref, onBeforeMount, nextTick } from 'vue';
  import { SearchOutline } from '@vicons/ionicons5';
  import {
    EnterOutlined,
    ArrowUpOutlined,
    ArrowDownOutlined,
    CloseOutlined,
    InteractionOutlined,
    LoadingOutlined,
  } from '@vicons/antd';
  import { onKeyStroke, useDebounceFn } from '@vueuse/core';
  import { getMenus } from '@/router/menus';
  import { cloneDeep } from 'lodash-es';
  import { filter } from '@/utils/helper/treeHelper';
  import { useGo } from '@/hooks/web/usePage';

  const isModal = ref(false);
  const loading = ref(false);
  const searchInput = ref();
  const searchKeyword = ref('');
  const searchResult = ref<SearchResult[]>([]);
  const activeIndex = ref(-1);
  let menuList: Menu[] = [];
  const go = useGo();

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

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

  function show() {
    isModal.value = true;
    nextTick(() => {
      searchInput.value.focus();
    });
  }

  function hide() {
    isModal.value = false;
  }

  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 replacePath(path) {
    return path.replace('//', '/');
  }

  function handlerSearchResult(filterMenu: Menu[], reg: RegExp, parent?: Menu) {
    const ret: SearchResult[] = [];
    filterMenu.forEach((item: Menu) => {
      const { title, path, key, icon, children, meta } = item;
      if (!meta?.hidden && reg.test(title) && !children?.length) {
        ret.push({
          name: parent?.title ? `${parent.title} > ${title}` : title,
          path: parent?.path ? replacePath(parent.path) : replacePath(path),
          icon,
          key,
        });
      }
      if (!meta?.hideChildrenInMenu && Array.isArray(children) && children.length) {
        ret.push(...handlerSearchResult(children as Menu[], 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="scss">
  .app-search {
    position: fixed;
    top: 60px;
    left: 50%;
    margin-left: -235px;

    .el-dialog__body {
      padding: 0 10px;
    }

    .el-dialog__footer {
      padding: 0;
    }

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

      :deep(.el-card__body) {
        padding: 0;
      }

      &-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: var(--n-color);
              border-radius: 4px;
              padding: 0 12px;
              width: 100%;
              color: var(--n-text-color);
              border-bottom: 1px solid var(--el-border-color);

              .el-icon {
                color: #969faf;
                margin-top: 7px;
              }
            }

            &-content {
              align-items: center;
              color: var(--n-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: var(--el-color-primary);
                color: #fff;

                .el-icon {
                  color: #fff;
                }

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

      &-footer {
        align-items: center;
        border-radius: 0 0 8px 8px;
        padding: 0 10px;
        box-shadow: 0 2px 12px 0 rgba(0, 0, 0, 0.1);
        display: flex;
        flex-shrink: 0;
        height: 44px;
        position: relative;
        user-select: none;
        width: 100%;
        box-sizing: border-box;

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

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

            span {
              color: #969faf;
            }
          }

          &-icon {
            align-items: center;
            background: linear-gradient(-225deg, var(--n-color), var(--n-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: var(--n-border-color);
  }
</style>
