<script setup lang="ts">
import { computed, nextTick, reactive, ref } from 'vue'
import { useQuery } from '@tanstack/vue-query'
import {
  isKbxProblem,
  kbxFieldReadonly,
  resolveKbxRecordStatePolicy,
  type KbxAuditEntry,
  type KbxConflictSnapshot,
  type KbxProblem,
  type KbxSearchField,
} from '@kbx/contracts'
import {
  KbxBarcodeField,
  KbxCheckbox,
  KbxDataGrid,
  KbxFormGrid,
  KbxFormSection,
  KbxInput,
  KbxLookup,
  KbxMasterPage,
  KbxRecordNavigator,
  KbxSearchPanel,
  KbxUnsavedChangesDialog,
  useKbxDirtyState,
  useKbxPageShortcuts,
} from '@kbx/ui'
import { itemApi } from './itemApi'
import {
  itemMasterColumns,
  itemMasterScreen,
  itemStatePolicies,
  itemWorkflow,
  type ItemMasterRow,
} from './item.definition'

const searchModel = ref({ keyword:'' })
const searchFields:KbxSearchField[] = [
  { key:'keyword', label:'검색', type:'text', width:'lg', placeholder:'품목코드/품목명' },
]

const selected = ref<ItemMasterRow[]>([])
const form = reactive<ItemMasterRow>(emptyItem())
const auditEntries = ref<KbxAuditEntry[]>([])
const conflict = ref<KbxConflictSnapshot | null>(null)
const problem = ref<KbxProblem | null>(null)
const failedCommand = ref<'load'|'save'|'deactivate'|null>(null)
const errors = ref<any[]>([])
const masterGrid = ref<any>(null)
const pendingTransition = ref<{ kind:'row'; row:ItemMasterRow } | { kind:'new' } | null>(null)
const savingTransition = ref(false)
const { dirty, touch, markSaved, reset:resetDirty } = useKbxDirtyState()

const status = computed(() => !form.id ? '신규' : form.active ? '사용' : '사용중지')
const policy = computed(() => resolveKbxRecordStatePolicy(itemStatePolicies, status.value))
const query = useQuery({
  queryKey:['erp','items'],
  queryFn:() => itemApi.search(searchModel.value.keyword),
  enabled:false,
})
const rows = computed(() => query.data.value?.items ?? [])
const currentRowIndex = computed(() => form.id ? rows.value.findIndex(row => row.id === form.id) : -1)
const currentRowPosition = computed(() => currentRowIndex.value >= 0 ? currentRowIndex.value + 1 : 0)
function navigateRecord(offset:number) {
  const index=currentRowIndex.value
  if(index<0)return
  const target=rows.value[index+offset]
  if(target) requestChoose([target])
}

function emptyItem():ItemMasterRow {
  return {
    id:'', code:'', name:'', categoryName:'', specification:'', unit:'EA', barcode:'',
    defaultWarehouseId:null, defaultWarehouseName:'', lotManaged:false, expiryManaged:false,
    active:true, version:0,
  }
}

async function search() {
  selected.value = []
  problem.value = null
  failedCommand.value = null
  await query.refetch()
}

async function loadRow(row:ItemMasterRow) {
  problem.value = null
  failedCommand.value = null
  try {
    const latest = await itemApi.get(row.id)
    const audit = await itemApi.audit(latest.id)
    selected.value = [latest]
    Object.assign(form, latest)
    auditEntries.value = audit
    errors.value = []
    conflict.value = null
    resetDirty()
    await nextTick()
    masterGrid.value?.selectRowByKey?.(latest.id)
  } catch (error) {
    if (isKbxProblem(error)) {
      problem.value = error
      failedCommand.value = 'load'
      await nextTick()
      masterGrid.value?.selectRowByKey?.(form.id || null)
      return
    }
    throw error
  }
}

async function requestChoose(rows:ItemMasterRow[]) {
  const row = rows[0]
  if (!row || row.id === form.id) return
  if (dirty.value) {
    pendingTransition.value = { kind:'row', row }
    await nextTick()
    masterGrid.value?.selectRowByKey?.(form.id || null)
    return
  }
  await loadRow(row)
}

function state(field:string) {
  return kbxFieldReadonly(policy.value, field)
}

function createNew() {
  Object.assign(form, emptyItem())
  selected.value = []
  auditEntries.value = []
  conflict.value = null
  problem.value = null
  failedCommand.value = null
  errors.value = []
  resetDirty()
  masterGrid.value?.clearSelection?.()
}

function requestNew() {
  if (dirty.value) {
    pendingTransition.value = { kind:'new' }
    return
  }
  createNew()
}

function copy() {
  const source = { ...form }
  Object.assign(form, { ...source, id:'', code:'', barcode:'', version:0, active:true })
  selected.value = []
  auditEntries.value = []
  conflict.value = null
  problem.value = null
  failedCommand.value = null
  errors.value = []
  resetDirty()
  masterGrid.value?.clearSelection?.()
  touch()
}

function payload() {
  return {
    id:form.id || undefined,
    version:form.version || undefined,
    code:form.code.trim(),
    name:form.name.trim(),
    categoryName:form.categoryName,
    specification:form.specification,
    unit:form.unit,
    barcode:form.barcode,
    defaultWarehouseId:form.defaultWarehouseId,
    lotManaged:form.lotManaged,
    expiryManaged:form.expiryManaged,
  }
}

async function save() {
  errors.value = []
  problem.value = null
  failedCommand.value = null
  if (!form.code.trim() || !form.name.trim()) {
    errors.value = [
      ...(!form.code.trim() ? [{ field:'code', code:'REQUIRED', message:'품목코드를 입력하세요.' }] : []),
      ...(!form.name.trim() ? [{ field:'name', code:'REQUIRED', message:'품목명을 입력하세요.' }] : []),
    ]
    return false
  }

  try {
    const result = form.id ? await itemApi.update(form.id, payload()) : await itemApi.create(payload())
    form.id = result.id
    form.version = result.version
    form.active = true
    markSaved()
    auditEntries.value = await itemApi.audit(form.id)
    await query.refetch()
    await nextTick()
    masterGrid.value?.selectRowByKey?.(form.id)
    return true
  } catch (error) {
    if (isKbxProblem(error) && error.type === 'validation') {
      errors.value = error.errors
      return false
    }
    if (isKbxProblem(error) && error.type === 'conflict' && form.id) {
      const latest = await itemApi.get(form.id)
      conflict.value = {
        code:error.code,
        title:error.title,
        detail:'저장하지 않은 입력은 유지됩니다. 최신 값과 비교한 뒤 다시 읽을 수 있습니다.',
        entityId:form.id,
        requestedVersion:form.version,
        currentVersion:error.currentVersion ?? latest.version,
        changes:[['code','품목코드'],['name','품목명'],['unit','단위'],['barcode','바코드']].map(([field,label]) => ({
          field,
          label,
          mine:(form as any)[field],
          latest:(latest as any)[field],
        })),
      }
      return false
    }
    if (isKbxProblem(error)) {
      problem.value = error
      failedCommand.value = 'save'
      return false
    }
    throw error
  }
}

async function applyPendingTransition() {
  const pending = pendingTransition.value
  pendingTransition.value = null
  if (!pending) return
  if (pending.kind === 'new') createNew()
  else await loadRow(pending.row)
}

async function saveThenTransition() {
  if (!pendingTransition.value) return
  savingTransition.value = true
  try {
    if (await save()) await applyPendingTransition()
  } finally {
    savingTransition.value = false
  }
}

async function deactivate() {
  if (!form.id) return
  problem.value = null
  failedCommand.value = null
  try {
    const result = await itemApi.deactivate(form.id, form.version)
    form.version = result.version
    form.active = false
    markSaved()
    auditEntries.value = await itemApi.audit(form.id)
    await query.refetch()
  } catch (error) {
    if (isKbxProblem(error) && error.type === 'conflict') {
      const latest = await itemApi.get(form.id)
      conflict.value = {
        code:error.code,
        title:error.title,
        currentVersion:error.currentVersion ?? latest.version,
        requestedVersion:form.version,
        entityId:form.id,
      }
      return
    }
    if (isKbxProblem(error)) {
      problem.value = error
      failedCommand.value = 'deactivate'
      return
    }
    throw error
  }
}

async function reloadConflict() {
  if (!form.id) return
  const latest = await itemApi.get(form.id)
  Object.assign(form, latest)
  auditEntries.value = await itemApi.audit(form.id)
  conflict.value = null
  problem.value = null
  failedCommand.value = null
  resetDirty()
  await nextTick()
  masterGrid.value?.selectRowByKey?.(form.id)
}

async function retryProblem() {
  const action = failedCommand.value
  const row = selected.value[0]
  problem.value = null
  failedCommand.value = null
  if (action === 'load' && row) return loadRow(row)
  if (action === 'save') return save()
  if (action === 'deactivate') return deactivate()
}

function dismissProblem() {
  problem.value = null
  failedCommand.value = null
}

async function command(id:string) {
  if (id === 'search') return search()
  if (id === 'new') return requestNew()
  if (id === 'copy') return copy()
  if (id === 'save') return save()
  if (id === 'deactivate') return deactivate()
}

useKbxPageShortcuts([
  { key:'F3', execute:search },
  { key:'F8', execute:save },
])
</script>

<template>
  <KbxMasterPage
    :screen="itemMasterScreen"
    :status="status"
    :dirty="dirty"
    :version="form.version || undefined"
    :errors="errors"
    :workflow="itemWorkflow"
    :conflict="conflict"
    :problem="problem"
    :audit-entries="auditEntries"
    list-title="품목 목록"
    :list-count="query.data.value?.totalCount ?? rows.length"
    list-count-unit="개"
    detail-title="품목 상세"
    :detail-description="form.id ? `${form.code} · ${form.name}` : '신규 품목을 등록합니다.'"
    :content-state="query.error.value ? 'error' : query.isFetching.value && !query.data.value ? 'loading' : !query.data.value ?'idle' : query.data.value.items.length === 0 ? 'empty' : 'ready'"
    :refreshing="query.isFetching.value && Boolean(query.data.value)"
    breadcrumb="ERP > 기준정보"
    @command="command"
    @transition="command"
    @reload-conflict="reloadConflict"
    @dismiss-conflict="conflict=null"
    @retry-problem="retryProblem"
    @dismiss-problem="dismissProblem"
  >
    <template #detail-actions>
      <KbxRecordNavigator
        :current="currentRowPosition"
        :total="rows.length"
        item-label="품목"
        :previous-disabled="dirty || currentRowPosition<=1"
        :next-disabled="dirty || currentRowPosition>=rows.length"
        @previous="navigateRecord(-1)"
        @next="navigateRecord(1)"
      />
    </template>
    <template #list>
      <KbxSearchPanel v-model="searchModel" :fields="searchFields" @search="search" />
      <KbxDataGrid
        ref="masterGrid"
        :rows="rows"
        :columns="itemMasterColumns"
        row-key="id"
        aria-label="품목 목록"
        selection="single"
        :active-row-key="form.id || undefined"
        :loading="query.isFetching.value"
        @selection-changed="requestChoose"
      />
    </template>

    <template #detail>
      <KbxFormSection title="기본정보" description="품목 식별과 분류에 필요한 핵심 기준정보입니다.">
        <KbxFormGrid>
          <KbxInput v-model="form.code" label="품목코드" required :readonly="Boolean(form.id) || state('code')" :error="errors.find(x => x.field === 'code')?.message" @update:model-value="touch" />
          <KbxInput v-model="form.name" label="품목명" required :readonly="state('name')" :error="errors.find(x => x.field === 'name')?.message" @update:model-value="touch" />
          <KbxInput v-model="form.categoryName" label="품목그룹" :readonly="state('categoryName')" @update:model-value="touch" />
          <KbxInput v-model="form.specification" label="규격" :readonly="state('specification')" @update:model-value="touch" />
          <KbxInput v-model="form.unit" label="단위" :readonly="state('unit')" @update:model-value="touch" />
        </KbxFormGrid>
      </KbxFormSection>

      <KbxFormSection title="물류정보" description="창고·바코드·LOT 등 현장 처리 기준을 정의합니다.">
        <KbxFormGrid>
          <KbxLookup v-model="form.defaultWarehouseId" entity="warehouse" label="기본창고" :readonly="state('defaultWarehouseId')" @selected="touch" />
          <KbxBarcodeField v-model="form.barcode" label="바코드" :readonly="state('barcode')" @update:model-value="touch" />
          <KbxCheckbox v-model="form.lotManaged" label="LOT 관리" :disabled="state('lotManaged')" @update:model-value="touch" />
          <KbxCheckbox v-model="form.expiryManaged" label="유통기한 관리" :disabled="state('expiryManaged')" @update:model-value="touch" />
        </KbxFormGrid>
      </KbxFormSection>
    </template>
  </KbxMasterPage>

  <KbxUnsavedChangesDialog
    :open="Boolean(pendingTransition)"
    :saving="savingTransition"
    @stay="pendingTransition=null"
    @discard="applyPendingTransition"
    @save="saveThenTransition"
  />
</template>
