import { ReportReadError } from '@/hooks/use-report-query' import type { DiagnosticCheck, DiagnosticStatus, SystemInfo } from './config' type RecordValue = Record const record = (value: unknown): value is RecordValue => !!value && typeof value === 'object' && !Array.isArray(value) const string = (value: unknown) => typeof value === 'string' ? value.trim() : typeof value === 'number' && Number.isFinite(value) ? String(value) : '' const boolean = (value: unknown) => typeof value === 'boolean' ? value : null const count = (value: unknown) => typeof value === 'number' && Number.isSafeInteger(value) && value >= 0 ? value : null function strings(value: unknown): string[] { if (!Array.isArray(value) || value.some(item => typeof item !== 'string')) throw new ReportReadError('The system report is incomplete. Refresh the dashboard and try again.') return value.map(item => item.trim()).filter(Boolean) } function group(root: RecordValue, key: string): RecordValue { if (!record(root[key])) throw new ReportReadError('The system report is incomplete. Refresh the dashboard and try again.') return root[key] as RecordValue } /** Normalize missing scalar values as unavailable, never as plausible defaults. */ export function parseSystemInfo(value: unknown): SystemInfo { if (!record(value)) throw new ReportReadError('The server did not return a readable system report. Please try again.') const wp = group(value, 'wordpress'), php = group(value, 'php'), mysql = group(value, 'mysql') const server = group(value, 'server'), woo = group(value, 'woocommerce'), swift = group(value, 'swiftCommerce') const req = group(value, 'requirements'), registration = group(value, 'registrationForms') const rwp = group(registration, 'wordpress'), rwc = group(registration, 'woocommerce'), conflict = group(registration, 'conflict') if (!strings(req.requiredExtensions).length) throw new ReportReadError('The extension checklist could not be read. Refresh the dashboard and try again.') return { checkedAt: string(value.checkedAt), requirements: { wordpressVersion: string(req.wordpressVersion), phpVersion: string(req.phpVersion), woocommerceVersion: string(req.woocommerceVersion), databaseVersion: string(req.databaseVersion), phpMemory: string(req.phpMemory), wordpressMemory: string(req.wordpressMemory), requiredExtensions: strings(req.requiredExtensions), recommendedExtensions: strings(req.recommendedExtensions), }, wordpress: { version: string(wp.version), multisite: boolean(wp.multisite), memoryLimit: string(wp.memoryLimit), adminMemoryLimit: string(wp.adminMemoryLimit), debugMode: boolean(wp.debugMode), cron: boolean(wp.cron), language: string(wp.language), timezone: string(wp.timezone), permalinkStructure: string(wp.permalinkStructure), }, php: { version: string(php.version), memoryLimit: string(php.memoryLimit), maxExecutionTime: string(php.maxExecutionTime), maxInputVars: string(php.maxInputVars), postMaxSize: string(php.postMaxSize), uploadMaxFilesize: string(php.uploadMaxFilesize), openBasedir: boolean(php.openBasedir), extensions: strings(php.extensions), disabledFunctions: strings(php.disabledFunctions), }, mysql: { version: string(mysql.version), maxConnections: string(mysql.maxConnections), maxPacketSize: string(mysql.maxPacketSize), storageEngine: string(mysql.storageEngine) }, server: { software: string(server.software), serverProtocol: string(server.serverProtocol), documentRoot: string(server.documentRoot), serverAddress: string(server.serverAddress) }, woocommerce: { version: string(woo.version), databaseVersion: string(woo.databaseVersion), currency: string(woo.currency), currencyPosition: string(woo.currencyPosition), thousandSeparator: string(woo.thousandSeparator), decimalSeparator: string(woo.decimalSeparator), productsCount: count(woo.productsCount), ordersCount: count(woo.ordersCount), }, swiftCommerce: { version: string(swift.version), proVersion: string(swift.proVersion), featuresEnabled: strings(swift.featuresEnabled) }, registrationForms: { wordpress: { usersCanRegister: boolean(rwp.usersCanRegister), emailVerificationEnabled: boolean(rwp.emailVerificationEnabled), registrationFormEnabled: boolean(rwp.registrationFormEnabled), activeOwner: string(rwp.activeOwner), registerFormHookCallbacks: count(rwp.registerFormHookCallbacks), registrationErrorsHookCallbacks: count(rwp.registrationErrorsHookCallbacks), }, woocommerce: { available: boolean(rwc.available), myAccountRegistrationEnabled: boolean(rwc.myAccountRegistrationEnabled), generateUsername: string(rwc.generateUsername), generatePassword: string(rwc.generatePassword), myAccountPageId: count(rwc.myAccountPageId), myAccountPageUsesShortcode: boolean(rwc.myAccountPageUsesShortcode), myAccountPageUsesBlock: boolean(rwc.myAccountPageUsesBlock), themeTemplateOverride: string(rwc.themeTemplateOverride), emailVerificationEnabled: boolean(rwc.emailVerificationEnabled), registrationFormEnabled: boolean(rwc.registrationFormEnabled), activeOwner: string(rwc.activeOwner), woocommerceRegisterFormHookCallbacks: count(rwc.woocommerceRegisterFormHookCallbacks), woocommerceRegistrationErrorsHookCallbacks: count(rwc.woocommerceRegistrationErrorsHookCallbacks), }, conflict: { registrationFormMode: string(conflict.registrationFormMode), wordpressConflict: boolean(conflict.wordpressConflict), woocommerceConflict: boolean(conflict.woocommerceConflict) }, warnings: strings(registration.warnings), }, } } export const statusLabel: Record = { pass: 'Passed', warning: 'Review', fail: 'Failed', unknown: 'Unavailable' } export function displayValue(value: string | number | boolean | null | undefined): string { if (value === null || value === undefined || value === '') return 'Unavailable' if (typeof value === 'boolean') return value ? 'Yes' : 'No' return typeof value === 'number' ? value.toLocaleString() : value } export function compareVersions(current: string, minimum: string): DiagnosticStatus { const parts = (value: string) => /^\d+(?:\.\d+){1,3}(?:[-+][\w.-]+)?$/.test(value.trim()) ? value.split(/[-+]/)[0].split('.').map(Number) : null const actual = parts(current), required = parts(minimum) if (!actual || !required) return 'unknown' for (let i = 0; i < Math.max(actual.length, required.length); i++) { if ((actual[i] || 0) > (required[i] || 0)) return 'pass' if ((actual[i] || 0) < (required[i] || 0)) return 'fail' } return 'pass' } export function memoryBytes(value: string): number | null { if (value.trim() === '-1') return Infinity const match = /^(\d+(?:\.\d+)?)\s*([KMGT]?)B?$/i.exec(value.trim()) if (!match) return null const bytes = Number(match[1]) * 1024 ** Math.max(0, ['', 'K', 'M', 'G', 'T'].indexOf(match[2].toUpperCase())) return Number.isFinite(bytes) ? bytes : null } export const displayMemory = (value: string) => value.trim() === '-1' ? 'Unlimited' : displayValue(value) export function compareMemory(current: string, recommendation: string): DiagnosticStatus { const actual = memoryBytes(current), recommended = memoryBytes(recommendation) if (actual === null || recommended === null) return 'unknown' return actual >= recommended ? 'pass' : 'warning' } const memoryHelp = 'https://developer.wordpress.org/advanced-administration/wordpress/wp-config/#increasing-memory-allocated-to-php' export function buildDiagnostics(info: SystemInfo): DiagnosticCheck[] { const req = info.requirements const versions: DiagnosticCheck[] = [ ['php-version', 'PHP version', info.php.version, req.phpVersion], ['wordpress-version', 'WordPress version', info.wordpress.version, req.wordpressVersion], ['woocommerce-version', 'WooCommerce version', info.woocommerce.version, req.woocommerceVersion], ].map(([id, name, current, expected]) => ({ id, name, current: displayValue(current), expected: expected ? `${expected}+` : 'Unavailable', kind: 'requirement', status: id === 'woocommerce-version' && info.registrationForms.woocommerce.available === false ? 'fail' : compareVersions(current, expected), detail: 'Compatibility minimum declared by the installed Swift Commerce plugin. This is not a security or end-of-life check.', action: id === 'php-version' ? 'Ask your hosting provider about a supported PHP version. Test compatibility on staging before upgrading.' : 'Review the installed version and Swift Commerce requirements. Back up and test updates on staging before changing a live store.', })) const dbStatus = compareVersions(info.mysql.version, req.databaseVersion) const checks: DiagnosticCheck[] = [...versions, { id: 'database-version', name: 'Database version', current: displayValue(info.mysql.version), expected: `${req.databaseVersion || 'Unavailable'}${req.databaseVersion ? '+' : ''}`, kind: 'recommendation', status: dbStatus === 'fail' ? 'warning' : dbStatus, detail: 'Existing Swift Commerce database baseline; hosting and database-vendor support should also be reviewed.', action: 'Ask your host to check the database version and compatibility before making any change.', }, { id: 'php-memory', name: 'PHP memory for this request', current: displayMemory(info.php.memoryLimit), expected: `${req.phpMemory || 'Unavailable'} recommended`, kind: 'recommendation', status: compareMemory(info.php.memoryLimit, req.phpMemory), detail: 'The PHP limit reported by this diagnostic request, not a measurement of storefront memory usage.', action: 'Ask your host whether the PHP memory limit is appropriate for your store and workload.', helpUrl: memoryHelp, }, { id: 'wordpress-memory', name: 'WordPress configured memory', current: displayMemory(info.wordpress.memoryLimit), expected: `${req.wordpressMemory || 'Unavailable'} recommended`, kind: 'recommendation', status: compareMemory(info.wordpress.memoryLimit, req.wordpressMemory), detail: `Configured default (WP_MEMORY_LIMIT), not the effective limit for every request. PHP currently reports ${displayMemory(info.php.memoryLimit)}; WordPress admin configuration is ${displayMemory(info.wordpress.adminMemoryLimit)}.`, action: 'Ask your host to review WP_MEMORY_LIMIT and WP_MAX_MEMORY_LIMIT alongside the PHP limit. A lower configured default alone does not prove a storefront failure.', helpUrl: memoryHelp, }] const installed = new Set(info.php.extensions.map(extension => extension.toLowerCase())) for (const [extensions, required] of [[req.requiredExtensions, true], [req.recommendedExtensions, false]] as const) { for (const extension of extensions) { const available = installed.has(extension.toLowerCase()) checks.push({ id: `extension-${extension}`, name: `PHP extension: ${extension}`, current: available ? 'Installed' : 'Not installed', expected: required ? 'Required extension' : 'Recommended extension', kind: required ? 'requirement' : 'recommendation', status: available ? 'pass' : required ? 'fail' : 'warning', detail: required ? 'Required by the current diagnostic checklist.' : 'Optional capability; absence does not by itself mean the store is broken.', action: `Ask your hosting provider whether ${extension} can be enabled for this site's PHP version.`, }) } } info.registrationForms.warnings.forEach((warning, index) => checks.push({ id: `registration-${index}`, name: 'Registration setup', current: warning, expected: 'Review registration configuration', kind: 'registration', status: 'warning', detail: 'A setup warning for a registration context targeted by an enabled feature. Hook counts are contextual diagnostics, not proof of a storefront conflict.', action: 'Review Registration Form, Email Verification and WordPress/WooCommerce account settings. Check the actual registration page before changing form ownership.', })) return checks } export function summarizeDiagnostics(checks: DiagnosticCheck[]) { return checks.reduce((counts, check) => ({ ...counts, [check.status]: counts[check.status] + 1 }), { pass: 0, warning: 0, fail: 0, unknown: 0 }) } export type ReportRow = [string, string | number | boolean | null | undefined] export interface ReportGroup { title: string; rows: ReportRow[] } export function environmentGroups(info: SystemInfo): ReportGroup[] { return [ { title: 'WordPress environment', rows: [ ['Version', info.wordpress.version], ['Multisite', info.wordpress.multisite], ['Configured default memory', displayMemory(info.wordpress.memoryLimit)], ['Configured admin memory', displayMemory(info.wordpress.adminMemoryLimit)], ['Debug mode', info.wordpress.debugMode], ['Built-in WP Cron enabled', info.wordpress.cron], ['Language', info.wordpress.language], ['Timezone', info.wordpress.timezone], ['Permalinks', info.wordpress.permalinkStructure], ] }, { title: 'PHP configuration', rows: [ ['Version', info.php.version], ['Memory for this request', displayMemory(info.php.memoryLimit)], ['Max execution time (seconds; 0 = unlimited)', info.php.maxExecutionTime], ['Max input variables', info.php.maxInputVars], ['Post maximum size', info.php.postMaxSize], ['Upload maximum size', info.php.uploadMaxFilesize], ['Directory restriction enabled', info.php.openBasedir], ['Installed extensions', info.php.extensions.join(', ')], ['Disabled functions', info.php.disabledFunctions.join(', ') || 'None reported'], ] }, { title: 'Database', rows: [['Version', info.mysql.version], ['Maximum connections', info.mysql.maxConnections], ['Maximum packet size', info.mysql.maxPacketSize], ['Default storage engine', info.mysql.storageEngine]] }, { title: 'Server', rows: [['Software', info.server.software], ['Protocol', info.server.serverProtocol]] }, { title: 'WooCommerce', rows: [['Version', info.woocommerce.version], ['Database version', info.woocommerce.databaseVersion], ['Currency', info.woocommerce.currency], ['Currency position', info.woocommerce.currencyPosition], ['Thousands separator', info.woocommerce.thousandSeparator], ['Decimal separator', info.woocommerce.decimalSeparator], ['Published products', info.woocommerce.productsCount], ['Orders', info.woocommerce.ordersCount]] }, { title: 'Swift Commerce', rows: [['Free version', info.swiftCommerce.version], ['Pro version', info.swiftCommerce.proVersion || 'Not active'], ['Enabled features', info.swiftCommerce.featuresEnabled.join(', ') || 'None'] ] }, ] } export function registrationRows(info: SystemInfo): ReportRow[] { const { wordpress: wp, woocommerce: wc, conflict } = info.registrationForms return [ ['WordPress public registration enabled', wp.usersCanRegister], ['WordPress form handled by', wp.activeOwner], ['WordPress Email Verification enabled', wp.emailVerificationEnabled], ['WordPress Registration Form enabled', wp.registrationFormEnabled], ['WooCommerce available', wc.available], ['My Account registration enabled', wc.myAccountRegistrationEnabled], ['WooCommerce form handled by', wc.activeOwner], ['WooCommerce Email Verification enabled', wc.emailVerificationEnabled], ['WooCommerce Registration Form enabled', wc.registrationFormEnabled], ['My Account page ID', wc.myAccountPageId === 0 ? 'Not assigned' : wc.myAccountPageId], ['My Account shortcode detected', wc.myAccountPageUsesShortcode], ['My Account block detected', wc.myAccountPageUsesBlock], ['Username generation setting', wc.generateUsername], ['Password generation setting', wc.generatePassword], ['WordPress register_form callbacks', wp.registerFormHookCallbacks], ['WordPress registration_errors callbacks', wp.registrationErrorsHookCallbacks], ['WooCommerce register_form callbacks', wc.woocommerceRegisterFormHookCallbacks], ['WooCommerce registration_errors callbacks', wc.woocommerceRegistrationErrorsHookCallbacks], ['Form ownership preference', conflict.registrationFormMode === 'registration_form' ? 'Registration Form' : conflict.registrationFormMode === 'email_verification' ? 'Email Verification' : 'Unavailable'], ['Both features target WordPress registration', conflict.wordpressConflict], ['Both features target WooCommerce registration', conflict.woocommerceConflict], ] } /** UI and support export consume the same checks and label/value groups. */ export function generateSystemReport(info: SystemInfo, checkedAt: Date, stale = false): string { const checks = buildDiagnostics(info), counts = summarizeDiagnostics(checks) const rows = (items: ReportRow[]) => items.map(([label, value]) => `${label}: ${displayValue(value).replace(/[\r\n]+/g, ' ')}`) return [ '### Swift Commerce System Report ###', `Data checked: ${checkedAt.toISOString()}`, `Report generated: ${new Date().toISOString()}`, `Freshness: ${stale ? 'Last successful result; refresh failed. Data may be out of date.' : 'Last successful check.'}`, 'Scope: compatibility, configuration recommendations, extensions and targeted registration setup; not an uptime, security or job-delivery audit.', 'Privacy: server IPs, document-root paths and theme-template paths are omitted. No credentials are collected.', '', `Checks: ${counts.fail} failed; ${counts.warning} to review; ${counts.unknown} unavailable; ${counts.pass} passed.`, '', '--- Diagnostic checks ---', ...checks.flatMap(check => [`[${statusLabel[check.status]}] ${check.name}`, `Current: ${check.current}`, `Expected: ${check.expected}`, check.detail, ...(check.status !== 'pass' && check.action ? [`Next step: ${check.action}`] : []), ...(check.status !== 'pass' && check.helpUrl ? [`Guide: ${check.helpUrl}`] : []), '']), ...environmentGroups(info).flatMap(group => [`--- ${group.title} ---`, ...rows(group.rows), '']), '--- Registration diagnostics ---', ...rows(registrationRows(info)), '', '### End of Report ###', ].join('\n') }