<script setup lang="ts">
import { computed, nextTick, ref, watch } from 'vue'
import type {
  KbxImportDefinition,
  KbxImportMapping,
  KbxImportSession,
  KbxImportProgressEvent,
} from '@kbx/contracts'
import KbxButton from './KbxButton.vue'
import KbxConfirm from './KbxConfirm.vue'
import KbxJobProgress from './KbxJobProgress.vue'
import KbxProgressSteps from './KbxProgressSteps.vue'
import { validateKbxImportFileCandidate, validateKbxImportMappings } from '../excel/importGuard'

const props = defineProps<{
  definition: KbxImportDefinition
  session: KbxImportSession | null
  busy?: boolean
  progress?: KbxImportProgressEvent | null
}>()

const emit = defineEmits<{
  upload: [File]
  saveMapping: [KbxImportMapping[]]
  saveNamedMapping: [string, KbxImportMapping[]]
  validate: []
  commit: []
  downloadTemplate: []
  downloadErrors: []
  cancel: []
}>()

const root = ref<HTMLElement | null>(null)
const fileInput = ref<HTMLInputElement | null>(null)
const localMapping = ref<KbxImportMapping[] | null>(null)
const dragActive = ref(false)
const mappingName = ref('')
const clientIssue = ref('')
const commitConfirmOpen = ref(false)
const errorIndex = ref(0)

const importSteps = [
  { key: 'file', label: '파일' },
  { key: 'mapping', label: '매핑' },
  { key: 'validation', label: '검증' },
  { key: 'preview', label: '미리보기' },
  { key: 'commit', label: '반영' },
  { key: 'result', label: '결과' },
]

const stage = computed<'file' | 'mapping' | 'validation' | 'preview' | 'commit' | 'result'>(() => {
  switch (props.session?.status) {
    case 'uploaded':
    case 'mapping-required':
      return 'mapping'
    case 'validating':
      return 'validation'
    case 'validated':
      return 'preview'
    case 'committing':
      return 'commit'
    case 'completed':
    case 'partially-completed':
    case 'failed':
    case 'cancelled':
      return 'result'
    case 'created':
    default:
      return 'file'
  }
})

watch(() => props.session?.id, () => {
  localMapping.value = null
  mappingName.value = ''
  clientIssue.value = ''
  commitConfirmOpen.value = false
  errorIndex.value = 0
})
watch(() => props.session?.errors?.length, () => { errorIndex.value = 0 })
watch(stage, async (current, previous) => {
  if (current === previous) return
  await nextTick()
  const heading = root.value?.querySelector<HTMLElement>(`[data-kbx-surface="${current}"] [data-kbx-stage-heading]`)
  heading?.focus({ preventScroll: true })
})

const mappings = computed({
  get: () => localMapping.value ?? (props.session?.mapping ?? []),
  set: value => { localMapping.value = value },
})

const importableFields = computed(() => props.definition.fields.filter(field => field.importable !== false))
const mappingIssues = computed(() => validateKbxImportMappings(props.definition, mappings.value))
const canValidate = computed(() => mappings.value.length > 0 && mappingIssues.value.length === 0 && !props.busy)
const canCommit = computed(() => (props.session?.validRows ?? 0) > 0 && props.session?.status === 'validated' && !props.busy)
const isFailed = computed(() => props.session?.status === 'failed')
const isCancelled = computed(() => props.session?.status === 'cancelled')
const isPartial = computed(() => props.session?.status === 'partially-completed')
const previewErrors = computed(() => (props.session?.errors ?? []).slice(0, 100))
const hasProjectedChangeCounts = computed(() => ((props.session?.createdRows ?? 0) + (props.session?.updatedRows ?? 0)) > 0)
const sessionContext = computed(() => {
  if (!props.session) return ''
  const rows = props.session.totalRows > 0 ? ` · 전체 ${props.session.totalRows.toLocaleString('ko-KR')}건` : ''
  return `${props.session.fileName}${rows}`
})

function chooseFile() { clientIssue.value = ''; fileInput.value?.click() }
function onFiles(files: FileList | null) {
  const file = files?.item(0)
  if (!file) return
  clientIssue.value = ''
  const issue = validateKbxImportFileCandidate(props.definition, file)
  if (issue) { clientIssue.value = issue; return }
  emit('upload', file)
}
function onDrop(event: DragEvent) {
  dragActive.value = false
  onFiles(event.dataTransfer?.files ?? null)
}
function setTarget(index: number, targetField: string) {
  const next = mappings.value.map((item, itemIndex) => itemIndex === index
    ? { ...item, targetField: targetField || null, source: 'manual' as const, confidence: undefined, reason: undefined }
    : item)
  mappings.value = next
  emit('saveMapping', next)
}
function requestCommit() { if (canCommit.value) commitConfirmOpen.value = true }
function confirmCommit() { commitConfirmOpen.value = false; emit('commit') }
async function focusError(index: number) {
  if (!previewErrors.value.length) return
  errorIndex.value = Math.max(0, Math.min(index, previewErrors.value.length - 1))
  await nextTick()
  root.value?.querySelector<HTMLElement>(`[data-import-error-index="${errorIndex.value}"]`)?.focus()
}
function focusNextError() {
  if (!previewErrors.value.length) return
  void focusError((errorIndex.value + 1) % previewErrors.value.length)
}
</script>

<template>
  <section ref="root" class="kbx-import" data-kbx-component="excel-import" :data-stage="stage" :aria-busy="busy || undefined">
    <header class="kbx-import__header">
      <div>
        <h2>{{ definition.title }}</h2>
        <p>파일 → 매핑 → 검증 → 미리보기 → 반영 → 결과 순서로 처리합니다. 원본 행 번호와 오류 이력은 유지됩니다.</p>
      </div>
      <KbxButton label="업로드 양식 다운로드" variant="secondary" @click="emit('downloadTemplate')" />
    </header>

    <KbxProgressSteps data-kbx-surface="progress-steps" :steps="importSteps" :current="stage" />

    <div v-if="session && stage !== 'file'" class="kbx-import__session-context" data-kbx-surface="import-context" aria-label="현재 업로드">
      <strong>현재 파일</strong><span>{{ sessionContext }}</span>
      <span v-if="session.validRows || session.invalidRows">정상 {{ session.validRows.toLocaleString('ko-KR') }} · 오류 {{ session.invalidRows.toLocaleString('ko-KR') }}</span>
    </div>

    <div v-if="stage === 'file'" class="kbx-import__drop" data-kbx-surface="file"
      :class="{ 'is-dragging': dragActive }"
      @dragover.prevent="dragActive = true"
      @dragleave.prevent="dragActive = false"
      @drop.prevent="onDrop">
      <input ref="fileInput" hidden type="file" accept=".xlsx,application/vnd.openxmlformats-officedocument.spreadsheetml.sheet" @change="onFiles(($event.target as HTMLInputElement).files)">
      <h3 data-kbx-stage-heading tabindex="-1">Excel 파일을 선택하세요.</h3>
      <span>.xlsx · 최대 {{ Math.round((definition.maxFileSizeBytes ?? 10485760) / 1048576) }}MB</span>
      <KbxButton label="파일 선택" variant="primary" :loading="busy" @click="chooseFile" />
      <small>Drag & Drop은 보조 기능이며 파일 선택 버튼은 항상 제공됩니다.</small>
      <p v-if="clientIssue" class="kbx-import__client-error" role="alert">{{ clientIssue }}</p>
    </div>

    <section v-else-if="stage === 'mapping'" class="kbx-import__mapping" data-kbx-surface="mapping">
      <header class="kbx-import__stage-head">
        <div><h3 data-kbx-stage-heading tabindex="-1">필드 매핑</h3><span>Excel 열과 시스템 필드를 1:1로 확인합니다. AI 추천은 확정값이 아닙니다.</span></div>
        <small>필수 필드는 모두 매핑되어야 검증을 시작할 수 있습니다.</small>
      </header>
      <div class="mapping-head"><span>Excel 열</span><span>시스템 필드</span><span>매핑 근거</span></div>
      <div v-for="(mapping, index) in mappings" :key="mapping.sourceColumn" class="mapping-row">
        <strong>{{ mapping.sourceColumn }}</strong>
        <select :value="mapping.targetField ?? ''" :aria-invalid="mapping.targetField ? mappings.filter(item=>item.targetField===mapping.targetField).length>1 : undefined" @change="setTarget(index, ($event.target as HTMLSelectElement).value)">
          <option value="">매핑하지 않음</option>
          <option v-for="field in importableFields" :key="field.key" :value="field.key">
            {{ field.label }}{{ field.required ? ' *' : '' }}
          </option>
        </select>
        <span class="mapping-reason">
          {{ mapping.source === 'ai' ? `AI 추천 ${Math.round((mapping.confidence ?? 0) * 100)}%` : mapping.reason ?? mapping.source }}
        </span>
      </div>
      <div v-if="mappingIssues.length" class="mapping-issues" role="alert">
        <strong>매핑을 확인하세요.</strong>
        <ul><li v-for="issue in mappingIssues" :key="`${issue.code}:${issue.fieldKey}`">{{ issue.message }}</li></ul>
      </div>
      <div class="mapping-save">
        <label>다음에도 사용할 매핑 이름 <input v-model="mappingName" maxlength="80" placeholder="예: 쿠팡 주문양식"></label>
        <KbxButton label="매핑 저장" variant="secondary" :disabled="!mappingName.trim() || Boolean(mappingIssues.length)" @click="emit('saveNamedMapping', mappingName.trim(), mappings)" />
      </div>
      <div class="kbx-import__actions">
        <KbxButton label="취소" variant="secondary" @click="emit('cancel')" />
        <KbxButton label="검증 시작" variant="primary" :disabled="!canValidate" :loading="busy" @click="emit('validate')" />
      </div>
    </section>

    <section v-else-if="stage === 'validation'" class="kbx-import__validation" data-kbx-surface="validation">
      <header class="kbx-import__stage-head"><div><h3 data-kbx-stage-heading tabindex="-1">Staging 검증</h3><span>형식·참조·업무규칙·중복을 검사합니다. 아직 Domain DB에는 반영하지 않습니다.</span></div></header>
      <KbxJobProgress :progress="progress ?? null" />
      <p v-if="!progress" class="kbx-import__job-note">검증 작업이 시작되었습니다. 서버 상태를 동기화하고 있습니다.</p>
    </section>

    <section v-else-if="stage === 'preview'" class="kbx-import__preview" data-kbx-surface="preview">
      <header class="kbx-import__stage-head">
        <div><h3 data-kbx-stage-heading tabindex="-1">반영 미리보기</h3><span>정상 데이터만 반영 대상이며 오류 데이터는 제외됩니다.</span></div>
        <small>반영 전 마지막으로 건수와 오류를 확인하세요.</small>
      </header>

      <div class="summary" aria-label="검증 결과">
        <div><span>전체</span><strong>{{ session?.totalRows.toLocaleString('ko-KR') }}</strong></div>
        <div><span>정상</span><strong>{{ session?.validRows.toLocaleString('ko-KR') }}</strong></div>
        <div><span>오류</span><strong>{{ session?.invalidRows.toLocaleString('ko-KR') }}</strong></div>
        <div><span>경고</span><strong>{{ session?.warningRows.toLocaleString('ko-KR') }}</strong></div>
      </div>

      <div v-if="hasProjectedChangeCounts" class="kbx-import__projection" aria-label="반영 예정 건수">
        <span>예상 신규 <strong>{{ session?.createdRows.toLocaleString('ko-KR') }}</strong></span>
        <span>예상 수정 <strong>{{ session?.updatedRows.toLocaleString('ko-KR') }}</strong></span>
        <span>제외 오류 <strong>{{ session?.invalidRows.toLocaleString('ko-KR') }}</strong></span>
      </div>
      <p v-else class="kbx-import__projection-note">반영 대상 {{ session?.validRows.toLocaleString('ko-KR') }}건 · 제외 오류 {{ session?.invalidRows.toLocaleString('ko-KR') }}건입니다. 신규/수정 구분은 서버 Preview가 제공하는 경우에만 표시합니다.</p>

      <div v-if="previewErrors.length" class="errors" data-kbx-surface="validation-errors">
        <div class="errors__toolbar">
          <div><strong>오류/경고</strong><span>현재 {{ errorIndex + 1 }} / {{ previewErrors.length }} · 화면에는 최대 100건</span></div>
          <div class="errors__toolbar-actions">
            <KbxButton label="첫 오류" variant="secondary" @click="focusError(0)" />
            <KbxButton label="다음 오류" variant="secondary" @click="focusNextError" />
            <KbxButton label="오류파일 다운로드" variant="secondary" @click="emit('downloadErrors')" />
          </div>
        </div>
        <div class="errors__head"><span>행</span><span>필드</span><span>사유</span></div>
        <div
          v-for="(error, index) in previewErrors"
          :key="`${error.rowNumber}-${error.field}-${error.code}`"
          class="errors__row"
          :data-severity="error.severity"
          :data-import-error-index="index"
          :aria-current="index === errorIndex ? 'true' : undefined"
          tabindex="-1"
        >
          <span>{{ error.rowNumber }}</span><span>{{ error.sourceColumn ?? error.field ?? '-' }}</span><span>{{ error.message }}</span>
        </div>
        <small v-if="(session?.errors?.length ?? 0) > 100">전체 오류는 오류파일로 확인하세요.</small>
      </div>

      <div class="kbx-import__actions">
        <KbxButton v-if="session?.invalidRows" label="오류파일 다운로드" variant="secondary" @click="emit('downloadErrors')" />
        <KbxButton label="정상 데이터 반영" variant="primary" :disabled="!canCommit" @click="requestCommit" />
      </div>
    </section>

    <section v-else-if="stage === 'commit'" class="kbx-import__commit" data-kbx-surface="commit">
      <header class="kbx-import__stage-head"><div><h3 data-kbx-stage-heading tabindex="-1">반영 작업</h3><span>백그라운드 작업으로 처리하며 화면을 이동해도 서버 작업은 계속됩니다.</span></div></header>
      <KbxJobProgress :progress="progress ?? null" />
      <p v-if="!progress" class="kbx-import__job-note">반영 작업이 시작되었습니다. 서버 상태를 동기화하고 있습니다.</p>
      <p class="kbx-import__job-note">진행 상태는 Job Center에서 다시 확인할 수 있습니다. 중복 반영을 피하기 위해 완료 전 같은 파일을 다시 제출하지 마세요.</p>
    </section>

    <section v-else class="kbx-import__result" data-kbx-surface="result" :data-result="session?.status">
      <header class="kbx-import__stage-head"><div><h3 data-kbx-stage-heading tabindex="-1">반영 결과</h3><span>성공·부분성공·실패를 구분하고 실패 데이터만 다시 처리할 수 있습니다.</span></div></header>
      <div v-if="isFailed" class="kbx-import__terminal is-error" role="alert">
        <strong>{{ session?.failure?.title ?? '반영 작업을 완료하지 못했습니다.' }}</strong>
        <p>{{ session?.failure?.detail ?? '원본 업로드와 작업 이력은 유지됩니다. 원인을 확인한 뒤 다시 진행하세요.' }}</p>
        <small v-if="session?.failure?.code">오류코드 {{ session.failure.code }}</small>
        <div class="kbx-import__actions"><KbxButton label="새 파일로 다시 시작" variant="secondary" @click="emit('cancel')" /></div>
      </div>
      <div v-else-if="isCancelled" class="kbx-import__terminal">
        <strong>반영 작업이 취소되었습니다.</strong><p>새 파일을 선택해 다시 시작할 수 있습니다.</p>
        <div class="kbx-import__actions"><KbxButton label="새 파일 선택" variant="secondary" @click="emit('cancel')" /></div>
      </div>
      <template v-else>
        <div class="summary summary--result" :class="{'is-partial':isPartial}">
          <div><span>신규</span><strong>{{ session?.createdRows.toLocaleString('ko-KR') }}</strong></div>
          <div><span>수정</span><strong>{{ session?.updatedRows.toLocaleString('ko-KR') }}</strong></div>
          <div><span>오류</span><strong>{{ session?.invalidRows.toLocaleString('ko-KR') }}</strong></div>
        </div>
        <p v-if="isPartial" class="kbx-import__partial">정상 데이터는 반영되었고 오류 데이터는 제외되었습니다. 오류파일로 실패 건만 다시 처리할 수 있습니다.</p>
        <div class="kbx-import__actions">
          <KbxButton v-if="isPartial && session?.invalidRows" label="오류파일 다운로드" variant="secondary" @click="emit('downloadErrors')" />
          <KbxButton label="새 업로드" variant="primary" @click="emit('cancel')" />
        </div>
      </template>
    </section>

    <KbxConfirm
      :open="commitConfirmOpen"
      title="정상 데이터를 반영하시겠습니까?"
      :detail="`${(session?.validRows ?? 0).toLocaleString('ko-KR')}건을 반영합니다. 오류 ${(session?.invalidRows ?? 0).toLocaleString('ko-KR')}건은 제외됩니다.`"
      level="high"
      :confirm-label="`${(session?.validRows ?? 0).toLocaleString('ko-KR')}건 반영`"
      @update:open="commitConfirmOpen=$event"
      @confirm="confirmCommit"
    />
  </section>
</template>

<style scoped>
.kbx-import{display:grid;gap:var(--kbx-space-4);min-width:0}.kbx-import__header{display:flex;justify-content:space-between;gap:var(--kbx-space-4);align-items:flex-start}.kbx-import__header h2{margin:0 0 var(--kbx-space-1);font-size:var(--kbx-font-xl)}.kbx-import__header p{margin:0;color:var(--kbx-color-text-muted);font-size:var(--kbx-font-sm)}.kbx-import__session-context{min-height:var(--kbx-control-height);display:flex;align-items:center;gap:var(--kbx-space-3);padding:0 var(--kbx-space-3);border:var(--kbx-border-width) solid var(--kbx-color-border);border-left:var(--kbx-accent-border-width) solid var(--kbx-color-module-accent);background:var(--kbx-color-surface-muted);font-size:var(--kbx-font-sm)}.kbx-import__session-context span:last-child{margin-left:auto;color:var(--kbx-color-text-muted)}.kbx-import__drop{min-height:var(--kbx-import-drop-min-height);border:var(--kbx-border-width) dashed var(--kbx-color-border-strong);display:flex;flex-direction:column;align-items:center;justify-content:center;gap:var(--kbx-space-2);background:var(--kbx-color-surface-muted)}.kbx-import__drop.is-dragging{outline:calc(var(--kbx-border-width) * 2) solid var(--kbx-color-focus);outline-offset:calc(var(--kbx-border-width) * -2)}.kbx-import__drop>span,.kbx-import__drop small{color:var(--kbx-color-text-muted)}.kbx-import__client-error{margin:0;color:var(--kbx-color-danger);font-size:var(--kbx-font-sm)}.kbx-import__mapping,.kbx-import__validation,.kbx-import__preview,.kbx-import__commit,.kbx-import__result{border:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface);padding:var(--kbx-space-3)}.kbx-import__stage-head{display:flex;align-items:flex-start;justify-content:space-between;gap:var(--kbx-space-4);padding-bottom:var(--kbx-space-3);border-bottom:var(--kbx-border-width) solid var(--kbx-color-border);margin-bottom:var(--kbx-space-3)}.kbx-import__stage-head>div{display:grid;gap:var(--kbx-space-1)}.kbx-import__stage-head h3,.kbx-import__drop h3{margin:0;font-size:var(--kbx-font-md)}.kbx-import__stage-head [data-kbx-stage-heading]:focus-visible,.kbx-import__drop [data-kbx-stage-heading]:focus-visible{outline:calc(var(--kbx-border-width) * 2) solid var(--kbx-color-focus);outline-offset:var(--kbx-space-1)}.kbx-import__stage-head span,.kbx-import__stage-head small{color:var(--kbx-color-text-muted);font-size:var(--kbx-font-sm)}.mapping-head,.mapping-row{display:grid;grid-template-columns:minmax(var(--kbx-import-source-column-min-width),1fr) minmax(var(--kbx-import-target-column-min-width),1.3fr) minmax(var(--kbx-import-reason-column-min-width),.8fr);gap:var(--kbx-space-3);min-height:var(--kbx-control-md);align-items:center;border-bottom:var(--kbx-border-width) solid var(--kbx-color-border)}.mapping-head{font-size:var(--kbx-font-xs);font-weight:600;color:var(--kbx-color-text-muted)}.mapping-row select{height:var(--kbx-control-height);border:var(--kbx-border-width) solid var(--kbx-color-border-strong);border-radius:var(--kbx-radius-sm);background:var(--kbx-color-surface)}.mapping-row select[aria-invalid="true"]{border-color:var(--kbx-color-danger);background:var(--kbx-color-danger-surface)}.mapping-reason{font-size:var(--kbx-font-xs);color:var(--kbx-color-text-muted)}.mapping-issues{margin-top:var(--kbx-space-3);padding:var(--kbx-space-3);border:var(--kbx-border-width) solid var(--kbx-color-danger-border);background:var(--kbx-color-danger-surface);font-size:var(--kbx-font-sm)}.mapping-issues ul{margin:var(--kbx-space-2) 0 0;padding-left:var(--kbx-space-5)}.mapping-save{display:flex;align-items:center;justify-content:flex-end;gap:var(--kbx-space-2);padding-top:var(--kbx-space-3)}.mapping-save label{display:flex;align-items:center;gap:var(--kbx-space-2);font-size:var(--kbx-font-sm)}.mapping-save input{width:var(--kbx-import-mapping-name-width);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)}.summary{display:grid;grid-template-columns:repeat(4,minmax(var(--kbx-summary-item-min-width),1fr));border:var(--kbx-border-width) solid var(--kbx-color-border);margin-bottom:var(--kbx-space-3)}.summary>div{padding:var(--kbx-space-3);display:flex;justify-content:space-between;border-right:var(--kbx-border-width) solid var(--kbx-color-border)}.summary>div:last-child{border-right:0}.summary strong{font-size:var(--kbx-font-xl)}.summary--result{grid-template-columns:repeat(3,minmax(var(--kbx-summary-item-min-width),1fr))}.summary--result.is-partial{border-color:var(--kbx-color-warning-border)}.kbx-import__projection{display:flex;flex-wrap:wrap;gap:var(--kbx-space-4);padding:var(--kbx-space-2) var(--kbx-space-3);margin-bottom:var(--kbx-space-3);border:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface-muted);font-size:var(--kbx-font-sm)}.kbx-import__projection-note,.kbx-import__job-note{margin:0 0 var(--kbx-space-3);padding:var(--kbx-space-2) var(--kbx-space-3);border-left:var(--kbx-accent-border-width) solid var(--kbx-color-info-border);background:var(--kbx-color-info-surface);color:var(--kbx-color-text-muted);font-size:var(--kbx-font-sm)}.errors{border:var(--kbx-border-width) solid var(--kbx-color-border)}.errors__toolbar{display:flex;justify-content:space-between;align-items:center;gap:var(--kbx-space-3);padding:var(--kbx-space-2);border-bottom:var(--kbx-border-width) solid var(--kbx-color-border);background:var(--kbx-color-surface-muted)}.errors__toolbar>div:first-child{display:grid;gap:var(--kbx-space-1)}.errors__toolbar span{font-size:var(--kbx-font-xs);color:var(--kbx-color-text-muted)}.errors__toolbar-actions{display:flex;gap:var(--kbx-space-2);flex-wrap:wrap}.errors__head,.errors__row{display:grid;grid-template-columns:var(--kbx-import-error-row-width) var(--kbx-import-error-field-width) 1fr;gap:var(--kbx-space-2);min-height:var(--kbx-control-height);align-items:center;padding:0 var(--kbx-space-2);border-bottom:var(--kbx-border-width) solid var(--kbx-color-border);font-size:var(--kbx-font-sm)}.errors__head{font-weight:600;background:var(--kbx-color-surface-muted)}.errors__row[data-severity="warning"]{background:var(--kbx-color-warning-surface)}.errors__row[data-severity="error"]{background:var(--kbx-color-danger-surface)}.errors__row[aria-current="true"]{outline:calc(var(--kbx-border-width) * 2) solid var(--kbx-color-focus);outline-offset:calc(var(--kbx-border-width) * -2)}.errors__row:last-of-type{border-bottom:0}.errors small{display:block;padding:var(--kbx-space-2);color:var(--kbx-color-text-muted)}.kbx-import__terminal{display:grid;gap:var(--kbx-space-2);padding:var(--kbx-space-4);background:var(--kbx-color-surface-muted);border:var(--kbx-border-width) solid var(--kbx-color-border)}.kbx-import__terminal.is-error{background:var(--kbx-color-danger-surface);border-color:var(--kbx-color-danger-border)}.kbx-import__terminal p,.kbx-import__partial{margin:0;color:var(--kbx-color-text-muted);font-size:var(--kbx-font-sm)}.kbx-import__actions{display:flex;justify-content:flex-end;gap:var(--kbx-space-2);margin-top:var(--kbx-space-3)}
@media(max-width:60rem){.kbx-import__header,.kbx-import__stage-head,.errors__toolbar{align-items:stretch;flex-direction:column}.kbx-import__session-context{align-items:flex-start;flex-direction:column;padding:var(--kbx-space-2) var(--kbx-space-3)}.kbx-import__session-context span:last-child{margin-left:0}.mapping-head,.mapping-row{grid-template-columns:minmax(0,1fr)}.mapping-head{display:none}.summary{overflow:auto;grid-template-columns:repeat(4,minmax(var(--kbx-summary-item-min-width),1fr))}}
@media(forced-colors:active){.kbx-import__session-context,.kbx-import__mapping,.kbx-import__validation,.kbx-import__preview,.kbx-import__commit,.kbx-import__result,.errors{border-color:CanvasText}.errors__row[aria-current="true"]{outline:.125rem solid Highlight}}
</style>
