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

const props = withDefaults(defineProps<{
  visible: boolean
  entity: string
  title: string
  initialQuery?: string
  columns?: KbxLookupColumnDefinition[]
  pageSize?: number
}>(), { initialQuery: '', pageSize: 30 })

const emit = defineEmits<{
  'update:visible': [boolean]
  select: [KbxLookupItem<string>]
}>()

const registry = inject(kbxLookupRegistryKey, {})
const query = ref('')
const items = ref<KbxLookupItem<string>[]>([])
const selectedIndex = ref(0)
const loading = ref(false)
const error = ref('')
const lastSearchedQuery = ref('')
const page = ref(1)
const totalCount = ref(0)
const root = ref<HTMLElement | null>(null)
let requestSequence = 0

const defaultColumns: KbxLookupColumnDefinition[] = [
  { key:'code', label:'코드', source:'code' },
  { key:'displayName', label:'명칭', source:'displayName' },
  { key:'status', label:'상태', source:'status', align:'center' },
]
const columns = computed(() => props.columns?.length ? props.columns : defaultColumns)
const safePageSize = computed(() => Math.max(10, Math.min(100, props.pageSize)))
const pageCount = computed(() => Math.max(1, Math.ceil(totalCount.value / safePageSize.value)))
const hasPrevious = computed(() => page.value > 1 && !loading.value)
const hasNext = computed(() => page.value < pageCount.value && !loading.value)

watch(() => [props.visible, props.entity] as const, async ([visible]) => {
  requestSequence += 1
  if (!visible) return
  query.value = props.initialQuery.slice(0, 120)
  page.value = 1
  items.value = []
  totalCount.value = 0
  selectedIndex.value = 0
  error.value = ''
  await search(1)
})

function valueOf(item: KbxLookupItem<string>, column: KbxLookupColumnDefinition) {
  switch (column.source) {
    case 'code': return item.code
    case 'displayName': return item.displayName
    case 'secondaryText': return item.secondaryText ?? ''
    case 'status': return item.status ?? ''
    default: return item.metadata?.[column.source.slice('metadata.'.length)] ?? ''
  }
}

async function search(nextPage = 1) {
  const provider = registry[props.entity]
  if (!provider) {
    items.value = []
    totalCount.value = 0
    error.value = '조회 공급자가 구성되지 않았습니다. 관리자에게 문의하세요.'
    return
  }

  const sequence = ++requestSequence
  loading.value = true
  error.value = ''
  try {
    const result = await provider.search({
      query: query.value.trim().slice(0, 120),
      page: nextPage,
      pageSize: safePageSize.value,
    })
    if (sequence !== requestSequence) return
    items.value = result.items
    totalCount.value = Math.max(0, result.totalCount)
    page.value = nextPage
    selectedIndex.value = 0
    lastSearchedQuery.value = query.value
    await nextTick()
    scrollSelectedIntoView()
  } catch {
    if (sequence !== requestSequence) return
    items.value = []
    totalCount.value = 0
    error.value = '조회하지 못했습니다. 네트워크 상태를 확인한 후 다시 조회하세요.'
  } finally {
    if (sequence === requestSequence) loading.value = false
  }
}

async function onQueryEnter() {
  if (!loading.value && !error.value && lastSearchedQuery.value === query.value && items.value[selectedIndex.value]) {
    select(items.value[selectedIndex.value])
    return
  }
  await search(1)
}

function move(delta: number) {
  if (!items.value.length || loading.value) return
  selectedIndex.value = Math.max(0, Math.min(items.value.length - 1, selectedIndex.value + delta))
  nextTick(scrollSelectedIntoView)
}

function scrollSelectedIntoView() {
  root.value?.querySelector<HTMLElement>('tr[aria-selected="true"]')?.scrollIntoView({ block:'nearest' })
}

function select(item: KbxLookupItem<string>) {
  emit('select', item)
  emit('update:visible', false)
}
</script>

<template>
  <KbxDialog :open="visible" :title="`${title} 검색`" size="lg" :restore-focus="false" @update:open="emit('update:visible', $event)">
    <div
      ref="root"
      class="kbx-lookup-dialog"
      data-kbx-component="lookup-dialog"
      :aria-busy="loading || undefined"
      @keydown.down.prevent="move(1)"
      @keydown.up.prevent="move(-1)"
      @keydown.esc.stop="emit('update:visible', false)"
    >
      <div class="kbx-lookup-dialog__search">
        <label class="sr-only" for="kbx-lookup-dialog-query">검색어</label>
        <input
          id="kbx-lookup-dialog-query"
          v-model="query"
          class="kbx-lookup-dialog__input"
          maxlength="120"
          autocomplete="off"
          autofocus
          placeholder="코드 또는 명칭"
          @input="error=''"
          @keydown.enter.stop.prevent="onQueryEnter"
        >
        <KbxButton label="조회" variant="secondary" :loading="loading" @click="search(1)" />
      </div>

      <div v-if="loading" class="kbx-lookup-dialog__state" role="status">조회 중...</div>
      <div v-else-if="error" class="kbx-lookup-dialog__state is-error" role="alert">
        <strong>조회할 수 없습니다.</strong><span>{{ error }}</span><KbxButton label="다시 조회" variant="secondary" @click="search(page)" />
      </div>
      <div v-else-if="!items.length" class="kbx-lookup-dialog__state">
        <strong>조회된 항목이 없습니다.</strong><span>검색어를 변경해 다시 조회하세요.</span>
      </div>
      <div v-else class="kbx-lookup-dialog__results">
        <table class="kbx-lookup-table">
          <thead><tr><th v-for="column in columns" :key="column.key" :style="{ width: column.width ? `${column.width}px` : undefined, textAlign: column.align }">{{ column.label }}</th></tr></thead>
          <tbody>
            <tr
              v-for="(item, index) in items"
              :key="String(item.id)"
              :aria-selected="index === selectedIndex"
              :class="{ selected: index === selectedIndex }"
              @click="selectedIndex = index"
              @dblclick="select(item)"
            >
              <td v-for="column in columns" :key="column.key" :style="{ textAlign: column.align }">{{ valueOf(item, column) }}</td>
            </tr>
          </tbody>
        </table>
      </div>

      <div class="kbx-lookup-dialog__footer">
        <span aria-live="polite">{{ totalCount.toLocaleString() }}건 · {{ page }} / {{ pageCount }} 페이지 · ↑↓ 이동 · Enter 선택 · Esc 닫기</span>
        <div class="kbx-lookup-dialog__footer-actions">
          <KbxButton label="이전" variant="secondary" :disabled="!hasPrevious" @click="search(page - 1)" />
          <KbxButton label="다음" variant="secondary" :disabled="!hasNext" @click="search(page + 1)" />
          <KbxButton label="선택" variant="primary" :disabled="!items[selectedIndex]" @click="items[selectedIndex] && select(items[selectedIndex])" />
        </div>
      </div>
    </div>
  </KbxDialog>
</template>

<style scoped>
.kbx-lookup-dialog{display:grid;gap:var(--kbx-space-3)}
.kbx-lookup-dialog__search{display:flex;gap:var(--kbx-space-2)}
.kbx-lookup-dialog__input{flex:1;height:var(--kbx-control-height);border:var(--kbx-border-width) solid var(--kbx-color-border-strong);border-radius:var(--kbx-radius-sm);padding:0 var(--kbx-space-2);font:inherit;color:var(--kbx-color-text);background:var(--kbx-color-surface)}
.kbx-lookup-dialog__results{max-height:60vh;overflow:auto;border:var(--kbx-border-width) solid var(--kbx-color-border)}
.kbx-lookup-dialog__state{min-height:var(--kbx-data-state-min-height);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kbx-space-2);padding:var(--kbx-space-4);border:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface-muted);text-align:center;color:var(--kbx-color-text-muted)}
.kbx-lookup-dialog__state strong{color:var(--kbx-color-text)}
.kbx-lookup-dialog__state.is-error{border-color:var(--kbx-color-danger-border);background:var(--kbx-color-danger-surface)}
.kbx-lookup-table{width:100%;border-collapse:collapse;font-size:var(--kbx-font-sm)}
.kbx-lookup-table th,.kbx-lookup-table td{border-bottom:var(--kbx-border-width) solid var(--kbx-color-border);padding:var(--kbx-space-2);text-align:left;white-space:nowrap}
.kbx-lookup-table th{position:sticky;top:0;background:var(--kbx-color-surface-muted);font-weight:600;z-index:1}
.kbx-lookup-table tr.selected{outline:calc(var(--kbx-border-width) * 2) solid var(--kbx-color-focus);outline-offset:calc(var(--kbx-border-width) * -2);background:var(--kbx-color-info-surface)}
.kbx-lookup-dialog__footer{display:flex;justify-content:space-between;align-items:center;gap:var(--kbx-space-3);color:var(--kbx-color-text-muted);font-size:var(--kbx-font-xs)}
.kbx-lookup-dialog__footer-actions{display:flex;gap:var(--kbx-space-2)}
.sr-only{position:absolute;width:1px;height:1px;padding:0;margin:-1px;overflow:hidden;clip:rect(0,0,0,0);white-space:nowrap;border:0}
</style>
