// Styles
import './VCommandPalette.scss'

// Components
import { VCommandPaletteSymbol } from './shared'
import { VCommandPaletteItem } from './VCommandPaletteItem'
import { VDialog } from '@/components/VDialog'
import { makeVDialogProps } from '@/components/VDialog/VDialog'
import { VList } from '@/components/VList'
import { VSheet } from '@/components/VSheet'
import { VTextField } from '@/components/VTextField'

// Composables
import { useCommandPaletteNavigation } from './composables/useCommandPaletteNavigation'
import { makeDensityProps } from '@/composables/density'
import { makeFilterProps, useFilter } from '@/composables/filter'
import { useHotkey } from '@/composables/hotkey'
import { useLocale } from '@/composables/locale'
import { useProxiedModel } from '@/composables/proxiedModel'

// Utilities
import { computed, nextTick, onUnmounted, provide, ref, shallowRef, toRef, watch, watchEffect } from 'vue'
import { isActionItem } from './types'
import { convertToUnit, genericComponent, getActiveElement, omit, propsFactory, useRender } from '@/util'

// Types
import type { PropType, Ref } from 'vue'
import type { VCommandPaletteItem as VCommandPaletteItemType } from './types'
import type { VListChildrenSlots } from '@/components/VList/VListChildren'
import type { OverlaySlots } from '@/components/VOverlay/VOverlay'

export const makeVCommandPaletteProps = propsFactory({
  modelValue: Boolean,
  search: String,
  items: {
    type: Array as PropType<VCommandPaletteItemType[]>,
    default: () => [],
  },
  placeholder: {
    type: String,
    default: '$vuetify.command.search',
  },
  inputIcon: {
    type: String,
    default: '$search',
  },
  hotkey: String,
  offsetTop: {
    type: [Number, String],
    default: '15vh',
  },
  closeOnSelect: {
    type: Boolean,
    default: true,
  },
  noDataText: {
    type: String,
    default: '$vuetify.noDataText',
  },
  listProps: Object as PropType<VList['$props']>,

  ...makeFilterProps({ filterKeys: ['title', 'subtitle'] }),
  ...makeDensityProps(),
  ...omit(makeVDialogProps({
    location: 'top center' as const,
    maxWidth: 500,
    origin: 'top center' as const,
    viewportMargin: 16,
  }), ['modelValue']),
}, 'VCommandPalette')

export type VCommandPaletteSlots = {
  activator: OverlaySlots['activator']
  default: never
  prepend: never
  append: never
  input: never
  'input.append-inner': never
  'no-data': never
  'list.prepend': never
  'list.subheader': VListChildrenSlots<any>['subheader']
  item: { item: VCommandPaletteItemType, index: number }
  'item.prepend': { item: VCommandPaletteItemType, index: number }
  'item.title': { item: VCommandPaletteItemType, index: number }
  'item.append': { item: VCommandPaletteItemType, index: number }
}

export const VCommandPalette = genericComponent<VCommandPaletteSlots>()({
  name: 'VCommandPalette',

  props: makeVCommandPaletteProps(),

  emits: {
    'update:modelValue': (value: boolean) => true,
    'update:search': (value: string) => true,
    'click:item': (item: VCommandPaletteItemType, event: MouseEvent | KeyboardEvent) => true,
    'before-select': (payload: {
      item: VCommandPaletteItemType
      event: MouseEvent | KeyboardEvent
      preventDefault: () => void
    }) => true,
  },

  setup (props, { emit, slots }) {
    const { t } = useLocale()
    const isOpen = useProxiedModel(props, 'modelValue')
    const searchQuery = useProxiedModel(props, 'search') as Ref<string>
    const searchInputRef = ref<VTextField>()
    const dialogRef = ref<VDialog>()
    const previouslyFocusedElement = shallowRef<HTMLElement | null>(null)

    const internalItems = computed(() =>
      props.items.map((item, index) => ({
        value: index,
        type: item.type,
        raw: item,
        ...('title' in item && { title: item.title }),
        ...('subtitle' in item && { subtitle: item.subtitle }),
      }))
    )

    const { filteredItems: filterResult } = useFilter(props, internalItems, searchQuery)

    const filteredItems = computed(() => filterResult.value.map(item => item.raw))

    const itemsForList = computed(() => {
      return filteredItems.value.map((item, idx) => ({
        ...item,
        value: idx,
      }))
    })

    function executeItem (item: VCommandPaletteItemType, event: MouseEvent | KeyboardEvent) {
      if ('onClick' in item && item.onClick) {
        item.onClick(event, item.value)
      }

      emit('click:item', item, event)

      if (!isActionItem(item) || !props.closeOnSelect) return

      let shouldClose = true
      emit('before-select', {
        item,
        event,
        preventDefault: () => {
          shouldClose = false
        },
      })

      if (shouldClose) {
        isOpen.value = false
      }
    }

    const navigation = useCommandPaletteNavigation({
      filteredItems,
      onItemClick: (item, event) => {
        executeItem(item, event)
      },
    })

    provide(VCommandPaletteSymbol, {
      items: computed(() => props.items),
      filteredItems,
      selectedIndex: navigation.selectedIndex,
      search: searchQuery,
      setSelectedIndex: navigation.setSelectedIndex,
    })

    // Register main hotkey with cleanup - using toRef for reactivity
    const hotkeyUnsubscribe = useHotkey(toRef(props, 'hotkey'), () => {
      isOpen.value = !isOpen.value
    })

    watchEffect(onCleanup => {
      if (!isOpen.value) {
        return
      }

      const hotkeyUnsubscribes: Array<() => void> = []

      function registerItemHotkeys (items: VCommandPaletteItemType[]) {
        items.forEach(item => {
          if (isActionItem(item) && item.hotkey) {
            const unsubscribe = useHotkey(item.hotkey, event => {
              event.preventDefault()
              executeItem(item, event as KeyboardEvent)
            }, { inputs: true })
            hotkeyUnsubscribes.push(unsubscribe)
          }
        })
      }

      registerItemHotkeys(props.items)

      onCleanup(() => {
        hotkeyUnsubscribes.forEach(unsubscribe => unsubscribe?.())
      })
    })

    function findNextSelectableIndex (startIndex: number, direction: 1 | -1): number {
      const items = filteredItems.value
      if (items.length === 0) return -1

      let index = startIndex
      const maxIterations = items.length

      for (let i = 0; i < maxIterations; i++) {
        index += direction
        if (index >= items.length) index = 0
        if (index < 0) index = items.length - 1

        if (isActionItem(items[index])) {
          return index
        }
      }

      return -1
    }

    function handleSearchKeydown (e: KeyboardEvent) {
      switch (e.key) {
        case 'ArrowDown': {
          e.preventDefault()
          const nextIndex = findNextSelectableIndex(navigation.selectedIndex.value, 1)
          if (nextIndex !== -1) {
            navigation.setSelectedIndex(nextIndex)
          }
          break
        }
        case 'ArrowUp': {
          e.preventDefault()
          const prevIndex = findNextSelectableIndex(navigation.selectedIndex.value, -1)
          if (prevIndex !== -1) {
            navigation.setSelectedIndex(prevIndex)
          }
          break
        }
        case 'Enter':
          e.preventDefault()
          navigation.executeSelected(e)
          break
        case 'Escape':
          e.preventDefault()
          isOpen.value = false
          break
      }
    }

    watch(isOpen, (newValue, oldValue) => {
      if (newValue && !oldValue) {
        previouslyFocusedElement.value = getActiveElement() as HTMLElement | null
        searchQuery.value = ''
        navigation.reset()

        // Use requestAnimationFrame to ensure DOM is fully rendered
        nextTick(() => {
          requestAnimationFrame(() => {
            if (searchInputRef.value && typeof searchInputRef.value.focus === 'function') {
              searchInputRef.value.focus()
            }
          })
        })
      } else if (!newValue && oldValue) {
        nextTick(() => {
          previouslyFocusedElement.value?.focus({ preventScroll: true })
          previouslyFocusedElement.value = null
        })
      }
    }, { immediate: true })

    onUnmounted(() => {
      hotkeyUnsubscribe()
      previouslyFocusedElement.value = null
    })

    useRender(() => {
      const dialogProps = VDialog.filterProps(omit(props, ['modelValue', 'class', 'style']))

      return (
        <VDialog
          ref={ dialogRef }
          v-model={ isOpen.value }
          class="v-command-palette"
          style={{
            '--v-command-palette-top-offset': convertToUnit(props.offsetTop),
          }}
          scrollable
          { ...dialogProps }
        >
          {{
            activator: slots.activator,
            default: () => (
              <VSheet
                class={ props.class }
                style={ props.style }
              >
                { slots.prepend?.() }

              <div class="v-command-palette__input-container">
                { slots.input?.() ?? (
                  <VTextField
                    ref={ searchInputRef }
                    v-model={ searchQuery.value }
                    density={ props.density }
                    placeholder={ t(props.placeholder) }
                    prependInnerIcon={ props.inputIcon }
                    autocomplete="off"
                    autofocus
                    singleLine
                    hideDetails
                    variant="solo"
                    flat
                    bgColor="transparent"
                    onKeydown={ handleSearchKeydown }
                    v-slots={{
                      'append-inner': slots['input.append-inner'],
                    }}
                  />
                )}
              </div>

                <div class="v-command-palette__content">
                  { filteredItems.value.length > 0 ? (
                    <VList
                      key="list"
                      class="v-command-palette__list"
                      density={ props.density }
                      items={ itemsForList.value }
                      itemType="type"
                      itemProps
                      activatable
                      { ...props.listProps }
                      navigationStrategy="track"
                      navigationIndex={ navigation.selectedIndex.value }
                      onUpdate:navigationIndex={ navigation.setSelectedIndex }
                      v-slots={{
                        prepend: slots['list.prepend'],
                        subheader: slots['list.subheader'],
                        item: ({ props: itemProps }: { props: any }) => (
                          slots.item?.({ item: itemProps, index: itemProps.index }) ?? (
                            <VCommandPaletteItem
                              key={ `item-${itemProps.index}` }
                              item={ itemProps }
                              index={ itemProps.index }
                              onExecute={ (event: MouseEvent | KeyboardEvent) => navigation.execute(itemProps.index, event) }
                              v-slots={{
                                prepend: slots['item.prepend']
                                  ? () => slots['item.prepend']?.({ item: itemProps, index: itemProps.index })
                                  : undefined,
                                title: slots['item.title']
                                  ? () => slots['item.title']?.({ item: itemProps, index: itemProps.index })
                                  : undefined,
                                append: slots['item.append']
                                  ? () => slots['item.append']?.({ item: itemProps, index: itemProps.index })
                                  : undefined,
                              }}
                            />
                          )
                        ),
                      }}
                    />
                  ) : (
                    <div key="no-data" class="v-command-palette__no-data">
                      { slots['no-data']?.() || t(props.noDataText) }
                    </div>
                  )}
                </div>

                { slots.append?.() }
              </VSheet>
            ),
          }}
        </VDialog>
      )
    })
  },
})

export type VCommandPalette = InstanceType<typeof VCommandPalette>
