/**
 * Structural regression test for App.tsx WS handler registrations.
 *
 * Guards the 5 onOrgSaved* callbacks that MUST all be registered in the
 * socket-handlers object. A missing one is silent (TypeScript-level the
 * callback is optional) and results in broken UX: earlier bug report
 * "can't switch saved orgs" was caused by the 3 of these being absent.
 *
 * Also guards:
 * - Toast state wiring
 * - useCallback stability for client.org* calls (no inline arrows in
 *   <OrgTab> props)
 *
 * Runs with `tsx` against node:assert/strict — matches repo convention
 * for zero-framework tests.
 *
 * Usage:
 *   tsx opc/plugins/office_ui/frontend_src/App.test.tsx
 */
import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import { dirname, join } from 'node:path'
import { fileURLToPath } from 'node:url'

const here = dirname(fileURLToPath(import.meta.url))
const src = readFileSync(join(here, 'App.tsx'), 'utf8')

// 1. All five onOrgSaved* handlers registered in the socket-handlers object.
for (const key of [
  'onOrgSavedList',
  'onOrgSavedSaveAs',
  'onOrgSavedCreate',
  'onOrgSavedLoad',
  'onOrgSavedDelete',
]) {
  assert.match(
    src,
    new RegExp(`^\\s+${key}:\\s*\\(payload\\)`, 'm'),
    `App.tsx must register "${key}:" in the socket handlers object`,
  )
}

// 2. Toast state + JSX render present.
assert.match(src, /const \[orgToast, setOrgToast\] = useState/, 'orgToast state must exist')
assert.match(src, /setOrgToast\(null\)/, 'orgToast auto-clear must exist')
assert.match(src, /org-toast org-toast--/, 'org-toast JSX must render class variant')

// 3. versionAtLoad tracking wired in onOrgInfo.
assert.match(
  src,
  /setSavedOrgVersionAtLoad\(prev =>/,
  'onOrgInfo must capture versionAtLoad via functional setState',
)

// 4. useCallback for client.org* — no inline arrows in <OrgTab> props.
assert.doesNotMatch(
  src,
  /onSavedOrg[A-Z][a-zA-Z]*={\(/,
  'OrgTab JSX must not use inline arrow functions for onSavedOrg* props',
)
for (const name of [
  'handleSavedOrgsList',
  'handleSavedOrgSaveAs',
  'handleSavedOrgCreate',
  'handleSavedOrgLoad',
  'handleSavedOrgDelete',
]) {
  assert.match(
    src,
    new RegExp(`const ${name} = useCallback`),
    `App.tsx must declare "${name}" as useCallback`,
  )
}

// 5. onOrgConfigImport narrowing comment present.
assert.match(
  src,
  /Fires only on manual YAML import/,
  'onOrgConfigImport must carry the narrowing comment',
)

// 6. project_index_push is an index-only seed. It must not hydrate chat,
// kanban, or full runtime stores; full runtime state belongs to collab_sync.
assert.match(
  src,
  /const isProjectIndexPush = type === 'project_index_push' \|\| syncScope === 'index'/,
  'project_index_push must be detected by event type and sync_scope',
)
assert.match(
  src,
  /if \(isProjectIndexPush\) \{[\s\S]*?preserveExistingWhenIncomingPartial: true[\s\S]*?clientRef\.current\?\.collabSync\(syncProjectId[\s\S]*?return[\s\S]*?\}\s+const cs2 = chatStoreRef\.current/,
  'project_index_push must preserve existing session detail, request full collab_sync, and return before chat/kanban hydration',
)
assert.doesNotMatch(
  src,
  /preserveTasksWhenIncomingEmpty: isProjectIndexPush/,
  'project_index_push must not call BoardStore.initFromBackend as a partial full-sync workaround',
)

// 7. Runtime tool display has two channels: currentTool is active-only,
// displayTool is the stable "last visible command" shown while the session
// remains running. This prevents a half-second header/composer flash when
// current_tool is cleared by tool_completed.
assert.match(
  src,
  /function runtimeStatusClearsDisplayTool/,
  'App.tsx must centralize terminal status clearing for stable displayTool',
)
assert.match(
  src,
  /boardRuntimePatch\.displayTool = currentTool/,
  'agent_runtime_update must copy a non-empty current_tool into board displayTool',
)
assert.match(
  src,
  /sessionRuntimePatch\.displayTool = currentTool/,
  'agent_runtime_update must copy a non-empty current_tool into session displayTool',
)
assert.match(
  src,
  /runtimeStatusClearsDisplayTool\(payload\.status\)/,
  'agent_runtime_update must clear displayTool only on terminal or idle statuses',
)
assert.match(
  src,
  /toolName \? \{ displayTool: toolName \}/,
  'runtime events carrying a non-empty tool_name must update stable displayTool (empty tool_name keeps the sticky last command)',
)

// 8. Assistant streaming drafts must disappear at real terminal boundaries.
assert.match(
  src,
  /evt\.type === 'turn_completed' \|\| evt\.type === 'turn_failed' \|\| evt\.type === 'checkpoint_saved'/,
  'runtime terminal/checkpoint events must clear task-mode Live Reply drafts',
)
assert.match(
  src,
  /detailHasFinalForDraft[\s\S]*runtime_v2_assistant[\s\S]*ss\.clearDraft\(detailTaskId\)/,
  'session_detail backfill of the final runtime assistant turn must clear matching Live Reply drafts',
)

console.log('App.test.tsx: OK (org handlers + snapshot boundary + runtime displayTool/draft contract)')
