import fs from 'node:fs'

const read=file=>fs.readFileSync(file,'utf8')
const expect=(condition,message)=>{if(!condition){console.error(`FAIL: ${message}`);process.exit(1)}console.log(`PASS: ${message}`)}

function versionAtLeast(source,min){
  const found=source.match(/version\s*:\s*['"](\d+)\.(\d+)\.(\d+)['"]/);if(!found)return false
  const a=found.slice(1).map(Number),b=min.split('.').map(Number)
  return a[0]>b[0]||(a[0]===b[0]&&(a[1]>b[1]||(a[1]===b[1]&&a[2]>=b[2])))
}

const app=read('apps/web/src/App.vue')
expect(/app-version="v\d+-fe-[^"]+"/.test(app),'Runtime metadata remains explicitly versioned for operational reproduction')

const operationsApi=read('apps/web/src/modules/common/operations/operationsApi.ts')
const operations=read('apps/web/src/modules/common/operations/OperationsQueuePage.vue')
expect(operationsApi.includes('ResolveWorkItemsResponse')&&operationsApi.includes('resolvedCount')&&operationsApi.includes('rejectedCount'),'T06 API preserves authoritative resolve counts')
expect(operations.includes("kind:'resolve'")&&operations.includes('succeeded:result.resolvedCount')&&operations.includes('skipped:result.rejectedCount'),'T06 maps HTTP success to authoritative business outcome instead of all-success')
expect(operations.includes('if(result.resolvedCount>0)')&&operations.includes("kbxTelemetry.track('exception.resolved'"),'T06 resolved telemetry is emitted only for actual server-resolved work')
expect(operations.includes('data-kbx-surface="authoritative-action-receipt"')&&operations.includes('actionReceiptEl.value?.focus'),'T06 persists and focuses the authoritative action receipt')
expect(operations.includes('해결 처리가 완료되지 않았습니다.')&&operations.includes('최신 상태 조회'),'T06 partial resolve gives explicit recovery guidance')

const reconcileApi=read('apps/web/src/modules/common/reconcile/reconcileApi.ts')
const reconcile=read('apps/web/src/modules/common/reconcile/ReconcilePage.vue')
expect(reconcileApi.includes('CreateReconcileExceptionsResponse')&&reconcileApi.includes('createdOrUpdatedCount')&&reconcileApi.includes('skippedCount'),'T07 API preserves authoritative exception-creation counts')
expect(reconcile.includes('created:result.createdOrUpdatedCount')&&reconcile.includes('skipped:result.skippedCount'),'T07 consumes server result counts without client inference')
expect(reconcile.includes('data-kbx-surface="reconcile-authoritative-receipt"')&&reconcile.includes('exceptionReceiptEl.value?.focus'),'T07 partial result persists as a focusable recovery receipt')
expect(reconcile.includes("router.push('/operations/exceptions')")&&reconcile.includes('원천값을 화면에서 직접 덮어쓰지 않습니다.'),'T07 routes to resolution work without mutating source truth')

const demo=read('apps/web/src/http/demo/installFrontendDemoApi.ts')
for(const marker of ['sourceModule','referenceNo','ageMinutes','allowManualResolution','expectedValue','actualValue','differenceValue','summary'])expect(demo.includes(marker),`Demo API carries real T06/T07 contract field: ${marker}`)
expect(demo.includes('createdOrUpdatedCount')&&demo.includes('skippedCount'),'Demo Reconcile mirrors authoritative create-exception outcome')
expect(demo.includes("item.status='resolved';item.version++"),'Demo resolve advances server-like version when authoritative state changes')

const picking=read('apps/web/src/modules/wms/picking/useWmsPicking.ts')
expect(picking.includes('type ScopedScanMutation')&&picking.includes('scope: string'),'T09 scan mutation captures issuing tenant/user scope')
expect(picking.includes('removePickingScan(input.command.idempotencyKey, input.scope)')&&picking.includes('queuePickingScan(input.command, input.scope)'),'T09 success/ambiguity always reads or writes the captured issuing scope')
expect(picking.includes('if (input.scope !== retryScope.value) return'),'T09 stale in-flight response cannot mutate a newly switched operator UI')
expect(picking.includes('const scope = retryScope.value')&&picking.includes('removePickingScan(item.command.idempotencyKey, scope)'),'T09 retry replay is pinned to its captured scope for the full async loop')
expect(picking.includes("taskTelemetry?.abandon('scope_changed')")&&picking.includes('lastConfirmedScan.value = null'),'T09 operator scope switch abandons prior task truth and clears prior server-confirmation display')

const pkg=JSON.parse(read('package.json'))
expect(/^pnpm@\d+\.\d+\.\d+$/.test(pkg.packageManager),'Root packageManager uses an exact Corepack-compatible pnpm semver')
expect(Boolean(pkg.devDependencies?.['@playwright/test'])&&Boolean(pkg.devDependencies?.vitest),'Root declares actual Playwright and Vitest test runners')
for(const script of ['test:unit','test:e2e','test:e2e:desktop','test:e2e:wms','test:fe','validate:fe-authoritative-outcome'])expect(Boolean(pkg.scripts?.[script]),`Root exposes executable FE QA script: ${script}`)

const playwright=read('playwright.config.ts')
expect(playwright.includes("baseURL")&&playwright.includes("command: 'pnpm dev:web:demo'"),'Playwright owns baseURL and starts the demo runtime when no external URL is supplied')
expect(playwright.includes('width: 1440, height: 900')&&playwright.includes('desktop-chromium'),'Playwright defines the KBX 1440×900 desktop evidence project')
expect(playwright.includes('width: 390, height: 844')&&playwright.includes('wms-mobile-chromium'),'Playwright defines the KBX 390×844 WMS evidence project')
expect(playwright.includes("trace: 'retain-on-failure'")&&playwright.includes("screenshot: 'only-on-failure'")&&playwright.includes("video: 'retain-on-failure'"),'Playwright retains failure evidence instead of ceremonial pass/fail only')

const opsE2e=read('tests/e2e/common-operations.spec.ts')
expect(opsE2e.includes('resolvedCount: 0, rejectedCount: 1')&&opsE2e.includes('authoritative-action-receipt')&&opsE2e.includes('toBeFocused()'),'T06 E2E proves HTTP 200 + business rejection and focusable recovery receipt')
expect(opsE2e.includes('다음 긴급건')&&opsE2e.includes('출고 연계 지연'),'T06 E2E proves corrected demo DTO drives urgent work navigation')

const recE2e=read('tests/e2e/common-reconcile.spec.ts')
expect(recE2e.includes('createdOrUpdatedCount: 0, skippedCount: 1')&&recE2e.includes('reconcile-authoritative-receipt')&&recE2e.includes('toBeFocused()'),'T07 E2E proves HTTP 200 + skipped business outcome and recovery focus')
expect(recE2e.includes('원천값을 화면에서 직접 덮어쓰지 않습니다'),'T07 E2E protects the source-truth policy in the actual UI flow')

const wmsE2e=read('tests/e2e/wms-picking.spec.ts')
expect(wmsE2e.includes("route.abort('failed')")&&wmsE2e.includes('scanBodies.length).toBe(2)'),'T09 E2E models response loss followed by one safe retry')
expect(wmsE2e.includes('scanBodies[1]?.idempotencyKey).toBe(scanBodies[0]?.idempotencyKey)'),'T09 E2E asserts the exact idempotency key is reused across ambiguous retry')
expect(wmsE2e.includes('아직 확인된 스캔 없음')&&wmsE2e.includes('재전송된 스캔을 서버에서 확인했습니다.'),'T09 E2E separates local scan from server-confirmed truth')

const qualityGate=read('.gitea/workflows/kbx-quality-gate.yaml')
const releaseGate=read('.gitea/workflows/kbx-release-readiness.yaml')
for(const marker of ['pnpm typecheck:web','pnpm test:unit','pnpm build:web:demo','pnpm test:e2e'])expect(qualityGate.includes(marker),`Quality Gate executes runtime FE evidence step when reproducible install is available: ${marker}`)
expect(qualityGate.includes('report-fe-runtime-readiness.mjs')&&qualityGate.includes('not reported as PASS'),'Quality Gate reports missing lockfile as blocked runtime evidence instead of fake PASS')
expect(releaseGate.includes('Frontend runtime evidence is BLOCKED')&&releaseGate.includes('exit 1'),'Release Readiness fails closed when reproducible FE runtime evidence cannot be installed')

const reference=read('apps/web/fe-reference/index.html')
expect(/KBX FE Reference Lab v\d+/.test(reference)&&reference.includes('Runtime Evidence'),'Static HTML reference remains explicitly versioned and identifies runtime-evidence intent')

for(const [file,minimum] of [
  ['apps/web/src/modules/common/operations/operations.definition.ts','1.2.0'],
  ['apps/web/src/modules/common/reconcile/reconcile.definition.ts','1.2.0'],
  ['apps/web/src/modules/wms/picking/picking.definition.ts','1.2.0'],
])expect(versionAtLeast(read(file),minimum),`${file} versions the v57 behavior contract at ${minimum} or later`)

console.log('PASS: FE authoritative outcome + runtime evidence hardening v57 contract')
