import type { PropType } from 'vue'
import process from 'node:process'
import { t } from '#cli/i18n'
import { TDialog, TInput, TText, TView, useTerminal } from '@simon_he/vue-tui'
import { computed, defineComponent, h, nextTick, ref, watch } from 'vue'
import {
  getAskUserQuestionId,
  normalizeAskUserQuestions as normalizeDimSdkAskUserQuestions,
} from '../../ask-user'
import { useUiTheme } from '../theme'
import { clamp } from '../utils/math'

type AskUserQuestion = Readonly<{
  id?: string
  allowCustom?: boolean
  question: string
  header: string
  kind?: 'choice' | 'input'
  multiline?: boolean
  options?: Array<string | { label: string, description?: string }>
  multiSelect?: boolean
  placeholder?: string
}>

export const DimSdkAskUserDialog = defineComponent({
  name: 'DimSdkAskUserDialog',
  props: {
    modelValue: { type: Boolean, required: true },
    toolCallId: { type: String, required: true },
    questions: {
      type: Array as PropType<readonly AskUserQuestion[]>,
      required: true,
    },
    w: { type: Number, required: true },
    h: { type: Number, required: true },
    placement: {
      type: String as PropType<
        | 'center'
        | 'top'
        | 'bottom'
        | 'left'
        | 'right'
        | 'top-left'
        | 'top-right'
        | 'bottom-left'
        | 'bottom-right'
      >,
      default: 'center',
    },
    offsetX: { type: Number, default: 0 },
    offsetY: { type: Number, default: 0 },
  },
  emits: {
    'update:modelValue': (_v: boolean) => true,
    'submit': (_answers: Record<string, string | string[]>) => true,
    'cancel': () => true,
  },
  setup(props, { emit }) {
    const ui = useUiTheme()
    const { events, render, scheduler } = useTerminal()
    // Track current question index for navigation
    const currentQuestionIndex = ref(0)
    const selectedDialogButtonIndex = ref(0)

    // For single-select: track selected option index per question
    const selectedOptionIndices = ref<Record<string, number>>({})

    // For multi-select: track selected option indices per question (TSelect multiple mode uses indices)
    const multiSelectedOptionIndices = ref<Record<string, number[]>>({})
    const multiActiveOptionIndices = ref<Record<string, number>>({})

    // For "Other" custom input per question
    const customInputs = ref<Record<string, string>>({})
    const customInputFocusNonce = ref<Record<string, number>>({})
    const focusTargets = ref<Record<string, 'select' | 'input' | 'buttons'>>({})
    const questions = computed(() =>
      normalizeDimSdkAskUserQuestions(props.questions as unknown),
    )

    function invalidateDialog() {
      void nextTick(() => {
        render.invalidatePlane('default')
        scheduler.invalidate({ priority: 'high' })
      })
    }

    function rectArea(rect: Readonly<{ w: number, h: number }> | null | undefined): number {
      if (!rect)
        return 0
      return Math.max(0, Number(rect.w) || 0) * Math.max(0, Number(rect.h) || 0)
    }

    function getDialogRootNode(): any | null {
      const manager: any = events.value
      if (!manager || typeof manager.debugNodes !== 'function')
        return null
      const nodes = (manager.debugNodes?.() ?? []).filter(
        (node: any) => node?.visible && node?.focusable,
      )
      if (nodes.length === 0)
        return null
      const targetW = Math.max(0, Math.floor(props.w))
      const targetH = Math.max(0, Math.floor(props.h))
      const rectDistance = (node: any): number => {
        const rect = node?.rect
        if (!rect)
          return Number.POSITIVE_INFINITY
        return Math.abs(Math.floor(Number(rect.w ?? 0)) - targetW)
          + Math.abs(Math.floor(Number(rect.h ?? 0)) - targetH)
      }
      return [...nodes].sort((left: any, right: any) => {
        const distanceDelta = rectDistance(left) - rectDistance(right)
        if (distanceDelta !== 0)
          return distanceDelta
        const zIndexDelta = Number(right?.zIndex ?? 0) - Number(left?.zIndex ?? 0)
        if (zIndexDelta !== 0)
          return zIndexDelta
        return rectArea(right?.rect) - rectArea(left?.rect)
      })[0] ?? null
    }

    function getActiveControlLayout(): Readonly<{
      select: { x: number, y: number, w: number, h: number } | null
      input: { x: number, y: number, w: number, h: number } | null
    }> {
      const w = Math.max(0, props.w - 4)
      const contentH = Math.max(0, props.h - 5)
      const qIdx = clamp(
        currentQuestionIndex.value,
        0,
        Math.max(0, questions.value.length - 1),
      )
      const q = questions.value[qIdx]
      if (!q) {
        return { select: null, input: null }
      }

      const key = String(qIdx)
      const isInputQuestion = q.kind === 'input'
      const allowCustom = isInputQuestion ? true : (q.allowCustom ?? true)
      const options = q.options || []
      const optionCount = options.length + (!isInputQuestion && allowCustom ? 1 : 0)
      const otherIndex = !isInputQuestion && allowCustom ? Math.max(0, optionCount - 1) : -1
      const isMultiSelect = q.multiSelect ?? false
      const isOtherSelected = otherIndex >= 0 && (isMultiSelect
        ? (multiSelectedOptionIndices.value[key] ?? []).includes(otherIndex)
        : (selectedOptionIndices.value[key] ?? 0) === otherIndex)

      let y = 0
      y += 2

      if (isInputQuestion) {
        if (y < contentH - 1) {
          return {
            select: null,
            input: { x: 0, y: y + 1, w, h: 1 },
          }
        }
        return { select: null, input: null }
      }

      const showHint = !isOtherSelected || contentH - y >= 6
      if (showHint)
        y += 2

      const selectH = Math.min(
        optionCount,
        Math.max(
          isOtherSelected ? 1 : 3,
          contentH - y - (isOtherSelected ? 2 : 0),
        ),
      )

      if (selectH <= 0 || y >= contentH - 2) {
        return { select: null, input: null }
      }

      const select = { x: 0, y, w, h: selectH }
      const input = isOtherSelected && y + selectH < contentH - 1
        ? { x: 0, y: y + selectH + 1, w, h: 1 }
        : null
      return { select, input }
    }

    function getExpectedControlRect(target: 'select' | 'input'): Readonly<{
      x: number
      y: number
      w: number
      h: number
    }> | null {
      const root = getDialogRootNode()
      const rootRect = root?.rect
      if (!rootRect)
        return null
      const layout = getActiveControlLayout()[target]
      if (!layout)
        return null
      return {
        x: Number(rootRect.x ?? 0) + 2 + layout.x,
        y: Number(rootRect.y ?? 0) + 2 + layout.y,
        w: layout.w,
        h: layout.h,
      }
    }

    function getFocusedNode(): any | null {
      const manager: any = events.value
      if (!manager || typeof manager.debugNodes !== 'function')
        return null
      const focusedId = manager.getFocused?.()
      if (!focusedId)
        return null
      return (manager.debugNodes?.() ?? []).find((node: any) => node?.id === focusedId) ?? null
    }

    function isExpectedControlFocused(target: 'select' | 'input'): boolean {
      const focusedNode = getFocusedNode()
      const expected = getExpectedControlRect(target)
      if (!focusedNode?.rect || !expected)
        return false
      return (
        focusedNode.rect.x === expected.x
        && focusedNode.rect.y === expected.y
        && focusedNode.rect.w === expected.w
        && focusedNode.rect.h === expected.h
      )
    }

    function focusActiveControl(): boolean {
      if (!props.modelValue)
        return false
      const manager: any = events.value
      if (
        !manager
        || typeof manager.debugNodes !== 'function'
        || typeof manager.focus !== 'function'
      ) {
        return false
      }
      const key = String(currentQuestionIndex.value)
      const targetKind = focusTargets.value[key]
      if (targetKind !== 'select' && targetKind !== 'input')
        return false
      const expectedLayout = getActiveControlLayout()[targetKind]
      if (!expectedLayout)
        return false
      const nodes = (manager.debugNodes?.() ?? []).filter(
        (node: any) => node?.visible && node?.focusable,
      )
      if (nodes.length === 0)
        return false
      const targetArea = rectArea(expectedLayout)
      const sizeDistance = (node: any): number => {
        const rect = node?.rect
        if (!rect)
          return Number.POSITIVE_INFINITY
        return Math.abs(Math.floor(Number(rect.w ?? 0)) - expectedLayout.w)
          + Math.abs(Math.floor(Number(rect.h ?? 0)) - expectedLayout.h)
      }
      const target = [...nodes].sort((left: any, right: any) => {
        const sizeDelta = sizeDistance(left) - sizeDistance(right)
        if (sizeDelta !== 0)
          return sizeDelta
        return Number(right?.zIndex ?? 0) - Number(left?.zIndex ?? 0)
          || Math.abs(rectArea(left?.rect) - targetArea) - Math.abs(rectArea(right?.rect) - targetArea)
      })[0]
      if (!target?.id)
        return false
      if (manager.getFocused?.() === target.id)
        return true
      manager.focus(target.id)
      render.invalidatePlane('default')
      scheduler.invalidate({ priority: 'high' })
      return true
    }

    function scheduleFocusActiveControl(attempts = 4): void {
      if (!props.modelValue || attempts <= 0)
        return
      queueMicrotask(() => {
        if (focusActiveControl() || attempts <= 1)
          return
        setTimeout(() => {
          scheduleFocusActiveControl(attempts - 1)
        }, 0)
      })
    }

    function debugSelect(event: string, detail: Record<string, unknown>): void {
      if (process.env.ASK_USER_DIALOG_DEBUG !== '1')
        return
      try {
        console.error(`ASK_USER_DIALOG_${event}`, JSON.stringify(detail))
      }
      catch {
        // Ignore debug logging failures.
      }
    }

    function isDialogButtonFocused(): boolean {
      const manager: any = events.value
      const focusedId =
        typeof manager?.getFocused === 'function' ? manager.getFocused() : null
      if (!focusedId || typeof manager?.debugNodes !== 'function')
        return false
      const node = manager.debugNodes().find((item: any) => item?.id === focusedId)
      return Number(node?.zIndex ?? 0) >= 2002
    }

    // Reset state when questions change
    watch(
      () => [props.toolCallId, questions.value.length] as const,
      () => {
        currentQuestionIndex.value = 0
        selectedDialogButtonIndex.value = 0
        const indices: Record<string, number> = {}
        const multi: Record<string, number[]> = {}
        const multiActive: Record<string, number> = {}
        const custom: Record<string, string> = {}
        const focusNonce: Record<string, number> = {}
        const focus: Record<string, 'select' | 'input' | 'buttons'> = {}
        for (let i = 0; i < questions.value.length; i++) {
          const key = String(i)
          const question = questions.value[i]
          indices[key] = 0
          multi[key] = []
          multiActive[key] = 0
          custom[key] = ''
          focusNonce[key] = 0
          focus[key] = question?.kind === 'input' ? 'input' : 'select'
        }
        selectedOptionIndices.value = indices
        multiSelectedOptionIndices.value = multi
        multiActiveOptionIndices.value = multiActive
        customInputs.value = custom
        customInputFocusNonce.value = focusNonce
        focusTargets.value = focus
      },
      { immediate: true },
    )

    // Build final answers from current state
    const buildAnswers = computed(() => {
      const result: Record<string, string | string[]> = {}
      const otherLabel = t('askUserDialog.otherOption')
      for (let i = 0; i < questions.value.length; i++) {
        const key = String(i)
        const q = questions.value[i]!
        const answerKey = getAskUserQuestionId(q, i)
        if (q.kind === 'input') {
          result[answerKey] = customInputs.value[key] || ''
          continue
        }
        const options = q.options || []
        const optionLabels = options.map(o => o.label)
        const optionsWithOther = q.allowCustom === false
          ? optionLabels
          : [
              ...optionLabels,
              otherLabel,
            ]

        if (q.multiSelect) {
          // Multi-select: collect all selected labels (excluding "Other")
          const selectedIndices = multiSelectedOptionIndices.value[key] ?? []
          const labels = selectedIndices
            .map(idx => optionsWithOther[idx])
            .filter(
              (label): label is string => Boolean(label) && label !== otherLabel,
            )

          // Include custom input if provided (used when "Other" is selected)
          const customVal = customInputs.value[key]?.trim()
          const otherIndex = q.allowCustom === false ? -1 : optionsWithOther.length - 1
          const otherSelected = otherIndex >= 0 && selectedIndices.includes(otherIndex)
          if (otherSelected && customVal) {
            labels.push(customVal)
          }
          result[answerKey] = labels.length > 0 ? labels : []
        }
        else {
          // Single-select: get selected label
          const idx = selectedOptionIndices.value[key] ?? 0
          const label = optionsWithOther[idx] ?? ''
          if (q.allowCustom !== false && label === otherLabel) {
            result[answerKey] = customInputs.value[key] || ''
          }
          else {
            result[answerKey] = label
          }
        }
      }
      return result
    })

    function setFocusTarget(
      key: string,
      target: 'select' | 'input' | 'buttons',
    ) {
      focusTargets.value = {
        ...focusTargets.value,
        [key]: target,
      }
      invalidateDialog()
    }

    function focusCustomInput(key: string) {
      setFocusTarget(key, 'input')
      const prev = customInputFocusNonce.value[key] ?? 0
      customInputFocusNonce.value = {
        ...customInputFocusNonce.value,
        [key]: prev + 1,
      }
    }

    function focusSelect(key: string) {
      setFocusTarget(key, 'select')
    }

    function focusQuestion(index: number) {
      const q = questions.value[index]
      if (!q)
        return
      if (q.kind === 'input')
        setFocusTarget(String(index), 'input')
      else
        focusSelect(String(index))
    }

    function setSingleSelectedIndex(
      key: string,
      nextIndex: number,
      focusInput = false,
    ) {
      selectedOptionIndices.value = {
        ...selectedOptionIndices.value,
        [key]: nextIndex,
      }
      if (focusInput)
        focusCustomInput(key)
      else
        focusSelect(key)
    }

    function toggleMultiSelectedIndex(
      key: string,
      nextIndex: number,
      focusInput = false,
    ) {
      const selected = new Set(multiSelectedOptionIndices.value[key] ?? [])
      const willSelect = !selected.has(nextIndex)
      if (willSelect)
        selected.add(nextIndex)
      else
        selected.delete(nextIndex)
      multiSelectedOptionIndices.value = {
        ...multiSelectedOptionIndices.value,
        [key]: [...selected].sort((a, b) => a - b),
      }
      multiActiveOptionIndices.value = {
        ...multiActiveOptionIndices.value,
        [key]: nextIndex,
      }
      if (focusInput && willSelect)
        focusCustomInput(key)
      else
        focusSelect(key)
    }

    function handleSubmit() {
      emit('submit', { ...buildAnswers.value })
      emit('update:modelValue', false)
    }

    function handleCancel() {
      emit('cancel')
      emit('update:modelValue', false)
    }

    watch(
      () => props.modelValue,
      (open, prev) => {
        if (!open || prev)
          return
        // Keep in sync with the button `default` logic below.
        selectedDialogButtonIndex.value = 1
        scheduleFocusActiveControl()
      },
      { immediate: true },
    )

    watch(
      () => [
        props.modelValue,
        currentQuestionIndex.value,
        focusTargets.value[String(currentQuestionIndex.value)] ?? 'select',
        customInputFocusNonce.value[String(currentQuestionIndex.value)] ?? 0,
      ] as const,
      ([open]) => {
        if (!open)
          return
        scheduleFocusActiveControl()
      },
      { immediate: true },
    )

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

      const w = Math.max(0, props.w - 4) // border + padding
      const contentH = Math.max(0, props.h - 5) // minus header/footer
      const dialogBg = ui.token('dialog.chrome')?.bg
      const dialogBgStyle = dialogBg ? { bg: dialogBg } : undefined
      const chromeStyle = ui.style(['dialog.chrome'])
      const bodyStyle = ui.style(['dialog.chrome'])
      const selectStyle = ui.style(['dialog.chrome', 'text.primary'])
      const inputStyle = ui.style(['input.field'], dialogBgStyle)
      const labelStyle = ui.style(['dialog.chrome', 'text.primary'], {
        bold: true,
      })
      const hintStyle = ui.style(['dialog.chrome', 'text.muted'])
      const warningStyle = ui.style(['dialog.chrome', 'status.warning'])
      const highlightStyle = ui.style(['select.item'])

      // Show one question at a time for better UX
      const qIdx = clamp(
        currentQuestionIndex.value,
        0,
        Math.max(0, questions.value.length - 1),
      )
      const q = questions.value[qIdx]
      const isMultiSelect = q?.multiSelect ?? false
      const key = String(qIdx)
      const isInputQuestion = q?.kind === 'input'
      const allowCustom = isInputQuestion ? true : (q?.allowCustom ?? true)

      if (!q) {
        return h(
          TDialog as any,
          {
            'modelValue': props.modelValue,
            'onUpdate:modelValue': (v: boolean) => emit('update:modelValue', v),
            'w': props.w,
            'h': props.h,
            'title': t('askUserDialog.noQuestionsTitle'),
            'placement': props.placement,
            'offsetX': props.offsetX,
            'offsetY': props.offsetY,
            'padding': 1,
            'style': chromeStyle,
            'buttons': [
              {
                label: t('common.close'),
                value: 'close',
                kind: 'primary',
                default: true,
              },
            ],
            'onConfirm': () => handleCancel(),
            'onClose': () => handleCancel(),
          },
          () => [
            h(TText, {
              x: 0,
              y: 0,
              w,
              h: contentH,
              value: '',
              style: bodyStyle,
            }),
            h(TText, {
              x: 0,
              y: 0,
              w,
              value: t('askUserDialog.noQuestionsBody'),
              style: hintStyle,
            }),
          ],
        )
      }

      const options = q.options || []
      const optionLabels = options.map((o: any) => o.label)
      const optionDetails = options.map((o: any) => String(o.description ?? ''))
      const optionsWithOther = !isInputQuestion && allowCustom
        ? [...optionLabels, t('askUserDialog.otherOption')]
        : optionLabels
      const optionDetailsWithOther = !isInputQuestion && allowCustom
        ? [...optionDetails, t('askUserDialog.otherDetail')]
        : optionDetails
      const otherIndex = !isInputQuestion && allowCustom
        ? optionsWithOther.length - 1
        : -1
      const renderFocusTarget = focusTargets.value[key] ?? (isInputQuestion ? 'input' : 'select')
      const renderSingleSelectedIndex = selectedOptionIndices.value[key] ?? 0
      const renderMultiSelectedIndices = multiSelectedOptionIndices.value[key] ?? []
      const renderMultiActiveIndex = multiActiveOptionIndices.value[key] ?? 0
      const renderCustomInput = customInputs.value[key] || ''
      const renderCustomInputFocusNonce = customInputFocusNonce.value[key] ?? 0
      const isOtherSelected = otherIndex >= 0 && (isMultiSelect
        ? renderMultiSelectedIndices.includes(otherIndex)
        : renderSingleSelectedIndex === otherIndex)
      const inputPlaceholder = q.placeholder || t('askUserDialog.customAnswerPlaceholder')
      debugSelect('RENDER', {
        questionIndex: qIdx,
        focusTarget: renderFocusTarget,
        singleSelectedIndex: renderSingleSelectedIndex,
        multiSelectedIndices: renderMultiSelectedIndices,
        multiActiveIndex: renderMultiActiveIndex,
        isOtherSelected,
      })

      const isLastQuestion = qIdx >= questions.value.length - 1
      const isFirstQuestion = qIdx <= 0

      // Keep button positions stable across questions so keyboard selection doesn't drift.
      // Prev is a no-op on the first question.
      const buttons = [
        { label: t('askUserDialog.prev'), value: 'prev' },
        isLastQuestion
          ? {
              label: t('askUserDialog.submit'),
              value: 'submit',
              kind: 'primary',
            }
          : {
              label: t('askUserDialog.next'),
              value: 'next',
              kind: 'primary',
            },
        { label: t('common.cancel'), value: 'cancel' },
      ]

      const applyButtonAction = (value: string) => {
        if (value === 'submit') {
          handleSubmit()
        }
        else if (value === 'next') {
          currentQuestionIndex.value = Math.min(
            currentQuestionIndex.value + 1,
            questions.value.length - 1,
          )
          selectedDialogButtonIndex.value = 1
          focusQuestion(currentQuestionIndex.value)
        }
        else if (value === 'prev') {
          if (!isFirstQuestion) {
            currentQuestionIndex.value = Math.max(
              currentQuestionIndex.value - 1,
              0,
            )
            selectedDialogButtonIndex.value = 1
            focusQuestion(currentQuestionIndex.value)
          }
        }
        else {
          handleCancel()
        }
        invalidateDialog()
      }

      const moveSingleSelectFromInput = (dir: -1 | 1) => {
        if (isInputQuestion)
          return
        const current = selectedOptionIndices.value[key] ?? 0
        const count = otherIndex + 1
        if (count <= 0)
          return
        const next = (current + dir + count) % count
        setSingleSelectedIndex(key, next, otherIndex >= 0 && next === otherIndex)
      }

      const handleInputKeydown = (e: any): boolean => {
        if (focusTargets.value[key] !== 'input')
          return false
        if (e.key === 'Enter' && !e.shiftKey) {
          e.preventDefault?.()
          e.stopPropagation?.()
          if (isInputQuestion)
            applyButtonAction(isLastQuestion ? 'submit' : 'next')
          else
            focusSelect(key)
          return true
        }
        if (!isInputQuestion && !isMultiSelect && e.key === 'ArrowDown') {
          e.preventDefault?.()
          e.stopPropagation?.()
          moveSingleSelectFromInput(1)
          return true
        }
        if (!isInputQuestion && !isMultiSelect && e.key === 'ArrowUp') {
          e.preventDefault?.()
          e.stopPropagation?.()
          moveSingleSelectFromInput(-1)
          return true
        }
        return false
      }

      const handleInputTypingFallback = (e: any): boolean => {
        if (focusTargets.value[key] !== 'input')
          return false
        if (isExpectedControlFocused('input'))
          return false
        if (e.altKey || e.ctrlKey || e.metaKey)
          return false
        if (e.key === 'Backspace') {
          e.preventDefault?.()
          e.stopPropagation?.()
          const current = String(customInputs.value[key] ?? '')
          customInputs.value = {
            ...customInputs.value,
            [key]: current.slice(0, Math.max(0, current.length - 1)),
          }
          invalidateDialog()
          return true
        }
        if (typeof e.key !== 'string' || e.key.length !== 1)
          return false
        e.preventDefault?.()
        e.stopPropagation?.()
        customInputs.value = {
          ...customInputs.value,
          [key]: `${customInputs.value[key] ?? ''}${e.key}`,
        }
        invalidateDialog()
        return true
      }

      const handleSelectKeydown = (e: any): boolean => {
        if (isInputQuestion || focusTargets.value[key] !== 'select')
          return false
        const optionCount = optionsWithOther.length
        if (optionCount <= 0)
          return false
        const isSpaceKey = e.key === ' ' || e.key === 'Space' || e.key === 'Spacebar' || e.code === 'Space'
        debugSelect('KEYDOWN', {
          key: e?.key ?? null,
          code: e?.code ?? null,
          focusTarget: focusTargets.value[key],
          isMultiSelect,
          selectedIndex: selectedOptionIndices.value[key] ?? null,
          multiActiveIndex: multiActiveOptionIndices.value[key] ?? null,
          otherIndex,
        })
        if (e.key === 'ArrowUp' || e.key === 'ArrowDown') {
          e.preventDefault?.()
          e.stopPropagation?.()
          const dir: -1 | 1 = e.key === 'ArrowUp' ? -1 : 1
          if (isMultiSelect) {
            const current = multiActiveOptionIndices.value[key] ?? 0
            const next = (current + dir + optionCount) % optionCount
            multiActiveOptionIndices.value = {
              ...multiActiveOptionIndices.value,
              [key]: next,
            }
            debugSelect('ARROW_MULTI', {
              key: e?.key ?? null,
              current,
              next,
            })
          }
          else {
            const current = selectedOptionIndices.value[key] ?? 0
            const next = (current + dir + optionCount) % optionCount
            debugSelect('ARROW_SINGLE', {
              key: e?.key ?? null,
              current,
              next,
            })
            setSingleSelectedIndex(key, next, otherIndex >= 0 && next === otherIndex)
            return true
          }
          invalidateDialog()
          return true
        }
        if (isSpaceKey) {
          e.preventDefault?.()
          e.stopPropagation?.()
          const activeIndex = isMultiSelect
            ? (multiActiveOptionIndices.value[key] ?? 0)
            : (selectedOptionIndices.value[key] ?? 0)
          if (isMultiSelect) {
            debugSelect('SPACE_MULTI', {
              activeIndex,
              selected: multiSelectedOptionIndices.value[key] ?? [],
            })
            toggleMultiSelectedIndex(
              key,
              activeIndex,
              activeIndex === otherIndex,
            )
          }
          else {
            debugSelect('SPACE_SINGLE', {
              activeIndex,
              selectedIndex: selectedOptionIndices.value[key] ?? null,
            })
            setSingleSelectedIndex(
              key,
              activeIndex,
              activeIndex === otherIndex,
            )
          }
          return true
        }
        if (
          otherIndex >= 0
          && typeof e.key === 'string'
          && e.key.length === 1
          && e.key !== ' '
          && !e.altKey
          && !e.ctrlKey
          && !e.metaKey
        ) {
          const activeIndex = isMultiSelect
            ? (multiActiveOptionIndices.value[key] ?? 0)
            : (selectedOptionIndices.value[key] ?? 0)
          if (activeIndex === otherIndex) {
            e.preventDefault?.()
            e.stopPropagation?.()
            if (isMultiSelect)
              toggleMultiSelectedIndex(key, otherIndex, true)
            else
              setSingleSelectedIndex(key, otherIndex, true)
            customInputs.value = {
              ...customInputs.value,
              [key]: `${customInputs.value[key] ?? ''}${e.key}`,
            }
            return true
          }
        }
        if (
          e.key === 'Enter'
          && !e.shiftKey
          && !isMultiSelect
          && (selectedOptionIndices.value[key] ?? 0) === otherIndex
        ) {
          e.preventDefault?.()
          e.stopPropagation?.()
          focusCustomInput(key)
          return true
        }
        return false
      }

      return h(
        TDialog as any,
        {
          'modelValue': props.modelValue,
          'onUpdate:modelValue': (v: boolean) => emit('update:modelValue', v),
          'focusable': true,
          'autoFocus': true,
          'onKeydownCapture': (e: any) => {
            if (!e?.defaultPrevented && !e?.ctrlKey && !e?.metaKey && !e?.altKey) {
              if (renderFocusTarget === 'select' && handleSelectKeydown(e))
                return
              if (handleInputKeydown(e))
                return
              if (handleInputTypingFallback(e))
                return
            }
            if (e?.key === 'Escape') {
              if ((isInputQuestion || isOtherSelected) && renderFocusTarget === 'input') {
                e.preventDefault?.()
                e.stopPropagation?.()
                if (!isInputQuestion)
                  focusSelect(key)
                return
              }
              e.preventDefault?.()
              e.stopPropagation?.()
              handleCancel()
            }
          },
          'w': props.w,
          'h': props.h,
          'title': t('askUserDialog.title', {
            index: qIdx + 1,
            total: questions.value.length,
            header: q.header || t('askUserDialog.defaultHeader'),
          }),
          'placement': props.placement,
          'offsetX': props.offsetX,
          'offsetY': props.offsetY,
          'backdrop': false,
          'padding': 1,
          'style': chromeStyle,
          'buttons': buttons,
          'closeOnConfirm': false,
          'onKeydown': (e: any) => {
            if (!e?.defaultPrevented && !e?.ctrlKey && !e?.metaKey && !e?.altKey) {
              if (renderFocusTarget === 'input') {
                if (e.key === 'Enter' && !e.shiftKey) {
                  e.preventDefault?.()
                  e.stopPropagation?.()
                  if (isInputQuestion)
                    applyButtonAction(isLastQuestion ? 'submit' : 'next')
                  else
                    focusSelect(key)
                  return
                }
                if (!isInputQuestion && !isMultiSelect && e.key === 'ArrowDown') {
                  e.preventDefault?.()
                  e.stopPropagation?.()
                  moveSingleSelectFromInput(1)
                  return
                }
                if (!isInputQuestion && !isMultiSelect && e.key === 'ArrowUp') {
                  e.preventDefault?.()
                  e.stopPropagation?.()
                  moveSingleSelectFromInput(-1)
                  return
                }
              }
            }
            if (
              e.key === 'Enter'
              && !e.shiftKey
              && renderFocusTarget !== 'input'
              && !isDialogButtonFocused()
            ) {
              e.preventDefault?.()
              e.stopPropagation?.()
              applyButtonAction(isLastQuestion ? 'submit' : 'next')
              return
            }
            // TSelect multiple prevents default on Enter, which makes TDialog ignore it.
            // We fallback here to confirm the currently highlighted dialog button.
            if (
              isMultiSelect
              && e?.key === 'Enter'
              && e.defaultPrevented
            ) {
              e.preventDefault?.()
              e.stopPropagation?.()
              const idx = clamp(
                selectedDialogButtonIndex.value,
                0,
                Math.max(0, buttons.length - 1),
              )
              const btn = buttons[idx]
              applyButtonAction(String(btn?.value ?? ''))
            }
            if (
              (e?.key === 'ArrowLeft' || e?.key === 'ArrowRight')
              && !e.defaultPrevented
            ) {
              const dir: 1 | -1 = e.key === 'ArrowLeft' ? -1 : 1
              e.preventDefault?.()
              e.stopPropagation?.()
              if (
                questions.value.length > 1
                && renderFocusTarget !== 'buttons'
              ) {
                currentQuestionIndex.value = clamp(
                  currentQuestionIndex.value + dir,
                  0,
                  questions.value.length - 1,
                )
                focusQuestion(currentQuestionIndex.value)
                invalidateDialog()
                return
              }
              selectedDialogButtonIndex.value = clamp(
                selectedDialogButtonIndex.value + dir,
                0,
                Math.max(0, buttons.length - 1),
              )
            }
          },
          'onConfirm': (btn: any) => {
            if (typeof btn?.index === 'number') {
              selectedDialogButtonIndex.value = clamp(
                btn.index,
                0,
                Math.max(0, buttons.length - 1),
              )
            }
            applyButtonAction(String(btn?.value ?? ''))
          },
          'onClose': () => handleCancel(),
        },
        () => {
          let y = 0
          const nodes: any[] = []

          nodes.push(
            h(TText, {
              x: 0,
              y: 0,
              w,
              h: contentH,
              value: '',
              style: bodyStyle,
            }),
          )

          if (!isInputQuestion && isOtherSelected && focusTargets.value[key] === 'buttons') {
            const focusProxyX = Math.max(0, w - 1)
            const focusProxyY = Math.max(0, contentH - 1)
            nodes.push(
              h(TView, {
                x: focusProxyX,
                y: focusProxyY,
                w: 1,
                h: 1,
                zIndex: 1002,
                focusable: true,
                autoFocus: true,
                onKeydown: (e: any) => {
                  if (e?.key === 'ArrowDown') {
                    e.preventDefault?.()
                    e.stopPropagation?.()
                    return
                  }
                  if (e?.key !== 'ArrowUp')
                    return
                  e.preventDefault?.()
                  e.stopPropagation?.()
                  focusTargets.value = { ...focusTargets.value, [key]: 'input' }
                },
              }),
            )
          }

          // Question text
          nodes.push(
            h(TText, {
              x: 0,
              y: y++,
              w,
              wrap: true,
              value: q.question || '',
              style: labelStyle,
            }),
          )
          y++

          if (isInputQuestion) {
            if (y < contentH - 1) {
              nodes.push(
                h(TText, {
                  x: 0,
                  y: y++,
                  w,
                  value: t('askUserDialog.customAnswerLabel'),
                  style: hintStyle,
                }),
              )
              nodes.push(
                h(TInput, {
                  'key': `custom-input:${key}:${renderCustomInputFocusNonce}`,
                  'x': 0,
                  'y': y,
                  'w': w,
                  'zIndex': 2003,
                  'modelValue': renderCustomInput,
                  'onUpdate:modelValue': (v: string) => {
                    customInputs.value = { ...customInputs.value, [key]: v }
                  },
                  'onFocus': () => {
                    setFocusTarget(key, 'input')
                  },
                  'onKeydown': (e: any) => {
                    if (e?.key === 'ArrowDown') {
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      focusTargets.value = {
                        ...focusTargets.value,
                        [key]: 'buttons',
                      }
                      return
                    }
                    if (e?.key === 'Enter' && !e?.shiftKey) {
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      applyButtonAction(isLastQuestion ? 'submit' : 'next')
                    }
                  },
                  'autoFocus': renderFocusTarget === 'input',
                  'placeholder': inputPlaceholder,
                  'style': inputStyle,
                }),
              )
            }
          }
          else if (isMultiSelect) {
            // Multi-select mode
            if (!isOtherSelected || contentH - y >= 6) {
              nodes.push(
                h(TText, {
                  x: 0,
                  y: y++,
                  w,
                  value: t('askUserDialog.multiSelectHint'),
                  style: warningStyle,
                }),
              )
              y++
            }
            const focusedIndex = renderMultiActiveIndex
            const selectH = Math.min(
              optionsWithOther.length,
              Math.max(
                isOtherSelected ? 1 : 3,
                contentH - y - (isOtherSelected ? 2 : 0),
              ),
            )
            if (selectH > 0 && y < contentH - 2) {
              const windowStart = Math.max(
                0,
                Math.min(
                  Math.max(0, optionsWithOther.length - selectH),
                  focusedIndex - Math.floor(selectH / 2),
                ),
              )
              nodes.push(
                h(TView, {
                  x: 0,
                  y,
                  w,
                  h: selectH,
                  zIndex: 2003,
                  focusable: true,
                  autoFocus: renderFocusTarget === 'select',
                  onFocus: () => {
                    if (
                      otherIndex >= 0
                      && renderFocusTarget === 'input'
                      && renderMultiSelectedIndices.includes(otherIndex)
                    )
                      return
                    focusSelect(key)
                  },
                  onKeydown: (e: any) => {
                    handleSelectKeydown(e)
                  },
                }),
              )
              for (let row = 0; row < selectH; row++) {
                const optionIndex = windowStart + row
                const label = optionsWithOther[optionIndex] ?? ''
                const detail = optionDetailsWithOther[optionIndex] ?? ''
                const rowY = y + row
                const selected = renderMultiSelectedIndices.includes(optionIndex)
                const focused = renderFocusTarget === 'select' && optionIndex === focusedIndex
                const marker = selected ? '[x]' : '[ ]'
                const prefix = focused ? '>' : ' '
                const lineValue = `${prefix} ${marker} ${label}`
                const lineStyle = focused
                  ? highlightStyle
                  : selected
                    ? labelStyle
                    : selectStyle
                nodes.push(
                  h(TText, {
                    x: 0,
                    y: rowY,
                    w,
                    value: lineValue,
                    style: lineStyle,
                    zIndex: 2003,
                  }),
                )
                if (detail) {
                  const detailWidth = Math.min(w, detail.length)
                  nodes.push(
                    h(TText, {
                      x: Math.max(0, w - detailWidth),
                      y: rowY,
                      w: detailWidth,
                      value: detail,
                      style: hintStyle,
                      zIndex: 2003,
                    }),
                  )
                }
                nodes.push(
                  h(TView, {
                    x: 0,
                    y: rowY,
                    w,
                    h: 1,
                    zIndex: 2004,
                    focusable: false,
                    onClick: (e: any) => {
                      e?.stopPropagation?.()
                      toggleMultiSelectedIndex(
                        key,
                        optionIndex,
                        optionIndex === otherIndex,
                      )
                    },
                  }),
                )
              }

              y += selectH + (isOtherSelected ? 0 : 1)

              if (isOtherSelected && y < contentH - 1) {
                nodes.push(
                  h(TText, {
                    x: 0,
                    y: y++,
                    w,
                    value: t('askUserDialog.customAnswerLabel'),
                    style: hintStyle,
                    zIndex: 2003,
                  }),
                )
                nodes.push(
                  h(TInput, {
                    'key': `custom-input:${key}:${renderCustomInputFocusNonce}`,
                    'x': 0,
                    'y': y,
                    'w': w,
                    'zIndex': 2003,
                    'modelValue': renderCustomInput,
                    'onUpdate:modelValue': (v: string) => {
                      customInputs.value = { ...customInputs.value, [key]: v }
                    },
                    'onFocus': () => {
                      setFocusTarget(key, 'input')
                    },
                    'onKeydown': (e: any) => {
                      if (e?.key === 'ArrowDown') {
                        e.preventDefault?.()
                        e.stopPropagation?.()
                        focusTargets.value = {
                          ...focusTargets.value,
                          [key]: 'buttons',
                        }
                        return
                      }
                      if (e?.key === 'Enter' && !e?.shiftKey) {
                        e.preventDefault?.()
                        e.stopPropagation?.()
                        const idx = clamp(
                          selectedDialogButtonIndex.value,
                          0,
                          Math.max(0, buttons.length - 1),
                        )
                        const btn = buttons[idx]
                        applyButtonAction(String(btn?.value ?? ''))
                        return
                      }
                      if (e?.key !== 'ArrowUp')
                        return
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      focusTargets.value = {
                        ...focusTargets.value,
                        [key]: 'select',
                      }
                    },
                    'autoFocus': renderFocusTarget === 'input',
                    'placeholder': inputPlaceholder,
                    'style': inputStyle,
                  }),
                )
              }
            }
          }
          else {
            // Single-select mode
            if (!isOtherSelected || contentH - y >= 6) {
              nodes.push(
                h(TText, {
                  x: 0,
                  y: y++,
                  w,
                  value: t('askUserDialog.singleSelectHint'),
                  style: warningStyle,
                }),
              )
              y++
            }

            const currentIdx = renderSingleSelectedIndex
            const selectH = Math.min(
              optionsWithOther.length,
              Math.max(
                isOtherSelected ? 1 : 3,
                contentH - y - (isOtherSelected ? 2 : 0),
              ),
            )
            if (selectH > 0 && y < contentH - 2) {
              const windowStart = Math.max(
                0,
                Math.min(
                  Math.max(0, optionsWithOther.length - selectH),
                  currentIdx - Math.floor(selectH / 2),
                ),
              )
              nodes.push(
                h(TView, {
                  x: 0,
                  y,
                  w,
                  h: selectH,
                  zIndex: 2003,
                  focusable: true,
                  autoFocus: renderFocusTarget === 'select',
                  onFocus: () => {
                    if (
                      otherIndex >= 0
                      && renderFocusTarget === 'input'
                      && renderSingleSelectedIndex === otherIndex
                    )
                      return
                    focusSelect(key)
                  },
                  onKeydown: (e: any) => {
                    handleSelectKeydown(e)
                  },
                }),
              )
              for (let row = 0; row < selectH; row++) {
                const optionIndex = windowStart + row
                const label = optionsWithOther[optionIndex] ?? ''
                const detail = optionDetailsWithOther[optionIndex] ?? ''
                const rowY = y + row
                const selected = optionIndex === currentIdx
                const focused = renderFocusTarget === 'select' && optionIndex === currentIdx
                const marker = selected ? '(*)' : '( )'
                const prefix = focused ? '>' : ' '
                const lineValue = `${prefix} ${marker} ${label}`
                const lineStyle = focused
                  ? highlightStyle
                  : selected
                    ? labelStyle
                    : selectStyle
                nodes.push(
                  h(TText, {
                    x: 0,
                    y: rowY,
                    w,
                    value: lineValue,
                    style: lineStyle,
                    zIndex: 2003,
                  }),
                )
                if (detail) {
                  const detailWidth = Math.min(w, detail.length)
                  nodes.push(
                    h(TText, {
                      x: Math.max(0, w - detailWidth),
                      y: rowY,
                      w: detailWidth,
                      value: detail,
                      style: hintStyle,
                      zIndex: 2003,
                    }),
                  )
                }
                nodes.push(
                  h(TView, {
                    x: 0,
                    y: rowY,
                    w,
                    h: 1,
                    zIndex: 2004,
                    focusable: false,
                    onClick: (e: any) => {
                      e?.stopPropagation?.()
                      setSingleSelectedIndex(
                        key,
                        optionIndex,
                        optionIndex === otherIndex,
                      )
                      if (optionIndex === otherIndex)
                        return
                      applyButtonAction(isLastQuestion ? 'submit' : 'next')
                    },
                  }),
                )
              }

              y += selectH + (isOtherSelected ? 0 : 1)
            }

            // Show custom input if "Other" is selected
            if (isOtherSelected && y < contentH - 1) {
              nodes.push(
                h(TText, {
                  x: 0,
                  y: y++,
                  w,
                  value: t('askUserDialog.customAnswerLabel'),
                  style: hintStyle,
                  zIndex: 2003,
                }),
              )
              nodes.push(
                h(TInput, {
                  'key': `custom-input:${key}:${renderCustomInputFocusNonce}`,
                  'x': 0,
                  'y': y,
                  'w': w,
                  'zIndex': 2003,
                  'modelValue': renderCustomInput,
                  'onUpdate:modelValue': (v: string) => {
                    customInputs.value = { ...customInputs.value, [key]: v }
                  },
                  'onFocus': () => {
                    setFocusTarget(key, 'input')
                  },
                  'onKeydown': (e: any) => {
                    if (e?.key === 'ArrowDown') {
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      moveSingleSelectFromInput(1)
                      return
                    }
                    if (e?.key === 'Enter' && !e?.shiftKey) {
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      focusSelect(key)
                      return
                    }
                    if (e?.key === 'Escape') {
                      e.preventDefault?.()
                      e.stopPropagation?.()
                      focusSelect(key)
                      return
                    }
                    if (e?.key !== 'ArrowUp')
                      return
                    e.preventDefault?.()
                    e.stopPropagation?.()
                    moveSingleSelectFromInput(-1)
                  },
                  'autoFocus': renderFocusTarget === 'input',
                  'placeholder': inputPlaceholder,
                  'style': inputStyle,
                }),
              )
            }
          }

          return nodes
        },
      )
    }
  },
})
