import assert from 'node:assert/strict'
import { readFileSync } from 'node:fs'
import test from 'node:test'
import { fileURLToPath } from 'node:url'

const sidebar = readFileSync(
  fileURLToPath(new URL('../src/components/layout/SidebarRail.vue', import.meta.url)),
  'utf8'
)

const sidebarStyles = readFileSync(
  fileURLToPath(new URL('../src/assets/styles/components/sidebar-rail.css', import.meta.url)),
  'utf8'
)

const topbar = readFileSync(
  fileURLToPath(new URL('../src/components/layout/TopBar.vue', import.meta.url)),
  'utf8'
)

const topbarStyles = readFileSync(
  fileURLToPath(new URL('../src/assets/styles/components/top-bar.css', import.meta.url)),
  'utf8'
)

const appShellRouteView = readFileSync(
  fileURLToPath(new URL('../src/views/AppShellRouteView.vue', import.meta.url)),
  'utf8'
)

const documentInbox = readFileSync(
  fileURLToPath(new URL('../src/composables/useDocumentCenterInbox.js', import.meta.url)),
  'utf8'
)

const documentNewState = readFileSync(
  fileURLToPath(new URL('../src/utils/documentCenterNewState.js', import.meta.url)),
  'utf8'
)

const notificationStatesService = readFileSync(
  fileURLToPath(new URL('../src/services/notificationStates.js', import.meta.url)),
  'utf8'
)

const topbarNotificationStates = readFileSync(
  fileURLToPath(new URL('../src/composables/useTopBarNotificationStates.js', import.meta.url)),
  'utf8'
)

test('sidebar no longer renders document center unread indicators', () => {
  assert.doesNotMatch(sidebar, /useDocumentCenterInbox/)
  assert.doesNotMatch(sidebar, /hasUnread: documentInboxHasUnread/)
  assert.doesNotMatch(sidebar, /hasNewMessage/)
  assert.doesNotMatch(sidebar, /nav-label-text/)
  assert.doesNotMatch(sidebar, /nav-unread-dot/)
  assert.match(sidebar, /<span class="nav-label">\{\{ item\.displayLabel \}\}<\/span>/)
  assert.doesNotMatch(sidebarStyles, /\.nav-label-text\s*\{/)
  assert.doesNotMatch(sidebarStyles, /\.nav-unread-dot/)
  assert.match(sidebarStyles, /\.nav-label\s*\{[\s\S]*overflow:\s*hidden;[\s\S]*text-overflow:\s*ellipsis;/)
})

test('topbar bell owns document center unread notifications', () => {
  assert.match(topbar, /useDocumentCenterInbox/)
  assert.match(topbar, /useTopBarNotificationStates/)
  assert.match(topbar, /resolveDocumentNotificationId/)
  assert.match(topbar, /notificationRows: documentInboxNotificationRows/)
  assert.match(topbar, /const documentNotificationItems = computed/)
  assert.match(topbar, /title: `\$\{row\.documentTypeLabel \|\| '单据'\} \$\{row\.documentNo \|\| row\.claimId \|\| '待生成'\}`/)
  assert.match(topbar, /description: resolveDocumentNotificationDescription\(row\)/)
  assert.match(topbar, /const unread = Boolean\(row\.isUnread\) && !isNotificationRead\(id\)/)
  assert.match(topbar, /markDocumentInboxRowRead\(item\.documentRow\)/)
  assert.match(topbar, /markNotificationStateRead\(item\)/)
  assert.match(topbar, /markDocumentInboxRowsRead\(documentRows\)/)
  assert.match(topbar, /hideNotificationStates\(currentItems\)/)
  assert.match(topbar, /loadNotificationStates\(\)/)
  assert.match(topbar, /const topbarNotificationCount = computed\(\(\) => \{[\s\S]*const count = unreadNotifications\.value\.length/)
  assert.doesNotMatch(topbar, /document-center-unread/)
  assert.doesNotMatch(topbar, /target: \{ type: 'documents-center' \}/)
  assert.doesNotMatch(topbar, /emit\('navigate', 'documents'\)/)
  assert.match(appShellRouteView, /@navigate="handleNavigate"/)
  assert.match(topbar, /startDocumentInboxPolling\(\)/)
  assert.match(topbar, /stopDocumentInboxPolling\(\)/)
  assert.match(topbar, /class="notification-clear-btn"/)
  assert.match(topbar, /notificationBulkActionLabel/)
  assert.match(topbar, /notificationBulkActionDisabled/)
  assert.match(topbar, /function handleNotificationBulkAction\(\)/)
  assert.match(topbar, /function markUnreadNotificationsRead\(\)/)
  assert.match(topbar, /function deleteReadNotifications\(\)/)
  assert.match(topbar, /function markNotificationRead\(item\)/)
  assert.match(topbar, /class="notification-avatar" :class="item\.tone"/)
  assert.match(topbarStyles, /\.notification-head-copy\s*\{[\s\S]*display:\s*grid;/)
  assert.match(topbarStyles, /\.notification-head-actions\s*\{[\s\S]*display:\s*inline-flex;/)
  assert.match(topbarStyles, /\.notification-popover\s*\{[\s\S]*max-height:\s*min\(560px,\s*calc\(100vh - 68px\)\);/)
  assert.match(topbarStyles, /\.notification-list\s*\{[\s\S]*max-height:\s*min\(420px,\s*calc\(100vh - 166px\)\);[\s\S]*overflow-y:\s*auto;/)
  assert.match(topbarStyles, /\.notification-preview\s*\{[\s\S]*-webkit-line-clamp:\s*2;/)
  assert.match(topbarStyles, /@media \(max-width: 640px\)[\s\S]*\.notification-popover\s*\{[\s\S]*position:\s*fixed;/)
  assert.match(topbarStyles, /\.notification-tabs button em\s*\{[\s\S]*border-radius:\s*4px;/)
  assert.match(topbarStyles, /\.notification-row\s*\{[\s\S]*grid-template-columns:\s*52px minmax\(0,\s*1fr\);/)
  assert.doesNotMatch(topbarStyles, /\.notification-dot/)
})

test('topbar notification bulk action label follows active tab semantics', () => {
  assert.match(topbar, />\s*\{\{ notificationBulkActionLabel \}\}\s*<\/button>/)
  assert.match(topbar, /:disabled="notificationBulkActionDisabled"/)
  assert.match(topbar, /@click="handleNotificationBulkAction"/)
  assert.match(topbar, /const notificationBulkActionLabel = computed\(\(\) => \(\s*notificationTab\.value === 'unread' \? '全部已读' : '删除已读'\s*\)\)/)
  assert.match(topbar, /const notificationBulkActionDisabled = computed\(\(\) => \(\s*notificationTab\.value === 'unread'\s*\? unreadNotifications\.value\.length === 0\s*: readNotifications\.value\.length === 0\s*\)\)/)
  assert.match(topbar, /function handleNotificationBulkAction\(\) \{[\s\S]*if \(notificationTab\.value === 'unread'\) \{[\s\S]*markUnreadNotificationsRead\(\)[\s\S]*return[\s\S]*deleteReadNotifications\(\)[\s\S]*\}/)
  assert.match(topbar, /function markUnreadNotificationsRead\(\) \{[\s\S]*const currentItems = unreadNotifications\.value[\s\S]*markDocumentInboxRowsRead\(documentRows\)[\s\S]*markNotificationStatesRead\(currentItems\)/)
  assert.match(topbar, /function deleteReadNotifications\(\) \{[\s\S]*const currentItems = readNotifications\.value[\s\S]*hideNotificationStates\(currentItems\)/)
  assert.doesNotMatch(topbar, />\s*清空通知\s*<\/button>/)
})

test('topbar notification popover uses reference-style avatar message rows', () => {
  assert.match(topbar, /class="notification-avatar" :class="item\.tone"/)
  assert.match(topbar, /class="notification-avatar-label"/)
  assert.match(topbar, /class="notification-avatar-badge"/)
  assert.match(topbar, /class="notification-row-content"/)
  assert.match(topbar, /class="notification-row-top"/)
  assert.match(topbar, /class="notification-row-title"/)
  assert.match(topbar, /class="notification-preview"/)
  assert.match(topbar, /class="notification-time"/)
  assert.match(topbar, /avatarLabel: resolveDocumentNotificationAvatarLabel\(row\)/)
  assert.match(topbar, /avatarLabel: resolveNotificationAvatarLabel\(item\)/)
  assert.match(topbar, /timeLabel:\s*formatNotificationTimeLabel\(row\.updatedAt \|\| row\.createdAt\)/)
  assert.match(topbar, /timeLabel:\s*formatNotificationTimeLabel\(item\.time \|\| item\.updatedAt \|\| item\.due\)/)
  assert.doesNotMatch(topbar, /class="notification-category-pill"/)
  assert.doesNotMatch(topbar, /class="notification-row-action"/)
  assert.doesNotMatch(topbar, /<time>\{\{ item\.time \}\}<\/time>/)
  assert.match(topbarStyles, /\.notification-row\s*\{[\s\S]*grid-template-columns:\s*52px minmax\(0,\s*1fr\);/)
  assert.match(topbarStyles, /\.notification-avatar\s*\{[\s\S]*border-radius:\s*999px;/)
  assert.match(topbarStyles, /\.notification-preview\s*\{[\s\S]*-webkit-line-clamp:\s*2;/)
  assert.match(topbarStyles, /\.notification-row-top\s*\{[\s\S]*justify-content:\s*space-between;/)
  assert.match(topbarStyles, /\.notification-time\s*\{[\s\S]*font-variant-numeric:\s*tabular-nums;/)
  assert.doesNotMatch(topbarStyles, /\.notification-category-pill/)
  assert.doesNotMatch(topbarStyles, /\.notification-row-action/)
})

test('topbar notification popover does not render a top accent line', () => {
  assert.doesNotMatch(topbarStyles, /\.notification-popover::before/)
  assert.doesNotMatch(topbarStyles, /height:\s*2px;[\s\S]*background:\s*var\(--theme-primary-active\)/)
})

test('topbar notification state is persisted through backend API with local fallback', () => {
  assert.match(notificationStatesService, /apiRequest\('\/notification-states'\)/)
  assert.match(notificationStatesService, /apiRequest\('\/notification-states',\s*\{[\s\S]*method:\s*'POST'/)
  assert.match(topbarNotificationStates, /fetchNotificationStates/)
  assert.match(topbarNotificationStates, /patchNotificationStates/)
  assert.match(topbarNotificationStates, /NOTIFICATION_READ_STORAGE_KEY/)
  assert.match(topbarNotificationStates, /NOTIFICATION_HIDDEN_STORAGE_KEY/)
  assert.match(topbarNotificationStates, /applyRemoteStates/)
  assert.match(topbarNotificationStates, /markNotificationStateRead/)
  assert.match(topbarNotificationStates, /markNotificationStatesRead/)
  assert.match(topbarNotificationStates, /hideNotificationStates/)
})

test('topbar notification bulk actions are wired to backend state API', () => {
  assert.match(topbar, /markNotificationStatesRead/)
  assert.match(topbar, /function markUnreadNotificationsRead\(\) \{[\s\S]*const currentItems = unreadNotifications\.value[\s\S]*markDocumentInboxRowsRead\(documentRows\)[\s\S]*markNotificationStatesRead\(currentItems\)/)
  assert.doesNotMatch(topbar, /function markUnreadNotificationsRead\(\) \{[\s\S]*currentItems\.forEach\(\(item\) => \{[\s\S]*markNotificationStateRead\(item\)/)
  assert.match(topbar, /function deleteReadNotifications\(\) \{[\s\S]*const currentItems = readNotifications\.value[\s\S]*hideNotificationStates\(currentItems\)/)
  assert.match(topbarNotificationStates, /function markNotificationStatesRead\(items\) \{[\s\S]*buildPatch\(item, \{ read: true, hidden: false \}\)[\s\S]*return syncNotificationPatches\(patches\)/)
  assert.match(topbarNotificationStates, /function hideNotificationStates\(items\) \{[\s\S]*buildPatch\(item, \{ read: true, hidden: true \}\)[\s\S]*return syncNotificationPatches\(patches\)/)
  assert.match(notificationStatesService, /apiRequest\('\/notification-states',\s*\{[\s\S]*method:\s*'POST'[\s\S]*body:\s*JSON\.stringify\(\{ states: batch \}\)/)
})

test('document inbox reuses document center viewed-key state', () => {
  assert.match(documentInbox, /DOCUMENT_VIEWED_KEYS_CHANGE_EVENT/)
  assert.match(documentInbox, /fetchNotificationStates/)
  assert.match(documentInbox, /mergeNotificationStatesIntoViewedDocumentKeys/)
  assert.match(documentInbox, /readViewedDocumentKeys/)
  assert.match(documentInbox, /countNewDocuments\(documentRows\.value, viewedDocumentKeys\.value\)/)
  assert.match(documentInbox, /const notificationRows = computed/)
  assert.match(documentInbox, /isUnread: isNewDocument\(row, viewedDocumentKeys\.value\)/)
  assert.match(documentInbox, /function markDocumentInboxRowRead\(row\)/)
  assert.match(documentInbox, /function markDocumentInboxRowsRead\(rows = documentRows\.value\)/)
  assert.match(documentInbox, /fetchExpenseClaims/)
  assert.match(documentInbox, /fetchApprovalExpenseClaims/)
  assert.match(documentInbox, /fetchArchivedExpenseClaims/)
  assert.match(documentInbox, /window\.addEventListener\(DOCUMENT_VIEWED_KEYS_CHANGE_EVENT, refreshViewedDocumentKeys\)/)
  assert.match(documentNewState, /export const DOCUMENT_VIEWED_KEYS_CHANGE_EVENT/)
  assert.match(documentNewState, /resolveDocumentNotificationId/)
  assert.match(documentNewState, /buildDocumentsViewedStatePatches/)
  assert.match(documentNewState, /window\.dispatchEvent\(new CustomEvent\(DOCUMENT_VIEWED_KEYS_CHANGE_EVENT\)\)/)
})
