import type { PropType } from 'vue'
import type { CommandPaletteOption } from './command-palette/types'
import { t } from '#cli/i18n'
import { useLayout, useTerminal } from '@simon_he/vue-tui'
import { computed, defineComponent, getCurrentInstance, ref, watch } from 'vue'
import { useUiTheme } from '../theme'
import { renderCommandPalette } from './command-palette/render'
import {
  clamp,
  filterCommandPaletteOptions,
  selectedFilteredIndex,
} from './command-palette/view-model'

export type { CommandPaletteOption } from './command-palette/types'

export const DimSdkCommandPalette = defineComponent({
  name: 'DimSdkCommandPalette',
  props: {
    modelValue: { type: Boolean, required: true },
    title: { type: String, default: '' },
    initialQuery: { type: String, default: '' },
    options: {
      type: Array as PropType<readonly CommandPaletteOption[]>,
      required: true,
    },
    selectedIndex: { type: Number, required: true },
    showRowDetails: { type: Boolean, default: false },
  },
  emits: {
    'update:modelValue': (_v: boolean) => true,
    'update:selectedIndex': (_v: number) => true,
    'select': (_label: string | null) => true,
    'close': () => true,
  },
  setup(props, { emit }) {
    const instance = getCurrentInstance()
    const ui = useUiTheme()
    const layout = useLayout()
    const { render, scheduler } = useTerminal()
    const cols = computed(() => layout.clipRect?.w ?? 0)
    const rows = computed(() => layout.clipRect?.h ?? 0)
    const query = ref('')
    const inputFocusEpoch = ref(0)
    let focusRetrySeq = 0
    const localSelectedIndex = ref(0)

    // Make title reactive to locale changes
    const title = computed(() => props.title || t('commandPalette.title'))

    watch(
      () => [props.modelValue, props.initialQuery] as const,
      ([open]) => {
        if (open) {
          const initialQuery = String(props.initialQuery ?? '')
          query.value = initialQuery
          localSelectedIndex.value = clamp(
            Math.trunc(Number(props.selectedIndex ?? 0)),
            0,
            Math.max(0, props.options.length - 1),
          )
          inputFocusEpoch.value += 1
          const retrySeq = ++focusRetrySeq
          setTimeout(() => {
            if (
              props.modelValue
              && retrySeq === focusRetrySeq
              && query.value === initialQuery
            ) {
              inputFocusEpoch.value += 1
            }
          }, 0)
          return
        }
        focusRetrySeq += 1
      },
    )

    watch(
      () => [props.modelValue, props.selectedIndex, props.options.length] as const,
      ([open, selectedIndex]) => {
        if (!open)
          return
        localSelectedIndex.value = clamp(
          Math.trunc(Number(selectedIndex ?? 0)),
          0,
          Math.max(0, props.options.length - 1),
        )
      },
      { immediate: true },
    )

    watch(query, () => {
      ;(instance?.proxy as { $forceUpdate?: () => void } | null)?.$forceUpdate?.()
      render.invalidatePlane('default')
      scheduler.invalidate({ priority: 'high' })
      scheduler.flushNow()
    }, { flush: 'post' })

    function onClose() {
      query.value = String(props.initialQuery ?? '')
      emit('close')
    }

    const filtered = computed(() =>
      filterCommandPaletteOptions(props.options ?? [], query.value),
    )
    const currentSelectedFilteredIndex = computed(() =>
      selectedFilteredIndex(filtered.value, localSelectedIndex.value),
    )
    const selectCurrent = (queryOverride?: string) => {
      const nextFiltered = queryOverride == null
        ? filtered.value
        : filterCommandPaletteOptions(props.options ?? [], queryOverride)
      const nextFilteredIndex = queryOverride == null
        ? currentSelectedFilteredIndex.value
        : selectedFilteredIndex(nextFiltered, localSelectedIndex.value)
      const current = nextFiltered[nextFilteredIndex]
      if (current?.opt.kind === 'separator')
        return
      emit('select', current?.opt?.label ?? null)
    }

    return () => {
      return renderCommandPalette({
        modelValue: props.modelValue,
        title: title.value,
        cols: cols.value,
        rows: rows.value,
        options: props.options,
        query: query.value,
        setQuery: (v: string) => (query.value = v),
        filtered: filtered.value,
        selectedIndex: localSelectedIndex.value,
        selectedFilteredIndex: currentSelectedFilteredIndex.value,
        onUpdateModelValue: (v: boolean) => emit('update:modelValue', v),
        onUpdateSelectedIndex: (v: number) => {
          localSelectedIndex.value = clamp(
            Math.trunc(Number(v ?? 0)),
            0,
            Math.max(0, props.options.length - 1),
          )
          emit('update:selectedIndex', localSelectedIndex.value)
          ;(instance?.proxy as { $forceUpdate?: () => void } | null)?.$forceUpdate?.()
        },
        selectCurrent,
        onSelect: (label: string | null) => emit('select', label),
        onClose,
        ui,
        showRowDetails: props.showRowDetails,
        inputFocusEpoch: inputFocusEpoch.value,
      })
    }
  },
})
