<script setup lang="ts">
import { computed, inject, nextTick, ref } from 'vue'
import { isKbxProblem, type KbxProblem } from '@kbx/contracts'
import { useQuery } from '@tanstack/vue-query'
import { useRouter } from 'vue-router'
import {
  KbxButton,
  KbxDataGrid,
  KbxDrawer,
  KbxPermissionHostKey,
  KbxQuickFilterBar,
  KbxReconcilePage,
  KbxSearchPanel,
  useKbxPageShortcuts,
  type KbxReconcileItem,
} from '@kbx/ui'
import { reconcileApi, type ReconcileFilter } from './reconcileApi'
import { reconcileColumns, reconcileScreen, reconcileSearchFields } from './reconcile.definition'

const searchModel = ref<Record<string, unknown>>({ status: 'mismatch' })
const applied = ref<ReconcileFilter>({ status: 'mismatch', page: 1, pageSize: 200 })
const selectedRows = ref<KbxReconcileItem[]>([])
const detail = ref<KbxReconcileItem | null>(null)
const problem=ref<KbxProblem|null>(null)
const exceptionReceipt=ref<{requested:number;created:number;skipped:number}|null>(null)
const exceptionReceiptEl=ref<HTMLElement|null>(null)
const router=useRouter()
let retryAction:(()=>Promise<void>)|null=null
const permissionHost=inject(KbxPermissionHostKey,null)
const canCreateException=computed(()=>permissionHost?.has('common.operations.create')??true)
const query = useQuery({ queryKey: computed(() => ['reconcile', applied.value]), queryFn: () => reconcileApi.search(applied.value) })
const pageContext=computed(()=>({label:'대사 결과',hint:'원천값을 직접 수정하지 않고 불일치는 해결 업무로 전환합니다.',metrics:[{key:'mismatch',label:'불일치',value:query.data.value?.summary.mismatchCount??0,tone:'danger' as const,emphasis:true},{key:'selected',label:'선택',value:selectedRows.value.length}]}))
const quickFilters=computed(()=>{
  const summary=query.data.value?.summary
  const active=String(searchModel.value.status??'')
  return [
    {key:'all',label:'전체',count:summary?.totalCount??0,active:active===''},
    {key:'mismatch',label:'불일치',count:summary?.mismatchCount??0,active:active==='mismatch',tone:'danger' as const},
    {key:'pending',label:'확인중',count:summary?.pendingCount??0,active:active==='pending',tone:'warning' as const},
    {key:'resolved',label:'해결',count:summary?.resolvedCount??0,active:active==='resolved'},
  ]
})

useKbxPageShortcuts([{ key: 'F3', execute: () => search() }])

function stringOrNull(value: unknown) { return value == null || value === '' ? null : String(value) }
async function search() {
  problem.value=null; retryAction=null
  selectedRows.value = []
  detail.value = null
  exceptionReceipt.value = null
  applied.value = {
    reconcileType: stringOrNull(searchModel.value.reconcileType),
    status: stringOrNull(searchModel.value.status),
    keyword: stringOrNull(searchModel.value.keyword),
    page: 1,
    pageSize: 200,
  }
  await query.refetch()
}
async function selectResultFilter(key:string){
  searchModel.value={...searchModel.value,status:key==='all'?null:key}
  await search()
}
async function runProblemAware(action:()=>Promise<void>){problem.value=null;retryAction=null;try{await action()}catch(error){if(isKbxProblem(error)){problem.value=error;retryAction=()=>runProblemAware(action);return}throw error}}
async function createExceptions(ids:string[]){
  if(!ids.length)return
  return runProblemAware(async()=>{
    const result=await reconcileApi.createExceptions(ids)
    exceptionReceipt.value={requested:ids.length,created:result.createdOrUpdatedCount,skipped:result.skippedCount}
    selectedRows.value=[]
    detail.value=null
    await query.refetch()
    await nextTick();exceptionReceiptEl.value?.focus({preventScroll:true})
  })
}
async function command(id: string) {
  if (id === 'search') return search()
  if (id === 'createException' && selectedRows.value.length) return createExceptions(selectedRows.value.map(x=>x.id))
}
async function createDetailException(){if(detail.value&&canCreateException.value)await createExceptions([detail.value.id])}
async function retryProblem(){const retry=retryAction;problem.value=null;retryAction=null;if(retry)await retry()}
function dismissProblem(){problem.value=null;retryAction=null}
async function openExceptionCenter(){await router.push('/operations/exceptions')}
</script>

<template>
  <KbxReconcilePage
    :screen="reconcileScreen"
    :summary="query.data.value?.summary"
    :selection-count="selectedRows.length"
    :context="pageContext"
    :content-state="query.error.value?'error':query.isFetching.value&&!query.data.value?'loading':query.data.value?.summary.totalCount===0?'empty':'ready'"
    :refreshing="query.isFetching.value&&Boolean(query.data.value)"
    :problem="problem"
    comparison-title="OMS ↔ WMS 대사"
    comparison-description="Expected · Actual · Difference · Reason · Resolution 순서로 불일치 근거를 확인합니다. 행을 두 번 클릭하면 근거를 유지한 채 상세를 확인합니다."
    :comparison-count="query.data.value?.summary.totalCount ?? 0"
    @command="command"
    @retry-problem="retryProblem"
    @dismiss-problem="dismissProblem"
  >
    <template #search><KbxSearchPanel v-model="searchModel" :fields="reconcileSearchFields" @search="search" /></template>
    <template #filters><KbxQuickFilterBar :items="quickFilters" aria-label="대사 결과 빠른 필터" @select="selectResultFilter" /></template>
    <template #comparison-actions><KbxButton label="선택건 예외 등록" variant="secondary" :disabled="selectedRows.length===0 || !canCreateException" :title="!canCreateException?'예외 등록 권한이 없습니다.':undefined" @click="command('createException')" /></template>
    <template #content>
      <section v-if="exceptionReceipt" ref="exceptionReceiptEl" class="reconcile-receipt" :data-result="exceptionReceipt.skipped>0?'partial':'success'" role="status" aria-live="polite" tabindex="-1" data-kbx-surface="reconcile-authoritative-receipt">
        <div>
          <strong>{{ exceptionReceipt.skipped>0 ? '예외 업무 등록이 일부 처리되었습니다.' : '예외 업무 등록을 완료했습니다.' }}</strong>
          <span>요청 {{ exceptionReceipt.requested.toLocaleString('ko-KR') }}건 · 생성/갱신 {{ exceptionReceipt.created.toLocaleString('ko-KR') }}건 · 건너뜀 {{ exceptionReceipt.skipped.toLocaleString('ko-KR') }}건</span>
          <small v-if="exceptionReceipt.skipped>0">건너뜀은 실행 시점에 더 이상 불일치/확인중 상태가 아니었던 건입니다. 서버의 최신 대사 상태를 기준으로 다음 행동을 결정하세요.</small>
        </div>
        <div class="reconcile-receipt__actions">
          <KbxButton v-if="exceptionReceipt.created>0" label="예외 센터 보기" variant="secondary" @click="openExceptionCenter" />
          <KbxButton v-if="exceptionReceipt.skipped>0" label="최신 대사 조회" variant="secondary" @click="search" />
          <KbxButton label="결과 닫기" variant="ghost" @click="exceptionReceipt=null" />
        </div>
      </section>
      <KbxDataGrid
        :rows="query.data.value?.items ?? []"
        :columns="reconcileColumns"
        row-key="id"
        selection="multiple"
        :loading="query.isFetching.value"
        @selection-changed="selectedRows = $event"
        @row-double-clicked="detail = $event"
      />
    </template>
    <template #detail>
      <KbxDrawer :open="Boolean(detail)" :title="detail ? `대사 근거 · ${detail.referenceNo}` : '대사 근거'" @update:open="value=>{if(!value)detail=null}">
        <article v-if="detail" class="reconcile-detail" :data-status="detail.status">
          <header>
            <span>{{ detail.reconcileType }}</span>
            <strong>{{ detail.status==='mismatch'?'불일치':detail.status==='pending'?'확인중':detail.status==='resolved'?'해결':'정상' }}</strong>
          </header>
          <section class="reconcile-detail__comparison" aria-label="대사 비교 근거">
            <div><span>{{ detail.sourceLabel }}</span><small>Expected</small><strong>{{ detail.expectedValue }}</strong></div>
            <div class="difference"><span>Difference</span><small>차이</small><strong>{{ detail.differenceValue ?? '-' }}</strong></div>
            <div><span>{{ detail.targetLabel }}</span><small>Actual</small><strong>{{ detail.actualValue }}</strong></div>
          </section>
          <dl>
            <div><dt>참조번호</dt><dd>{{ detail.referenceNo }}</dd></div>
            <div><dt>원인</dt><dd>{{ detail.reasonText || '원인 미확인' }}</dd></div>
            <div><dt>확인시각</dt><dd>{{ detail.occurredAt }}</dd></div>
            <div><dt>처리상태</dt><dd>{{ detail.status }}</dd></div>
          </dl>
          <p class="reconcile-detail__policy">원천값을 화면에서 직접 덮어쓰지 않습니다. 불일치는 예외 업무로 등록한 뒤 담당자가 근거와 업무 규칙을 확인합니다.</p>
        </article>
        <template #footer>
          <div class="reconcile-detail__actions">
            <KbxButton label="닫기" variant="secondary" @click="detail=null" />
            <KbxButton v-if="detail && ['mismatch','pending'].includes(detail.status)" label="이 건 예외 등록" variant="primary" :disabled="!canCreateException" :title="!canCreateException?'예외 등록 권한이 없습니다.':undefined" @click="createDetailException" />
          </div>
        </template>
      </KbxDrawer>
    </template>
    <template #footer>불일치 건은 원천 데이터를 직접 수정하지 않고 예외 업무로 전환해 담당자가 원인을 확인합니다.</template>
  </KbxReconcilePage>
</template>

<style scoped>
.reconcile-receipt{display:flex;align-items:center;justify-content:space-between;gap:var(--kbx-space-3);margin-bottom:var(--kbx-space-3);padding:var(--kbx-space-2) var(--kbx-space-3);border:var(--kbx-border-width) solid var(--kbx-color-success-border);border-left:var(--kbx-accent-border-width) solid var(--kbx-color-success-border);background:var(--kbx-color-success-surface);font-size:var(--kbx-font-sm)}.reconcile-receipt[data-result="partial"]{border-color:var(--kbx-color-warning-border);border-left-color:var(--kbx-color-warning-border);background:var(--kbx-color-warning-surface)}.reconcile-receipt>div:first-child{display:grid;gap:var(--kbx-space-1)}.reconcile-receipt span,.reconcile-receipt small{color:var(--kbx-color-text-muted)}.reconcile-receipt__actions{display:flex;flex-wrap:wrap;justify-content:flex-end;gap:var(--kbx-space-2)}
.reconcile-detail{display:grid;gap:var(--kbx-space-4)}.reconcile-detail>header{display:flex;align-items:center;justify-content:space-between;gap:var(--kbx-space-2);padding:var(--kbx-space-2) var(--kbx-space-3);border:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface-muted)}.reconcile-detail[data-status="mismatch"]>header{border-color:var(--kbx-color-danger-border);background:var(--kbx-color-danger-surface)}.reconcile-detail__comparison{display:grid;grid-template-columns:minmax(0,1fr) minmax(var(--kbx-summary-item-min-width),.7fr) minmax(0,1fr);border:var(--kbx-border-width) solid var(--kbx-color-border)}.reconcile-detail__comparison>div{display:grid;gap:var(--kbx-space-1);padding:var(--kbx-space-3);border-right:var(--kbx-border-width) solid var(--kbx-color-border)}.reconcile-detail__comparison>div:last-child{border-right:0}.reconcile-detail__comparison span,.reconcile-detail__comparison small{color:var(--kbx-color-text-muted);font-size:var(--kbx-font-xs)}.reconcile-detail__comparison strong{font-size:var(--kbx-font-xl)}.reconcile-detail__comparison .difference strong{color:var(--kbx-color-danger)}.reconcile-detail dl{display:grid;gap:var(--kbx-space-2);margin:0}.reconcile-detail dl div{display:grid;grid-template-columns:var(--kbx-label-width) minmax(0,1fr);gap:var(--kbx-space-2)}.reconcile-detail dt{color:var(--kbx-color-text-muted)}.reconcile-detail dd{margin:0}.reconcile-detail__policy{margin:0;padding:var(--kbx-space-3);border-left:var(--kbx-accent-border-width) solid var(--kbx-color-warning-border);background:var(--kbx-color-warning-surface);font-size:var(--kbx-font-sm)}.reconcile-detail__actions{display:flex;justify-content:flex-end;gap:var(--kbx-space-2)}
@media(max-width:60rem){.reconcile-receipt{align-items:stretch;flex-direction:column}.reconcile-receipt__actions{justify-content:flex-start}}
@media(max-width:40rem){.reconcile-detail__comparison{grid-template-columns:1fr}.reconcile-detail__comparison>div{border-right:0;border-bottom:var(--kbx-border-width) solid var(--kbx-color-border)}.reconcile-detail__comparison>div:last-child{border-bottom:0}}
@media(forced-colors:active){.reconcile-receipt,.reconcile-receipt[data-result="partial"]{border-color:CanvasText;border-left-color:Highlight}}
</style>
