<script setup>
const keys = useKeys();
</script>
<template>
  <div>
    <!-- Search Bar Button -->
    <button
      @click="open"
      class="
        focus:outline-none
        shadow-button
        flex flex-row
        gap-1
        text-base text-gray-700
        bg-gray-100
        rounded-md
        h-8
        w-48
        px-3
        items-center
        hover:bg-gray-200
      "
    >
      <feather-icon name="search" class="w-4 h-4" />
      <p>{{ t`Search` }}</p>
      <div v-if="!inputValue" class="text-gray-500 ml-auto">
        {{ modKey('k') }}
      </div>
    </button>
  </div>

  <!-- Search Modal -->
  <Modal :open-modal="openModal">
    <!-- Search Input -->
    <div class="p-1">
      <input
        ref="input"
        type="search"
        autocomplete="off"
        spellcheck="false"
        :placeholder="t`Type to search...`"
        v-model="inputValue"
        @focus="search"
        @input="search"
        @keydown.up="up"
        @keydown.down="down"
        @keydown.enter="() => select()"
        @keydown.esc="close"
        class="
          bg-gray-100
          text-2xl
          focus:outline-none
          w-full
          placeholder-gray-700
          text-gray-900
          rounded-md
          p-3
        "
      />
    </div>
    <hr v-if="suggestions.length" />

    <!-- Search List -->
    <div :style="`max-height: ${49 * 6 - 1}px`" class="overflow-scroll">
      <div
        v-for="(si, i) in suggestions"
        :key="`${i}-${si.key}`"
        ref="suggestions"
        class="hover:bg-blue-100 cursor-pointer"
        :class="idx === i ? 'bg-blue-100' : ''"
        @click="select(i)"
      >
        <div
          class="flex flex-row w-full justify-between px-3 items-center"
          style="height: 48px"
        >
          <p class="text-gray-900">
            {{ si.label }}
          </p>
          <div
            class="text-base px-2 py-1 rounded-xl flex items-center"
            :class="groupColorClassMap[si.group]"
          >
            {{ groupLabelMap[si.group] }}
          </div>
        </div>
        <hr v-if="i !== suggestions.length - 1" />
      </div>
    </div>

    <!-- Footer -->
    <hr />
    <div class="m-1 flex justify-between items-center flex-col gap-2 text-sm select-none">
      <!-- Group Filters -->
      <div class="flex flex-row gap-2">
        <button
          v-for="(g, i) in searchGroups"
          :key="g"
          class="border px-1 py-0.5 rounded-lg"
          :class="getGroupFilterButtonClass(g)"
          @click="groupFilters[g] = !groupFilters[g]"
        >
          {{ groupLabelMap[g]
          }}<span
            class="ml-2 whitespace-nowrap brightness-50 tracking-tighter"
            :class="`text-${groupColorMap[g]}-500`"
            >{{ modKey(String(i + 1)) }}</span
          >
        </button>
      </div>

      <!-- Keybindings Help -->
      <div class="flex text-sm gap-8 text-gray-500">
        <p>↑↓ {{ t`Navigate` }}</p>
        <p>↩ {{ t`Select` }}</p>
        <p><span class="tracking-tighter">esc</span> {{ t`Close` }}</p>
      </div>
    </div>
  </Modal>
</template>
<script>
import { t } from 'fyo';
import { fuzzyMatch } from 'src/utils';
import { getBgTextColorClass } from 'src/utils/colors';
import { getSearchList, searchGroups } from 'src/utils/search';
import { routeTo } from 'src/utils/ui';
import { useKeys } from 'src/utils/vueUtils';
import { getIsNullOrUndef } from 'utils/';
import { nextTick, watch } from 'vue';
import Modal from './Modal.vue';

export default {
  data() {
    return {
      idx: 0,
      searchGroups,
      openModal: false,
      inputValue: '',
      searchList: [],
      groupFilters: {
        List: true,
        Report: true,
        Create: true,
        Page: true,
        Docs: true,
      },
    };
  },
  components: { Modal },
  mounted() {
    this.makeSearchList();
    watch(this.keys, (keys) => {
      if (
        keys.size === 2 &&
        keys.has('KeyK') &&
        (keys.has('MetaLeft') || keys.has('ControlLeft'))
      ) {
        this.open();
      }

      if (!this.openModal) {
        return;
      }

      if (keys.size === 1 && keys.has('Escape')) {
        this.close();
      }

      const input = this.$refs.input;
      if (!getIsNullOrUndef(input) && document.activeElement !== input) {
        input.focus();
      }

      this.setFilter(keys);
    });
    this.openModal = false;
  },
  activated() {
    this.openModal = false;
  },
  methods: {
    setFilter(keys) {
      if (!keys.has('MetaLeft') && !keys.has('ControlLeft')) {
        return;
      }

      if (!keys.size === 2) {
        return;
      }

      const matches = [...keys].join(',').match(/Digit(\d+)/);
      if (!matches) {
        return;
      }

      const digit = matches[1];
      const index = parseInt(digit) - 1;
      const group = searchGroups[index];
      if (!group || this.groupFilters[group] === undefined) {
        return;
      }

      this.groupFilters[group] = !this.groupFilters[group];
    },
    modKey(key) {
      key = key.toUpperCase();
      if (this.platform === 'Mac') {
        return `⌘ ${key}`;
      }

      return `Ctrl ${key}`;
    },
    open() {
      this.openModal = true;
      nextTick(() => {
        this.$refs.input.focus();
      });
    },
    close() {
      this.openModal = false;
      this.reset();
    },
    reset() {
      this.searchGroups.forEach((g) => {
        this.groupFilters[g] = true;
      });
      this.inputValue = '';
    },
    up() {
      this.idx = Math.max(this.idx - 1, 0);
      this.scrollToHighlighted();
    },
    down() {
      this.idx = Math.max(
        Math.min(this.idx + 1, this.suggestions.length - 1),
        0
      );
      this.scrollToHighlighted();
    },
    select(idx) {
      this.idx = idx ?? this.idx;
      this.suggestions[this.idx]?.action();
      this.close();
    },
    scrollToHighlighted() {
      const ref = this.$refs.suggestions[this.idx];
      ref.scrollIntoView({ block: 'nearest' });
    },
    async makeSearchList() {
      const searchList = getSearchList();
      this.searchList = searchList.map((d) => {
        if (d.route && !d.action) {
          d.action = () => {
            routeTo(d.route);
          };
        }
        return d;
      });
    },
    getGroupFilterButtonClass(g) {
      const isOn = this.groupFilters[g];
      const color = this.groupColorMap[g];
      if (isOn) {
        return `${getBgTextColorClass(color)} border-${color}-100`;
      }

      return `text-${color}-600 border-${color}-100`;
    },
  },
  computed: {
    groupLabelMap() {
      return {
        Create: t`Create`,
        List: t`List`,
        Report: t`Report`,
        Docs: t`Docs`,
        Page: t`Page`,
      };
    },
    groupColorMap() {
      return {
        Docs: 'blue',
        Create: 'green',
        List: 'teal',
        Report: 'yellow',
        Page: 'orange',
      };
    },
    groupColorClassMap() {
      return searchGroups.reduce((map, g) => {
        map[g] = getBgTextColorClass(this.groupColorMap[g]);
        return map;
      }, {});
    },
    suggestions() {
      const filters = new Set(
        this.searchGroups.filter((g) => this.groupFilters[g])
      );

      return this.searchList
        .filter((si) => filters.has(si.group))
        .map((si) => ({
          ...fuzzyMatch(this.inputValue, `${si.label} ${si.group}`),
          si,
        }))
        .filter(({ isMatch }) => isMatch)
        .sort((a, b) => a.distance - b.distance)
        .map(({ si }) => si);
    },
  },
};
</script>
<style scoped>
input[type='search']::-webkit-search-decoration,
input[type='search']::-webkit-search-cancel-button,
input[type='search']::-webkit-search-results-button,
input[type='search']::-webkit-search-results-decoration {
  display: none;
}
</style>
