import type { VNode } from 'vue'
import type { UiThemeContext } from '../../theme'
import type { CommandPaletteOption } from './types'
import type { FilteredOption } from './view-model'
import { t } from '#cli/i18n'
import {
  TBox,
  TInput,
  TSelect,
  TText,
  TView,
  wrapByCells,
} from '@simon_he/vue-tui'
import { h } from 'vue'
import { clamp } from './view-model'

export function renderCommandPalette(
  opts: Readonly<{
    modelValue: boolean
    title: string
    cols: number
    rows: number
    options: readonly CommandPaletteOption[]
    query: string
    setQuery: (v: string) => void
    filtered: readonly FilteredOption[]
    selectedIndex: number
    selectedFilteredIndex: number
    onUpdateModelValue: (v: boolean) => void
    onUpdateSelectedIndex: (v: number) => void
    onSelect: (label: string | null) => void
    selectCurrent: (queryOverride?: string) => void
    onClose: () => void
    ui: UiThemeContext
    showRowDetails: boolean
    inputFocusEpoch: number
  }>,
): VNode | null {
  if (!opts.modelValue)
    return null

  const maxW = Math.max(0, Math.floor(opts.cols))
  const maxH = Math.max(0, Math.floor(opts.rows))
  const w
    = maxW <= 0 ? 0 : clamp(Math.floor(maxW * 0.78), Math.min(44, maxW), maxW)
  const maxDialogH = maxH <= 0 ? 0 : Math.max(10, maxH - 4)
  const minDialogH = Math.min(12, maxDialogH)
  const preferredH = maxH <= 0 ? 0 : Math.floor(maxH * 0.78)
  const hgt = maxH <= 0 ? 0 : clamp(preferredH, minDialogH, maxDialogH)
  const x = Math.max(0, Math.floor((opts.cols - w) / 2))
  const y = clamp(
    Math.floor((opts.rows - hgt) / 2),
    0,
    Math.max(0, opts.rows - hgt),
  )
  const innerW = Math.max(0, w - 2)
  const innerH = Math.max(0, hgt - 2)
  const contentW = Math.max(0, innerW - 2) // box padding=1
  const contentH = Math.max(0, innerH - 2)

  const list = opts.filtered
  const dividerY = 1
  const listY = 2
  const hintY = Math.max(listY, contentH - 1)
  const detailY = Math.max(listY, hintY - 2)
  const listH = clamp(list.length, 1, Math.max(1, detailY - listY))

  const windowSize = Math.max(1, listH)
  const maxOffset = Math.max(0, list.length - windowSize)
  const offset = clamp(
    opts.selectedFilteredIndex - Math.floor(windowSize / 2),
    0,
    maxOffset,
  )
  const visible = list.slice(offset, offset + windowSize)

  const active = list[opts.selectedFilteredIndex]?.opt
  const detailText = active?.detail ? String(active.detail) : ''
  const detailLines = detailText
    ? wrapByCells(detailText, Math.max(1, contentW)).slice(0, 2)
    : []

  const dialogBg = opts.ui.token('dialog.chrome')?.bg
  const dialogBgStyle = dialogBg ? { bg: dialogBg } : undefined
  const chromeStyle = opts.ui.style(['dialog.chrome'])
  const inputStyle = opts.ui.style(['input.field'], dialogBgStyle)
  const listStyle = opts.ui.style(['dialog.chrome', 'text.primary'])
  const highlightStyle = { ...listStyle, bg: 'blue' as const }
  const dividerStyle = opts.ui.style(['dialog.chrome', 'border.default'])
  const hintStyle = opts.ui.style(['dialog.chrome', 'text.muted'])
  const detailStyle = opts.ui.style(['dialog.chrome', 'text.muted'])
  const emptyStyle = opts.ui.style(['dialog.chrome', 'text.muted'])
  const bodyBg = opts.ui.style(['dialog.chrome'])

  const close = () => {
    opts.onUpdateModelValue(false)
    opts.onClose()
  }

  const isSelectable = (index: number): boolean =>
    list[index]?.opt.kind !== 'separator'

  const nextSelectableIndex = (start: number, delta: number): number | null => {
    if (!list.length)
      return null
    const step = delta >= 0 ? 1 : -1
    for (let offset = 1; offset <= list.length; offset++) {
      const next = (start + step * offset + list.length) % list.length
      if (isSelectable(next))
        return next
    }
    return null
  }

  const moveSelection = (delta: number) => {
    if (!list.length)
      return
    const next = nextSelectableIndex(opts.selectedFilteredIndex, delta)
    if (next == null)
      return
    const idx = list[next]?.idx
    if (idx != null)
      opts.onUpdateSelectedIndex(idx)
  }

  return h(
    TView,
    {
      x,
      y,
      w,
      h: hgt,
      zIndex: 100,
      onKeydownCapture: (e: any) => {
        if (e?.key === 'Escape') {
          e?.preventDefault?.()
          e?.stopPropagation?.()
          close()
          return
        }
        if (e?.key === 'ArrowDown' || e?.code === 'ArrowDown') {
          e?.preventDefault?.()
          e?.stopPropagation?.()
          moveSelection(1)
          return
        }
        if (e?.key === 'ArrowUp' || e?.code === 'ArrowUp') {
          e?.preventDefault?.()
          e?.stopPropagation?.()
          moveSelection(-1)
        }
      },
    },
    () =>
      h(
        TBox,
        {
          x: 0,
          y: 0,
          w,
          h: hgt,
          border: true,
          title: opts.title,
          padding: 1,
          style: chromeStyle,
        },
        () => [
              h(TText, {
                x: 0,
                y: 0,
                w: contentW,
                h: contentH,
                value: '',
                style: bodyBg,
              }),
              h(TInput, {
                'key': `command-palette-input-${opts.inputFocusEpoch}`,
                'x': 0,
                'y': 0,
                'w': contentW,
                'modelValue': opts.query,
                'onUpdate:modelValue': (v: string) => opts.setQuery(v),
                'onChange': (v: string) => opts.selectCurrent(v),
                'placeholder': t('commandPalette.searchPlaceholder'),
                'placeholderWhenFocused': true,
                'cursorToEndOnFirstFocus': true,
                'cursorToEndOnExternalUpdate': true,
                'autoFocus': true,
                'style': inputStyle,
                'onKeydown': (e: any) => {
                  if (e?.key === 'ArrowDown' || e?.code === 'ArrowDown') {
                    e.preventDefault?.()
                    moveSelection(1)
                  }
                  else if (e?.key === 'ArrowUp' || e?.code === 'ArrowUp') {
                    e.preventDefault?.()
                    moveSelection(-1)
                  }
                },
              }),
              contentW > 0
                ? h(TText, {
                    x: 0,
                    y: dividerY,
                    w: contentW,
                    value: '─'.repeat(contentW),
                    style: dividerStyle,
                  })
                : null,
              list.length
                ? h(TSelect, {
                    'key': `command-palette-list:${opts.query}:${list.length}:${offset}`,
                    'x': 0,
                    'y': listY,
                    'w': contentW,
                    'h': listH,
                    'options': visible.map(x => ({
                      kind: x.opt.kind,
                      label: x.opt.label,
                      ...(opts.showRowDetails ? { detail: x.opt.detail } : {}),
                      style: x.opt.style,
                      highlightStyle: x.opt.highlightStyle,
                      detailStyle: x.opt.detailStyle,
                      highlightDetailStyle: x.opt.highlightDetailStyle,
                      labelAccentRanges: x.opt.labelAccentRanges,
                      detailAccentRanges: x.opt.detailAccentRanges,
                      detailAccentSegments: x.opt.detailAccentSegments,
                      accentStyle: x.opt.accentStyle,
                      highlightAccentStyle: x.opt.highlightAccentStyle,
                      labelHighlightRanges: x.labelHighlightRanges,
                      detailHighlightRanges: x.detailHighlightRanges,
                    })) as any,
                    'modelValue': clamp(
                      opts.selectedFilteredIndex - offset,
                      0,
                      Math.max(0, visible.length - 1),
                    ),
                    'onUpdate:modelValue': (v: number) => {
                      const picked
                        = visible[clamp(v, 0, Math.max(0, visible.length - 1))]
                      if (picked)
                        opts.onUpdateSelectedIndex(picked.idx)
                    },
                    'style': listStyle,
                    'highlightStyle': highlightStyle,
                    'autoFocus': false,
                    'closeOnBlur': false,
                    'onChange': (v: string | null) => opts.onSelect(v),
                    'onClose': close,
                  })
                : h(TText, {
                    x: 0,
                    y: listY,
                    w: contentW,
                    h: listH,
                    value: t('commandPalette.noMatches'),
                    style: emptyStyle,
                  }),
              detailLines.length
                ? detailLines.map((line, i) =>
                    h(TText, {
                      key: `detail-${i}`,
                      x: 0,
                      y: detailY + i,
                      w: contentW,
                      value: line,
                      style: detailStyle,
                    }),
                  )
                : h(TText, {
                    x: 0,
                    y: detailY,
                    w: contentW,
                    h: 2,
                    value: '',
                    style: bodyBg,
                  }),
              h(TText, {
                x: 0,
                y: hintY,
                w: contentW,
                value: t('commandPalette.hint'),
                style: hintStyle,
              }),
              contentW > 0
                ? h(TText, {
                    x: Math.max(0, contentW - 1),
                    y: hintY,
                    w: 1,
                    value: ' ',
                    style: bodyBg,
                  })
                : null,
        ],
      ),
  )
}
