import { t } from '#cli/i18n'
import { TBox, TDialog, TInput, TSelect, TText } from '@simon_he/vue-tui'
import { computed, defineComponent, h, ref, watch, watchEffect } from 'vue'
import { useUiTheme } from '../theme'
import { computeMatchHighlightRanges } from './command-palette/match'
import { withFocusedInputTip } from './input-clear-tip'

type Option = Readonly<{ label: string, detail?: string }>

export const DimSdkCustomProviderModelsFetchDialog = defineComponent({
  name: 'DimSdkCustomProviderModelsFetchDialog',
  props: {
    modelValue: { type: Boolean, required: true },
    w: { type: Number, required: true },
    h: { type: Number, required: true },
    options: { type: Array as () => Option[], required: true },
    selectedIndices: { type: Array as () => number[], required: true },
  },
  emits: {
    'update:modelValue': (_v: boolean) => true,
    'update:selectedIndices': (_v: number[]) => true,
    'confirm': (_indices: number[]) => true,
    'cancel': () => true,
  },
  setup(props, { emit }) {
    const ui = useUiTheme()
    const query = ref('')
    const activeFilteredIndex = ref(0)
    const selected = ref<number[]>([])
    const searchFocused = ref(false)

    watch(
      () => props.modelValue,
      (open) => {
        if (open) {
          query.value = ''
          activeFilteredIndex.value = 0
          searchFocused.value = true
        }
        else {
          searchFocused.value = false
        }
      },
      { immediate: true },
    )

    watch(
      () => props.selectedIndices,
      (next) => {
        const max = Math.max(0, props.options.length - 1)
        const set = new Set<number>()
        for (const v of next ?? []) {
          if (typeof v !== 'number' || !Number.isFinite(v))
            continue
          const idx = Math.trunc(v)
          if (idx < 0 || idx > max)
            continue
          set.add(idx)
        }
        selected.value = [...set].sort((a, b) => a - b)
      },
      { immediate: true },
    )

    watch(
      () => query.value,
      () => {
        activeFilteredIndex.value = 0
      },
    )

    const filtered = computed(() => {
      const q = String(query.value ?? '')
        .trim()
        .toLowerCase()
      if (!q)
        return props.options.map((opt, idx) => ({ opt, idx }))
      return props.options
        .map((opt, idx) => ({ opt, idx }))
        .filter(({ opt }) => {
          const label = String(opt.label ?? '').toLowerCase()
          const detail = String(opt.detail ?? '').toLowerCase()
          return label.includes(q) || detail.includes(q)
        })
    })

    watchEffect(() => {
      const total = filtered.value.length
      if (total <= 0) {
        activeFilteredIndex.value = 0
        return
      }
      const max = Math.max(0, total - 1)
      activeFilteredIndex.value = Math.max(
        0,
        Math.min(activeFilteredIndex.value, max),
      )
    })

    function moveActive(delta: number): void {
      const total = filtered.value.length
      if (total <= 0)
        return
      const max = Math.max(0, total - 1)
      let next = activeFilteredIndex.value + delta
      if (next < 0)
        next = max
      else if (next > max)
        next = 0
      activeFilteredIndex.value = next
    }

    function setSelected(next: number[]): void {
      const max = Math.max(0, props.options.length - 1)
      const set = new Set<number>()
      for (const v of next) {
        if (typeof v !== 'number' || !Number.isFinite(v))
          continue
        const idx = Math.trunc(v)
        if (idx < 0 || idx > max)
          continue
        set.add(idx)
      }
      const indices = [...set].sort((a, b) => a - b)
      selected.value = indices
      emit('update:selectedIndices', indices)
    }

    function toggleOriginal(index: number): void {
      const max = Math.max(0, props.options.length - 1)
      const nextIndex = Math.max(0, Math.min(Math.trunc(index), max))
      const set = new Set(selected.value)
      if (set.has(nextIndex))
        set.delete(nextIndex)
      else set.add(nextIndex)
      setSelected([...set])
    }

    function toggleActive(): void {
      const originalIdx = filtered.value[activeFilteredIndex.value]?.idx
      if (typeof originalIdx !== 'number')
        return
      toggleOriginal(originalIdx)
    }

    return () => {
      if (!props.modelValue)
        return null

      const dialogW = Math.max(60, Math.floor(props.w))
      const dialogH = Math.max(18, Math.floor(props.h))
      const innerW = Math.max(0, dialogW - 4)
      const innerH = Math.max(0, dialogH - 4)
      const listH = Math.max(4, innerH - 5)

      const dialogBg = ui.token('dialog.chrome')?.bg
      const dialogBgStyle = dialogBg ? { bg: dialogBg } : undefined
      const fieldStyle = ui.style(['input.field'], dialogBgStyle)
      const listStyle = ui.style(['dialog.chrome', 'text.primary'])
      const highlightStyle = { ...listStyle, bg: 'blue' as const }
      const hintStyle = ui.style(['dialog.chrome', 'text.muted'])

      const selectedSet = new Set(selected.value ?? [])
      const q = String(query.value ?? '').trim()
      const visibleOptions = filtered.value.map((item) => {
        const checked = selectedSet.has(item.idx)
        const prefix = checked ? '[x] ' : '[ ] '
        const labelHighlightRanges = computeMatchHighlightRanges(
          item.opt.label,
          q,
        ).map(range => ({
          start: range.start + prefix.length,
          end: range.end + prefix.length,
        }))
        const detailHighlightRanges = computeMatchHighlightRanges(
          item.opt.detail ?? '',
          q,
        )
        return {
          label: `${prefix}${item.opt.label}`,
          detail: item.opt.detail,
          labelHighlightRanges,
          detailHighlightRanges,
        }
      })

      return h(
        TDialog as any,
        {
          'modelValue': props.modelValue,
          'onUpdate:modelValue': (v: boolean) => emit('update:modelValue', v),
          'w': dialogW,
          'h': dialogH,
          'title': t('providerDialogs.modelsFetchDialog.title'),
          'placement': 'center',
          'padding': 1,
          'backdrop': false,
          'closeOnBackdrop': false,
          'style': ui.style(['dialog.chrome']),
          'buttons': [
            {
              label: t('common.apply'),
              value: 'apply',
              kind: 'primary',
              default: true,
            },
            { label: t('common.cancel'), value: 'cancel' },
          ],
          'closeOnConfirm': false,
          'onConfirm': (btn: any) => {
            if (btn?.value === 'apply')
              emit('confirm', selected.value)
            else emit('cancel')
          },
          'onClose': () => emit('cancel'),
          'onKeydownCapture': (e: any) => {
            if (e?.key === 'Escape') {
              e.preventDefault?.()
              emit('cancel')
            }
          },
        },
        () => [
          h(TInput, {
            'x': 0,
            'y': 0,
            'w': innerW,
            'modelValue': query.value,
            'onUpdate:modelValue': (v: string) => (query.value = v),
            'placeholder': t(
              'providerDialogs.modelsFetchDialog.searchPlaceholder',
            ),
            'placeholderWhenFocused': true,
            'autoFocus': true,
            'style': fieldStyle,
            'onFocus': () => {
              searchFocused.value = true
            },
            'onBlur': () => {
              searchFocused.value = false
            },
            'onKeydown': (e: any) => {
              if (e?.key === 'ArrowDown') {
                e.preventDefault?.()
                moveActive(1)
                return
              }
              if (e?.key === 'ArrowUp') {
                e.preventDefault?.()
                moveActive(-1)
                return
              }
              if (
                e?.code === 'Space'
                || e?.key === ' '
                || e?.key === 'Spacebar'
              ) {
                e.preventDefault?.()
                toggleActive()
                return
              }
              if (e?.key === 'Enter') {
                e.preventDefault?.()
                emit('confirm', selected.value)
              }
            },
          }),
          h(
            TBox,
            {
              x: 0,
              y: 1,
              w: innerW,
              h: listH,
              border: false,
              padding: 0,
              style: ui.style(['dialog.chrome']),
            },
            () =>
              h(TSelect, {
                'x': 0,
                'y': 0,
                'w': innerW,
                'h': listH,
                'options': visibleOptions as any,
                'modelValue': activeFilteredIndex.value,
                'multiple': false,
                'onUpdate:modelValue': (v: number) => {
                  if (!Number.isFinite(v))
                    return
                  const max = Math.max(0, filtered.value.length - 1)
                  activeFilteredIndex.value = Math.max(
                    0,
                    Math.min(Math.trunc(v), max),
                  )
                },
                'onChange': () => toggleActive(),
                'onKeydown': (e: any) => {
                  if (
                    e?.code === 'Space'
                    || e?.key === ' '
                    || e?.key === 'Spacebar'
                  ) {
                    e.preventDefault?.()
                    toggleActive()
                    return
                  }
                  if (e?.key === 'Enter') {
                    e.preventDefault?.()
                    emit('confirm', selected.value)
                  }
                },
                'style': listStyle,
                'highlightStyle': highlightStyle,
                'autoFocus': false,
                'closeOnBlur': false,
                'onClose': () => emit('cancel'),
              }),
          ),
          h(TText, {
            x: 0,
            y: innerH - 2,
            w: innerW,
            value: withFocusedInputTip(
              t('providerDialogs.modelsFetchDialog.hint'),
              searchFocused.value,
              t('common.inputClearTip'),
            ),
            style: hintStyle,
          }),
        ],
      )
    }
  },
})
