<script setup lang="ts">
import type { CSSProperties } from 'vue';

import type { MenuRecordRaw } from '@easyflow/types';

import { nextTick, onMounted, onUnmounted, ref, watch } from 'vue';

import { Search, X } from '@easyflow/icons';
import { $t } from '@easyflow/locales';
import { isWindowsOs } from '@easyflow/utils';

import { onClickOutside, useMagicKeys, whenever } from '@vueuse/core';

import SearchPanel from './search-panel.vue';

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

const props = withDefaults(
  defineProps<{ enableShortcutKey?: boolean; menus?: MenuRecordRaw[] }>(),
  {
    enableShortcutKey: true,
    menus: () => [],
  },
);

const keyword = ref('');
const isOpen = ref(false);
const hasResults = ref(false);
const searchRootRef = ref<HTMLElement>();
const searchPanelRef = ref<HTMLElement>();
const searchInputRef = ref<HTMLInputElement>();
const panelStyle = ref<CSSProperties>({});
const shouldMountPanel = ref(false);

const searchShellStyle: CSSProperties = {
  backgroundColor: 'hsl(var(--surface-panel) / 0.18)',
  backgroundImage:
    'linear-gradient(180deg, hsl(var(--glass-border) / 0.4), hsl(var(--glass-tint) / 0.2) 18%, hsl(var(--surface-panel) / 0.12) 100%)',
  backdropFilter: 'blur(calc(var(--glass-blur) * 1.25)) saturate(178%)',
  border: '1px solid hsl(var(--glass-border) / 0.42)',
  boxShadow:
    'inset 0 1px 0 hsl(var(--glass-border) / 0.44), var(--shadow-toolbar), 0 12px 24px -18px hsl(var(--nav-ambient) / 0.18)',
};
const searchPanelStyle: CSSProperties = {
  backgroundColor: 'hsl(var(--surface-panel) / 0.22)',
  backgroundImage:
    'linear-gradient(180deg, hsl(var(--glass-border) / 0.42), hsl(var(--glass-tint) / 0.18) 16%, hsl(var(--surface-panel) / 0.16) 100%)',
  backdropFilter: 'blur(calc(var(--glass-blur) * 1.9)) saturate(190%)',
  border: '1px solid hsl(var(--glass-border) / 0.44)',
  boxShadow:
    'inset 0 1px 0 hsl(var(--glass-border) / 0.42), var(--shadow-float), 0 16px 32px -24px hsl(var(--nav-ambient) / 0.14)',
  zIndex: 'calc(var(--popup-z-index) + 10)',
};
const expandedWidth = 'clamp(10rem, 14vw, 12rem)';

function updatePanelPosition() {
  const root = searchRootRef.value;
  if (!root) {
    return;
  }
  const rect = root.getBoundingClientRect();
  const viewportWidth = window.innerWidth;
  const desiredWidth = Math.min(420, Math.max(300, rect.width + 120));
  const width = Math.min(desiredWidth, viewportWidth - 16);
  const right = Math.max(8, viewportWidth - rect.right);
  const left = Math.max(8, viewportWidth - right - width);

  panelStyle.value = {
    left: `${left}px`,
    top: `${rect.bottom + 10}px`,
    width: `${Math.min(width, viewportWidth - left - 8)}px`,
  };
}

function focusSearchInput() {
  nextTick(() => {
    searchInputRef.value?.focus();
  });
}

function openSearch() {
  if (isOpen.value) {
    focusSearchInput();
    return;
  }
  isOpen.value = true;
}

function handleClose() {
  isOpen.value = false;
  hasResults.value = false;
}

function toggleOpen() {
  isOpen.value = !isOpen.value;
}

watch(isOpen, (open) => {
  if (open) {
    shouldMountPanel.value = true;
    nextTick(() => {
      updatePanelPosition();
      focusSearchInput();
    });
  } else {
    keyword.value = '';
    shouldMountPanel.value = false;
  }
});

onClickOutside(
  searchRootRef,
  () => {
    if (isOpen.value) {
      handleClose();
    }
  },
  { ignore: [searchPanelRef] },
);

const handleViewportChange = () => {
  if (isOpen.value && hasResults.value) {
    updatePanelPosition();
  }
};

watch(keyword, () => {
  if (isOpen.value && hasResults.value) {
    nextTick(updatePanelPosition);
  }
});

function handleResultsChange(count: number) {
  hasResults.value = count > 0;
  if (isOpen.value && shouldMountPanel.value) {
    nextTick(updatePanelPosition);
  }
}

const keys = useMagicKeys();
const cmd = isWindowsOs() ? keys['ctrl+k'] : keys['cmd+k'];
whenever(cmd!, () => {
  if (props.enableShortcutKey) {
    openSearch();
  }
});

const preventDefaultBrowserSearchHotKey = (event: KeyboardEvent) => {
  if (event.key?.toLowerCase() === 'k' && (event.metaKey || event.ctrlKey)) {
    event.preventDefault();
  }
};

const toggleKeydownListener = () => {
  if (props.enableShortcutKey) {
    window.addEventListener('keydown', preventDefaultBrowserSearchHotKey);
  } else {
    window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);
  }
};

watch(() => props.enableShortcutKey, toggleKeydownListener);

onMounted(() => {
  toggleKeydownListener();
  window.addEventListener('resize', handleViewportChange);
  window.addEventListener('scroll', handleViewportChange, true);

  onUnmounted(() => {
    window.removeEventListener('keydown', preventDefaultBrowserSearchHotKey);
    window.removeEventListener('resize', handleViewportChange);
    window.removeEventListener('scroll', handleViewportChange, true);
  });
});
</script>

<template>
  <div ref="searchRootRef" class="relative flex items-center justify-end">
    <div
      :style="[searchShellStyle, { width: isOpen ? expandedWidth : '2.25rem' }]"
      class="group flex h-9 items-center overflow-hidden rounded-2xl transition-[width,background-color,box-shadow,transform,border-color] duration-200 ease-out"
      :class="
        isOpen
          ? '-translate-y-0.5 cursor-text border-[hsl(var(--glass-border))/0.5]'
          : 'cursor-pointer hover:-translate-y-0.5 hover:border-[hsl(var(--glass-border))/0.52]'
      "
      @click="openSearch"
    >
      <button
        type="button"
        class="hover:text-foreground flex h-full w-9 flex-shrink-0 items-center justify-center text-[hsl(var(--nav-item-muted-foreground))] outline-none transition-colors focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
        :aria-expanded="isOpen"
        :aria-label="$t('ui.widgets.search.searchNavigate')"
        @click.stop="toggleOpen"
      >
        <Search class="size-4" />
      </button>
      <input
        ref="searchInputRef"
        v-model="keyword"
        :placeholder="$t('ui.widgets.search.searchNavigate')"
        class="placeholder:text-muted-foreground min-w-0 flex-1 bg-transparent pr-2 text-sm font-medium text-[hsl(var(--text-strong))] outline-none transition-opacity duration-150"
        :class="isOpen ? 'opacity-100' : 'pointer-events-none w-0 opacity-0'"
        @keydown.esc.prevent.stop="handleClose"
      />
      <button
        v-if="isOpen"
        type="button"
        class="hover:text-foreground mr-1 flex h-7 w-7 flex-shrink-0 items-center justify-center rounded-xl text-[hsl(var(--text-muted))] outline-none transition-colors hover:bg-[hsl(var(--nav-item-hover))/0.92] focus-visible:ring-2 focus-visible:ring-[hsl(var(--primary))/0.2]"
        :aria-label="$t('ui.widgets.search.close')"
        @click.stop="handleClose"
      >
        <X class="size-4" />
      </button>
    </div>

    <Teleport to="body">
      <transition
        enter-active-class="transition duration-200 ease-out"
        enter-from-class="translate-y-1.5 opacity-0"
        enter-to-class="translate-y-0 opacity-100"
        leave-active-class="transition duration-150 ease-in"
        leave-from-class="translate-y-0 opacity-100"
        leave-to-class="translate-y-1 opacity-0"
      >
        <div
          v-if="isOpen && shouldMountPanel"
          ref="searchPanelRef"
          :style="[searchPanelStyle, panelStyle]"
          class="duration-180 fixed overflow-hidden rounded-[28px] transition-[opacity,transform] ease-out"
          :class="
            hasResults
              ? 'pointer-events-auto opacity-100'
              : 'pointer-events-none opacity-0'
          "
        >
          <div class="max-h-[420px] px-2 py-2">
            <SearchPanel
              :keyword="keyword"
              :menus="menus"
              @close="handleClose"
              @results-change="handleResultsChange"
            />
          </div>
        </div>
      </transition>
    </Teleport>
  </div>
</template>
