<script setup lang="ts">
import { computed, inject, ref, watch } from 'vue'
import type { KbxFieldState, KbxLookupColumnDefinition, KbxLookupItem } from '@kbx/contracts'
import { kbxLookupRegistryKey } from '../lookup/registry'
import KbxLookupDialog from './KbxLookupDialog.vue'

const props = withDefaults(defineProps<{
  modelValue: string | null
  entity: string
  label: string
  required?: boolean
  readonly?: boolean
  disabled?: boolean
  error?: string
  warning?: string
  helpText?: string
  state?: KbxFieldState
  columns?: KbxLookupColumnDefinition[]
  pageSize?: number
}>(), { state:'default', pageSize:30 })

const emit = defineEmits<{
  'update:modelValue': [string | null]
  selected: [KbxLookupItem<string>]
}>()

const registry = inject(kbxLookupRegistryKey, {})
const provider = computed(() => registry[props.entity])
const code = ref('')
const displayName = ref('')
const open = ref(false)
const root = ref<HTMLElement | null>(null)
const localError = ref('')
const resolving = ref(false)
let editingCode = false
let triggerElement: HTMLElement | null = null
let moveAfterSelect = false
let resolveSequence = 0
const uid=`kbx-lookup-${Math.random().toString(36).slice(2)}`
const messageId=`${uid}-message`
const effectiveState=computed(()=>props.error||localError.value?'error':props.warning?'warning':props.state)
const message=computed(()=>props.error||localError.value||props.warning||props.helpText||'')

watch(() => [props.modelValue, props.entity] as const, async ([id]) => {
  const sequence=++resolveSequence
  localError.value=''
  if (!id) {
    displayName.value = ''
    if (!editingCode) code.value = ''
    editingCode = false
    resolving.value=false
    return
  }
  if(!provider.value){code.value='';displayName.value='';localError.value='조회 공급자가 구성되지 않았습니다.';return}
  resolving.value=true
  try{
    const item = await provider.value.resolveById(id)
    if(sequence!==resolveSequence)return
    if(item)applyDisplay(item)
    else{code.value='';displayName.value='';localError.value='선택된 항목을 다시 확인하세요.'}
  }catch{
    if(sequence===resolveSequence)localError.value='선택 정보를 불러오지 못했습니다. 다시 시도하세요.'
  }finally{if(sequence===resolveSequence)resolving.value=false}
}, { immediate: true })

function applyDisplay(item: KbxLookupItem<string>) { code.value=item.code; displayName.value=item.displayName }
function focusNextControl(){
  queueMicrotask(()=>{
    if(!root.value)return
    const scope=root.value.closest('form,[data-kbx-surface="header-form"],[data-kbx-surface="detail"],.kbx-screen-frame__body') ?? document
    const selector='input:not([disabled]):not([readonly]), select:not([disabled]), textarea:not([disabled]):not([readonly]), button:not([disabled]), [tabindex]:not([tabindex="-1"])'
    const all=Array.from(scope.querySelectorAll<HTMLElement>(selector)).filter(element=>element.offsetParent!==null&&!element.closest('[aria-hidden="true"]'))
    const owned=Array.from(root.value.querySelectorAll<HTMLElement>(selector))
    const ownedTabbable=owned.filter(element=>all.includes(element))
    const anchor=ownedTabbable[ownedTabbable.length-1]
    const index=anchor?all.indexOf(anchor):-1
    ;(index>=0?all[index+1]:null)?.focus()
  })
}
async function resolveCode() {
  const normalized=code.value.trim()
  localError.value=''
  if(!normalized){emit('update:modelValue',null);displayName.value='';return}
  if (!provider.value){localError.value='조회 공급자가 구성되지 않았습니다.';return}
  const sequence=++resolveSequence
  resolving.value=true
  try{
    const item = await provider.value.resolveByCode(normalized)
    if(sequence!==resolveSequence)return
    if (item) {
      const dialogWasOpen=open.value
      select(item)
      if(!dialogWasOpen)focusNextControl()
    }
    else localError.value = '일치하는 항목이 없습니다. F2로 조회하세요.'
  }catch{
    if(sequence===resolveSequence)localError.value='코드를 확인하지 못했습니다. 네트워크 상태를 확인하세요.'
  }finally{if(sequence===resolveSequence)resolving.value=false}
}
function onCodeInput(value: string) {
  code.value=value; displayName.value=''; localError.value=''; editingCode=true
  emit('update:modelValue', null)
  queueMicrotask(()=>{editingCode=false})
}
function show() {
  if (props.readonly || props.disabled) return
  if(!provider.value){localError.value='조회 공급자가 구성되지 않았습니다.';return}
  triggerElement = document.activeElement instanceof HTMLElement ? document.activeElement : null
  moveAfterSelect = false
  open.value = true
}
function select(item: KbxLookupItem<string>) {
  ++resolveSequence
  applyDisplay(item); localError.value=''; resolving.value=false; moveAfterSelect=true; emit('update:modelValue',item.id); emit('selected',item); open.value=false
}
function restoreFocus(){
  if(moveAfterSelect)focusNextControl()
  else queueMicrotask(()=>triggerElement?.focus())
  moveAfterSelect=false
}
function onRootKeydown(event:KeyboardEvent){if(event.key==='F2'){event.preventDefault();show()}}
</script>

<template>
  <div ref="root" class="kbx-lookup" :data-state="effectiveState" :aria-busy="resolving || undefined" @keydown="onRootKeydown">
    <label class="kbx-lookup__label" :for="`${uid}-code`">{{ label }}<span v-if="required" aria-hidden="true"> *</span></label>
    <div class="kbx-lookup__control">
      <input :id="`${uid}-code`" :value="code" class="kbx-lookup__code" :readonly="readonly" :disabled="disabled" :aria-label="`${label} 코드`" autocomplete="off" :aria-invalid="Boolean(error||localError)" :aria-describedby="message?messageId:undefined" @input="onCodeInput(($event.target as HTMLInputElement).value)" @keydown.enter.prevent="resolveCode">
      <input :value="displayName" class="kbx-lookup__name" readonly :disabled="disabled" :aria-label="`${label} 선택된 이름`">
      <button type="button" class="kbx-lookup__search" :disabled="readonly || disabled || resolving" :title="`${label} 조회 F2`" :aria-label="`${label} 검색`" @click="show">{{ resolving ? '확인중' : '검색' }}</button>
    </div>
    <span v-if="message" :id="messageId" class="kbx-lookup__message" :role="error||localError?'alert':undefined">{{message}}</span>

    <KbxLookupDialog v-model:visible="open" :entity="entity" :title="label" :initial-query="displayName || code" :columns="columns" :page-size="pageSize" @select="select" @update:visible="value => { open=value; if(!value)restoreFocus() }" />
  </div>
</template>

<style scoped>
.kbx-lookup{display:grid;grid-template-columns:var(--kbx-label-width) minmax(0,1fr);gap:var(--kbx-space-2);align-items:start;font-size:var(--kbx-font-md)}.kbx-lookup__label{padding-top:var(--kbx-space-2);font-weight:500;white-space:nowrap}.kbx-lookup__control{display:grid;grid-template-columns:minmax(var(--kbx-lookup-code-width),auto) minmax(var(--kbx-lookup-name-min-width),1fr) var(--kbx-lookup-button-width);min-width:0}.kbx-lookup input,.kbx-lookup button{height:var(--kbx-control-height);border:var(--kbx-border-width) solid var(--kbx-color-border-strong);font:inherit;color:var(--kbx-color-text)}.kbx-lookup input{padding:0 var(--kbx-space-2);min-width:0;background:var(--kbx-color-surface)}.kbx-lookup__code{border-radius:var(--kbx-radius-sm) 0 0 var(--kbx-radius-sm)}.kbx-lookup__name{background:var(--kbx-color-surface-muted)!important;border-left:0!important}.kbx-lookup__search{border-radius:0 var(--kbx-radius-sm) var(--kbx-radius-sm) 0;background:var(--kbx-color-surface);border-left:0!important}.kbx-lookup[data-state="changed"] .kbx-lookup__code{border-color:var(--kbx-color-changed-border);background:var(--kbx-color-changed-surface)}.kbx-lookup[data-state="warning"] .kbx-lookup__code{border-color:var(--kbx-color-warning-border);background:var(--kbx-color-warning-surface)}.kbx-lookup[data-state="ai-suggested"] .kbx-lookup__code{border-color:var(--kbx-color-ai-border);background:var(--kbx-color-ai-surface)}.kbx-lookup[data-state="error"] .kbx-lookup__code{border-color:var(--kbx-color-danger);background:var(--kbx-color-danger-surface)}.kbx-lookup__message{grid-column:2;color:var(--kbx-color-text-muted);font-size:var(--kbx-font-xs)}.kbx-lookup[data-state="warning"] .kbx-lookup__message{color:var(--kbx-color-warning-text)}.kbx-lookup[data-state="error"] .kbx-lookup__message{color:var(--kbx-color-danger)}.kbx-lookup[data-state="ai-suggested"] .kbx-lookup__message{color:var(--kbx-color-ai-text)}
</style>
