@jmruthers/pace-core 0.6.5 → 0.6.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +104 -0
- package/README.md +5 -403
- package/audit-tool/00-dependencies.cjs +394 -0
- package/audit-tool/audits/01-pace-core-compliance.cjs +556 -0
- package/audit-tool/audits/02-project-structure.cjs +255 -0
- package/audit-tool/audits/03-architecture.cjs +196 -0
- package/audit-tool/audits/04-code-quality.cjs +149 -0
- package/audit-tool/audits/05-styling.cjs +224 -0
- package/audit-tool/audits/06-security-rbac.cjs +544 -0
- package/audit-tool/audits/07-api-tech-stack.cjs +301 -0
- package/audit-tool/audits/08-testing-documentation.cjs +202 -0
- package/audit-tool/audits/09-operations.cjs +208 -0
- package/audit-tool/index.cjs +291 -0
- package/audit-tool/utils/code-utils.cjs +218 -0
- package/audit-tool/utils/file-utils.cjs +230 -0
- package/audit-tool/utils/report-utils.cjs +241 -0
- package/core-usage-manifest.json +93 -0
- package/cursor-rules/00-standards-overview.mdc +156 -0
- package/cursor-rules/01-pace-core-compliance.mdc +586 -0
- package/cursor-rules/02-project-structure.mdc +42 -4
- package/cursor-rules/{03-solid-principles.mdc → 03-architecture.mdc} +126 -10
- package/cursor-rules/04-code-quality.mdc +419 -0
- package/cursor-rules/{08-markup-quality.mdc → 05-styling.mdc} +104 -34
- package/cursor-rules/06-security-rbac.mdc +518 -0
- package/cursor-rules/07-api-tech-stack.mdc +377 -0
- package/cursor-rules/08-testing-documentation.mdc +324 -0
- package/cursor-rules/09-operations.mdc +365 -0
- package/dist/{AuthService-Cb34EQs3.d.ts → AuthService-DmfO5rGS.d.ts} +10 -0
- package/dist/DataTable-7PMH7XN7.js +15 -0
- package/dist/{DataTable-BMRU8a1j.d.ts → DataTable-DRUIgtUH.d.ts} +1 -1
- package/dist/{PublicPageProvider-QTFVrL-Z.d.ts → PublicPageProvider-DlsCaR5v.d.ts} +33 -72
- package/dist/UnifiedAuthProvider-ZT6TIGM7.js +7 -0
- package/dist/api-Y4MQWOFW.js +4 -0
- package/dist/audit-MYQXYZFU.js +3 -0
- package/dist/{chunk-DGUM43GV.js → chunk-3RG5ZIWI.js} +1 -4
- package/dist/{chunk-QXHPKYJV.js → chunk-4SXLQIZO.js} +1 -26
- package/dist/{chunk-UPPMRMYG.js → chunk-5X4QLXRG.js} +73 -151
- package/dist/chunk-6F3IILHI.js +62 -0
- package/dist/{chunk-E66EQZE6.js → chunk-6GLLNA6U.js} +3 -9
- package/dist/{chunk-ZSAAAMVR.js → chunk-6QYDGKQY.js} +1 -4
- package/dist/{chunk-FMUCXFII.js → chunk-7ILTDCL2.js} +9 -5
- package/dist/{chunk-M43Y4SSO.js → chunk-A3W6LW53.js} +15 -13
- package/dist/{chunk-63FOKYGO.js → chunk-AHU7G2R5.js} +2 -11
- package/dist/{chunk-HU2C6SSC.js → chunk-BM4CQ5P3.js} +606 -559
- package/dist/chunk-C7NSAPTL.js +1 -0
- package/dist/{chunk-J36DSWQK.js → chunk-FEJLJNWA.js} +7 -41
- package/dist/{chunk-IHB5DR3H.js → chunk-FTCRZOG2.js} +188 -387
- package/dist/{chunk-G37KK66H.js → chunk-FYHN4DD5.js} +60 -19
- package/dist/chunk-GHYHJTYV.js +994 -0
- package/dist/{chunk-VBXEHIUJ.js → chunk-HF6O3O37.js} +6 -88
- package/dist/{chunk-FFQEQTNW.js → chunk-IUBRCBSY.js} +134 -45
- package/dist/{chunk-6COVEUS7.js → chunk-JGWDVX64.js} +983 -1034
- package/dist/{chunk-RGAWHO7N.js → chunk-L4XMVJKY.js} +77 -222
- package/dist/chunk-MBADTM7L.js +64 -0
- package/dist/{chunk-M7MPQISP.js → chunk-OJ4SKRSV.js} +3 -16
- package/dist/{chunk-IVOFDYWT.js → chunk-Q7Q7V5NV.js} +2109 -1604
- package/dist/{chunk-JGRYX5UX.js → chunk-S7DKJPLT.js} +29 -58
- package/dist/{chunk-PWLANIRT.js → chunk-TTRFSOKR.js} +1 -7
- package/dist/{chunk-5DRSZLL2.js → chunk-UH3NTO3F.js} +1 -6
- package/dist/{chunk-NTM7ZSB6.js → chunk-VBCS3DUA.js} +261 -168
- package/dist/{chunk-EFN2EIMK.js → chunk-ZFYPMX46.js} +271 -87
- package/dist/{chunk-L4OXEN46.js → chunk-ZKAWKYT4.js} +10 -24
- package/dist/components.d.ts +7 -5
- package/dist/components.js +46 -257
- package/dist/{database.generated-CzIvgcPu.d.ts → database.generated-CcnC_DRc.d.ts} +4795 -3691
- package/dist/eslint-rules/index.cjs +35 -0
- package/{src/eslint-rules/pace-core-compliance.cjs → dist/eslint-rules/rules/01-pace-core-compliance.cjs} +234 -235
- package/dist/eslint-rules/rules/04-code-quality.cjs +290 -0
- package/dist/eslint-rules/rules/05-styling.cjs +61 -0
- package/dist/eslint-rules/rules/06-security-rbac.cjs +806 -0
- package/dist/eslint-rules/rules/07-api-tech-stack.cjs +263 -0
- package/dist/eslint-rules/rules/08-testing.cjs +94 -0
- package/dist/eslint-rules/utils/helpers.cjs +42 -0
- package/dist/eslint-rules/utils/manifest-loader.cjs +75 -0
- package/dist/hooks.d.ts +6 -6
- package/dist/hooks.js +62 -172
- package/dist/icons/index.d.ts +1 -0
- package/dist/icons/index.js +1 -0
- package/dist/index.d.ts +12 -11
- package/dist/index.js +67 -660
- package/dist/providers.d.ts +2 -2
- package/dist/providers.js +8 -35
- package/dist/rbac/eslint-rules.d.ts +46 -44
- package/dist/rbac/eslint-rules.js +7 -4
- package/dist/rbac/index.d.ts +109 -586
- package/dist/rbac/index.js +14 -207
- package/dist/styles/index.js +2 -12
- package/dist/theming/runtime.d.ts +14 -1
- package/dist/theming/runtime.js +3 -19
- package/dist/{timezone-CHhWg6b4.d.ts → timezone-BZe_eUxx.d.ts} +175 -1
- package/dist/{types-CkbwOr4Y.d.ts → types-DXstZpNI.d.ts} +4 -17
- package/dist/types-t9H8qKRw.d.ts +55 -0
- package/dist/types.d.ts +1 -1
- package/dist/types.js +7 -94
- package/dist/{usePublicRouteParams-ClnV4tnv.d.ts → usePublicRouteParams-MamNgwqe.d.ts} +20 -20
- package/dist/utils.d.ts +24 -117
- package/dist/utils.js +54 -392
- package/docs/README.md +17 -7
- package/docs/api/README.md +4 -402
- package/docs/api/modules.md +301 -871
- package/docs/api-reference/components.md +21 -21
- package/docs/api-reference/deprecated.md +31 -6
- package/docs/api-reference/hooks.md +80 -80
- package/docs/api-reference/rpc-functions.md +78 -3
- package/docs/api-reference/types.md +1 -1
- package/docs/api-reference/utilities.md +1 -1
- package/docs/architecture/README.md +1 -1
- package/docs/core-concepts/events.md +3 -3
- package/docs/core-concepts/organisations.md +6 -6
- package/docs/core-concepts/permissions.md +6 -6
- package/docs/documentation-index.md +12 -18
- package/docs/getting-started/cursor-rules.md +3 -23
- package/docs/getting-started/dependencies.md +650 -0
- package/docs/getting-started/documentation-index.md +1 -1
- package/docs/getting-started/examples/README.md +4 -4
- package/docs/getting-started/examples/full-featured-app.md +1 -1
- package/docs/getting-started/faq.md +2 -2
- package/docs/getting-started/installation-guide.md +20 -7
- package/docs/getting-started/quick-reference.md +4 -4
- package/docs/getting-started/quick-start.md +23 -12
- package/docs/implementation-guides/authentication.md +15 -15
- package/docs/implementation-guides/component-styling.md +1 -1
- package/docs/implementation-guides/data-tables.md +126 -33
- package/docs/implementation-guides/datatable-rbac-usage.md +1 -1
- package/docs/implementation-guides/dynamic-colors.md +3 -3
- package/docs/implementation-guides/file-upload-storage.md +2 -2
- package/docs/implementation-guides/hierarchical-datatable.md +40 -60
- package/docs/implementation-guides/inactivity-tracking.md +3 -3
- package/docs/implementation-guides/large-datasets.md +3 -2
- package/docs/implementation-guides/organisation-security.md +2 -2
- package/docs/implementation-guides/performance.md +2 -2
- package/docs/implementation-guides/permission-enforcement.md +5 -1
- package/docs/migration/V0.3.44_organisation-context-timing-fix.md +1 -1
- package/docs/migration/V0.4.0_rbac-migration.md +6 -6
- package/docs/rbac/MIGRATION_GUIDE.md +819 -0
- package/docs/rbac/RBAC_CONTRACT.md +724 -0
- package/docs/rbac/README.md +17 -8
- package/docs/rbac/advanced-patterns.md +6 -6
- package/docs/rbac/api-reference.md +20 -20
- package/docs/rbac/edge-functions-guide.md +376 -0
- package/docs/rbac/event-based-apps.md +3 -3
- package/docs/rbac/examples.md +41 -41
- package/docs/rbac/getting-started.md +37 -37
- package/docs/rbac/performance.md +1 -1
- package/docs/rbac/quick-start.md +52 -52
- package/docs/rbac/secure-client-protection.md +1 -35
- package/docs/rbac/troubleshooting.md +1 -1
- package/docs/security/README.md +5 -5
- package/docs/standards/0-standards-overview.md +220 -0
- package/docs/standards/1-pace-core-compliance-standards.md +986 -0
- package/docs/standards/2-project-structure-standards.md +949 -0
- package/docs/standards/3-architecture-standards.md +606 -0
- package/docs/standards/4-code-quality-standards.md +728 -0
- package/docs/standards/5-styling-standards.md +348 -0
- package/docs/standards/{07-rbac-and-rls-standard.md → 6-security-rbac-standards.md} +269 -66
- package/docs/standards/7-api-tech-stack-standards.md +662 -0
- package/docs/standards/8-testing-documentation-standards.md +401 -0
- package/docs/standards/9-operations-standards.md +1102 -0
- package/docs/standards/README.md +185 -57
- package/docs/troubleshooting/README.md +4 -4
- package/docs/troubleshooting/common-issues.md +2 -2
- package/docs/troubleshooting/debugging.md +9 -9
- package/docs/troubleshooting/migration.md +4 -4
- package/docs/troubleshooting/organisation-context-setup.md +42 -19
- package/eslint-config-pace-core.cjs +33 -6
- package/package.json +35 -23
- package/scripts/install-cursor-rules.cjs +25 -6
- package/scripts/install-eslint-config.cjs +284 -0
- package/src/__tests__/fixtures/supabase.ts +1 -1
- package/src/__tests__/helpers/__tests__/component-test-utils.test.tsx +3 -3
- package/src/__tests__/helpers/__tests__/optimized-test-setup.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/supabaseMock.test.ts +1 -1
- package/src/__tests__/helpers/__tests__/test-providers.test.tsx +2 -2
- package/src/__tests__/helpers/__tests__/test-utils.test.tsx +13 -13
- package/src/__tests__/helpers/component-test-utils.tsx +1 -1
- package/src/__tests__/helpers/supabaseMock.ts +2 -2
- package/src/__tests__/integration/UserProfile.test.tsx +14 -14
- package/src/__tests__/public-recipe-view.test.ts +38 -9
- package/src/__tests__/rbac/PagePermissionGuard.test.tsx +6 -6
- package/src/__tests__/templates/accessibility.test.template.tsx +9 -9
- package/src/__tests__/templates/component.test.template.tsx +18 -15
- package/src/components/Button/Button.tsx +5 -1
- package/src/components/Calendar/Calendar.tsx +201 -47
- package/src/components/ContextSelector/ContextSelector.tsx +106 -119
- package/src/components/DataTable/AUDIT_REPORT.md +293 -0
- package/src/components/DataTable/__tests__/DataTableCore.test.tsx +10 -2
- package/src/components/DataTable/__tests__/a11y.basic.test.tsx +10 -4
- package/src/components/DataTable/__tests__/test-utils/sharedTestUtils.tsx +9 -9
- package/src/components/DataTable/components/ColumnFilter.tsx +63 -74
- package/src/components/DataTable/components/ColumnVisibilityDropdown.tsx +43 -41
- package/src/components/DataTable/components/DataTableCore.tsx +186 -13
- package/src/components/DataTable/components/DataTableErrorBoundary.tsx +9 -11
- package/src/components/DataTable/components/DataTableLayout.tsx +35 -21
- package/src/components/DataTable/components/EditFields.tsx +23 -3
- package/src/components/DataTable/components/EditableRow.tsx +12 -9
- package/src/components/DataTable/components/EmptyState.tsx +10 -9
- package/src/components/DataTable/components/FilterRow.tsx +2 -4
- package/src/components/DataTable/components/ImportModal.tsx +124 -126
- package/src/components/DataTable/components/LoadingState.tsx +5 -6
- package/src/components/DataTable/components/RowComponent.tsx +12 -0
- package/src/components/DataTable/components/SortIndicator.tsx +50 -0
- package/src/components/DataTable/components/__tests__/COVERAGE_NOTE.md +4 -4
- package/src/components/DataTable/components/__tests__/ColumnFilter.test.tsx +23 -82
- package/src/components/DataTable/components/__tests__/DataTableErrorBoundary.test.tsx +37 -9
- package/src/components/DataTable/components/__tests__/EmptyState.test.tsx +7 -4
- package/src/components/DataTable/components/__tests__/FilterRow.test.tsx +12 -4
- package/src/components/DataTable/components/__tests__/LoadingState.test.tsx +41 -27
- package/src/components/DataTable/components/hooks/usePermissionTracking.ts +0 -4
- package/src/components/DataTable/components/index.ts +2 -1
- package/src/components/DataTable/hooks/__tests__/useDataTableState.test.ts +51 -47
- package/src/components/DataTable/hooks/useDataTablePermissions.ts +24 -21
- package/src/components/DataTable/hooks/useDataTableState.ts +125 -9
- package/src/components/DataTable/hooks/useTableColumns.ts +40 -2
- package/src/components/DataTable/hooks/useTableHandlers.ts +11 -0
- package/src/components/DataTable/types.ts +5 -18
- package/src/components/DataTable/utils/a11yUtils.ts +17 -0
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.test.tsx +2 -1
- package/src/components/DatePickerWithTimezone/DatePickerWithTimezone.tsx +11 -15
- package/src/components/DateTimeField/DateTimeField.tsx +10 -9
- package/src/components/Dialog/Dialog.test.tsx +128 -104
- package/src/components/Dialog/Dialog.tsx +742 -24
- package/src/components/ErrorBoundary/ErrorBoundary.tsx +77 -79
- package/src/components/FileDisplay/FileDisplay.test.tsx +4 -2
- package/src/components/FileDisplay/FileDisplay.tsx +23 -17
- package/src/components/FileUpload/FileUpload.test.tsx +52 -14
- package/src/components/FileUpload/FileUpload.tsx +112 -130
- package/src/components/Form/Form.test.tsx +6 -8
- package/src/components/Form/Form.tsx +365 -4
- package/src/components/NavigationMenu/NavigationMenu.test.tsx +14 -13
- package/src/components/NavigationMenu/useNavigationFiltering.ts +11 -21
- package/src/components/PaceAppLayout/PaceAppLayout.test.tsx +6 -4
- package/src/components/PaceAppLayout/PaceAppLayout.tsx +11 -15
- package/src/components/PaceLoginPage/PaceLoginPage.test.tsx +108 -61
- package/src/components/PaceLoginPage/PaceLoginPage.tsx +27 -3
- package/src/components/Progress/Progress.tsx +2 -4
- package/src/components/ProtectedRoute/ProtectedRoute.tsx +8 -8
- package/src/components/Select/Select.tsx +109 -98
- package/src/components/Select/types.ts +4 -1
- package/src/components/UserMenu/UserMenu.tsx +9 -6
- package/src/hooks/__tests__/ServiceHooks.test.tsx +16 -16
- package/src/hooks/__tests__/hooks.integration.test.tsx +55 -57
- package/src/hooks/__tests__/useAppConfig.unit.test.ts +129 -67
- package/src/hooks/__tests__/useFocusTrap.unit.test.tsx +97 -97
- package/src/hooks/__tests__/usePublicEvent.simple.test.ts +149 -67
- package/src/hooks/__tests__/usePublicEvent.test.ts +149 -79
- package/src/hooks/__tests__/usePublicEvent.unit.test.ts +158 -109
- package/src/hooks/__tests__/useSessionDraft.test.ts +163 -0
- package/src/hooks/__tests__/useSessionRestoration.unit.test.tsx +10 -5
- package/src/hooks/public/usePublicEvent.ts +67 -195
- package/src/hooks/public/usePublicEventLogo.test.ts +70 -17
- package/src/hooks/public/usePublicEventLogo.ts +24 -14
- package/src/hooks/public/usePublicFileDisplay.ts +2 -2
- package/src/hooks/public/usePublicRouteParams.ts +5 -5
- package/src/hooks/useAppConfig.ts +28 -26
- package/src/hooks/useEventTheme.test.ts +217 -239
- package/src/hooks/useEventTheme.ts +16 -28
- package/src/hooks/useFileDisplay.ts +2 -2
- package/src/hooks/useOrganisationPermissions.ts +5 -7
- package/src/hooks/useQueryCache.ts +0 -1
- package/src/hooks/useSessionDraft.ts +380 -0
- package/src/hooks/useSessionRestoration.ts +3 -1
- package/src/icons/index.ts +27 -0
- package/src/index.ts +5 -0
- package/src/providers/OrganisationProvider.tsx +23 -14
- package/src/providers/UnifiedAuthProvider.smoke.test.tsx +21 -21
- package/src/providers/__tests__/AuthProvider.test.tsx +21 -21
- package/src/providers/__tests__/EventProvider.test.tsx +61 -61
- package/src/providers/__tests__/InactivityProvider.test.tsx +56 -56
- package/src/providers/__tests__/OrganisationProvider.test.tsx +75 -75
- package/src/providers/__tests__/ProviderLifecycle.test.tsx +37 -37
- package/src/providers/__tests__/UnifiedAuthProvider.test.tsx +103 -103
- package/src/providers/services/EventServiceProvider.tsx +1 -24
- package/src/providers/services/UnifiedAuthProvider.tsx +5 -48
- package/src/providers/services/__tests__/AuthServiceProvider.integration.test.tsx +7 -7
- package/src/providers/services/__tests__/UnifiedAuthProvider.integration.test.tsx +13 -10
- package/src/rbac/__tests__/adapters.comprehensive.test.tsx +7 -457
- package/src/rbac/__tests__/auth-rbac.e2e.test.tsx +33 -7
- package/src/rbac/adapters.tsx +7 -295
- package/src/rbac/api.test.ts +44 -56
- package/src/rbac/api.ts +10 -17
- package/src/rbac/cache-invalidation.ts +0 -1
- package/src/rbac/compliance/index.ts +10 -0
- package/src/rbac/compliance/pattern-detector.ts +553 -0
- package/src/rbac/compliance/runtime-compliance.ts +22 -0
- package/src/rbac/components/AccessDenied.tsx +150 -0
- package/src/rbac/components/NavigationGuard.tsx +12 -20
- package/src/rbac/components/PagePermissionGuard.tsx +4 -24
- package/src/rbac/components/__tests__/NavigationGuard.test.tsx +21 -8
- package/src/rbac/components/index.ts +3 -41
- package/src/rbac/eslint-rules.js +1 -1
- package/src/rbac/hooks/index.ts +0 -3
- package/src/rbac/hooks/permissions/index.ts +0 -3
- package/src/rbac/hooks/permissions/useAccessLevel.ts +4 -8
- package/src/rbac/hooks/usePermissions.ts +0 -3
- package/src/rbac/hooks/useResolvedScope.test.ts +57 -47
- package/src/rbac/hooks/useResolvedScope.ts +58 -140
- package/src/rbac/hooks/useResourcePermissions.test.ts +124 -38
- package/src/rbac/hooks/useResourcePermissions.ts +139 -48
- package/src/rbac/hooks/useRoleManagement.test.ts +65 -22
- package/src/rbac/hooks/useRoleManagement.ts +147 -19
- package/src/rbac/hooks/useSecureSupabase.ts +4 -8
- package/src/rbac/index.ts +7 -9
- package/src/rbac/utils/contextValidator.ts +9 -7
- package/src/services/AuthService.ts +130 -18
- package/src/services/EventService.ts +4 -97
- package/src/services/InactivityService.ts +16 -0
- package/src/services/OrganisationService.ts +7 -44
- package/src/services/__tests__/OrganisationService.test.ts +26 -8
- package/src/services/base/BaseService.ts +0 -3
- package/src/styles/core.css +7 -0
- package/src/theming/__tests__/parseEventColours.test.ts +9 -3
- package/src/theming/parseEventColours.ts +22 -10
- package/src/types/database.generated.ts +4733 -3809
- package/src/utils/__tests__/lazyLoad.unit.test.tsx +42 -39
- package/src/utils/__tests__/organisationContext.unit.test.ts +9 -10
- package/src/utils/context/organisationContext.test.ts +13 -28
- package/src/utils/context/organisationContext.ts +21 -52
- package/src/utils/dynamic/dynamicUtils.ts +1 -1
- package/src/utils/file-reference/index.ts +39 -15
- package/src/utils/formatting/formatDateTime.test.ts +3 -2
- package/src/utils/google-places/loadGoogleMapsScript.ts +29 -4
- package/src/utils/index.ts +4 -1
- package/src/utils/persistence/__tests__/keyDerivation.test.ts +135 -0
- package/src/utils/persistence/__tests__/sensitiveFieldDetection.test.ts +123 -0
- package/src/utils/persistence/keyDerivation.ts +304 -0
- package/src/utils/persistence/sensitiveFieldDetection.ts +212 -0
- package/src/utils/security/secureStorage.ts +5 -5
- package/src/utils/storage/README.md +1 -1
- package/src/utils/storage/helpers.ts +3 -3
- package/src/utils/supabase/createBaseClient.ts +147 -0
- package/src/utils/timezone/timezone.test.ts +1 -2
- package/src/utils/timezone/timezone.ts +1 -1
- package/src/utils/validation/csrf.ts +4 -4
- package/cursor-rules/00-pace-core-compliance.mdc +0 -331
- package/cursor-rules/01-standards-compliance.mdc +0 -244
- package/cursor-rules/04-testing-standards.mdc +0 -268
- package/cursor-rules/05-bug-reports-and-features.mdc +0 -246
- package/cursor-rules/06-code-quality.mdc +0 -309
- package/cursor-rules/07-tech-stack-compliance.mdc +0 -214
- package/cursor-rules/CHANGELOG.md +0 -119
- package/cursor-rules/README.md +0 -192
- package/dist/DataTable-AOVNCPTX.js +0 -175
- package/dist/DataTable-AOVNCPTX.js.map +0 -1
- package/dist/UnifiedAuthProvider-4SBX4LU5.js +0 -18
- package/dist/UnifiedAuthProvider-4SBX4LU5.js.map +0 -1
- package/dist/api-O6HTBX5Y.js +0 -52
- package/dist/api-O6HTBX5Y.js.map +0 -1
- package/dist/audit-V53FV5AG.js +0 -17
- package/dist/audit-V53FV5AG.js.map +0 -1
- package/dist/chunk-5DRSZLL2.js.map +0 -1
- package/dist/chunk-63FOKYGO.js.map +0 -1
- package/dist/chunk-6COVEUS7.js.map +0 -1
- package/dist/chunk-AFVQODI2.js +0 -263
- package/dist/chunk-AFVQODI2.js.map +0 -1
- package/dist/chunk-DGUM43GV.js.map +0 -1
- package/dist/chunk-E66EQZE6.js.map +0 -1
- package/dist/chunk-EFN2EIMK.js.map +0 -1
- package/dist/chunk-FFQEQTNW.js.map +0 -1
- package/dist/chunk-FMUCXFII.js.map +0 -1
- package/dist/chunk-G37KK66H.js.map +0 -1
- package/dist/chunk-G7QEZTYQ.js +0 -2053
- package/dist/chunk-G7QEZTYQ.js.map +0 -1
- package/dist/chunk-HU2C6SSC.js.map +0 -1
- package/dist/chunk-IHB5DR3H.js.map +0 -1
- package/dist/chunk-IVOFDYWT.js.map +0 -1
- package/dist/chunk-J36DSWQK.js.map +0 -1
- package/dist/chunk-JGRYX5UX.js.map +0 -1
- package/dist/chunk-KQCRWDSA.js +0 -1
- package/dist/chunk-KQCRWDSA.js.map +0 -1
- package/dist/chunk-L4OXEN46.js.map +0 -1
- package/dist/chunk-LMC26NLJ.js +0 -84
- package/dist/chunk-LMC26NLJ.js.map +0 -1
- package/dist/chunk-M43Y4SSO.js.map +0 -1
- package/dist/chunk-M7MPQISP.js.map +0 -1
- package/dist/chunk-NTM7ZSB6.js.map +0 -1
- package/dist/chunk-PWLANIRT.js.map +0 -1
- package/dist/chunk-QXHPKYJV.js.map +0 -1
- package/dist/chunk-RGAWHO7N.js.map +0 -1
- package/dist/chunk-UPPMRMYG.js.map +0 -1
- package/dist/chunk-VBXEHIUJ.js.map +0 -1
- package/dist/chunk-ZSAAAMVR.js.map +0 -1
- package/dist/components.js.map +0 -1
- package/dist/contextValidator-5OGXSPKS.js +0 -9
- package/dist/contextValidator-5OGXSPKS.js.map +0 -1
- package/dist/eslint-rules/pace-core-compliance.cjs +0 -510
- package/dist/hooks.js.map +0 -1
- package/dist/index.js.map +0 -1
- package/dist/providers.js.map +0 -1
- package/dist/rbac/eslint-rules.js.map +0 -1
- package/dist/rbac/index.js.map +0 -1
- package/dist/styles/index.js.map +0 -1
- package/dist/theming/runtime.js.map +0 -1
- package/dist/types.js.map +0 -1
- package/dist/utils.js.map +0 -1
- package/docs/best-practices/README.md +0 -472
- package/docs/best-practices/accessibility.md +0 -601
- package/docs/best-practices/common-patterns.md +0 -516
- package/docs/best-practices/deployment.md +0 -1103
- package/docs/best-practices/performance.md +0 -1328
- package/docs/best-practices/security.md +0 -940
- package/docs/best-practices/testing.md +0 -1034
- package/docs/rbac/compliance/compliance-guide.md +0 -544
- package/docs/standards/01-architecture-standard.md +0 -44
- package/docs/standards/02-api-and-rpc-standard.md +0 -39
- package/docs/standards/03-component-standard.md +0 -32
- package/docs/standards/04-code-style-standard.md +0 -32
- package/docs/standards/05-security-standard.md +0 -44
- package/docs/standards/06-testing-and-docs-standard.md +0 -29
- package/docs/standards/pace-core-compliance.md +0 -432
- package/scripts/audit/core/checks/accessibility.cjs +0 -197
- package/scripts/audit/core/checks/api-usage.cjs +0 -191
- package/scripts/audit/core/checks/bundle.cjs +0 -142
- package/scripts/audit/core/checks/compliance.cjs +0 -2706
- package/scripts/audit/core/checks/config.cjs +0 -54
- package/scripts/audit/core/checks/coverage.cjs +0 -84
- package/scripts/audit/core/checks/dependencies.cjs +0 -994
- package/scripts/audit/core/checks/documentation.cjs +0 -268
- package/scripts/audit/core/checks/environment.cjs +0 -116
- package/scripts/audit/core/checks/error-handling.cjs +0 -340
- package/scripts/audit/core/checks/forms.cjs +0 -172
- package/scripts/audit/core/checks/heuristics.cjs +0 -68
- package/scripts/audit/core/checks/hooks.cjs +0 -334
- package/scripts/audit/core/checks/imports.cjs +0 -244
- package/scripts/audit/core/checks/performance.cjs +0 -325
- package/scripts/audit/core/checks/routes.cjs +0 -117
- package/scripts/audit/core/checks/state.cjs +0 -130
- package/scripts/audit/core/checks/structure.cjs +0 -65
- package/scripts/audit/core/checks/style.cjs +0 -584
- package/scripts/audit/core/checks/testing.cjs +0 -122
- package/scripts/audit/core/checks/typescript.cjs +0 -61
- package/scripts/audit/core/scanner.cjs +0 -199
- package/scripts/audit/core/utils.cjs +0 -137
- package/scripts/audit/index.cjs +0 -223
- package/scripts/audit/reporters/console.cjs +0 -151
- package/scripts/audit/reporters/json.cjs +0 -54
- package/scripts/audit/reporters/markdown.cjs +0 -124
- package/scripts/audit-consuming-app.cjs +0 -86
- package/src/components/DataTable/components/DataTableBody.tsx +0 -454
- package/src/components/DataTable/components/DraggableColumnHeader.tsx +0 -156
- package/src/components/DataTable/components/ExpandButton.tsx +0 -113
- package/src/components/DataTable/components/GroupHeader.tsx +0 -54
- package/src/components/DataTable/components/ViewRowModal.tsx +0 -68
- package/src/components/DataTable/components/VirtualizedDataTable.tsx +0 -525
- package/src/components/DataTable/components/__tests__/ExpandButton.test.tsx +0 -462
- package/src/components/DataTable/components/__tests__/GroupHeader.test.tsx +0 -393
- package/src/components/DataTable/components/__tests__/ViewRowModal.test.tsx +0 -476
- package/src/components/DataTable/components/__tests__/VirtualizedDataTable.test.tsx +0 -128
- package/src/components/DataTable/core/DataTableContext.tsx +0 -216
- package/src/components/DataTable/core/__tests__/DataTableContext.test.tsx +0 -136
- package/src/components/DataTable/hooks/__tests__/useColumnReordering.test.ts +0 -570
- package/src/components/DataTable/hooks/useColumnReordering.ts +0 -123
- package/src/components/DataTable/utils/debugTools.ts +0 -514
- package/src/eslint-rules/pace-core-compliance.js +0 -638
- package/src/rbac/components/EnhancedNavigationMenu.test.tsx +0 -555
- package/src/rbac/components/EnhancedNavigationMenu.tsx +0 -293
- package/src/rbac/components/NavigationProvider.test.tsx +0 -481
- package/src/rbac/components/NavigationProvider.tsx +0 -345
- package/src/rbac/components/PagePermissionProvider.test.tsx +0 -476
- package/src/rbac/components/PagePermissionProvider.tsx +0 -279
- package/src/rbac/components/PermissionEnforcer.tsx +0 -312
- package/src/rbac/components/RoleBasedRouter.tsx +0 -440
- package/src/rbac/components/SecureDataProvider.test.tsx +0 -543
- package/src/rbac/components/SecureDataProvider.tsx +0 -339
- package/src/rbac/components/__tests__/EnhancedNavigationMenu.test.tsx +0 -620
- package/src/rbac/components/__tests__/NavigationProvider.test.tsx +0 -726
- package/src/rbac/components/__tests__/PagePermissionProvider.test.tsx +0 -661
- package/src/rbac/components/__tests__/PermissionEnforcer.test.tsx +0 -881
- package/src/rbac/components/__tests__/RoleBasedRouter.test.tsx +0 -783
- package/src/rbac/components/__tests__/SecureDataProvider.fixed.test.tsx +0 -645
- package/src/rbac/components/__tests__/SecureDataProvider.test.tsx +0 -659
- package/src/rbac/hooks/permissions/useCachedPermissions.ts +0 -79
- package/src/rbac/hooks/permissions/useHasAllPermissions.ts +0 -90
- package/src/rbac/hooks/permissions/useHasAnyPermission.ts +0 -90
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useZodForm.ts","../src/hooks/useFormDialog.ts","../src/hooks/useOrganisationPermissions.ts","../src/utils/storage/index.ts","../src/hooks/public/usePublicEvent.ts","../src/hooks/public/usePublicEventLogo.ts","../src/hooks/public/usePublicRouteParams.ts"],"sourcesContent":["\n/**\n * Zod Form Hook\n * \n * Enhanced form hook with Zod validation\n */\n\nimport { useForm } from 'react-hook-form';\nimport { zodResolver } from '@hookform/resolvers/zod';\nimport { z } from 'zod';\n\ninterface UseZodFormProps<T extends z.ZodTypeAny> {\n schema: T;\n defaultValues?: Partial<z.infer<T>>;\n mode?: 'onSubmit' | 'onBlur' | 'onChange' | 'onTouched' | 'all';\n}\n\nexport function useZodForm<T extends z.ZodTypeAny>({\n schema,\n defaultValues,\n mode = 'onSubmit'\n}: UseZodFormProps<T>) {\n return useForm<z.infer<T>>({\n resolver: zodResolver(schema),\n defaultValues: defaultValues as any,\n mode\n });\n}\n","/**\n * @file useFormDialog Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.1.0\n * \n * Generic React hook for managing form dialog state (open/close, form data, reset behavior).\n * \n * @example\n * ```ts\n * interface ContactFormData {\n * name: string;\n * email: string;\n * }\n * \n * function ContactDialog() {\n * const { isOpen, formData, openDialog, closeDialog, handleOpenChange } = \n * useFormDialog<ContactFormData>();\n * \n * // Use in Dialog component\n * // <Dialog open={isOpen} onOpenChange={handleOpenChange}>...</Dialog>\n * }\n * ```\n */\n\nimport { useState } from 'react';\n\n/**\n * Options for configuring the useFormDialog hook behavior\n */\nexport interface UseFormDialogOptions<T = unknown> {\n /**\n * Callback invoked when the dialog open state changes\n * @param open - The new open state\n */\n onOpenChange?: (open: boolean) => void;\n \n /**\n * Whether to reset form data when the dialog closes\n * @default true\n */\n resetOnClose?: boolean;\n}\n\n/**\n * Return type for the useFormDialog hook\n */\nexport interface UseFormDialogReturn<T = unknown> {\n /**\n * Current open state of the dialog\n */\n isOpen: boolean;\n \n /**\n * Current form data (null when dialog is closed or no data provided)\n */\n formData: T | null;\n \n /**\n * Open the dialog with optional form data\n * @param data - Optional form data to populate the dialog\n */\n openDialog: (data?: T | null) => void;\n \n /**\n * Close the dialog\n */\n closeDialog: () => void;\n \n /**\n * Handler for controlled dialog components (e.g., Radix UI Dialog)\n * @param open - The new open state\n */\n handleOpenChange: (open: boolean) => void;\n}\n\n/**\n * Generic React hook for managing form dialog state.\n * \n * Provides state management for form dialogs including:\n * - Open/close state\n * - Form data management\n * - Automatic reset on close (configurable)\n * - Controlled and uncontrolled usage patterns\n * \n * @param options - Configuration options for the hook\n * @returns Object containing dialog state and control functions\n * \n * @example\n * ```ts\n * // Basic usage\n * const { isOpen, openDialog, closeDialog } = useFormDialog();\n * \n * // With form data\n * interface UserData {\n * id: string;\n * name: string;\n * }\n * const { isOpen, formData, openDialog } = useFormDialog<UserData>();\n * \n * // Open with data\n * openDialog({ id: '1', name: 'John' });\n * \n * // Controlled usage with callback\n * const { handleOpenChange, isOpen } = useFormDialog({\n * onOpenChange: (open) => console.log('Dialog state:', open),\n * resetOnClose: false\n * });\n * ```\n */\nexport function useFormDialog<T = unknown>({\n onOpenChange,\n resetOnClose = true,\n}: UseFormDialogOptions<T> = {}): UseFormDialogReturn<T> {\n const [isOpen, setIsOpen] = useState(false);\n const [formData, setFormData] = useState<T | null>(null);\n\n // React Compiler handles memoization automatically\n const openDialog = (data: T | null = null) => {\n setFormData(data);\n setIsOpen(true);\n onOpenChange?.(true);\n };\n\n const closeDialog = () => {\n setIsOpen(false);\n if (resetOnClose) {\n setFormData(null);\n }\n onOpenChange?.(false);\n };\n\n const handleOpenChange = (open: boolean) => {\n setIsOpen(open);\n if (!open && resetOnClose) {\n setFormData(null);\n }\n onOpenChange?.(open);\n };\n\n return {\n isOpen,\n formData,\n openDialog,\n closeDialog,\n handleOpenChange,\n };\n}\n","/**\n * @file useOrganisationPermissions Hook\n * @package @jmruthers/pace-core\n * @module Hooks/useOrganisationPermissions\n * @since 0.4.0\n *\n * Hook for managing organisation-specific permissions and role validation.\n * Provides secure access to user's role and permissions within organisations.\n *\n * @example\n * ```tsx\n * function OrganisationComponent() {\n * const { \n * isOrgAdmin, \n * canManageMembers,\n * userRole,\n * hasOrganisationAccess\n * } = useOrganisationPermissions();\n * \n * return (\n * <section>\n * {isOrgAdmin && <AdminPanel />}\n * {canManageMembers && <MemberManagement />}\n * <p>Your role: {userRole}</p>\n * </section>\n * );\n * }\n * \n * // For specific organisation\n * function MultiOrgComponent() {\n * const permissions = useOrganisationPermissions('org-123');\n * \n * if (!permissions.hasOrganisationAccess) {\n * return <main>No access to this organisation</main>;\n * }\n * \n * return <main>Role in org-123: {permissions.userRole}</main>;\n * }\n * ```\n *\n * @security\n * - Validates user membership in organisation\n * - Provides role-based permission checks\n * - Ensures secure access to organisation data\n * - Real-time permission validation\n */\n\nimport { useMemo } from 'react';\nimport { useOrganisations } from './useOrganisations';\nimport { useOrganisationSecurity } from './useOrganisationSecurity';\nimport type { OrganisationRole, OrganisationPermission } from '../types/organisation';\n\n/**\n * Return value of the useOrganisationPermissions hook.\n * Provides organisation-specific permissions and role information.\n */\nexport interface UseOrganisationPermissionsReturn {\n /** User's role in the organisation */\n userRole: OrganisationRole | 'no_access';\n \n /** Whether user has organisation admin role */\n isOrgAdmin: boolean;\n \n /** Whether user is a super admin */\n isSuperAdmin: boolean;\n \n /** Whether user can moderate content */\n canModerate: boolean;\n \n /** Whether user can manage members */\n canManageMembers: boolean;\n \n /** Whether user can manage organisation settings */\n canManageSettings: boolean;\n \n /** Whether user can manage events */\n canManageEvents: boolean;\n \n /** Whether user has any admin privileges */\n hasAdminPrivileges: boolean;\n \n /** Whether user has access to the organisation */\n hasOrganisationAccess: boolean;\n \n /** Check if user has specific permission */\n hasPermission: (permission: OrganisationPermission) => boolean;\n \n /** Get all permissions for the user */\n getAllPermissions: () => OrganisationPermission[];\n \n /** Organisation ID being checked */\n organisationId: string;\n}\n\n/**\n * Hook to access organisation-specific permissions and roles\n * \n * @param orgId - Optional organisation ID. Defaults to currently selected organisation\n * @returns Organisation permissions and role information\n */\nexport function useOrganisationPermissions(orgId?: string): UseOrganisationPermissionsReturn {\n const { \n selectedOrganisation, \n getUserRole, \n validateOrganisationAccess,\n ensureOrganisationContext\n } = useOrganisations();\n \n // Get super admin context if available (may not be available in all contexts)\n let superAdminContext: { isSuperAdmin: boolean } = { isSuperAdmin: false };\n try {\n superAdminContext = useOrganisationSecurity().superAdminContext;\n } catch {\n // Not available in this context, default to false\n }\n\n const organisationId = useMemo(() => {\n if (orgId) {\n return orgId;\n }\n try {\n const currentOrg = ensureOrganisationContext();\n return currentOrg.id;\n } catch {\n return '';\n }\n }, [orgId, ensureOrganisationContext]);\n\n const userRole = useMemo(() => {\n if (!organisationId) return 'no_access';\n const role = getUserRole(organisationId);\n // Map to valid OrganisationRole or 'no_access'\n if (role === 'org_admin' || role === 'leader' || role === 'member' || role === 'supporter') {\n return role as OrganisationRole;\n }\n return 'no_access';\n }, [organisationId, getUserRole]);\n\n const hasOrganisationAccess = useMemo(() => {\n if (!organisationId) return false;\n return validateOrganisationAccess(organisationId);\n }, [organisationId, validateOrganisationAccess]);\n\n const permissions = useMemo(() => {\n if (!hasOrganisationAccess || userRole === 'no_access') {\n return {\n isOrgAdmin: false,\n isSuperAdmin: false,\n canModerate: false,\n canManageMembers: false,\n canManageSettings: false,\n canManageEvents: false,\n hasAdminPrivileges: false\n };\n }\n\n const isOrgAdmin = userRole === 'org_admin';\n const isLeader = userRole === 'leader';\n const isMember = userRole === 'member';\n const isSupporter = userRole === 'supporter';\n\n // Super admin status - database backed (user_metadata can be spoofed)\n // Get super admin status from the security hook\n const isSuperAdmin = superAdminContext.isSuperAdmin;\n\n return {\n isOrgAdmin,\n isSuperAdmin,\n canModerate: isSuperAdmin || isOrgAdmin || isLeader,\n canManageMembers: isSuperAdmin || isOrgAdmin || isLeader, // Leaders can manage members\n canManageSettings: isSuperAdmin || isOrgAdmin,\n canManageEvents: isSuperAdmin || isOrgAdmin || isLeader,\n hasAdminPrivileges: isSuperAdmin || isOrgAdmin || isLeader // Leaders have admin privileges\n };\n }, [hasOrganisationAccess, userRole]);\n\n const hasPermission = useMemo(() => {\n return (permission: OrganisationPermission): boolean => {\n if (!hasOrganisationAccess || userRole === 'no_access') {\n return false;\n }\n\n // Super admin has all permissions (org_admin acts as super admin within org)\n if (userRole === 'org_admin' || permission === '*') {\n return true;\n }\n\n // Map permissions to roles using the defined permissions from organisation types\n const rolePermissions: Record<OrganisationRole, OrganisationPermission[]> = {\n supporter: ['view_basic'],\n member: ['view_basic', 'view_details'],\n leader: ['view_basic', 'view_details', 'moderate_content', 'manage_events'],\n org_admin: ['view_basic', 'view_details', 'moderate_content', 'manage_events', 'manage_members', 'manage_settings']\n };\n\n const userPermissions = rolePermissions[userRole as OrganisationRole] || [];\n return userPermissions.includes(permission) || userPermissions.includes('*');\n };\n }, [hasOrganisationAccess, userRole]);\n\n const getAllPermissions = useMemo(() => {\n return (): OrganisationPermission[] => {\n if (!hasOrganisationAccess || userRole === 'no_access') {\n return [];\n }\n\n const rolePermissions: Record<OrganisationRole, OrganisationPermission[]> = {\n supporter: ['view_basic'],\n member: ['view_basic', 'view_details'],\n leader: ['view_basic', 'view_details', 'moderate_content', 'manage_events'],\n org_admin: ['view_basic', 'view_details', 'moderate_content', 'manage_events', 'manage_members', 'manage_settings']\n };\n\n return rolePermissions[userRole as OrganisationRole] || [];\n };\n }, [hasOrganisationAccess, userRole]);\n\n return useMemo(() => ({\n userRole,\n organisationId,\n hasOrganisationAccess,\n hasPermission,\n getAllPermissions,\n ...permissions\n }), [userRole, organisationId, hasOrganisationAccess, hasPermission, getAllPermissions, permissions]);\n} ","/**\n * Storage utilities for pace-core\n * \n * Provides app-segregated file storage with organisation-scoped access\n */\n\nexport * from './types';\nexport * from './config';\nexport * from './helpers';\n\n// Import functions for StorageUtils class\nimport {\n uploadFile,\n getPublicUrl,\n getSignedUrl,\n deleteFile,\n downloadFile,\n listFiles,\n archiveFile,\n generateFilePath,\n generateUniqueFileName,\n extractFileMetadata\n} from './helpers';\n\n// Re-export commonly used functions for convenience\nexport {\n uploadFile,\n getPublicUrl,\n getSignedUrl,\n deleteFile,\n downloadFile,\n listFiles,\n archiveFile,\n generateFilePath,\n generateUniqueFileName,\n extractFileMetadata\n};\n\nexport {\n validateFileSize,\n getFileSizeLimit,\n formatFileSize\n} from './config';\n\n\nexport {\n FILE_SIZE_LIMITS,\n DEFAULT_FILE_SIZE_LIMIT,\n STORAGE_CONFIG,\n APP_PATH_MAPPING\n} from './config';\n\n/**\n * StorageUtils class for convenient access to storage functions\n */\nexport class StorageUtils {\n static generateFilePath = generateFilePath;\n static generateUniqueFileName = generateUniqueFileName;\n static extractFileMetadata = extractFileMetadata;\n static uploadFile = uploadFile;\n static getPublicUrl = getPublicUrl;\n static getSignedUrl = getSignedUrl;\n static deleteFile = deleteFile;\n static downloadFile = downloadFile;\n static listFiles = listFiles;\n static archiveFile = archiveFile;\n}\n","/**\n * @file Public Event Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for accessing public event data without authentication.\n * Provides event information by event_code for public pages.\n *\n * Features:\n * - No authentication required\n * - Caching for performance\n * - Error handling and loading states\n * - TypeScript support\n * - Automatic refetch capabilities\n *\n * @example\n * ```tsx\n * import { usePublicEvent } from '@jmruthers/pace-core';\n *\n * function PublicEventPage() {\n * const { eventCode } = usePublicRouteParams();\n * const { event, isLoading, error, refetch } = usePublicEvent(eventCode);\n *\n * if (isLoading) return <div>Loading event...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * if (!event) return <div>Event not found</div>;\n *\n * return (\n * <div>\n * <h1>{event.event_name}</h1>\n * <p>Date: {event.event_date}</p>\n * <p>Venue: {event.event_venue}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @accessibility\n * - No direct accessibility concerns (hook)\n * - Enables accessible public event display\n * - Supports screen reader friendly loading states\n *\n * @security\n * - Only returns public-safe event data\n * - Validates event_code before querying\n * - No sensitive information exposed\n * - Rate limiting applied at database level\n *\n * @performance\n * - Built-in caching with TTL\n * - Minimal re-renders with stable references\n * - Lazy loading support\n * - Error boundary integration\n *\n * @dependencies\n * - React 19+ - Hooks and effects\n * - @supabase/supabase-js - Database integration\n * - Event types - Type definitions\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport { createClient } from '@supabase/supabase-js';\nimport type { Event } from '../../types/event';\nimport type { Database } from '../../types/database';\nimport { assertOrganisationId } from '../../types/core';\nimport { usePublicPageContext } from '../../components/PublicLayout/PublicPageProvider';\nimport { logger } from '../../utils/core/logger';\n\n// Simple in-memory cache for public data\nconst publicDataCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n/**\n * Return value of the usePublicEvent hook.\n * Provides event data, loading state, error, and refetch function.\n */\nexport interface UsePublicEventReturn {\n /** The event data, null if not loaded or not found */\n event: Event | null;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UsePublicEventOptions {\n /** Cache TTL in milliseconds (default: 5 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n}\n\n/**\n * Hook for accessing public event data by event_code\n * \n * This hook provides access to public event information without requiring\n * authentication. It includes caching, error handling, and loading states.\n * \n * @param eventCode - The event code to look up\n * @param options - Configuration options for caching and behavior\n * @returns Object containing event data, loading state, error, and refetch function\n */\nexport function usePublicEvent(\n eventCode: string,\n options: UsePublicEventOptions = {}\n): UsePublicEventReturn {\n const {\n cacheTtl = 5 * 60 * 1000, // 5 minutes\n enableCache = true\n } = options;\n\n const [event, setEvent] = useState<Event | null>(null);\n const [isLoading, setIsLoading] = useState<boolean>(true);\n const [error, setError] = useState<Error | null>(null);\n\n // Get environment variables from public page context or fallback to direct access\n let environment: { supabaseUrl: string | null; supabaseKey: string | null };\n \n try {\n environment = usePublicPageContext().environment;\n } catch {\n // Fallback to direct environment variable access if not in PublicPageProvider\n environment = {\n supabaseUrl: (import.meta as any).env?.VITE_SUPABASE_URL || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_URL || null,\n supabaseKey: (import.meta as any).env?.VITE_SUPABASE_PUBLISHABLE_KEY || (import.meta as any).env?.NEXT_PUBLIC_SUPABASE_PUBLISHABLE_KEY || null\n };\n }\n \n // Create a simple Supabase client for public access\n const supabase = useMemo(() => {\n if (typeof window === 'undefined') return null;\n \n if (!environment.supabaseUrl || !environment.supabaseKey) {\n logger.warn('usePublicEvent', 'Missing Supabase environment variables. Please ensure VITE_SUPABASE_URL and VITE_SUPABASE_PUBLISHABLE_KEY are set in your environment.');\n return null;\n }\n\n return createClient<Database>(environment.supabaseUrl, environment.supabaseKey);\n }, [environment.supabaseUrl, environment.supabaseKey]);\n\n // Helper function to try refreshing schema cache\n const refreshSchemaCache = useCallback(async () => {\n try {\n // Try to trigger a schema refresh by querying a system table\n await (supabase as any).from('information_schema.routines').select('routine_name').limit(1);\n } catch (error) {\n // Ignore errors, this is just an attempt to refresh cache\n logger.debug('usePublicEvent', 'Schema cache refresh attempt failed:', error);\n }\n }, [supabase]);\n\n const fetchEvent = useCallback(async (): Promise<void> => {\n if (!eventCode || !supabase) {\n setError(new Error('Invalid event code or Supabase client not available'));\n setIsLoading(false);\n return;\n }\n\n // Check cache first\n const cacheKey = `public_event_${eventCode}`;\n if (enableCache) {\n const cached = publicDataCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n setEvent(cached.data);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n let eventData: any = null;\n\n try {\n // Try to call the public event RPC function first\n const response = await (supabase as any).rpc('get_public_event_by_code', {\n event_code_param: eventCode\n });\n \n const data = response?.data;\n const rpcError = response?.error;\n\n if (rpcError) {\n // If RPC function doesn't exist or schema cache issue, try refresh first, then fallback\n if (rpcError.message?.includes('Could not find the function') || \n rpcError.message?.includes('does not exist') ||\n rpcError.message?.includes('schema cache')) {\n logger.warn('usePublicEvent', 'RPC function not found or schema cache issue, attempting refresh:', rpcError.message);\n \n // Try to refresh schema cache first\n await refreshSchemaCache();\n \n // Try RPC call one more time after refresh\n try {\n const retryResponse = await (supabase as any).rpc('get_public_event_by_code', {\n event_code_param: eventCode\n });\n \n const retryData = retryResponse?.data;\n const retryError = retryResponse?.error;\n \n if (!retryError && retryData && retryData.length > 0) {\n eventData = retryData[0];\n } else {\n throw new Error('RPC still failing after cache refresh');\n }\n } catch (retryError) {\n logger.warn('usePublicEvent', 'RPC still failing after cache refresh, falling back to direct table access');\n \n // Fallback: Direct table access with public RLS policy\n const tableResponse2 = await (supabase as any)\n .from('core_events')\n .select(`\n event_id,\n event_name,\n event_date,\n event_venue,\n event_participants,\n event_colours,\n organisation_id,\n event_days,\n event_typicalunit,\n event_rounddown,\n event_youthmultiplier,\n event_catering_email,\n event_news,\n event_billing,\n event_email\n `)\n .eq('event_code', eventCode)\n .eq('is_visible', true)\n .not('organisation_id', 'is', null)\n .limit(1)\n .single();\n\n const tableData = tableResponse2?.data;\n const tableError = tableResponse2?.error;\n\n if (tableError) {\n throw new Error(tableError?.message || 'Failed to fetch event from table');\n }\n\n if (!tableData) {\n setEvent(null);\n setError(new Error('Event not found'));\n return;\n }\n\n // Get event logo from core_file_references\n const logoResponse = await (supabase as any)\n .from('core_file_references')\n .select('file_path')\n .eq('table_name', 'core_events')\n .eq('record_id', tableData.event_id)\n .eq('is_public', true)\n .eq('file_metadata->>category', 'event_logos')\n .limit(1)\n .single();\n \n const logoData = logoResponse?.data;\n\n eventData = {\n ...tableData,\n event_logo: logoData?.file_path || null\n };\n }\n } else {\n // For RPC errors that aren't schema cache issues, throw immediately without fallback\n const errorMessage = rpcError?.message || rpcError?.toString() || 'Failed to fetch event';\n setEvent(null);\n setError(new Error(errorMessage));\n setIsLoading(false);\n return;\n }\n } else {\n if (!data || data.length === 0 || !data[0]) {\n setEvent(null);\n setError(new Error('Event not found'));\n return;\n }\n eventData = data[0];\n }\n } catch (rpcError) {\n // If RPC call fails for any reason (including schema cache issues), try direct table access\n logger.warn('usePublicEvent', 'RPC call failed, falling back to direct table access:', rpcError);\n \n const tableResponse = await (supabase as any)\n .from('core_events')\n .select(`\n event_id,\n event_name,\n event_date,\n event_venue,\n event_participants,\n event_colours,\n organisation_id,\n event_days,\n event_typicalunit,\n event_rounddown,\n event_youthmultiplier,\n event_catering_email,\n event_news,\n event_billing,\n event_email\n `)\n .eq('event_code', eventCode)\n .eq('is_visible', true)\n .not('organisation_id', 'is', null)\n .limit(1)\n .single();\n\n const tableData = tableResponse?.data;\n const tableError = tableResponse?.error;\n\n if (tableError) {\n throw new Error(tableError?.message || 'Failed to fetch event from table');\n }\n\n if (!tableData) {\n setEvent(null);\n setError(new Error('Event not found'));\n return;\n }\n\n // Get event logo from core_file_references\n const logoResponse = await (supabase as any)\n .from('core_file_references')\n .select('file_path')\n .eq('table_name', 'core_events')\n .eq('record_id', tableData.event_id)\n .eq('is_public', true)\n .eq('file_metadata->>category', 'event_logos')\n .limit(1)\n .single();\n \n const logoData = logoResponse?.data;\n\n eventData = {\n ...tableData,\n event_logo: logoData?.file_path || null\n };\n }\n \n // Transform to Event type\n const transformedEvent: Event = {\n id: eventData.event_id,\n event_id: eventData.event_id,\n event_name: eventData.event_name,\n event_code: eventCode,\n event_date: eventData.event_date,\n event_venue: eventData.event_venue,\n event_participants: eventData.event_participants,\n event_logo: eventData.event_logo,\n event_colours: eventData.event_colours,\n organisation_id: assertOrganisationId(eventData.organisation_id),\n is_visible: true,\n created_at: new Date().toISOString(),\n updated_at: new Date().toISOString()\n };\n\n setEvent(transformedEvent);\n\n // Cache the result\n if (enableCache) {\n publicDataCache.set(cacheKey, {\n data: transformedEvent,\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n\n } catch (err) {\n logger.error('usePublicEvent', 'Error fetching event:', err);\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setEvent(null);\n } finally {\n setIsLoading(false);\n }\n }, [eventCode, supabase, cacheTtl, enableCache]);\n\n // Fetch event when eventCode changes\n useEffect(() => {\n fetchEvent();\n }, [fetchEvent]);\n\n const refetch = useCallback(async (): Promise<void> => {\n // Clear cache for this event\n if (enableCache) {\n const cacheKey = `public_event_${eventCode}`;\n publicDataCache.delete(cacheKey);\n }\n await fetchEvent();\n }, [fetchEvent, eventCode, enableCache]);\n\n return {\n event,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached public event data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearPublicEventCache(): void {\n for (const [key] of publicDataCache) {\n if (key.startsWith('public_event_')) {\n publicDataCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getPublicEventCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(publicDataCache.keys()).filter(key => key.startsWith('public_event_'));\n return {\n size: keys.length,\n keys\n };\n}\n","/**\n * @file Public Event Logo Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for accessing public event logo URLs without authentication.\n * Provides logo URLs with fallback handling for public pages.\n *\n * Features:\n * - No authentication required\n * - Automatic fallback to event initials\n * - Caching for performance\n * - Error handling and loading states\n * - TypeScript support\n * - Image validation\n *\n * @example\n * ```tsx\n * import { usePublicEventLogo } from '@jmruthers/pace-core';\n *\n * function EventHeader() {\n * const { logoUrl, fallbackText, isLoading, error } = usePublicEventLogo(\n * eventId, \n * eventName, \n * organisationId\n * );\n *\n * if (isLoading) return <div>Loading logo...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return (\n * <div>\n * {logoUrl ? (\n * <img src={logoUrl} alt={`${eventName} logo`} />\n * ) : (\n * <div className=\"logo-fallback\">{fallbackText}</div>\n * )}\n * </div>\n * );\n * }\n * ```\n *\n * @accessibility\n * - No direct accessibility concerns (hook)\n * - Enables accessible logo display with proper alt text\n * - Supports screen reader friendly fallbacks\n *\n * @security\n * - Only returns public-safe logo URLs\n * - Validates image existence before returning URL\n * - No sensitive information exposed\n * - Rate limiting applied at storage level\n *\n * @performance\n * - Built-in caching with TTL\n * - Image validation and optimization\n * - Minimal re-renders with stable references\n * - Lazy loading support\n *\n * @dependencies\n * - React 19+ - Hooks and effects\n * - @supabase/supabase-js - Storage integration\n * - Event types - Type definitions\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { createLogger } from '../../utils/core/logger';\n\nconst log = createLogger('usePublicEventLogo');\n\n// Simple in-memory cache for public data\nconst publicDataCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n/**\n * Return value of the usePublicEventLogo hook.\n * Provides logo URL, fallback text, loading state, error, and refetch function.\n */\nexport interface UsePublicEventLogoReturn {\n /** The logo URL if available, null if not found or error */\n logoUrl: string | null;\n /** Fallback text (event initials) if no logo is available */\n fallbackText: string;\n /** Whether the logo is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the logo */\n refetch: () => Promise<void>;\n}\n\nexport interface UsePublicEventLogoOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Whether to validate image existence (default: true) */\n validateImage?: boolean;\n /** Custom fallback text generator */\n generateFallbackText?: (eventName: string) => string;\n /** Supabase client instance (required) */\n supabase: SupabaseClient<Database>;\n}\n\n/**\n * Generate fallback text from event name (first letter of each word)\n */\nfunction defaultGenerateFallbackText(eventName: string): string {\n if (!eventName) return 'EV';\n \n return eventName\n .split(' ')\n .map(word => word.charAt(0).toUpperCase())\n .join('')\n .substring(0, 3); // Max 3 characters\n}\n\n/**\n * Hook for accessing public event logo URLs\n * \n * This hook provides access to event logo URLs without requiring\n * authentication. It includes fallback handling and image validation.\n * \n * @param eventId - The event ID to fetch logo for\n * @param eventName - The event name for fallback text generation\n * @param organisationId - The organisation ID for storage path\n * @param options - Configuration options for caching and behavior\n * @returns Object containing logo URL, fallback text, loading state, error, and refetch function\n */\nexport function usePublicEventLogo(\n eventId: string | undefined,\n eventName: string | undefined,\n organisationId: string | undefined,\n options: UsePublicEventLogoOptions\n): UsePublicEventLogoReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n validateImage = true,\n generateFallbackText = defaultGenerateFallbackText,\n supabase\n } = options;\n\n const [logoUrl, setLogoUrl] = useState<string | null>(null);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Generate fallback text\n const fallbackText = useMemo(() => {\n return eventName ? generateFallbackText(eventName) : 'EV';\n }, [eventName, generateFallbackText]);\n\n const fetchLogo = useCallback(async (): Promise<void> => {\n if (!eventId || !organisationId || !supabase) {\n setLogoUrl(null);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisationId)) {\n log.warn('Invalid organisationId format (not a valid UUID):', organisationId);\n // Don't return early - let the database handle the validation\n // This allows for more graceful error handling\n }\n\n // Check cache first\n const cacheKey = `public_logo_${eventId}_${organisationId}`;\n if (enableCache) {\n const cached = publicDataCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n setLogoUrl(cached.data);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n // Call the public logo RPC function\n const { data, error: rpcError } = await (supabase as any).rpc('get_public_event_logo', {\n event_id_param: eventId,\n organisation_id_param: organisationId\n });\n\n if (rpcError) {\n throw new Error(rpcError.message || 'Failed to fetch logo');\n }\n\n if (!data || data.length === 0 || !data[0] || !data[0].logo_url) {\n setLogoUrl(null);\n return;\n }\n\n const logoUrl = data[0].logo_url;\n\n // Validate image existence if requested\n if (validateImage) {\n try {\n const response = await fetch(logoUrl, { method: 'HEAD' });\n if (!response.ok) {\n log.warn('Logo URL not accessible:', logoUrl);\n setLogoUrl(null);\n return;\n }\n } catch (fetchError) {\n log.warn('Error validating logo URL:', fetchError);\n setLogoUrl(null);\n return;\n }\n }\n\n setLogoUrl(logoUrl);\n\n // Cache the result\n if (enableCache) {\n publicDataCache.set(cacheKey, {\n data: logoUrl,\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n\n } catch (err) {\n log.error('Error fetching logo:', err);\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setLogoUrl(null);\n } finally {\n setIsLoading(false);\n }\n }, [eventId, organisationId, supabase, cacheTtl, enableCache, validateImage]);\n\n // Fetch logo when parameters change\n useEffect(() => {\n if (eventId && organisationId) {\n fetchLogo();\n } else {\n setLogoUrl(null);\n setIsLoading(false);\n setError(null);\n }\n }, [fetchLogo, eventId, organisationId]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!eventId || !organisationId) return;\n \n // Clear cache for this logo\n if (enableCache) {\n const cacheKey = `public_logo_${eventId}_${organisationId}`;\n publicDataCache.delete(cacheKey);\n }\n await fetchLogo();\n }, [fetchLogo, eventId, organisationId, enableCache]);\n\n return {\n logoUrl,\n fallbackText,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached public logo data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearPublicLogoCache(): void {\n for (const [key] of publicDataCache) {\n if (key.startsWith('public_logo_')) {\n publicDataCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getPublicLogoCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(publicDataCache.keys()).filter(key => key.startsWith('public_logo_'));\n return {\n size: keys.length,\n keys\n };\n}\n","/**\n * @file Public Route Params Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for extracting and validating public route parameters.\n * Provides event code extraction and validation for public pages.\n *\n * Features:\n * - URL parameter extraction\n * - Event code validation\n * - TypeScript support\n * - Error handling\n * - Route pattern support\n *\n * @example\n * ```tsx\n * import { usePublicRouteParams } from '@jmruthers/pace-core';\n *\n * function PublicEventPage() {\n * const { eventCode, eventId, event, error, isLoading } = usePublicRouteParams();\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n * if (!event) return <div>Event not found</div>;\n *\n * return (\n * <div>\n * <h1>{event.event_name}</h1>\n * <p>Event Code: {eventCode}</p>\n * </div>\n * );\n * }\n * ```\n *\n * @accessibility\n * - No direct accessibility concerns (hook)\n * - Enables accessible route parameter handling\n * - Supports screen reader friendly error states\n *\n * @security\n * - Validates event codes before processing\n * - Sanitizes URL parameters\n * - No sensitive information exposed\n * - Rate limiting applied at route level\n *\n * @performance\n * - Minimal re-renders with stable references\n * - Efficient parameter extraction\n * - Caching integration\n *\n * @dependencies\n * - React 19+ - Hooks and effects\n * - React Router - URL parameter extraction\n * - Event types - Type definitions\n */\n\nimport { useState, useEffect, useCallback, useMemo } from 'react';\nimport { useParams, useLocation } from 'react-router-dom';\nimport type { Event } from '../../types/event';\nimport { usePublicEvent } from './usePublicEvent';\n\n/**\n * Return value of the usePublicRouteParams hook.\n * Provides event code, event ID, event data, loading state, error, and refetch function.\n */\nexport interface UsePublicRouteParamsReturn {\n /** The event code from the URL */\n eventCode: string | null;\n /** The event ID (resolved from event code) */\n eventId: string | null;\n /** The full event object */\n event: Event | null;\n /** Whether the route parameters are being processed */\n isLoading: boolean;\n /** Any error that occurred during processing */\n error: Error | null;\n /** Function to manually refetch the event data */\n refetch: () => Promise<void>;\n}\n\ninterface UsePublicRouteParamsOptions {\n /** Whether to automatically fetch event data (default: true) */\n fetchEventData?: boolean;\n /** Custom event code parameter name (default: 'eventCode') */\n eventCodeParam?: string;\n /** Whether to validate event code format (default: true) */\n validateEventCode?: boolean;\n}\n\n/**\n * Validate event code format\n * Event codes should be alphanumeric with optional hyphens/underscores in the middle\n * Supports 2-50 characters\n */\nfunction validateEventCodeFormat(eventCode: string): boolean {\n if (!eventCode || typeof eventCode !== 'string') return false;\n \n // Length check: 2-50 characters\n if (eventCode.length < 2 || eventCode.length > 50) return false;\n \n // For 2-character codes: both must be alphanumeric\n if (eventCode.length === 2) {\n return /^[a-zA-Z0-9]{2}$/.test(eventCode);\n }\n \n // For 3+ character codes: alphanumeric at start and end, hyphens/underscores allowed in middle\n // Must not start or end with hyphen/underscore\n const eventCodeRegex = /^[a-zA-Z0-9][a-zA-Z0-9_-]{1,48}[a-zA-Z0-9]$/;\n const matchesFormat = eventCodeRegex.test(eventCode);\n \n if (!matchesFormat) return false;\n \n // Additional check: no consecutive hyphens or underscores\n if (eventCode.includes('--') || eventCode.includes('__') || eventCode.includes('-_') || eventCode.includes('_-')) {\n return false;\n }\n \n return true;\n}\n\n/**\n * Hook for extracting and validating public route parameters\n * \n * This hook extracts event codes from URL parameters and optionally\n * fetches the corresponding event data. It provides validation and\n * error handling for public routes.\n * \n * @param options - Configuration options for behavior\n * @returns Object containing route parameters, event data, loading state, error, and refetch function\n */\nexport function usePublicRouteParams(\n options: UsePublicRouteParamsOptions = {}\n): UsePublicRouteParamsReturn {\n const {\n fetchEventData = true,\n eventCodeParam = 'eventCode',\n validateEventCode = true\n } = options;\n\n const params = useParams();\n const location = useLocation();\n \n const [error, setError] = useState<Error | null>(null);\n\n // Extract event code from URL parameters\n const eventCode = useMemo(() => {\n const code = params[eventCodeParam] as string;\n \n if (!code) {\n // Don't set error immediately - let the component handle missing eventCode gracefully\n return null;\n }\n\n // Validate event code format if requested\n if (validateEventCode && !validateEventCodeFormat(code)) {\n setError(new Error(`Invalid event code format: ${code}`));\n return null;\n }\n\n setError(null);\n return code;\n }, [params, eventCodeParam, validateEventCode]);\n\n // Use the public event hook to fetch event data\n const {\n event,\n isLoading: eventLoading,\n error: eventError,\n refetch: refetchEvent\n } = usePublicEvent(eventCode || '', {\n enableCache: true,\n cacheTtl: 5 * 60 * 1000 // 5 minutes\n });\n\n // Determine if we should show loading state\n const isLoading = useMemo(() => {\n if (!fetchEventData) return false;\n return eventLoading;\n }, [fetchEventData, eventLoading]);\n\n // Determine the final error state\n const finalError = useMemo(() => {\n if (error) return error;\n if (eventError) return eventError;\n return null;\n }, [error, eventError]);\n\n // Extract event ID from event data\n const eventId = useMemo(() => {\n if (!event) return null;\n return event.event_id || event.id;\n }, [event]);\n\n // Refetch function\n const refetch = useCallback(async (): Promise<void> => {\n if (!fetchEventData) return;\n await refetchEvent();\n }, [fetchEventData, refetchEvent]);\n\n return {\n eventCode,\n eventId,\n event: fetchEventData ? event : null,\n isLoading,\n error: finalError,\n refetch\n };\n}\n\n/**\n * Hook for extracting just the event code without fetching event data\n * Useful when you only need the event code and will fetch data separately\n */\nexport function usePublicEventCode(\n eventCodeParam: string = 'eventCode'\n): { eventCode: string | null; error: Error | null } {\n const params = useParams();\n \n const eventCode = useMemo(() => {\n const code = params[eventCodeParam] as string;\n \n if (!code) {\n return null;\n }\n\n // Validate event code format\n if (!validateEventCodeFormat(code)) {\n return null;\n }\n\n return code;\n }, [params, eventCodeParam]);\n\n const error = useMemo(() => {\n if (!eventCode) {\n return new Error(`Event code parameter '${eventCodeParam}' not found or invalid`);\n }\n return null;\n }, [eventCode, eventCodeParam]);\n\n return {\n eventCode,\n error\n };\n}\n\n/**\n * Utility function to generate public route paths\n */\nexport function generatePublicRoutePath(\n eventCode: string,\n pageName: string = 'index'\n): string {\n if (!eventCode || !validateEventCodeFormat(eventCode)) {\n throw new Error('Invalid event code for route generation');\n }\n \n return `/public/event/${eventCode}/${pageName}`;\n}\n\n/**\n * Utility function to extract event code from a public route path\n * Supports 2-50 character event codes\n */\nexport function extractEventCodeFromPath(path: string): string | null {\n const match = path.match(/^\\/public\\/event\\/([a-zA-Z0-9_-]{2,50})(?:\\/.*)?$/);\n if (!match) return null;\n \n const eventCode = match[1];\n // Validate the extracted code using the same validation function\n if (!validateEventCodeFormat(eventCode)) {\n return null;\n }\n \n return eventCode;\n}\n"],"mappings":"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","names":["useState","useMemo","useState","useMemo","error","useState","useEffect","useCallback","useMemo","publicDataCache","useState","useMemo","useCallback","logoUrl","error","useEffect","useState","useCallback","useMemo","useState","useMemo","useCallback"]}
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
{"version":3,"sources":["../src/hooks/useDebounce.ts","../src/hooks/useQueryCache.ts","../src/hooks/useAddressAutocomplete.ts","../src/utils/storage/config.ts","../src/utils/storage/helpers.ts","../src/hooks/public/usePublicFileDisplay.ts","../src/hooks/useFileDisplay.ts","../src/utils/file-reference/index.ts","../src/hooks/useEventTheme.ts","../src/hooks/usePreventTabReload.ts"],"sourcesContent":["\nimport { useState, useEffect } from 'react';\n\n/**\n * Hook for debouncing a value.\n * Returns a debounced version of the value that only updates after the specified delay.\n * \n * @template T - The type of the value to debounce\n * @param value - The value to debounce\n * @param delay - The delay in milliseconds\n * @returns The debounced value\n */\nexport function useDebounce<T>(value: T, delay: number): T {\n const [debouncedValue, setDebouncedValue] = useState<T>(value);\n\n useEffect(() => {\n const handler = setTimeout(() => {\n setDebouncedValue(value);\n }, delay);\n\n return () => {\n clearTimeout(handler);\n };\n }, [value, delay]);\n\n return debouncedValue;\n}\n","/**\n * Query Result Caching Hook\n * @package @jmruthers/pace-core\n * @module Hooks/QueryCache\n * @since 2.0.0\n * \n * Provides in-memory caching for frequently accessed data to eliminate duplicate queries.\n * Useful for caching user profiles, app pages, and other relatively static data.\n */\n\nimport { useCallback, useRef, useEffect } from 'react';\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { Database } from '../types/database';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useQueryCache');\n\ninterface CachedQueryEntry<T> {\n data: T;\n expiresAt: number; // Unix timestamp\n promise?: Promise<T>; // For in-flight requests\n}\n\n/**\n * In-memory cache for query results\n * Key format: `table:filterKey:filterValue`\n */\nconst queryCache = new Map<string, CachedQueryEntry<any>>();\n\n// Cleanup interval (every 5 minutes)\nconst CLEANUP_INTERVAL_MS = 5 * 60 * 1000;\nlet cleanupTimer: ReturnType<typeof setInterval> | null = null;\n\nfunction runCacheCleanup() {\n const now = Date.now();\n const expiredKeys: string[] = [];\n \n queryCache.forEach((entry, key) => {\n if (entry.expiresAt <= now) {\n expiredKeys.push(key);\n }\n });\n \n expiredKeys.forEach(key => {\n queryCache.delete(key);\n log.debug(`Removed expired query from cache: ${key}`);\n });\n}\n\n// Initialize cleanup timer once\nif (typeof window !== 'undefined' && !cleanupTimer) {\n cleanupTimer = setInterval(runCacheCleanup, CLEANUP_INTERVAL_MS);\n log.debug('Query cache cleanup initialized.');\n \n // Cleanup on page unload to prevent memory leaks\n window.addEventListener('beforeunload', () => {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared on page unload.');\n }\n });\n}\n\n/**\n * Manually cleanup the query cache cleanup timer\n * Useful for testing or when the module is reloaded\n */\nexport function cleanupQueryCache(): void {\n if (cleanupTimer) {\n clearInterval(cleanupTimer);\n cleanupTimer = null;\n log.debug('Query cache cleanup timer cleared.');\n }\n}\n\nexport interface UseQueryCacheOptions {\n /** Time to live in seconds (default: 300 = 5 minutes) */\n ttl?: number;\n /** Whether to enable caching (default: true) */\n enabled?: boolean;\n}\n\nexport interface UseQueryCacheReturn {\n /** Get cached query result or fetch if not cached */\n getCachedQuery: <T>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options?: UseQueryCacheOptions\n ) => Promise<T>;\n /** Invalidate a specific cached query */\n invalidateQuery: (table: string, filterKey: string, filterValue: string) => void;\n /** Clear all cached queries */\n clearCache: () => void;\n /** Get cache statistics */\n getCacheStats: () => { size: number; keys: string[] };\n}\n\n/**\n * Hook for query result caching\n * \n * Provides caching for frequently accessed data to eliminate duplicate queries.\n * Automatically handles cache expiration and cleanup.\n * \n * @param supabase - Supabase client (optional, can be passed in fetchFn)\n * @returns Query cache utilities\n * \n * @example\n * ```tsx\n * const { getCachedQuery } = useQueryCache(supabase);\n * \n * const person = await getCachedQuery(\n * 'core_person',\n * 'user_id',\n * userId,\n * async () => {\n * const { data } = await supabase\n * .from('core_person')\n * .select('id, first_name, last_name, email')\n * .eq('user_id', userId)\n * .single();\n * return data;\n * },\n * { ttl: 300 } // 5 minutes\n * );\n * ```\n */\n/**\n * Hook for in-memory query result caching.\n * Provides caching for frequently accessed data to eliminate duplicate queries.\n * \n * @param supabase - Optional Supabase client instance\n * @returns Query cache utilities including get, invalidate, and clear functions\n */\nexport function useQueryCache(supabase?: SupabaseClient<Database>): UseQueryCacheReturn {\n const getCachedQuery = useCallback(async <T,>(\n table: string,\n filterKey: string,\n filterValue: string,\n fetchFn: () => Promise<T>,\n options: UseQueryCacheOptions = {}\n ): Promise<T> => {\n const { ttl = 300, enabled = true } = options; // Default 5 minutes\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n const now = Date.now();\n \n if (!enabled) {\n return fetchFn();\n }\n \n // Check cache\n const cached = queryCache.get(cacheKey);\n if (cached) {\n // If data is still valid, return it\n if (cached.expiresAt > now && cached.data !== undefined) {\n log.debug(`Cache hit for query: ${cacheKey}`);\n return cached.data as T;\n }\n \n // If there's an in-flight request, wait for it\n if (cached.promise) {\n log.debug(`Waiting for in-flight request: ${cacheKey}`);\n return cached.promise as Promise<T>;\n }\n }\n \n // Fetch data\n log.debug(`Cache miss for query: ${cacheKey}, fetching...`);\n const fetchPromise = fetchFn();\n \n // Store promise for in-flight request deduplication\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + (ttl * 1000),\n promise: fetchPromise,\n });\n \n try {\n const data = await fetchPromise;\n \n // Update cache with actual data\n queryCache.set(cacheKey, {\n data,\n expiresAt: now + (ttl * 1000),\n });\n \n log.debug(`Cached query result: ${cacheKey}, expires in ${ttl}s`);\n return data;\n } catch (error) {\n // Remove failed request from cache\n queryCache.delete(cacheKey);\n log.error(`Query failed for ${cacheKey}:`, error);\n throw error;\n }\n }, []);\n \n const invalidateQuery = useCallback((table: string, filterKey: string, filterValue: string) => {\n const cacheKey = `${table}:${filterKey}:${filterValue}`;\n queryCache.delete(cacheKey);\n log.debug(`Invalidated query cache: ${cacheKey}`);\n }, []);\n \n const clearCache = useCallback(() => {\n queryCache.clear();\n log.debug('Cleared all query cache entries.');\n }, []);\n \n const getCacheStats = useCallback(() => {\n return {\n size: queryCache.size,\n keys: Array.from(queryCache.keys()),\n };\n }, []);\n \n return {\n getCachedQuery,\n invalidateQuery,\n clearCache,\n getCacheStats,\n };\n}\n\n/**\n * Pre-configured cache helpers for common queries\n */\nexport const queryCacheHelpers: {\n pacePersonByUserId: <T>(supabase: SupabaseClient<Database>, userId: string, fetchFn: () => Promise<T>) => Promise<T>;\n paceMemberByPersonId: <T>(supabase: SupabaseClient<Database>, personId: string, fetchFn: () => Promise<T>) => Promise<T>;\n rbacAppPagesByAppId: <T>(supabase: SupabaseClient<Database>, appId: string, fetchFn: () => Promise<T>) => Promise<T>;\n} = {\n /**\n * Cache core_person queries by user_id\n * TTL: 5 minutes\n */\n pacePersonByUserId: <T>(\n supabase: SupabaseClient<Database>,\n userId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_person:user_id:${userId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache core_member queries by person_id\n * TTL: 5 minutes\n */\n paceMemberByPersonId: <T>(\n supabase: SupabaseClient<Database>,\n personId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `core_member:person_id:${personId}`;\n const now = Date.now();\n const ttl = 300 * 1000; // 5 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n \n /**\n * Cache rbac_app_pages queries by app_id\n * TTL: 15 minutes (app pages are relatively static)\n */\n rbacAppPagesByAppId: <T>(\n supabase: SupabaseClient<Database>,\n appId: string,\n fetchFn: () => Promise<T>\n ): Promise<T> => {\n const cacheKey = `rbac_app_pages:app_id:${appId}`;\n const now = Date.now();\n const ttl = 15 * 60 * 1000; // 15 minutes\n \n const cached = queryCache.get(cacheKey);\n if (cached && cached.expiresAt > now && cached.data !== undefined) {\n return Promise.resolve(cached.data as T);\n }\n \n if (cached?.promise) {\n return cached.promise as Promise<T>;\n }\n \n const promise = fetchFn();\n queryCache.set(cacheKey, {\n data: undefined as any,\n expiresAt: now + ttl,\n promise,\n });\n \n promise.then(data => {\n queryCache.set(cacheKey, { data, expiresAt: now + ttl });\n }).catch(() => {\n queryCache.delete(cacheKey);\n });\n \n return promise;\n },\n};\n\n","/**\n * @file Address Autocomplete Hook\n * @package @jmruthers/pace-core\n * @module Hooks/AddressAutocomplete\n * @since 0.1.0\n *\n * Hook for managing Google Places API autocomplete functionality.\n * Provides debounced input, caching, and address selection.\n */\n\nimport { useState, useEffect, useCallback, useRef, useMemo } from 'react';\nimport { useDebounce } from './useDebounce';\nimport { useQueryCache } from './useQueryCache';\nimport {\n fetchPlaceAutocomplete,\n fetchPlaceDetails,\n createAddressFromPlaceResult,\n getAddressByPlaceId,\n} from '../utils/google-places';\nimport type {\n GooglePlaceAutocompletePrediction,\n ParsedAddress,\n AutocompleteOptions,\n} from '../utils/google-places';\n\nexport interface UseAddressAutocompleteOptions {\n /** Google Places API options */\n autocompleteOptions?: AutocompleteOptions;\n /** Debounce delay in milliseconds (default: 300) */\n debounceDelay?: number;\n /** Enable caching (default: true) */\n cacheEnabled?: boolean;\n /** Cache TTL configuration */\n cacheTTL?: {\n /** Autocomplete cache TTL in seconds (default: 3600 = 1 hour) */\n autocomplete?: number;\n /** Place details cache TTL in seconds (default: 86400 = 24 hours) */\n placeDetails?: number;\n };\n}\n\nexport interface UseAddressAutocompleteReturn {\n /** Array of autocomplete suggestions */\n suggestions: GooglePlaceAutocompletePrediction[];\n /** Loading state */\n isLoading: boolean;\n /** Error state */\n error: Error | null;\n /** Select an address by place_id */\n selectAddress: (placeId: string) => Promise<ParsedAddress | null>;\n /** Get address by place_id (uses cache) */\n getAddressByPlaceId: (placeId: string) => Promise<ParsedAddress | null>;\n /** Clear suggestions */\n clearSuggestions: () => void;\n}\n\n/**\n * Hook for Google Places API autocomplete with caching\n *\n * @param apiKey - Google Places API key\n * @param inputValue - Current input value\n * @param options - Hook configuration options\n * @returns Autocomplete state and functions\n *\n * @example\n * ```tsx\n * const { suggestions, isLoading, selectAddress } = useAddressAutocomplete(\n * apiKey,\n * inputValue,\n * { debounceDelay: 300, cacheEnabled: true }\n * );\n * ```\n */\nexport function useAddressAutocomplete(\n apiKey: string,\n inputValue: string,\n options: UseAddressAutocompleteOptions = {}\n): UseAddressAutocompleteReturn {\n const {\n debounceDelay = 300,\n cacheEnabled = true,\n cacheTTL = {\n autocomplete: 3600, // 1 hour\n placeDetails: 86400, // 24 hours\n },\n autocompleteOptions,\n } = options;\n\n const [suggestions, setSuggestions] = useState<GooglePlaceAutocompletePrediction[]>([]);\n const [isLoading, setIsLoading] = useState(false);\n const [error, setError] = useState<Error | null>(null);\n\n const debouncedInput = useDebounce(inputValue, debounceDelay);\n const { getCachedQuery } = useQueryCache();\n const abortControllerRef = useRef<AbortController | null>(null);\n\n // Memoize autocompleteOptions to prevent unnecessary re-renders\n const memoizedAutocompleteOptions = useMemo(\n () => autocompleteOptions,\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [JSON.stringify(autocompleteOptions)]\n );\n\n // Fetch autocomplete suggestions\n useEffect(() => {\n // Cancel previous request if still in flight\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n\n // Reset state if input is empty\n if (!debouncedInput.trim()) {\n setSuggestions([]);\n setIsLoading(false);\n setError(null);\n return;\n }\n\n // Don't fetch if no API key\n if (!apiKey) {\n setError(new Error('Google Places API key is required'));\n return;\n }\n\n setIsLoading(true);\n setError(null);\n\n const fetchSuggestions = async () => {\n try {\n let predictions: GooglePlaceAutocompletePrediction[];\n\n if (cacheEnabled) {\n // Use cached query with TTL\n predictions = await getCachedQuery(\n 'google-places-autocomplete',\n 'query',\n debouncedInput,\n async () => {\n return fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n },\n { ttl: cacheTTL.autocomplete, enabled: true }\n );\n } else {\n // Direct fetch without cache\n predictions = await fetchPlaceAutocomplete(debouncedInput, apiKey, memoizedAutocompleteOptions);\n }\n\n setSuggestions(predictions);\n setIsLoading(false);\n } catch (err) {\n // Ignore aborted requests\n if (err instanceof Error && err.name === 'AbortError') {\n return;\n }\n\n const error = err instanceof Error ? err : new Error('Failed to fetch autocomplete suggestions');\n setError(error);\n setSuggestions([]);\n setIsLoading(false);\n }\n };\n\n fetchSuggestions();\n\n // Cleanup function to cancel in-flight requests\n return () => {\n if (abortControllerRef.current) {\n abortControllerRef.current.abort();\n }\n };\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [debouncedInput, apiKey, cacheEnabled, cacheTTL.autocomplete]);\n\n // Select address by place_id\n const selectAddress = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n setIsLoading(true);\n setError(null);\n\n try {\n let placeDetails;\n\n if (cacheEnabled) {\n // Use cached query with TTL\n placeDetails = await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n return fetchPlaceDetails(placeId, apiKey);\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n // Direct fetch without cache\n placeDetails = await fetchPlaceDetails(placeId, apiKey);\n }\n\n const parsedAddress = createAddressFromPlaceResult(placeDetails);\n setIsLoading(false);\n return parsedAddress;\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to fetch place details');\n setError(error);\n setIsLoading(false);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Get address by place_id (utility function)\n const getAddressByPlaceIdFn = useCallback(\n async (placeId: string): Promise<ParsedAddress | null> => {\n if (!placeId || !apiKey) {\n return null;\n }\n\n try {\n if (cacheEnabled) {\n // Use cached query with TTL\n return await getCachedQuery(\n 'google-places-details',\n 'place_id',\n placeId,\n async () => {\n const result = await getAddressByPlaceId(placeId, apiKey);\n if (!result) {\n throw new Error('Failed to fetch address');\n }\n return result;\n },\n { ttl: cacheTTL.placeDetails, enabled: true }\n );\n } else {\n return await getAddressByPlaceId(placeId, apiKey);\n }\n } catch (err) {\n const error = err instanceof Error ? err : new Error('Failed to get address by place_id');\n setError(error);\n return null;\n }\n },\n // eslint-disable-next-line react-hooks/exhaustive-deps\n [apiKey, cacheEnabled, cacheTTL.placeDetails]\n );\n\n // Clear suggestions\n const clearSuggestions = useCallback(() => {\n setSuggestions([]);\n setError(null);\n }, []);\n\n return {\n suggestions,\n isLoading,\n error,\n selectAddress,\n getAddressByPlaceId: getAddressByPlaceIdFn,\n clearSuggestions,\n };\n}\n\n","/**\n * Storage configuration for pace-core\n */\n\nimport { FileSizeLimits, StorageConfig } from './types';\n\n/**\n * File size limits by MIME type (in bytes)\n */\nexport const FILE_SIZE_LIMITS: FileSizeLimits = {\n // Images\n 'image/jpeg': 5 * 1024 * 1024, // 5MB\n 'image/png': 5 * 1024 * 1024, // 5MB\n 'image/gif': 10 * 1024 * 1024, // 10MB (for animations)\n 'image/webp': 5 * 1024 * 1024, // 5MB\n 'image/svg+xml': 1 * 1024 * 1024, // 1MB (vector graphics)\n \n // Documents\n 'application/pdf': 50 * 1024 * 1024, // 50MB\n 'application/msword': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.wordprocessingml.document': 25 * 1024 * 1024, // 25MB\n 'application/vnd.ms-excel': 25 * 1024 * 1024, // 25MB\n 'application/vnd.openxmlformats-officedocument.spreadsheetml.sheet': 25 * 1024 * 1024, // 25MB\n \n // Archives\n 'application/zip': 100 * 1024 * 1024, // 100MB\n 'application/x-rar-compressed': 100 * 1024 * 1024, // 100MB\n \n // Text files\n 'text/plain': 1 * 1024 * 1024, // 1MB\n 'text/csv': 10 * 1024 * 1024, // 10MB\n 'application/json': 10 * 1024 * 1024, // 10MB\n};\n\n/**\n * Default file size limit for unknown MIME types\n */\nexport const DEFAULT_FILE_SIZE_LIMIT = 10 * 1024 * 1024; // 10MB\n\n/**\n * App-specific path mappings for file organization\n * Maps app names to their primary category in the organization-first structure\n */\nexport const APP_PATH_MAPPING: Record<string, string> = {\n 'PACE': 'event_logos',\n 'TRAC': 'trac_accommodation', // Default category for TRAC files\n 'MEDI': 'documents',\n 'CAKE': 'documents'\n};\n\n/**\n * Storage configuration\n */\nexport const STORAGE_CONFIG: StorageConfig = {\n bucketName: 'files',\n fileSizeLimits: FILE_SIZE_LIMITS,\n defaultFileSizeLimit: DEFAULT_FILE_SIZE_LIMIT,\n};\n\n/**\n * Get the file size limit for a given MIME type\n */\nexport function getFileSizeLimit(mimeType: string): number {\n return STORAGE_CONFIG.fileSizeLimits[mimeType] || STORAGE_CONFIG.defaultFileSizeLimit;\n}\n\n/**\n * Get the bucket name based on whether the file is public or private\n * @param isPublic - Whether the file should be publicly accessible\n * @returns The bucket name: 'public-files' for public files, 'files' for private files\n */\nexport function getBucketName(isPublic: boolean): 'files' | 'public-files' {\n return isPublic ? 'public-files' : 'files';\n}\n\n/**\n * Validate file size against limits\n */\nexport function validateFileSize(file: File): { isValid: boolean; error?: string } {\n const limit = getFileSizeLimit(file.type);\n \n if (file.size > limit) {\n const limitMB = Math.round(limit / (1024 * 1024));\n const fileMB = Math.round(file.size / (1024 * 1024));\n return {\n isValid: false,\n error: `File size (${fileMB}MB) exceeds limit (${limitMB}MB) for ${file.type}`\n };\n }\n \n return { isValid: true };\n}\n\n/**\n * Get human-readable file size\n */\nexport function formatFileSize(bytes: number): string {\n if (bytes === 0) return '0 Bytes';\n if (bytes < 0) return `${bytes} Bytes`; // Handle negative numbers\n \n const k = 1024;\n const sizes = ['Bytes', 'KB', 'MB', 'GB', 'TB'];\n const i = Math.floor(Math.log(bytes) / Math.log(k));\n \n // Ensure we don't exceed the available size units\n const sizeIndex = Math.min(Math.max(i, 0), sizes.length - 1);\n \n return parseFloat((bytes / Math.pow(k, sizeIndex)).toFixed(2)) + ' ' + sizes[sizeIndex];\n}\n","/**\n * Storage helper functions for pace-core\n */\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n StorageUploadOptions, \n StorageUploadResult, \n StorageFileMetadata,\n StorageUrlOptions,\n StorageListOptions,\n StorageListResult,\n StorageFileInfo\n} from './types';\nimport { validateFileSize, STORAGE_CONFIG, getBucketName } from './config';\nimport { createLogger } from '../core/logger';\n\nconst log = createLogger('StorageHelpers');\n\n/**\n * Generate a file path based on organization-first or user-first structure\n * - If orgId is provided: {orgId}/{folder}/filename\n * - If userId is provided (and orgId is not): users/{userId}/{folder}/filename\n */\nexport function generateFilePath(options: StorageUploadOptions, fileName: string): string {\n const { orgId, userId, isPublic = false, customPath } = options;\n \n // Validate that either orgId or userId is provided\n if (!orgId && !userId) {\n throw new Error('Either orgId or userId is required for file path generation');\n }\n \n // Determine base path: organisation-based or user-based\n const basePath = orgId ? orgId : `users/${userId}`;\n \n if (isPublic) {\n // Public files go to {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n return `${basePath}/public/${fileName}`;\n }\n \n // Organization-first or user-first structure: {basePath}/{folder}/filename\n if (customPath) {\n return `${basePath}/${customPath}/${fileName}`;\n }\n \n // Use customPath if available, otherwise default to files\n const pathFolder = customPath || 'files';\n return `${basePath}/${pathFolder}/${fileName}`;\n}\n\n/**\n * Generate a unique filename with timestamp and UUID\n * Sanitizes the original filename to remove spaces and invalid characters for storage compatibility\n */\nexport function generateUniqueFileName(originalName: string): string {\n const timestamp = Date.now();\n const uuid = crypto.randomUUID();\n const extension = originalName.split('.').pop() || '';\n let baseName = originalName.replace(/\\.[^/.]+$/, '');\n \n // Sanitize the base name for storage compatibility\n // Replace spaces with underscores and remove invalid characters\n baseName = baseName\n .trim()\n .replace(/\\s+/g, '_') // Replace spaces with underscores\n .replace(/[<>:\"/\\\\|?*]/g, '') // Remove invalid file name characters\n .replace(/\\.\\./g, '') // Remove directory traversal attempts\n .replace(/^\\.+|\\.+$/g, '') // Remove leading/trailing dots\n .substring(0, 200); // Limit length to leave room for timestamp and UUID\n \n // If there's no extension, don't add one\n if (!extension || extension === originalName) {\n return `${timestamp}-${uuid}-${baseName}`;\n }\n \n return `${timestamp}-${uuid}-${baseName}.${extension}`;\n}\n\n/**\n * Extract file metadata from a File object\n */\nexport async function extractFileMetadata(\n file: File, \n options: StorageUploadOptions,\n uploadedBy: string\n): Promise<StorageFileMetadata> {\n const metadata: StorageFileMetadata = {\n mimeType: file.type,\n size: file.size,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName || 'pace-core',\n uploadedBy,\n uploadedAt: new Date().toISOString(),\n tags: options.tags || [],\n isPublic: options.isPublic || false,\n customMetadata: options.metadata || {}\n };\n\n // Extract image dimensions if it's an image\n if (file.type.startsWith('image/')) {\n try {\n const dimensions = await getImageDimensions(file);\n metadata.width = dimensions.width;\n metadata.height = dimensions.height;\n } catch (error) {\n // Non-critical error - image dimensions are optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - dimensions are optional\n }\n }\n\n // Generate file hash if possible\n try {\n metadata.hash = await generateFileHash(file);\n } catch (error) {\n // Non-critical error - file hash is optional metadata\n // Using Logger would be better, but this is in a utility function\n // For now, silently continue - hash is optional\n }\n\n return metadata;\n}\n\n/**\n * Get image dimensions from a File object\n */\nasync function getImageDimensions(file: File): Promise<{ width: number; height: number }> {\n return new Promise((resolve, reject) => {\n const img = new Image();\n const url = URL.createObjectURL(file);\n \n img.onload = () => {\n URL.revokeObjectURL(url);\n resolve({ width: img.width, height: img.height });\n };\n \n img.onerror = () => {\n URL.revokeObjectURL(url);\n reject(new Error('Could not load image'));\n };\n \n img.src = url;\n });\n}\n\n/**\n * Generate a hash for a file\n */\nasync function generateFileHash(file: File): Promise<string> {\n const buffer = await file.arrayBuffer();\n const hashBuffer = await crypto.subtle.digest('SHA-256', buffer);\n const hashArray = Array.from(new Uint8Array(hashBuffer));\n const hashHex = hashArray.map(b => b.toString(16).padStart(2, '0')).join('');\n return `sha256:${hashHex}`;\n}\n\n/**\n * Ensure a folder exists in the storage bucket\n * In Supabase storage, folders are created automatically when files are uploaded,\n * but this function explicitly creates the folder structure by uploading a placeholder\n * file if the folder doesn't exist\n * @param supabase - Supabase client instance\n * @param folderPath - Folder path to ensure exists (e.g., 'orgId/folder')\n * @param bucketName - Bucket name\n * @returns True if folder exists or was created, false on error\n */\nasync function ensureFolderExists(\n supabase: SupabaseClient,\n folderPath: string,\n bucketName: string\n): Promise<boolean> {\n try {\n // Check if folder exists by trying to list it\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(folderPath, {\n limit: 1\n });\n\n // If listing succeeds (even with empty data), the folder exists\n if (!error) {\n return true;\n }\n\n // If we get a \"not found\" error, the folder doesn't exist yet\n // Create it by uploading a placeholder file\n // Supabase storage doesn't support empty folders, so we create a .keep file\n const placeholderPath = `${folderPath}/.keep`;\n const placeholderBlob = new Blob([''], { type: 'text/plain' });\n const placeholderFile = new File([placeholderBlob], '.keep', { type: 'text/plain' });\n\n const { error: uploadError } = await supabase.storage\n .from(bucketName)\n .upload(placeholderPath, placeholderFile, {\n cacheControl: '3600',\n upsert: true, // Use upsert to avoid errors if file already exists\n contentType: 'text/plain'\n });\n\n if (uploadError) {\n // If we can't create the placeholder, log it but don't fail\n // The folder will be created automatically when we upload the actual file\n log.debug(`Could not create folder placeholder (will be created on upload): ${uploadError.message}`);\n return true; // Still return true - folder will be created on actual file upload\n }\n\n // Folder structure now exists\n return true;\n } catch (error) {\n // If there's an exception, log it but proceed anyway\n // The folder structure will be created when we upload the actual file\n log.debug(`Folder creation exception (will be created on upload): ${error instanceof Error ? error.message : 'Unknown error'}`);\n return true; // Return true to proceed - folder will be created on actual file upload\n }\n}\n\n/**\n * Upload a file to Supabase storage with app segregation\n */\nexport async function uploadFile(\n supabase: SupabaseClient,\n file: File,\n options: StorageUploadOptions\n): Promise<StorageUploadResult> {\n try {\n // Validate file size\n const sizeValidation = validateFileSize(file);\n if (!sizeValidation.isValid) {\n return {\n success: false,\n error: sizeValidation.error\n };\n }\n\n // Generate unique filename and path\n const uniqueFileName = generateUniqueFileName(file.name);\n const filePath = generateFilePath(options, uniqueFileName);\n\n // Extract folder path from file path (everything except the filename)\n const folderPath = filePath.substring(0, filePath.lastIndexOf('/'));\n\n // Extract metadata\n const metadata = await extractFileMetadata(file, options, 'current-user'); // TODO: Get actual user ID\n\n // Select bucket based on isPublic flag\n const bucketName = getBucketName(options.isPublic || false);\n\n // Ensure folder exists (Supabase creates folders automatically on upload,\n // but we verify the path is accessible)\n await ensureFolderExists(supabase, folderPath, bucketName);\n\n // Upload file to Supabase\n // Note: Supabase will automatically create the folder structure if it doesn't exist\n const { data, error } = await supabase.storage\n .from(bucketName)\n .upload(filePath, file, {\n cacheControl: '3600',\n upsert: false,\n contentType: file.type\n });\n\n if (error) {\n return {\n success: false,\n error: `Upload failed: ${error.message}`\n };\n }\n\n // Generate public URL if file is public\n let publicUrl: string | undefined;\n if (options.isPublic) {\n const { data: urlData } = supabase.storage\n .from(bucketName)\n .getPublicUrl(filePath);\n publicUrl = urlData.publicUrl;\n }\n\n return {\n success: true,\n path: filePath,\n publicUrl,\n metadata\n };\n\n } catch (error) {\n return {\n success: false,\n error: `Upload failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * Get a public URL for a file\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: true)\n * @returns Public URL for the file\n */\nexport function getPublicUrl(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = true\n): string {\n if (!supabase) {\n throw new Error('Supabase client is required to generate a public URL');\n }\n\n if (!path || typeof path !== 'string') {\n throw new Error('A valid storage path is required to generate a public URL');\n }\n\n // If the path is already an absolute URL, return it directly\n if (/^https?:\\/\\//i.test(path)) {\n return path;\n }\n\n // Normalise path by trimming whitespace and leading slashes\n let normalisedPath = path.trim().replace(/^\\/+/, '');\n\n if (!normalisedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n const { bucketNameFromPath, storagePath } = resolveBucketHint(normalisedPath);\n\n const bucketName = bucketNameFromPath || getBucketName(isPublic);\n\n const { data } = supabase.storage\n .from(bucketName)\n .getPublicUrl(storagePath);\n\n return data.publicUrl;\n}\n\nfunction resolveBucketHint(pathWithPotentialBucket: string): { bucketNameFromPath: string | null; storagePath: string } {\n const BUCKET_NAME_PATTERN = /^[a-z0-9][a-z0-9-_\\.]{1,62}$/i;\n const UUID_PATTERN = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n const KNOWN_BUCKET_NAMES = new Set(['files', 'public-files']);\n\n const trimmedPath = pathWithPotentialBucket.trim();\n\n if (!trimmedPath) {\n throw new Error('Storage path cannot be empty after normalisation');\n }\n\n // Support \"bucket::path\" notation to remove ambiguity with colon usage in file names.\n const doubleColonIndex = trimmedPath.indexOf('::');\n if (doubleColonIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, doubleColonIndex).trim();\n const remainingPath = trimmedPath.slice(doubleColonIndex + 2).replace(/^\\/+/, '');\n\n if (potentialBucket && remainingPath && BUCKET_NAME_PATTERN.test(potentialBucket)) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n // Support \"bucket/path\" hints, provided the first segment looks like a bucket name and not a directory prefix.\n const firstSlashIndex = trimmedPath.indexOf('/');\n if (firstSlashIndex > 0) {\n const potentialBucket = trimmedPath.slice(0, firstSlashIndex).trim();\n const remainingPath = trimmedPath.slice(firstSlashIndex + 1).replace(/^\\/+/, '');\n\n if (\n potentialBucket &&\n remainingPath &&\n BUCKET_NAME_PATTERN.test(potentialBucket) &&\n !UUID_PATTERN.test(potentialBucket) &&\n (KNOWN_BUCKET_NAMES.has(potentialBucket) || !potentialBucket.includes('-')) &&\n !/^\\d+$/.test(potentialBucket)\n ) {\n return {\n bucketNameFromPath: potentialBucket,\n storagePath: remainingPath\n };\n }\n }\n\n return {\n bucketNameFromPath: null,\n storagePath: trimmedPath\n };\n}\n\n/**\n * Get a signed URL for a protected file\n * Private files are always in the 'files' bucket, so this always uses 'files' bucket\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - URL options including expiry time\n * @returns Signed URL with expiration timestamp, or null if failed\n */\nexport async function getSignedUrl(\n supabase: SupabaseClient,\n path: string,\n options: StorageUrlOptions\n): Promise<{ url: string; expiresAt: string } | null> {\n try {\n // Signed URLs are only for private files, which are always in the 'files' bucket\n const bucketName = getBucketName(false);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .createSignedUrl(path, options.expiresIn || 3600);\n\n if (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n\n return {\n url: data.signedUrl,\n expiresAt: new Date(Date.now() + (options.expiresIn || 3600) * 1000).toISOString()\n };\n } catch (error) {\n log.error('Failed to create signed URL:', error);\n return null;\n }\n}\n\n// Global URL cache for batch operations (shared with useFileUrlCache)\nconst globalUrlCache = new Map<string, { url: string; expiresAt: number }>();\nconst MAX_CACHE_SIZE = 500;\nconst DEFAULT_TTL_MS = 3600 * 1000;\n\nfunction getCacheKey(fileId: string, filePath: string, isPublic: boolean): string {\n return `file-url:${fileId}:${isPublic ? 'public' : 'private'}`;\n}\n\nfunction cleanupUrlCache(): void {\n const now = Date.now();\n \n // Remove expired entries\n for (const [key, value] of globalUrlCache.entries()) {\n if (value.expiresAt < now) {\n globalUrlCache.delete(key);\n }\n }\n \n // Enforce size limit by removing oldest entries\n if (globalUrlCache.size > MAX_CACHE_SIZE) {\n const entries = Array.from(globalUrlCache.entries());\n entries.sort((a, b) => a[1].expiresAt - b[1].expiresAt);\n \n const toRemove = Math.floor(MAX_CACHE_SIZE * 0.2);\n for (let i = 0; i < toRemove && i < entries.length; i++) {\n globalUrlCache.delete(entries[i][0]);\n }\n }\n}\n\n/**\n * Generate URLs for multiple file references in parallel\n * This batches URL generation to reduce sequential requests and uses caching\n * @param supabase - Supabase client instance\n * @param fileReferences - Array of file references to generate URLs for\n * @param options - URL options including expiry time and organisation ID\n * @returns Map of file ID to URL string (only includes successful URL generations)\n */\nexport async function generateFileUrlsBatch(\n supabase: SupabaseClient,\n fileReferences: Array<{ id: string; file_path: string; is_public: boolean }>,\n options: StorageUrlOptions & { orgId?: string }\n): Promise<Map<string, string>> {\n const urlMap = new Map<string, string>();\n \n if (fileReferences.length === 0) {\n return urlMap;\n }\n\n const now = Date.now();\n const ttl = (options.expiresIn || 3600) * 1000; // Convert seconds to milliseconds\n \n // Separate files into cached, public, and private\n const publicFiles: Array<{ id: string; file_path: string }> = [];\n const privateFiles: Array<{ id: string; file_path: string }> = [];\n const uncachedFiles: Array<{ id: string; file_path: string; is_public: boolean }> = [];\n\n for (const fileRef of fileReferences) {\n const cacheKey = getCacheKey(fileRef.id, fileRef.file_path, fileRef.is_public);\n const cached = globalUrlCache.get(cacheKey);\n \n // Use cached URL if still valid\n if (cached && cached.expiresAt > now) {\n urlMap.set(fileRef.id, cached.url);\n continue;\n }\n \n // Add to processing queue\n if (fileRef.is_public) {\n publicFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n } else {\n privateFiles.push({ id: fileRef.id, file_path: fileRef.file_path });\n }\n uncachedFiles.push(fileRef);\n }\n\n // Generate public URLs synchronously (they're just string concatenation)\n for (const file of publicFiles) {\n try {\n const url = getPublicUrl(supabase, file.file_path, true);\n if (url) {\n urlMap.set(file.id, url);\n // Cache the URL\n const cacheKey = getCacheKey(file.id, file.file_path, true);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n } catch (err) {\n log.error(`Failed to generate public URL for file ${file.id}:`, err);\n }\n }\n\n // Generate signed URLs in parallel using Promise.all\n if (privateFiles.length > 0) {\n const signedUrlPromises = privateFiles.map(async (file) => {\n try {\n const signedUrlResult = await getSignedUrl(supabase, file.file_path, {\n appName: options.appName || 'pace-core',\n orgId: options.orgId,\n expiresIn: options.expiresIn || 3600\n });\n const url = signedUrlResult?.url || null;\n \n // Cache the URL if generated successfully\n if (url) {\n const cacheKey = getCacheKey(file.id, file.file_path, false);\n globalUrlCache.set(cacheKey, {\n url,\n expiresAt: now + ttl\n });\n }\n \n return { id: file.id, url };\n } catch (err) {\n log.error(`Failed to generate signed URL for file ${file.id}:`, err);\n return { id: file.id, url: null };\n }\n });\n\n const signedUrlResults = await Promise.all(signedUrlPromises);\n \n for (const result of signedUrlResults) {\n if (result.url) {\n urlMap.set(result.id, result.url);\n }\n }\n }\n\n // Clean up cache after adding new entries\n if (uncachedFiles.length > 0) {\n cleanupUrlCache();\n }\n\n return urlMap;\n}\n\n/**\n * Delete a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns Success status and optional error message\n */\nexport async function deleteFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { error } = await supabase.storage\n .from(bucketName)\n .remove([path]);\n\n if (error) {\n return {\n success: false,\n error: `Delete failed: ${error.message}`\n };\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Delete failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n\n/**\n * List files in an organization scope\n * @param supabase - Supabase client instance\n * @param options - List options including bucket selection via isPublic\n * @returns List of files with metadata\n */\nexport async function listFiles(\n supabase: SupabaseClient,\n options: StorageListOptions & { isPublic?: boolean }\n): Promise<StorageListResult> {\n try {\n // Select bucket based on isPublic flag (default to private files bucket)\n const bucketName = getBucketName(options.isPublic || false);\n \n // Validate that either orgId or userId is provided\n if (!options.orgId && !options.userId) {\n throw new Error('Either orgId or userId is required for listing files');\n }\n \n // Organization-first or user-first structure\n const basePath = options.orgId ? options.orgId : `users/${options.userId}`;\n const pathPrefix = `${basePath}/`;\n const searchPath = options.pathPrefix ? `${pathPrefix}${options.pathPrefix}` : pathPrefix;\n\n const { data, error } = await supabase.storage\n .from(bucketName)\n .list(searchPath, {\n limit: options.limit || 100,\n offset: options.offset || 0,\n sortBy: { column: 'created_at', order: 'desc' }\n });\n\n if (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n\n const files: StorageFileInfo[] = (data || []).map(item => ({\n name: item.name,\n path: `${searchPath}${item.name}`,\n size: item.metadata?.size || 0,\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n lastModified: item.updated_at || item.created_at || new Date().toISOString(),\n metadata: {\n mimeType: item.metadata?.mimetype || 'application/octet-stream',\n size: item.metadata?.size || 0,\n ...(options.orgId && { orgId: options.orgId }),\n ...(options.userId && { userId: options.userId }),\n appName: options.appName,\n uploadedBy: 'unknown',\n uploadedAt: item.created_at || new Date().toISOString(),\n isPublic: options.isPublic || false\n }\n }));\n\n return {\n files,\n totalCount: files.length,\n hasMore: files.length >= (options.limit || 100)\n };\n } catch (error) {\n log.error('Failed to list files:', error);\n return { files: [], totalCount: 0, hasMore: false };\n }\n}\n\n/**\n * Download a file from storage\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param isPublic - Whether the file is in the public-files bucket (default: false)\n * @returns File blob with metadata, or null if failed\n */\nexport async function downloadFile(\n supabase: SupabaseClient,\n path: string,\n isPublic: boolean = false\n): Promise<{ blob: Blob; metadata: { name: string; size: number; type: string } } | null> {\n try {\n const bucketName = getBucketName(isPublic);\n \n const { data, error } = await supabase.storage\n .from(bucketName)\n .download(path);\n\n if (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n\n if (!data) {\n return null;\n }\n\n // Extract file name from path\n const fileName = path.split('/').pop() || 'download';\n \n // Get file metadata\n const { data: fileInfo } = await supabase.storage\n .from(bucketName)\n .list(path.split('/').slice(0, -1).join('/'), {\n search: fileName\n });\n\n const metadata = fileInfo?.[0]?.metadata || {};\n \n return {\n blob: data,\n metadata: {\n name: fileName,\n size: metadata.size || data.size,\n type: metadata.mimetype || 'application/octet-stream'\n }\n };\n } catch (error) {\n log.error('Failed to download file:', error);\n return null;\n }\n}\n\n/**\n * Move a file to archived location (soft delete)\n * @param supabase - Supabase client instance\n * @param path - File path within the bucket\n * @param options - Archive options including bucket selection via isPublic\n */\nexport async function archiveFile(\n supabase: SupabaseClient,\n path: string,\n options: { appName: string; orgId?: string; userId?: string; isPublic?: boolean }\n): Promise<{ success: boolean; error?: string }> {\n try {\n const bucketName = getBucketName(options.isPublic || false);\n \n // Generate archived path for organization-first or user-first structure\n let archivedPath: string;\n if (options.orgId) {\n archivedPath = path.replace(`${options.orgId}/`, `archived/${options.orgId}/`);\n } else if (options.userId) {\n archivedPath = path.replace(`users/${options.userId}/`, `archived/users/${options.userId}/`);\n } else {\n throw new Error('Either orgId or userId is required for archiving files');\n }\n \n // Copy file to archived location\n const { error: copyError } = await supabase.storage\n .from(bucketName)\n .copy(path, archivedPath);\n\n if (copyError) {\n return {\n success: false,\n error: `Archive failed: ${copyError.message}`\n };\n }\n\n // Delete original file\n const deleteResult = await deleteFile(supabase, path, options.isPublic || false);\n if (!deleteResult.success) {\n return deleteResult;\n }\n\n return { success: true };\n } catch (error) {\n return {\n success: false,\n error: `Archive failed: ${error instanceof Error ? error.message : 'Unknown error'}`\n };\n }\n}\n","/**\n * @file Public File Display Hook\n * @package @jmruthers/pace-core\n * @module Hooks/Public\n * @since 1.0.0\n *\n * A React hook for accessing file references in public contexts without authentication.\n * Provides file URLs and metadata for public pages.\n *\n * Features:\n * - No authentication required\n * - Only returns public files (is_public = true)\n * - Caching for performance\n * - Error handling and loading states\n * - TypeScript support\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { usePublicFileDisplay } from '@jmruthers/pace-core';\n *\n * function PublicFileView() {\n * const { fileUrl, fileReference, isLoading, error } = usePublicFileDisplay(\n * 'event',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS,\n * { supabase }\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport type { Database } from '../../types/database';\nimport { FileReference, FileCategory } from '../../types/file-reference';\nimport { getPublicUrl, generateFileUrlsBatch } from '../../utils/storage/helpers';\nimport { logger } from '../../utils/core/logger';\n\n// Simple in-memory cache for public file data\nconst publicFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n/**\n * Return value of the usePublicFileDisplay hook.\n * Provides file URLs, references, loading state, error, and refetch function.\n */\nexport interface UsePublicFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UsePublicFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient<Database>;\n}\n\n/**\n * Hook for accessing public file references\n * \n * This hook provides access to file references without requiring\n * authentication. It only returns public files and generates public URLs.\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function usePublicFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UsePublicFileDisplayOptions\n): UsePublicFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('usePublicFileDisplay', 'Invalid organisationId format (not a valid UUID)', { organisation_id });\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = publicFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n setFileUrl(cachedData.fileUrl || null);\n setFileReference(cachedData.fileReference || null);\n setFileReferences(cachedData.fileReferences || []);\n setFileUrls(cachedData.fileUrls || new Map());\n setFileCount(cachedData.fileCount || 0);\n setIsLoading(false);\n setError(null);\n return;\n }\n }\n\n try {\n setIsLoading(true);\n setError(null);\n\n let files: any[] = [];\n\n // When organisation_id is undefined, search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n if (organisation_id === undefined) {\n logger.debug('usePublicFileDisplay', 'organisation_id is undefined, searching both user-scoped and organisation-scoped files:', {\n table_name,\n record_id,\n category\n });\n\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: any[] = [];\n let orgScopedFiles: any[] = [];\n\n // Query user-scoped files\n if (category) {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n userScopedFiles = userData\n .filter((item: any) => item.is_public === true && item.id && item.file_path && item.file_metadata)\n .map((item: any) => ({\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n }));\n }\n } else {\n const { data: userData, error: userRpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: null\n });\n\n if (!userRpcError && userData) {\n const ids = userData.map((item: any) => item.id);\n if (ids.length > 0) {\n const { data: fullData } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true);\n userScopedFiles = fullData || [];\n }\n }\n }\n\n // For public pages, we can't query user's organisations (user is anonymous)\n // But we can try to find organisation-scoped files by querying the record's context\n // For now, we'll just use user-scoped files. If needed, the page context can provide organisation_id\n // Note: This is a limitation of public pages - they should provide organisation_id if needed\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n files = allFiles;\n\n logger.debug('usePublicFileDisplay', 'Found files with undefined organisation_id:', {\n userScopedCount: userScopedFiles.length,\n orgScopedCount: orgScopedFiles.length,\n totalCount: files.length\n });\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - use RPC to get files by category\n const rpcParams = {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n };\n \n const { data, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_by_category_list', rpcParams);\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_by_category_list',\n table_name,\n record_id,\n category,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file reference');\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n files = [];\n } else {\n // Construct file reference objects from RPC response\n // This avoids RLS issues - the RPC already validated permissions and filtered public files\n files = data\n .filter((item: any) => {\n // RPC should only return public files for public context, but verify\n return item.is_public === true && item.id && item.file_path && item.file_metadata;\n })\n .map((item: any) => {\n // Construct complete file reference from RPC response + function parameters\n return {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: item.file_metadata || {},\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id || null,\n is_public: true, // RPC already filtered for public files\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString()\n };\n });\n }\n } else {\n // Multiple files mode - use RPC to get all files\n const { data: fileIds, error: rpcError } = await (supabase as any)\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (rpcError) {\n logger.error('usePublicFileDisplay', 'RPC function error', {\n function: 'data_file_reference_list',\n table_name,\n record_id,\n organisation_id,\n error: rpcError.message,\n errorCode: rpcError.code,\n errorDetails: rpcError.details,\n errorHint: rpcError.hint\n });\n throw new Error(rpcError.message || 'Failed to fetch file references');\n }\n\n if (!fileIds || fileIds.length === 0) {\n files = [];\n } else {\n // Fetch full file reference data for each ID, but only public files\n const ids = fileIds.map((item: any) => item.id);\n const { data: fullData, error: fetchError } = await supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .in('id', ids)\n .eq('is_public', true); // Only public files in public context\n\n if (fetchError) {\n throw new Error(fetchError.message || 'Failed to fetch file references');\n }\n\n files = fullData || [];\n }\n }\n }\n\n // Ensure all files are public (category RPC might return both)\n const publicFiles = files.filter((f: any) => f.is_public === true);\n\n if (publicFiles.length === 0) {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n \n // Cache empty result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n return;\n }\n\n // Convert to FileReference format\n const fileRefs: FileReference[] = publicFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? true,\n created_at: f.created_at,\n updated_at: f.updated_at\n }));\n\n setFileReferences(fileRefs);\n setFileCount(fileRefs.length);\n\n if (category && fileRefs.length > 0) {\n // Single file mode - get first file\n const firstFile = fileRefs[0];\n setFileReference(firstFile);\n \n // Generate public URL\n const url = getPublicUrl(supabase, firstFile.file_path, true);\n setFileUrl(url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, fileRefs, {\n appName: 'pace-core',\n orgId: organisation_id,\n expiresIn: 3600\n });\n setFileUrls(urlMap);\n setFileReference(null);\n setFileUrl(null);\n }\n\n // Cache the result\n if (enableCache) {\n publicFileCache.set(cacheKey, {\n data: {\n fileUrl: category ? (fileRefs.length > 0 ? getPublicUrl(supabase, fileRefs[0].file_path, true) : null) : null,\n fileReference: category && fileRefs.length > 0 ? fileRefs[0] : null,\n fileReferences: fileRefs,\n fileUrls: category ? new Map() : (() => {\n const urlMap = new Map<string, string>();\n for (const fileRef of fileRefs) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n return urlMap;\n })(),\n fileCount: fileRefs.length\n },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n }\n\n } catch (err) {\n logger.error('usePublicFileDisplay', 'Error fetching files', {\n table_name,\n record_id,\n organisation_id,\n category,\n error: err instanceof Error ? err.message : 'Unknown error',\n errorDetails: err instanceof Error ? err.stack : String(err)\n });\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n setError(error);\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n } finally {\n setIsLoading(false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n if (table_name && record_id) {\n fetchFiles();\n } else {\n setFileUrl(null);\n setFileReference(null);\n setFileReferences([]);\n setFileUrls(new Map());\n setFileCount(0);\n setIsLoading(false);\n setError(null);\n }\n }, [fetchFiles, table_name, record_id, organisation_id]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `public_file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n publicFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached public file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearPublicFileDisplayCache(): void {\n for (const [key] of publicFileCache) {\n if (key.startsWith('public_file_')) {\n publicFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getPublicFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(publicFileCache.keys()).filter(key => key.startsWith('public_file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n","/**\n * @file File Display Hook (Authenticated)\n * @package @jmruthers/pace-core\n * @module Hooks\n *\n * A React hook for accessing file references in authenticated contexts.\n * Can handle both public and private files using the core_file_references system.\n *\n * Features:\n * - Works in authenticated contexts\n * - Supports both public and private files\n * - Automatic signed URL generation for private files\n * - Caching for performance\n * - Error handling and loading states\n * - Supports both single file (category) and multiple files\n *\n * @example\n * ```tsx\n * import { useFileDisplay } from '@jmruthers/pace-core';\n *\n * function FileView() {\n * const { fileUrl, fileReference, isLoading, error } = useFileDisplay(\n * 'core_events',\n * eventId,\n * organisationId,\n * FileCategory.EVENT_LOGOS\n * );\n *\n * if (isLoading) return <div>Loading...</div>;\n * if (error) return <div>Error: {error.message}</div>;\n *\n * return fileUrl ? <img src={fileUrl} alt=\"File\" /> : null;\n * }\n * ```\n */\n\nimport { useState, useEffect, useCallback, useRef } from 'react';\nimport type { SupabaseClient } from '@supabase/supabase-js';\nimport { FileReference, FileCategory } from '../types/file-reference';\nimport { getPublicUrl, getSignedUrl, generateFileUrlsBatch } from '../utils/storage/helpers';\nimport { createFileReferenceService } from '../utils/file-reference';\nimport { logger } from '../utils/core/logger';\n\n// Simple in-memory cache for authenticated file data\nconst authenticatedFileCache = new Map<string, { data: any; timestamp: number; ttl: number }>();\n\n// Cache size limit to prevent memory leaks\nconst MAX_CACHE_SIZE = 100;\n\n// Helper function to clean up expired entries and enforce size limit\nfunction cleanupCache() {\n const now = Date.now();\n const entries = Array.from(authenticatedFileCache.entries());\n \n // Remove expired entries\n const expiredKeys: string[] = [];\n entries.forEach(([key, value]) => {\n if (now - value.timestamp >= value.ttl) {\n expiredKeys.push(key);\n }\n });\n expiredKeys.forEach(key => authenticatedFileCache.delete(key));\n \n // If still over limit, remove oldest entries\n if (authenticatedFileCache.size > MAX_CACHE_SIZE) {\n const sorted = entries\n .filter(([key]) => !expiredKeys.includes(key))\n .sort((a, b) => a[1].timestamp - b[1].timestamp);\n const toRemove = sorted.slice(0, authenticatedFileCache.size - MAX_CACHE_SIZE);\n toRemove.forEach(([key]) => authenticatedFileCache.delete(key));\n }\n}\n\n/**\n * Return value of the useFileDisplay hook.\n * Provides file URLs, references, loading state, error, and refetch function.\n */\nexport interface UseFileDisplayReturn {\n /** Single file URL if category is provided and file found, null otherwise */\n fileUrl: string | null;\n /** Single file reference if category is provided and file found, null otherwise */\n fileReference: FileReference | null;\n /** Array of all file references for the record (when category not provided or for multiple files) */\n fileReferences: FileReference[];\n /** Map of file IDs to URLs for multiple files */\n fileUrls: Map<string, string>;\n /** Total count of files for the record */\n fileCount: number;\n /** Whether the data is currently loading */\n isLoading: boolean;\n /** Any error that occurred during loading */\n error: Error | null;\n /** Function to manually refetch the data */\n refetch: () => Promise<void>;\n}\n\nexport interface UseFileDisplayOptions {\n /** Cache TTL in milliseconds (default: 30 minutes) */\n cacheTtl?: number;\n /** Whether to enable caching (default: true) */\n enableCache?: boolean;\n /** Supabase client instance (required) */\n supabase: SupabaseClient | null;\n}\n\n/**\n * Hook for accessing file references in authenticated contexts\n * \n * This hook provides access to file references for authenticated users.\n * It supports both public and private files, generating appropriate URLs\n * (public URLs for public files, signed URLs for private files).\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to filter by (for single file mode)\n * @param options - Configuration options for caching and behavior\n * @returns Object containing file data, loading state, error, and refetch function\n */\nexport function useFileDisplay(\n table_name: string | undefined,\n record_id: string | undefined,\n organisation_id: string | undefined,\n category: FileCategory | undefined,\n options: UseFileDisplayOptions\n): UseFileDisplayReturn {\n const {\n cacheTtl = 30 * 60 * 1000, // 30 minutes\n enableCache = true,\n supabase\n } = options;\n\n const [fileUrl, setFileUrl] = useState<string | null>(null);\n const [fileReference, setFileReference] = useState<FileReference | null>(null);\n const [fileReferences, setFileReferences] = useState<FileReference[]>([]);\n const [fileUrls, setFileUrls] = useState<Map<string, string>>(new Map());\n const [fileCount, setFileCount] = useState<number>(0);\n const [isLoading, setIsLoading] = useState<boolean>(false);\n const [error, setError] = useState<Error | null>(null);\n\n // Ref to track if component is mounted and effect is active\n // This prevents state updates from stale async operations\n const isMountedRef = useRef(true);\n\n // Helper to safely set state only if component is still mounted\n const safeSetState = <T,>(setter: (value: T) => void, value: T) => {\n if (isMountedRef.current) {\n setter(value);\n }\n };\n\n const fetchFiles = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) {\n safeSetState(setFileUrl, null);\n safeSetState(setFileReference, null);\n safeSetState(setFileReferences, []);\n safeSetState(setFileUrls, new Map());\n safeSetState(setFileCount, 0);\n safeSetState(setIsLoading, false);\n return;\n }\n\n // Validate UUID format for organisationId to prevent database errors (only if provided)\n if (organisation_id) {\n const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;\n if (!uuidRegex.test(organisation_id)) {\n logger.warn('useFileDisplay', 'Invalid organisationId format (not a valid UUID):', organisation_id);\n }\n }\n\n // Check cache first\n // When organisation_id is undefined, use 'undefined' in cache key to distinguish from explicit null\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n if (enableCache) {\n const cached = authenticatedFileCache.get(cacheKey);\n if (cached && Date.now() - cached.timestamp < cached.ttl) {\n const cachedData = cached.data;\n \n // FIX: Regenerate signed URL for private files if missing\n if (cachedData.fileReference && !cachedData.fileUrl && \n cachedData.fileReference.is_public === false && supabase) {\n // Regenerate signed URL without fetching from database\n try {\n const signedUrlResult = await getSignedUrl(supabase, cachedData.fileReference.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n const regeneratedUrl = signedUrlResult?.url || null;\n safeSetState(setFileUrl, regeneratedUrl);\n safeSetState(setFileReference, cachedData.fileReference);\n safeSetState(setFileReferences, cachedData.fileReferences || []);\n safeSetState(setFileUrls, cachedData.fileUrls || new Map());\n safeSetState(setFileCount, cachedData.fileCount || 0);\n safeSetState(setIsLoading, false);\n safeSetState(setError, null);\n return;\n } catch (err) {\n // If signed URL regeneration fails, fall through to normal fetch\n logger.warn('useFileDisplay', 'Failed to regenerate signed URL from cache, falling back to fetch:', err);\n }\n }\n \n // Normal cache hit for public files or files with URLs\n safeSetState(setFileUrl, cachedData.fileUrl || null);\n safeSetState(setFileReference, cachedData.fileReference || null);\n safeSetState(setFileReferences, cachedData.fileReferences || []);\n safeSetState(setFileUrls, cachedData.fileUrls || new Map());\n safeSetState(setFileCount, cachedData.fileCount || 0);\n safeSetState(setIsLoading, false);\n safeSetState(setError, null);\n return;\n }\n }\n\n try {\n safeSetState(setIsLoading, true);\n safeSetState(setError, null);\n\n const service = createFileReferenceService(supabase);\n let files: FileReference[] = [];\n\n // When organisation_id is undefined (not provided), search both user-scoped (null) and organisation-scoped files\n // This allows FileDisplay to work without requiring the organisation_id prop\n // Note: Explicitly passing null or empty string should only search user-scoped files\n const shouldSearchBothScopes = organisation_id === undefined;\n \n if (shouldSearchBothScopes) {\n // First, try user-scoped files (organisation_id = null)\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined // Explicitly pass undefined for user-scoped files (service converts to null for RPC)\n );\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying user-scoped files:', err);\n userScopedFiles = [];\n }\n\n // For organisation-scoped files, we need to query the user's organisations\n // Get user's organisations from the authenticated session\n try {\n const { data: { user }, error: userError } = await supabase.auth.getUser();\n if (userError) {\n logger.warn('useFileDisplay', 'Error getting user:', userError);\n }\n \n if (user) {\n // Query user's active organisation memberships\n const { data: memberships, error: membershipError } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id);\n\n if (membershipError) {\n logger.warn('useFileDisplay', 'Error querying organisation memberships:', membershipError);\n }\n\n if (memberships && memberships.length > 0) {\n // Try each organisation the user belongs to\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n \n // Query each organisation in parallel\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n orgId\n );\n } else {\n return await service.listFileReferences(\n table_name,\n record_id,\n orgId\n );\n }\n } catch (err) {\n // Silently fail for individual org queries - user might not have access\n return [];\n }\n });\n\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n } else {\n // When user has no organisation memberships, try querying files with any organisation_id\n // as a fallback - the file might be organisation-scoped but accessible via RLS\n // This handles edge cases where RLS allows access but we can't enumerate organisations\n try {\n // Try querying without organisation_id filter - let RLS handle security\n let fallbackQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n if (category) {\n fallbackQuery = fallbackQuery.eq('file_metadata->>category', category);\n }\n\n const { data: fallbackFiles } = await fallbackQuery;\n \n if (fallbackFiles && fallbackFiles.length > 0) {\n // Convert to FileReference format\n orgScopedFiles = fallbackFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n }\n } catch (err) {\n logger.warn('useFileDisplay', 'Error querying organisation-scoped files:', err);\n orgScopedFiles = [];\n }\n\n // Merge results: prefer organisation-scoped files if both exist, otherwise use user-scoped\n // Sort by created_at DESC to get most recent first\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n // If we have both types, prefer organisation-scoped (non-null organisation_id)\n // Otherwise, use whatever we found\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n // Prefer organisation-scoped files - filter to only those with organisation_id\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n // Use all files found (either user-scoped or org-scoped, but not both)\n files = allFiles;\n }\n\n // If no files found through RPC, try a direct query as fallback\n // This handles cases where RLS policy allows access but RPC security check is too strict\n // (e.g., core_person files where user owns the person record but record_id != user_id)\n if (files.length === 0) {\n try {\n let directQuery = supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id)\n .order('created_at', { ascending: false });\n\n // If category is provided, filter by category in metadata\n if (category) {\n directQuery = directQuery.eq('file_metadata->>category', category);\n }\n\n const { data: directFiles } = await directQuery;\n\n if (directFiles && directFiles.length > 0) {\n // Convert to FileReference format\n files = directFiles.map((f: any) => ({\n id: f.id,\n table_name: f.table_name,\n record_id: f.record_id,\n file_path: f.file_path,\n file_metadata: f.file_metadata || {},\n organisation_id: f.organisation_id,\n app_id: f.app_id,\n is_public: f.is_public ?? false,\n created_at: f.created_at,\n updated_at: f.updated_at\n })) as FileReference[];\n }\n } catch (err) {\n // Silently fail - RLS may block or other error\n }\n }\n } else {\n // organisation_id is provided (or explicitly null) - use normal query\n // CRITICAL: When category is provided, MUST use RPC function, not direct queries\n // Category is stored in file_metadata JSONB field, not a direct column\n if (category) {\n // Single file mode - get files by category using RPC\n // Removed verbose debug log - only log on errors\n files = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n organisation_id\n );\n } else {\n // Multiple files mode - get all files using RPC\n files = await service.listFileReferences(\n table_name,\n record_id,\n organisation_id\n );\n }\n\n // Fallback: If no files found and organisation_id is falsy (undefined, null, empty string),\n // try searching both scopes as a fallback\n // This handles cases where the prop might be passed as empty string or the check above didn't catch it\n if (files.length === 0 && (!organisation_id || organisation_id === '')) {\n // Try the dual-scope search logic\n let userScopedFiles: FileReference[] = [];\n let orgScopedFiles: FileReference[] = [];\n\n try {\n if (category) {\n userScopedFiles = await service.getFilesByCategory(\n table_name,\n record_id,\n category,\n undefined\n );\n } else {\n userScopedFiles = await service.listFileReferences(\n table_name,\n record_id,\n undefined\n );\n }\n } catch (err) {\n // Silently fail\n }\n\n try {\n const { data: { user } } = await supabase.auth.getUser();\n if (user) {\n const { data: memberships } = await supabase\n .from('core_organisation_memberships')\n .select('organisation_id')\n .eq('user_id', user.id)\n .or('status.is.null,status.eq.active');\n\n if (memberships && memberships.length > 0) {\n const orgIds = memberships.map(m => m.organisation_id).filter(Boolean) as string[];\n const orgQueries = orgIds.map(async (orgId) => {\n try {\n if (category) {\n return await service.getFilesByCategory(table_name, record_id, category, orgId);\n } else {\n return await service.listFileReferences(table_name, record_id, orgId);\n }\n } catch (err) {\n return [];\n }\n });\n const orgResults = await Promise.all(orgQueries);\n orgScopedFiles = orgResults.flat();\n }\n }\n } catch (err) {\n // Silently fail\n }\n\n // Merge results\n const allFiles = [...userScopedFiles, ...orgScopedFiles];\n allFiles.sort((a, b) => {\n const aTime = new Date(a.created_at).getTime();\n const bTime = new Date(b.created_at).getTime();\n return bTime - aTime;\n });\n\n if (orgScopedFiles.length > 0 && userScopedFiles.length > 0) {\n files = allFiles.filter(f => f.organisation_id !== null);\n } else {\n files = allFiles;\n }\n }\n }\n\n if (files.length === 0) {\n safeSetState(setFileUrl, null);\n safeSetState(setFileReference, null);\n safeSetState(setFileReferences, []);\n safeSetState(setFileUrls, new Map());\n safeSetState(setFileCount, 0);\n \n // Cache empty result\n if (enableCache) {\n authenticatedFileCache.set(cacheKey, {\n data: { fileUrl: null, fileReference: null, fileReferences: [], fileUrls: new Map(), fileCount: 0 },\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n return;\n }\n\n safeSetState(setFileReferences, files);\n safeSetState(setFileCount, files.length);\n\n if (category && files.length > 0) {\n // Single file mode - get first file\n const firstFile = files[0];\n // Removed verbose debug logs - only log on errors\n safeSetState(setFileReference, firstFile);\n \n // Generate URL based on file visibility\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n } else {\n const signedUrlResult = await getSignedUrl(supabase, firstFile.file_path, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n url = signedUrlResult?.url || null;\n // Only log if URL generation fails\n if (!url) {\n logger.warn('useFileDisplay', 'Failed to generate signed URL for file:', {\n file_path: firstFile.file_path,\n record_id,\n table_name\n });\n }\n }\n safeSetState(setFileUrl, url);\n } else {\n // Multiple files mode - generate URLs for all files in batch\n const urlMap = await generateFileUrlsBatch(supabase, files, {\n appName: 'pace-core',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: 3600\n });\n safeSetState(setFileUrls, urlMap);\n safeSetState(setFileReference, null);\n safeSetState(setFileUrl, null);\n }\n\n // Cache the result\n if (enableCache) {\n // Prepare cache data\n let cacheData: any = {\n fileReference: category && files.length > 0 ? files[0] : null,\n fileReferences: files,\n fileUrls: new Map(),\n fileCount: files.length\n };\n\n if (category && files.length > 0) {\n const firstFile = files[0];\n let url: string | null = null;\n if (firstFile.is_public) {\n url = getPublicUrl(supabase, firstFile.file_path, true);\n }\n cacheData.fileUrl = url;\n } else {\n const urlMap = new Map<string, string>();\n for (const fileRef of files) {\n if (fileRef.is_public) {\n const url = getPublicUrl(supabase, fileRef.file_path, true);\n if (url) {\n urlMap.set(fileRef.id, url);\n }\n }\n }\n cacheData.fileUrls = urlMap;\n }\n\n authenticatedFileCache.set(cacheKey, {\n data: cacheData,\n timestamp: Date.now(),\n ttl: cacheTtl\n });\n cleanupCache();\n }\n\n } catch (err) {\n logger.error('useFileDisplay', 'Error fetching files:', err);\n const error = err instanceof Error ? err : new Error('Unknown error occurred');\n safeSetState(setError, error);\n safeSetState(setFileUrl, null);\n safeSetState(setFileReference, null);\n safeSetState(setFileReferences, []);\n safeSetState(setFileUrls, new Map());\n safeSetState(setFileCount, 0);\n } finally {\n safeSetState(setIsLoading, false);\n }\n }, [table_name, record_id, organisation_id, category, supabase, cacheTtl, enableCache]);\n\n // Fetch files when parameters change\n useEffect(() => {\n // Mark as mounted when effect runs\n isMountedRef.current = true;\n\n if (table_name && record_id && supabase) {\n // Call fetchFiles - it will check isMountedRef before setting state\n fetchFiles().catch((err) => {\n // Only set error if component is still mounted\n if (isMountedRef.current) {\n safeSetState(setError, err instanceof Error ? err : new Error('Unknown error'));\n }\n });\n } else {\n // Only update state if component is still mounted\n safeSetState(setFileUrl, null);\n safeSetState(setFileReference, null);\n safeSetState(setFileReferences, []);\n safeSetState(setFileUrls, new Map());\n safeSetState(setFileCount, 0);\n safeSetState(setIsLoading, false);\n safeSetState(setError, null);\n }\n\n // Cleanup: mark as unmounted when dependencies change or component unmounts\n // This prevents state updates from stale requests\n return () => {\n isMountedRef.current = false;\n };\n // fetchFiles is memoized; we only need to re-run when parameters change\n // eslint-disable-next-line react-hooks/exhaustive-deps\n }, [table_name, record_id, organisation_id, supabase]);\n\n const refetch = useCallback(async (): Promise<void> => {\n if (!table_name || !record_id || !supabase) return;\n \n // Clear cache for this file\n if (enableCache) {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n }\n await fetchFiles();\n }, [fetchFiles, table_name, record_id, organisation_id, category, supabase, enableCache]);\n\n return {\n fileUrl,\n fileReference,\n fileReferences,\n fileUrls,\n fileCount,\n isLoading,\n error,\n refetch\n };\n}\n\n/**\n * Clear all cached authenticated file data\n * Useful for testing or when you need to force refresh all data\n */\nexport function clearFileDisplayCache(): void {\n for (const [key] of authenticatedFileCache) {\n if (key.startsWith('file_')) {\n authenticatedFileCache.delete(key);\n }\n }\n}\n\n/**\n * Get cache statistics for debugging\n */\nexport function getFileDisplayCacheStats(): { size: number; keys: string[] } {\n const keys = Array.from(authenticatedFileCache.keys()).filter(key => key.startsWith('file_'));\n return {\n size: keys.length,\n keys\n };\n}\n\n/**\n * Invalidate cache for a specific file display entry\n * Useful for clearing cache after file uploads or updates\n * \n * @param table_name - The table name containing the file reference\n * @param record_id - The record ID that owns the file(s)\n * @param organisation_id - The organisation ID for storage path\n * @param category - Optional file category to invalidate (if provided, also invalidates 'all' category)\n */\nexport function invalidateFileDisplayCache(\n table_name: string,\n record_id: string,\n organisation_id: string | null | undefined,\n category?: FileCategory\n): void {\n const cacheKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_${category || 'all'}`;\n authenticatedFileCache.delete(cacheKey);\n // Also invalidate 'all' category if specific category invalidated\n if (category) {\n const allCategoryKey = `file_${table_name}_${record_id}_${organisation_id === undefined ? 'undefined' : (organisation_id ?? 'null')}_all`;\n authenticatedFileCache.delete(allCategoryKey);\n }\n}\n\n","// File Reference Service\n// Provides CRUD operations for the centralized file reference system\n\nimport { SupabaseClient } from '@supabase/supabase-js';\nimport { \n FileReference, \n FileUploadOptions, \n FileReferenceService, \n FileUploadResult,\n FileCategory,\n FileMetadata\n} from '../../types/file-reference';\nimport { uploadFile, getPublicUrl, getSignedUrl, deleteFile, extractFileMetadata } from '../storage/helpers';\nimport { setOrganisationContext } from '../context/organisationContext';\nimport { invalidateFileDisplayCache } from '../../hooks/useFileDisplay';\nimport { createLogger } from '../core/logger';\nimport { assertAppId } from '../../types/core';\nimport { isSuperAdmin } from '../../rbac/api';\n\nconst log = createLogger('FileReferenceService');\n\nexport class FileReferenceServiceImpl implements FileReferenceService {\n constructor(private supabase: SupabaseClient) {}\n\n /**\n * Helper method to get app name from app ID\n */\n private async getAppName(appId: string): Promise<string> {\n try {\n const { data, error } = await this.supabase\n .from('rbac_apps')\n .select('name')\n .eq('id', appId)\n .single();\n \n if (error || !data) {\n return 'unknown';\n }\n \n return data.name || 'unknown';\n } catch {\n return 'unknown';\n }\n }\n\n /**\n * Creates a file reference by uploading a file to storage and linking it in the database.\n * \n * Storage Flow:\n * 1. Upload file to storage bucket first (files or public-files based on is_public flag)\n * - Path format: {orgId}/{folder}/{timestamp-uuid-filename}\n * - Bucket selection: 'files' (private) or 'public-files' (public)\n * 2. Extract file metadata (dimensions, hash, etc.)\n * 3. Set organisation context for RLS policies\n * 4. Create database reference via RPC function\n * 5. If DB insert fails, rollback by deleting uploaded file\n * \n * This ensures atomicity: either both storage and DB succeed, or both are cleaned up.\n */\n async createFileReference(options: FileUploadOptions, file: File): Promise<FileReference> {\n try {\n\n // organisation_id is optional for user-scoped files (e.g., profile photos)\n const isUserScoped = !options.organisation_id && options.userId;\n if (!isUserScoped && !options.organisation_id) {\n throw new Error('organisation_id is required for file upload, or userId must be provided for user-scoped files');\n }\n if (!options.table_name) {\n throw new Error('table_name is required for file upload');\n }\n if (!options.record_id) {\n throw new Error('record_id is required for file upload');\n }\n if (!options.folder) {\n throw new Error('folder is required for file upload. The folder prop determines the storage path.');\n }\n\n // For user-scoped files, we MUST use auth.uid() for the path to match RLS policies\n // Get the authenticated user ID from the Supabase session\n let authenticatedUserId: string | undefined = undefined;\n if (isUserScoped) {\n const { data: { user: authUser }, error: authError } = await this.supabase.auth.getUser();\n if (authError || !authUser) {\n throw new Error('User must be authenticated to upload user-scoped files');\n }\n authenticatedUserId = authUser.id;\n log.debug('Using authenticated user ID for user-scoped file upload', { userId: authenticatedUserId });\n }\n\n // Step 1: Upload file to storage bucket first\n // This generates a unique path: {orgId}/{folder}/{timestamp-uuid-filename} or users/{auth.uid()}/{folder}/{timestamp-uuid-filename}\n // Bucket is automatically selected based on is_public flag\n const uploadResult = await uploadFile(this.supabase, file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false,\n customPath: options.folder // Use folder prop as the custom path segment\n });\n if (!uploadResult.success) {\n throw new Error(`Failed to upload file: ${uploadResult.error}`);\n }\n\n if (!uploadResult.path) {\n throw new Error('File upload did not return a path');\n }\n\n const filePath = uploadResult.path;\n\n // Step 2: Extract file metadata (dimensions, hash, etc.)\n const metadata = await extractFileMetadata(file, {\n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: authenticatedUserId || (isUserScoped ? undefined : options.userId), // Use auth.uid() for user-scoped files\n isPublic: options.is_public || false\n }, 'system');\n\n // Step 3: Set organisation context in database session before creating file reference\n // Skip for user-scoped files (no org context needed)\n if (!isUserScoped && options.organisation_id) {\n await setOrganisationContext(this.supabase, options.organisation_id);\n }\n\n // Step 4: Create file reference in database using RPC function\n // This links the storage path to the record in core_file_references table\n const { data, error } = await this.supabase\n .rpc('data_file_reference_create', {\n p_table_name: options.table_name,\n p_record_id: options.record_id,\n p_file_path: filePath, // Storage path from step 1\n p_organisation_id: options.organisation_id ?? null,\n p_app_id: options.app_id,\n p_page_context: options.pageContext,\n p_event_id: options.event_id || null, // Pass event_id for event-based apps\n p_file_metadata: {\n fileName: file.name,\n fileType: file.type,\n fileSize: file.size,\n category: options.category,\n ...metadata,\n ...options.custom_metadata\n },\n p_is_public: options.is_public || false,\n p_user_id: authenticatedUserId || options.userId || null // Pass authenticated user ID for user-scoped files\n });\n\n // Step 5: Rollback - if database insert fails, clean up uploaded file\n if (error) {\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to create file reference: ${error.message}`);\n }\n\n // Check if RPC returned null (permission denied or other failure)\n if (!data || data === null) {\n // Before throwing permission error, check if user is super-admin\n // If super-admin, the RPC should have allowed the upload, so this is likely a different issue\n let isSuperAdminUser = false;\n try {\n const { data: { user: authUser } } = await this.supabase.auth.getUser();\n if (authUser) {\n isSuperAdminUser = await isSuperAdmin(authUser.id);\n }\n } catch (superAdminCheckError) {\n // If super-admin check fails, continue with permission error\n log.warn('Failed to check super-admin status', superAdminCheckError);\n }\n\n // Clean up the uploaded file since DB insert failed\n await deleteFile(this.supabase, filePath, options.is_public || false);\n\n // Provide more helpful error message\n const appName = await this.getAppName(options.app_id).catch(() => 'unknown');\n const pageContextDisplay = options.pageContext || 'undefined';\n \n if (isSuperAdminUser) {\n // Super-admin should have been allowed - this suggests a different issue\n throw new Error(\n `File upload failed for super-admin user. This may indicate a database issue. ` +\n `Page context: '${pageContextDisplay}', App: '${appName}'. ` +\n `Please check that the page '${pageContextDisplay}' exists in rbac_app_pages table for app '${appName}'.`\n );\n } else {\n throw new Error(\n `File upload denied: insufficient permissions. You need 'create:page.${pageContextDisplay}' or 'update:page.${pageContextDisplay}' permission for the '${pageContextDisplay}' page. ` +\n `Make sure the page exists in rbac_app_pages table for app '${appName}'.`\n );\n }\n }\n\n // Get the created file reference\n const { data: fileRef, error: fetchError } = await this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('id', data)\n .single();\n\n if (fetchError || !fileRef) {\n // Clean up uploaded file if we can't fetch the reference\n await deleteFile(this.supabase, filePath, options.is_public || false);\n throw new Error(`Failed to fetch created file reference: ${fetchError?.message}`);\n }\n\n // Invalidate cache for this file display entry so newly uploaded files appear immediately\n // For user-scoped files, pass null for organisation_id\n invalidateFileDisplayCache(\n options.table_name,\n options.record_id,\n options.organisation_id || null,\n options.category\n );\n\n return fileRef as FileReference;\n } catch (error) {\n log.error('Error creating file reference:', error);\n throw error;\n }\n }\n\n async getFileReference(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n let query = this.supabase\n .from('core_file_references')\n .select('id, table_name, record_id, file_path, file_metadata, organisation_id, app_id, is_public, created_at, updated_at')\n .eq('table_name', table_name)\n .eq('record_id', record_id);\n \n // Handle NULL organisation_id for user-owned files\n if (organisation_id === null || organisation_id === undefined) {\n query = query.is('organisation_id', null);\n } else {\n query = query.eq('organisation_id', organisation_id);\n }\n \n const { data, error } = await query.single();\n\n if (error) {\n if (error.code === 'PGRST116') {\n return null; // No rows found\n }\n throw new Error(`Failed to get file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error getting file reference:', error);\n throw error;\n }\n }\n\n async getFileUrl(table_name: string, record_id: string, organisation_id?: string): Promise<string | null> {\n try {\n // Get file reference to check if it's public\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n if (!fileRef) {\n return null;\n }\n\n // For public files, RPC returns file path - generate public URL client-side\n if (fileRef.is_public) {\n const { data: pathData } = await this.supabase\n .rpc('data_file_reference_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id\n });\n\n if (!pathData) {\n return null;\n }\n\n // Generate public URL using bucket-aware helper\n return getPublicUrl(this.supabase, pathData, true);\n } else {\n // For private files, use signed URL\n return await this.getSignedUrl(table_name, record_id, organisation_id);\n }\n } catch (error) {\n log.error('Error getting file URL:', error);\n throw error;\n }\n }\n\n async getSignedUrl(table_name: string, record_id: string, organisation_id?: string, expires_in: number = 3600): Promise<string | null> {\n try {\n // Get file path from RPC function\n const { data: filePath, error } = await this.supabase\n .rpc('data_file_reference_signed_url_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_expires_in: expires_in\n });\n\n if (error) {\n throw new Error(`Failed to get signed URL: ${error.message}`);\n }\n\n if (!filePath) {\n return null;\n }\n\n // Generate signed URL client-side using bucket-aware helper (files bucket for private files)\n const signedUrlResult = await getSignedUrl(this.supabase, filePath, {\n appName: 'file-reference',\n orgId: organisation_id,\n userId: organisation_id ? undefined : record_id,\n expiresIn: expires_in\n });\n\n return signedUrlResult?.url || null;\n } catch (error) {\n log.error('Error getting signed URL:', error);\n throw error;\n }\n }\n\n async updateFileReference(id: string, updates: Partial<FileReference>): Promise<FileReference> {\n try {\n const { data, error } = await this.supabase\n .from('core_file_references')\n .update(updates)\n .eq('id', id)\n .select()\n .single();\n\n if (error) {\n throw new Error(`Failed to update file reference: ${error.message}`);\n }\n\n return data as FileReference;\n } catch (error) {\n log.error('Error updating file reference:', error);\n throw error;\n }\n }\n\n async deleteFileReference(table_name: string, record_id: string, organisation_id?: string, delete_file: boolean = false): Promise<boolean> {\n try {\n // Get file reference first to determine bucket\n const fileRef = await this.getFileReference(table_name, record_id, organisation_id);\n \n const { error } = await this.supabase\n .rpc('data_file_reference_delete', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null,\n p_delete_file: delete_file\n });\n\n if (error) {\n throw new Error(`Failed to delete file reference: ${error.message}`);\n }\n\n // If delete_file is true and we have the file reference, delete from storage\n if (delete_file && fileRef) {\n await deleteFile(this.supabase, fileRef.file_path, fileRef.is_public || false);\n }\n\n return true;\n } catch (error) {\n log.error('Error deleting file reference:', error);\n throw error;\n }\n }\n\n async listFileReferences(table_name: string, record_id: string, organisation_id?: string): Promise<FileReference[]> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to list file references: ${error.message}`);\n }\n\n // RPC returns: id, file_path, file_metadata, is_public, created_at\n // We can construct FileReference objects directly from RPC response + function parameters\n // This avoids a second query and reduces network requests\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => item.id && item.file_path && item.file_metadata)\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error listing file references:', error);\n throw error;\n }\n }\n\n async getFileCount(table_name: string, record_id: string, organisation_id?: string): Promise<number> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_count_get', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file count: ${error.message}`);\n }\n\n return data || 0;\n } catch (error) {\n log.error('Error getting file count:', error);\n throw error;\n }\n }\n\n async getFileReferenceById(id: string, organisation_id?: string): Promise<FileReference | null> {\n try {\n const { data, error } = await this.supabase\n .rpc('data_file_reference_get', {\n p_file_reference_id: id,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n throw new Error(`Failed to get file reference by ID: ${error.message}`);\n }\n\n if (!data || data.length === 0) {\n return null;\n }\n\n return data[0] as FileReference;\n } catch (error) {\n log.error('Error getting file reference by ID:', error);\n throw error;\n }\n }\n\n async getFilesByCategory(\n table_name: string, \n record_id: string, \n category: FileCategory, \n organisation_id?: string\n ): Promise<FileReference[]> {\n try {\n // CRITICAL: Use RPC function to get files by category - this correctly filters on file_metadata->>'category'\n // NOTE: We MUST use RPC function. Direct queries with .eq('category', ...) will FAIL with HTTP 406\n // because there is NO 'category' column. The category is stored in the JSONB file_metadata field.\n const { data, error } = await this.supabase\n .rpc('data_file_reference_by_category_list', {\n p_table_name: table_name,\n p_record_id: record_id,\n p_category: category,\n p_organisation_id: organisation_id ?? null\n });\n\n if (error) {\n // Provide clear error message about category filtering\n log.error('RPC ERROR getting files by category:', {\n error,\n errorCode: error.code,\n errorMessage: error.message,\n errorDetails: error.details,\n table_name,\n record_id,\n category,\n organisation_id,\n message: 'CRITICAL: Category filtering MUST use RPC function data_file_reference_by_category_list. Direct queries with .eq(\\'category\\', ...) will FAIL with HTTP 406 because category is stored in file_metadata JSONB field, not a direct column.'\n });\n throw new Error(`Failed to get files by category: ${error.message}. Category filtering uses file_metadata JSONB field, not a direct column. RPC function required.`);\n }\n\n // RPC returns partial data with: id, file_path, file_metadata, is_public, created_at\n // We have table_name, record_id, organisation_id from function parameters\n // We can construct FileReference objects directly without another query (avoiding RLS issues)\n if (!data || data.length === 0) {\n return [];\n }\n\n // Construct FileReference objects from RPC response\n // This avoids RLS issues with direct queries - the RPC already validated permissions\n interface RpcFileItem {\n id: string;\n file_path: string;\n file_metadata: { category?: string; app_id?: string; [key: string]: unknown };\n is_public?: boolean;\n created_at?: string;\n }\n const fileReferences: FileReference[] = data\n .filter((item: RpcFileItem) => {\n // Verify category matches (defensive check)\n const fileCategory = item.file_metadata?.category;\n const matches = fileCategory === category;\n if (!matches) {\n log.warn('File category mismatch in RPC response:', {\n fileId: item.id,\n expectedCategory: category,\n actualCategory: fileCategory\n });\n }\n return matches && item.id && item.file_path && item.file_metadata;\n })\n .map((item: RpcFileItem) => {\n // Extract file name and type from file_path\n const fileName = item.file_path.split('/').pop() || 'unknown';\n const fileType = fileName.split('.').pop() || 'unknown';\n \n // Construct complete FileReference from RPC response + function parameters\n const fileRef: FileReference = {\n id: item.id,\n table_name: table_name,\n record_id: record_id,\n file_path: item.file_path,\n file_metadata: {\n fileName,\n fileType,\n category: (item.file_metadata?.category as FileCategory) || FileCategory.GENERAL_DOCUMENTS,\n ...(item.file_metadata || {}),\n } as FileMetadata,\n organisation_id: organisation_id ?? null,\n app_id: item.file_metadata?.app_id ? assertAppId(item.file_metadata.app_id) : assertAppId(''), // May not be in metadata, use empty string\n is_public: item.is_public ?? false,\n created_at: item.created_at || new Date().toISOString(),\n updated_at: item.created_at || new Date().toISOString() // RPC doesn't return updated_at, use created_at\n };\n return fileRef;\n });\n\n return fileReferences;\n } catch (error) {\n log.error('Error getting files by category:', error);\n throw error;\n }\n }\n\n async uploadMultipleFiles(\n options: FileUploadOptions,\n files: File[]\n ): Promise<import('../../types/file-reference').BulkUploadResult> {\n const success: FileReference[] = [];\n const failed: { file: File; error: string }[] = [];\n const results: Array<{ file: File; result: FileUploadResult | null; error?: string }> = [];\n\n // Upload files sequentially to avoid overwhelming the server\n for (const file of files) {\n try {\n const fileReference = await this.createFileReference(options, file);\n\n success.push(fileReference);\n results.push({\n file,\n result: {\n file_reference: fileReference,\n file_url: fileReference.is_public\n ? getPublicUrl(this.supabase, fileReference.file_path, true)\n : '',\n },\n });\n } catch (error) {\n const message = error instanceof Error ? error.message : 'Unknown error';\n failed.push({ file, error: message });\n results.push({ file, result: null, error: message });\n }\n }\n\n return {\n success,\n failed,\n total: results.length,\n successful: success.length,\n results,\n };\n }\n}\n\n// Factory function to create file reference service\nexport function createFileReferenceService(supabase: SupabaseClient): FileReferenceService {\n return new FileReferenceServiceImpl(supabase);\n}\n\n// Helper function to upload file and create reference in one operation\nexport async function uploadFileWithReference(\n supabase: SupabaseClient,\n options: FileUploadOptions,\n file: File\n): Promise<FileUploadResult> {\n \n const service = createFileReferenceService(supabase);\n const fileReference = await service.createFileReference(options, file);\n \n const fileUrl = options.is_public \n ? getPublicUrl(supabase, fileReference.file_path, true)\n : await getSignedUrl(supabase, fileReference.file_path, { \n appName: 'file-reference',\n orgId: options.organisation_id || undefined,\n userId: options.userId || undefined,\n expiresIn: 3600 \n });\n\n const urlString = typeof fileUrl === 'string' ? fileUrl : fileUrl?.url || '';\n\n return {\n file_reference: fileReference,\n file_url: urlString,\n signed_url: options.is_public ? undefined : urlString || undefined\n };\n}\n","/**\n * @file Event Theme Hook\n * @package @jmruthers/pace-core\n * @module Hooks/EventTheme\n * @since 2.0.0\n * \n * Hook that automatically applies event-specific theming when an event is selected.\n * This ensures consistent UX across all apps in the pace suite.\n * \n * Works in two modes:\n * 1. Authenticated mode: Uses EventProvider context via useEvents() hook\n * 2. Public page mode: Accepts event prop directly (no EventProvider required)\n * \n * @example\n * ```tsx\n * // Authenticated pages (with EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function MyApp() {\n * // Automatically applies event colors when event is selected via EventProvider\n * useEventTheme();\n * \n * return <main>Your app content</main>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public pages (without EventProvider)\n * import { useEventTheme } from '@jmruthers/pace-core/hooks';\n * \n * function PublicPageLayout({ event }) {\n * // Applies event colors directly from event prop\n * useEventTheme(event);\n * \n * return <main>Public page content</main>;\n * }\n * ```\n */\n\nimport { useEffect } from 'react';\nimport { useLocation } from 'react-router-dom';\nimport { useEvents } from './useEvents';\nimport { applyPalette, clearPalette } from '../theming/runtime';\nimport { parseAndNormalizeEventColours } from '../theming/parseEventColours';\nimport type { Event } from '../types/event';\nimport { createLogger } from '../utils/core/logger';\n\nconst log = createLogger('useEventTheme');\n\n/**\n * Hook that automatically applies event-specific theming\n * \n * This hook applies event colors using the theming system. It works in two modes:\n * \n * **Authenticated Mode (default):**\n * - Uses EventProvider context via useEvents() hook\n * - Automatically watches selectedEvent changes\n * - Used in authenticated pages with EventProvider\n * \n * **Public Page Mode:**\n * - Accepts event prop directly\n * - No EventProvider required\n * - Used in public pages without authentication context\n * \n * Behavior:\n * - Applies event colors when an event with `event_colours` is provided\n * - Clears theming when no event is provided\n * - Skips theme application when on the login route to prevent login screen styling\n * - Handles cleanup and error cases gracefully\n * \n * @param event - Optional event object. If provided, uses this event directly (public page mode).\n * If not provided, uses EventProvider context via useEvents() (authenticated mode).\n * \n * @returns void - This is an effect hook with no return value\n * \n * @example\n * ```tsx\n * // Authenticated mode - uses EventProvider\n * function MyApp() {\n * useEventTheme(); // Watches selectedEvent from EventProvider\n * return <main>App content</main>;\n * }\n * ```\n * \n * @example\n * ```tsx\n * // Public page mode - uses event prop\n * function PublicPageLayout({ event }) {\n * useEventTheme(event); // Uses event prop directly\n * return <main>Public content</main>;\n * }\n * ```\n */\n/**\n * Hook that automatically applies event-specific theming.\n * Works in authenticated mode (via EventProvider) or public page mode (via event prop).\n * \n * @param event - Optional event object for public page mode. If not provided, uses EventProvider context.\n */\nexport function useEventTheme(event?: Event | null): void {\n const location = useLocation();\n \n // Try to get event from EventProvider context (authenticated mode)\n // Only use useEvents() if event prop is not provided\n let selectedEvent: Event | null | undefined;\n try {\n if (event === undefined) {\n // No event prop provided, try to use EventProvider context\n const eventsContext = useEvents();\n selectedEvent = eventsContext.selectedEvent;\n } else {\n // Event prop provided, use it directly (public page mode)\n selectedEvent = event;\n }\n } catch (error) {\n // useEvents() throws if EventProvider is not available\n // This is expected for public pages - use the event prop if provided\n if (event !== undefined) {\n selectedEvent = event;\n } else {\n // No event prop and no EventProvider - can't apply theme\n selectedEvent = null;\n }\n }\n\n useEffect(() => {\n // Skip theme application when on login route\n // This prevents event colors from styling the login screen\n const isOnLoginRoute = location.pathname === '/login' || location.pathname.startsWith('/login');\n \n if (isOnLoginRoute) {\n // Clear any active theme when on login route\n clearPalette();\n return;\n }\n\n // If there's no selected event, clear any dynamic theming\n if (!selectedEvent) {\n clearPalette();\n return;\n }\n\n // Check if the event has theme colors\n const eventColours = selectedEvent.event_colours;\n\n // Parse and normalize event_colours using shared utility\n const normalized = parseAndNormalizeEventColours(eventColours);\n \n if (!normalized) {\n clearPalette();\n return;\n }\n\n // Apply the normalized palette\n try {\n applyPalette(normalized);\n } catch (error) {\n log.error('Failed to apply event palette:', error);\n }\n\n // Cleanup function to clear palette when component unmounts or event changes\n return () => {\n // Don't clear on unmount since we want the theme to persist\n // The next event selection will update it\n };\n }, [selectedEvent, location.pathname]);\n}\n","/**\n * @file usePreventTabReload Hook\n * @package @jmruthers/pace-core\n * @module Hooks\n * @since 0.6.0\n *\n * Prevents full page reloads when switching browser tabs.\n * Handles browser back-forward cache (bfcache) restoration and tab visibility changes.\n */\n\nimport { useEffect, useRef } from 'react';\n\nexport interface UsePreventTabReloadOptions {\n /**\n * Whether to enable the prevention logic\n * @default true\n */\n enabled?: boolean;\n\n /**\n * Grace period in milliseconds to wait after tab becomes visible\n * before allowing any potential reloads\n * @default 2000\n */\n gracePeriodMs?: number;\n}\n\n/**\n * Hook to prevent full page reloads when switching browser tabs.\n * \n * This hook handles:\n * - Browser back-forward cache (bfcache) restoration\n * - Tab visibility changes\n * - Prevents unwanted page reloads when returning to a tab\n * \n * @param options - Configuration options\n * \n * @example\n * ```tsx\n * import { usePreventTabReload } from '@jmruthers/pace-core';\n * \n * function MyComponent() {\n * usePreventTabReload();\n * // Component code...\n * }\n * ```\n */\nexport function usePreventTabReload(options: UsePreventTabReloadOptions = {}): void {\n const { enabled = true, gracePeriodMs = 2000 } = options;\n const isRestoringFromCacheRef = useRef(false);\n const gracePeriodTimeoutRef = useRef<ReturnType<typeof setTimeout> | null>(null);\n\n useEffect(() => {\n if (!enabled || typeof window === 'undefined') return;\n\n // Handle pageshow event - detects when page is restored from bfcache\n const handlePageShow = (event: PageTransitionEvent) => {\n // If page was restored from bfcache, prevent any reload behavior\n if (event.persisted) {\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period to prevent reloads\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Handle visibility changes - when tab becomes visible\n const handleVisibilityChange = () => {\n if (!document.hidden) {\n // Tab just became visible - set flag to prevent reloads\n isRestoringFromCacheRef.current = true;\n \n // Clear any existing timeout\n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n \n // Set grace period\n gracePeriodTimeoutRef.current = setTimeout(() => {\n isRestoringFromCacheRef.current = false;\n }, gracePeriodMs);\n }\n };\n\n // Add event listeners\n window.addEventListener('pageshow', handlePageShow);\n document.addEventListener('visibilitychange', handleVisibilityChange);\n\n return () => {\n window.removeEventListener('pageshow', handlePageShow);\n document.removeEventListener('visibilitychange', handleVisibilityChange);\n \n if (gracePeriodTimeoutRef.current) {\n clearTimeout(gracePeriodTimeoutRef.current);\n }\n };\n }, [enabled, gracePeriodMs]);\n}\n\n"],"mappings":";;;;;;;;;;;;;;;;;;;;;;;;;;;;;AACA,SAAS,UAAU,iBAAiB;AAW7B,SAAS,YAAe,OAAU,OAAkB;AACzD,QAAM,CAAC,gBAAgB,iBAAiB,IAAI,SAAY,KAAK;AAE7D,YAAU,MAAM;AACd,UAAM,UAAU,WAAW,MAAM;AAC/B,wBAAkB,KAAK;AAAA,IACzB,GAAG,KAAK;AAER,WAAO,MAAM;AACX,mBAAa,OAAO;AAAA,IACtB;AAAA,EACF,GAAG,CAAC,OAAO,KAAK,CAAC;AAEjB,SAAO;AACT;;;AChBA,SAAS,mBAAsC;AAK/C,IAAM,MAAM,aAAa,eAAe;AAYxC,IAAM,aAAa,oBAAI,IAAmC;AAG1D,IAAM,sBAAsB,IAAI,KAAK;AACrC,IAAI,eAAsD;AAE1D,SAAS,kBAAkB;AACzB,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,cAAwB,CAAC;AAE/B,aAAW,QAAQ,CAAC,OAAO,QAAQ;AACjC,QAAI,MAAM,aAAa,KAAK;AAC1B,kBAAY,KAAK,GAAG;AAAA,IACtB;AAAA,EACF,CAAC;AAED,cAAY,QAAQ,SAAO;AACzB,eAAW,OAAO,GAAG;AACrB,QAAI,MAAM,qCAAqC,GAAG,EAAE;AAAA,EACtD,CAAC;AACH;AAGA,IAAI,OAAO,WAAW,eAAe,CAAC,cAAc;AAClD,iBAAe,YAAY,iBAAiB,mBAAmB;AAC/D,MAAI,MAAM,kCAAkC;AAG5C,SAAO,iBAAiB,gBAAgB,MAAM;AAC5C,QAAI,cAAc;AAChB,oBAAc,YAAY;AAC1B,qBAAe;AACf,UAAI,MAAM,mDAAmD;AAAA,IAC/D;AAAA,EACF,CAAC;AACH;AAMO,SAAS,oBAA0B;AACxC,MAAI,cAAc;AAChB,kBAAc,YAAY;AAC1B,mBAAe;AACf,QAAI,MAAM,oCAAoC;AAAA,EAChD;AACF;AA8DO,SAAS,cAAc,UAA0D;AACtF,QAAM,iBAAiB,YAAY,OACjC,OACA,WACA,aACA,SACA,UAAgC,CAAC,MAClB;AACf,UAAM,EAAE,MAAM,KAAK,UAAU,KAAK,IAAI;AACtC,UAAM,WAAW,GAAG,KAAK,IAAI,SAAS,IAAI,WAAW;AACrD,UAAM,MAAM,KAAK,IAAI;AAErB,QAAI,CAAC,SAAS;AACZ,aAAO,QAAQ;AAAA,IACjB;AAGA,UAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,QAAI,QAAQ;AAEV,UAAI,OAAO,YAAY,OAAO,OAAO,SAAS,QAAW;AACvD,YAAI,MAAM,wBAAwB,QAAQ,EAAE;AAC5C,eAAO,OAAO;AAAA,MAChB;AAGA,UAAI,OAAO,SAAS;AAClB,YAAI,MAAM,kCAAkC,QAAQ,EAAE;AACtD,eAAO,OAAO;AAAA,MAChB;AAAA,IACF;AAGA,QAAI,MAAM,yBAAyB,QAAQ,eAAe;AAC1D,UAAM,eAAe,QAAQ;AAG7B,eAAW,IAAI,UAAU;AAAA,MACvB,MAAM;AAAA,MACN,WAAW,MAAO,MAAM;AAAA,MACxB,SAAS;AAAA,IACX,CAAC;AAED,QAAI;AACF,YAAM,OAAO,MAAM;AAGnB,iBAAW,IAAI,UAAU;AAAA,QACvB;AAAA,QACA,WAAW,MAAO,MAAM;AAAA,MAC1B,CAAC;AAED,UAAI,MAAM,wBAAwB,QAAQ,gBAAgB,GAAG,GAAG;AAChE,aAAO;AAAA,IACT,SAAS,OAAO;AAEd,iBAAW,OAAO,QAAQ;AAC1B,UAAI,MAAM,oBAAoB,QAAQ,KAAK,KAAK;AAChD,YAAM;AAAA,IACR;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,QAAM,kBAAkB,YAAY,CAAC,OAAe,WAAmB,gBAAwB;AAC7F,UAAM,WAAW,GAAG,KAAK,IAAI,SAAS,IAAI,WAAW;AACrD,eAAW,OAAO,QAAQ;AAC1B,QAAI,MAAM,4BAA4B,QAAQ,EAAE;AAAA,EAClD,GAAG,CAAC,CAAC;AAEL,QAAM,aAAa,YAAY,MAAM;AACnC,eAAW,MAAM;AACjB,QAAI,MAAM,kCAAkC;AAAA,EAC9C,GAAG,CAAC,CAAC;AAEL,QAAM,gBAAgB,YAAY,MAAM;AACtC,WAAO;AAAA,MACL,MAAM,WAAW;AAAA,MACjB,MAAM,MAAM,KAAK,WAAW,KAAK,CAAC;AAAA,IACpC;AAAA,EACF,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAKO,IAAM,oBAIT;AAAA;AAAA;AAAA;AAAA;AAAA,EAKF,oBAAoB,CAClB,UACA,QACA,YACe;AACf,UAAM,WAAW,uBAAuB,MAAM;AAC9C,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,MAAM;AAElB,UAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,QAAI,UAAU,OAAO,YAAY,OAAO,OAAO,SAAS,QAAW;AACjE,aAAO,QAAQ,QAAQ,OAAO,IAAS;AAAA,IACzC;AAEA,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,UAAU,QAAQ;AACxB,eAAW,IAAI,UAAU;AAAA,MACvB,MAAM;AAAA,MACN,WAAW,MAAM;AAAA,MACjB;AAAA,IACF,CAAC;AAED,YAAQ,KAAK,UAAQ;AACnB,iBAAW,IAAI,UAAU,EAAE,MAAM,WAAW,MAAM,IAAI,CAAC;AAAA,IACzD,CAAC,EAAE,MAAM,MAAM;AACb,iBAAW,OAAO,QAAQ;AAAA,IAC5B,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,sBAAsB,CACpB,UACA,UACA,YACe;AACf,UAAM,WAAW,yBAAyB,QAAQ;AAClD,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,MAAM;AAElB,UAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,QAAI,UAAU,OAAO,YAAY,OAAO,OAAO,SAAS,QAAW;AACjE,aAAO,QAAQ,QAAQ,OAAO,IAAS;AAAA,IACzC;AAEA,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,UAAU,QAAQ;AACxB,eAAW,IAAI,UAAU;AAAA,MACvB,MAAM;AAAA,MACN,WAAW,MAAM;AAAA,MACjB;AAAA,IACF,CAAC;AAED,YAAQ,KAAK,UAAQ;AACnB,iBAAW,IAAI,UAAU,EAAE,MAAM,WAAW,MAAM,IAAI,CAAC;AAAA,IACzD,CAAC,EAAE,MAAM,MAAM;AACb,iBAAW,OAAO,QAAQ;AAAA,IAC5B,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,qBAAqB,CACnB,UACA,OACA,YACe;AACf,UAAM,WAAW,yBAAyB,KAAK;AAC/C,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,MAAM,KAAK,KAAK;AAEtB,UAAM,SAAS,WAAW,IAAI,QAAQ;AACtC,QAAI,UAAU,OAAO,YAAY,OAAO,OAAO,SAAS,QAAW;AACjE,aAAO,QAAQ,QAAQ,OAAO,IAAS;AAAA,IACzC;AAEA,QAAI,QAAQ,SAAS;AACnB,aAAO,OAAO;AAAA,IAChB;AAEA,UAAM,UAAU,QAAQ;AACxB,eAAW,IAAI,UAAU;AAAA,MACvB,MAAM;AAAA,MACN,WAAW,MAAM;AAAA,MACjB;AAAA,IACF,CAAC;AAED,YAAQ,KAAK,UAAQ;AACnB,iBAAW,IAAI,UAAU,EAAE,MAAM,WAAW,MAAM,IAAI,CAAC;AAAA,IACzD,CAAC,EAAE,MAAM,MAAM;AACb,iBAAW,OAAO,QAAQ;AAAA,IAC5B,CAAC;AAED,WAAO;AAAA,EACT;AACF;;;AC/UA,SAAS,YAAAA,WAAU,aAAAC,YAAW,eAAAC,cAAa,UAAAC,SAAQ,eAAe;AA+D3D,SAAS,uBACd,QACA,YACA,UAAyC,CAAC,GACZ;AAC9B,QAAM;AAAA,IACJ,gBAAgB;AAAA,IAChB,eAAe;AAAA,IACf,WAAW;AAAA,MACT,cAAc;AAAA;AAAA,MACd,cAAc;AAAA;AAAA,IAChB;AAAA,IACA;AAAA,EACF,IAAI;AAEJ,QAAM,CAAC,aAAa,cAAc,IAAIC,UAA8C,CAAC,CAAC;AACtF,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAS,KAAK;AAChD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAErD,QAAM,iBAAiB,YAAY,YAAY,aAAa;AAC5D,QAAM,EAAE,eAAe,IAAI,cAAc;AACzC,QAAM,qBAAqBC,QAA+B,IAAI;AAG9D,QAAM,8BAA8B;AAAA,IAClC,MAAM;AAAA;AAAA,IAEN,CAAC,KAAK,UAAU,mBAAmB,CAAC;AAAA,EACtC;AAGA,EAAAC,WAAU,MAAM;AAEd,QAAI,mBAAmB,SAAS;AAC9B,yBAAmB,QAAQ,MAAM;AAAA,IACnC;AAGA,QAAI,CAAC,eAAe,KAAK,GAAG;AAC1B,qBAAe,CAAC,CAAC;AACjB,mBAAa,KAAK;AAClB,eAAS,IAAI;AACb;AAAA,IACF;AAGA,QAAI,CAAC,QAAQ;AACX,eAAS,IAAI,MAAM,mCAAmC,CAAC;AACvD;AAAA,IACF;AAEA,iBAAa,IAAI;AACjB,aAAS,IAAI;AAEb,UAAM,mBAAmB,YAAY;AACnC,UAAI;AACF,YAAI;AAEJ,YAAI,cAAc;AAEhB,wBAAc,MAAM;AAAA,YAClB;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY;AACV,qBAAO,uBAAuB,gBAAgB,QAAQ,2BAA2B;AAAA,YACnF;AAAA,YACA,EAAE,KAAK,SAAS,cAAc,SAAS,KAAK;AAAA,UAC9C;AAAA,QACF,OAAO;AAEL,wBAAc,MAAM,uBAAuB,gBAAgB,QAAQ,2BAA2B;AAAA,QAChG;AAEA,uBAAe,WAAW;AAC1B,qBAAa,KAAK;AAAA,MACpB,SAAS,KAAK;AAEZ,YAAI,eAAe,SAAS,IAAI,SAAS,cAAc;AACrD;AAAA,QACF;AAEA,cAAMC,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,0CAA0C;AAC/F,iBAASA,MAAK;AACd,uBAAe,CAAC,CAAC;AACjB,qBAAa,KAAK;AAAA,MACpB;AAAA,IACF;AAEA,qBAAiB;AAGjB,WAAO,MAAM;AACX,UAAI,mBAAmB,SAAS;AAC9B,2BAAmB,QAAQ,MAAM;AAAA,MACnC;AAAA,IACF;AAAA,EAEF,GAAG,CAAC,gBAAgB,QAAQ,cAAc,SAAS,YAAY,CAAC;AAGhE,QAAM,gBAAgBC;AAAA,IACpB,OAAO,YAAmD;AACxD,UAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,eAAO;AAAA,MACT;AAEA,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,UAAI;AACF,YAAI;AAEJ,YAAI,cAAc;AAEhB,yBAAe,MAAM;AAAA,YACnB;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY;AACV,qBAAO,kBAAkB,SAAS,MAAM;AAAA,YAC1C;AAAA,YACA,EAAE,KAAK,SAAS,cAAc,SAAS,KAAK;AAAA,UAC9C;AAAA,QACF,OAAO;AAEL,yBAAe,MAAM,kBAAkB,SAAS,MAAM;AAAA,QACxD;AAEA,cAAM,gBAAgB,6BAA6B,YAAY;AAC/D,qBAAa,KAAK;AAClB,eAAO;AAAA,MACT,SAAS,KAAK;AACZ,cAAMD,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,+BAA+B;AACpF,iBAASA,MAAK;AACd,qBAAa,KAAK;AAClB,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,IAEA,CAAC,QAAQ,cAAc,SAAS,YAAY;AAAA,EAC9C;AAGA,QAAM,wBAAwBC;AAAA,IAC5B,OAAO,YAAmD;AACxD,UAAI,CAAC,WAAW,CAAC,QAAQ;AACvB,eAAO;AAAA,MACT;AAEA,UAAI;AACF,YAAI,cAAc;AAEhB,iBAAO,MAAM;AAAA,YACX;AAAA,YACA;AAAA,YACA;AAAA,YACA,YAAY;AACV,oBAAM,SAAS,MAAM,oBAAoB,SAAS,MAAM;AACxD,kBAAI,CAAC,QAAQ;AACX,sBAAM,IAAI,MAAM,yBAAyB;AAAA,cAC3C;AACA,qBAAO;AAAA,YACT;AAAA,YACA,EAAE,KAAK,SAAS,cAAc,SAAS,KAAK;AAAA,UAC9C;AAAA,QACF,OAAO;AACL,iBAAO,MAAM,oBAAoB,SAAS,MAAM;AAAA,QAClD;AAAA,MACF,SAAS,KAAK;AACZ,cAAMD,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,mCAAmC;AACxF,iBAASA,MAAK;AACd,eAAO;AAAA,MACT;AAAA,IACF;AAAA;AAAA,IAEA,CAAC,QAAQ,cAAc,SAAS,YAAY;AAAA,EAC9C;AAGA,QAAM,mBAAmBC,aAAY,MAAM;AACzC,mBAAe,CAAC,CAAC;AACjB,aAAS,IAAI;AAAA,EACf,GAAG,CAAC,CAAC;AAEL,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA,qBAAqB;AAAA,IACrB;AAAA,EACF;AACF;;;ACjQO,IAAM,mBAAmC;AAAA;AAAA,EAE9C,cAAc,IAAI,OAAO;AAAA;AAAA,EACzB,aAAa,IAAI,OAAO;AAAA;AAAA,EACxB,aAAa,KAAK,OAAO;AAAA;AAAA,EACzB,cAAc,IAAI,OAAO;AAAA;AAAA,EACzB,iBAAiB,IAAI,OAAO;AAAA;AAAA;AAAA,EAG5B,mBAAmB,KAAK,OAAO;AAAA;AAAA,EAC/B,sBAAsB,KAAK,OAAO;AAAA;AAAA,EAClC,2EAA2E,KAAK,OAAO;AAAA;AAAA,EACvF,4BAA4B,KAAK,OAAO;AAAA;AAAA,EACxC,qEAAqE,KAAK,OAAO;AAAA;AAAA;AAAA,EAGjF,mBAAmB,MAAM,OAAO;AAAA;AAAA,EAChC,gCAAgC,MAAM,OAAO;AAAA;AAAA;AAAA,EAG7C,cAAc,IAAI,OAAO;AAAA;AAAA,EACzB,YAAY,KAAK,OAAO;AAAA;AAAA,EACxB,oBAAoB,KAAK,OAAO;AAAA;AAClC;AAKO,IAAM,0BAA0B,KAAK,OAAO;AAM5C,IAAM,mBAA2C;AAAA,EACtD,QAAQ;AAAA,EACR,QAAQ;AAAA;AAAA,EACR,QAAQ;AAAA,EACR,QAAQ;AACV;AAKO,IAAM,iBAAgC;AAAA,EAC3C,YAAY;AAAA,EACZ,gBAAgB;AAAA,EAChB,sBAAsB;AACxB;AAKO,SAAS,iBAAiB,UAA0B;AACzD,SAAO,eAAe,eAAe,QAAQ,KAAK,eAAe;AACnE;AAOO,SAAS,cAAc,UAA6C;AACzE,SAAO,WAAW,iBAAiB;AACrC;AAKO,SAAS,iBAAiB,MAAkD;AACjF,QAAM,QAAQ,iBAAiB,KAAK,IAAI;AAExC,MAAI,KAAK,OAAO,OAAO;AACrB,UAAM,UAAU,KAAK,MAAM,SAAS,OAAO,KAAK;AAChD,UAAM,SAAS,KAAK,MAAM,KAAK,QAAQ,OAAO,KAAK;AACnD,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,cAAc,MAAM,sBAAsB,OAAO,WAAW,KAAK,IAAI;AAAA,IAC9E;AAAA,EACF;AAEA,SAAO,EAAE,SAAS,KAAK;AACzB;;;AC1EA,IAAMC,OAAM,aAAa,gBAAgB;AAOlC,SAAS,iBAAiB,SAA+B,UAA0B;AACxF,QAAM,EAAE,OAAO,QAAQ,WAAW,OAAO,WAAW,IAAI;AAGxD,MAAI,CAAC,SAAS,CAAC,QAAQ;AACrB,UAAM,IAAI,MAAM,6DAA6D;AAAA,EAC/E;AAGA,QAAM,WAAW,QAAQ,QAAQ,SAAS,MAAM;AAEhD,MAAI,UAAU;AAEZ,QAAI,YAAY;AACd,aAAO,GAAG,QAAQ,IAAI,UAAU,IAAI,QAAQ;AAAA,IAC9C;AACA,WAAO,GAAG,QAAQ,WAAW,QAAQ;AAAA,EACvC;AAGA,MAAI,YAAY;AACd,WAAO,GAAG,QAAQ,IAAI,UAAU,IAAI,QAAQ;AAAA,EAC9C;AAGA,QAAM,aAAa,cAAc;AACjC,SAAO,GAAG,QAAQ,IAAI,UAAU,IAAI,QAAQ;AAC9C;AAMO,SAAS,uBAAuB,cAA8B;AACnE,QAAM,YAAY,KAAK,IAAI;AAC3B,QAAM,OAAO,OAAO,WAAW;AAC/B,QAAM,YAAY,aAAa,MAAM,GAAG,EAAE,IAAI,KAAK;AACnD,MAAI,WAAW,aAAa,QAAQ,aAAa,EAAE;AAInD,aAAW,SACR,KAAK,EACL,QAAQ,QAAQ,GAAG,EACnB,QAAQ,iBAAiB,EAAE,EAC3B,QAAQ,SAAS,EAAE,EACnB,QAAQ,cAAc,EAAE,EACxB,UAAU,GAAG,GAAG;AAGnB,MAAI,CAAC,aAAa,cAAc,cAAc;AAC5C,WAAO,GAAG,SAAS,IAAI,IAAI,IAAI,QAAQ;AAAA,EACzC;AAEA,SAAO,GAAG,SAAS,IAAI,IAAI,IAAI,QAAQ,IAAI,SAAS;AACtD;AAKA,eAAsB,oBACpB,MACA,SACA,YAC8B;AAC9B,QAAM,WAAgC;AAAA,IACpC,UAAU,KAAK;AAAA,IACf,MAAM,KAAK;AAAA,IACX,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;AAAA,IAC5C,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,OAAO;AAAA,IAC/C,SAAS,QAAQ,WAAW;AAAA,IAC5B;AAAA,IACA,aAAY,oBAAI,KAAK,GAAE,YAAY;AAAA,IACnC,MAAM,QAAQ,QAAQ,CAAC;AAAA,IACvB,UAAU,QAAQ,YAAY;AAAA,IAC9B,gBAAgB,QAAQ,YAAY,CAAC;AAAA,EACvC;AAGA,MAAI,KAAK,KAAK,WAAW,QAAQ,GAAG;AAClC,QAAI;AACF,YAAM,aAAa,MAAM,mBAAmB,IAAI;AAChD,eAAS,QAAQ,WAAW;AAC5B,eAAS,SAAS,WAAW;AAAA,IAC/B,SAAS,OAAO;AAAA,IAIhB;AAAA,EACF;AAGA,MAAI;AACF,aAAS,OAAO,MAAM,iBAAiB,IAAI;AAAA,EAC7C,SAAS,OAAO;AAAA,EAIhB;AAEA,SAAO;AACT;AAKA,eAAe,mBAAmB,MAAwD;AACxF,SAAO,IAAI,QAAQ,CAAC,SAAS,WAAW;AACtC,UAAM,MAAM,IAAI,MAAM;AACtB,UAAM,MAAM,IAAI,gBAAgB,IAAI;AAEpC,QAAI,SAAS,MAAM;AACjB,UAAI,gBAAgB,GAAG;AACvB,cAAQ,EAAE,OAAO,IAAI,OAAO,QAAQ,IAAI,OAAO,CAAC;AAAA,IAClD;AAEA,QAAI,UAAU,MAAM;AAClB,UAAI,gBAAgB,GAAG;AACvB,aAAO,IAAI,MAAM,sBAAsB,CAAC;AAAA,IAC1C;AAEA,QAAI,MAAM;AAAA,EACZ,CAAC;AACH;AAKA,eAAe,iBAAiB,MAA6B;AAC3D,QAAM,SAAS,MAAM,KAAK,YAAY;AACtC,QAAM,aAAa,MAAM,OAAO,OAAO,OAAO,WAAW,MAAM;AAC/D,QAAM,YAAY,MAAM,KAAK,IAAI,WAAW,UAAU,CAAC;AACvD,QAAM,UAAU,UAAU,IAAI,OAAK,EAAE,SAAS,EAAE,EAAE,SAAS,GAAG,GAAG,CAAC,EAAE,KAAK,EAAE;AAC3E,SAAO,UAAU,OAAO;AAC1B;AAYA,eAAe,mBACb,UACA,YACA,YACkB;AAClB,MAAI;AAEF,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,QACpC,KAAK,UAAU,EACf,KAAK,YAAY;AAAA,MAChB,OAAO;AAAA,IACT,CAAC;AAGH,QAAI,CAAC,OAAO;AACV,aAAO;AAAA,IACT;AAKA,UAAM,kBAAkB,GAAG,UAAU;AACrC,UAAM,kBAAkB,IAAI,KAAK,CAAC,EAAE,GAAG,EAAE,MAAM,aAAa,CAAC;AAC7D,UAAM,kBAAkB,IAAI,KAAK,CAAC,eAAe,GAAG,SAAS,EAAE,MAAM,aAAa,CAAC;AAEnF,UAAM,EAAE,OAAO,YAAY,IAAI,MAAM,SAAS,QAC3C,KAAK,UAAU,EACf,OAAO,iBAAiB,iBAAiB;AAAA,MACxC,cAAc;AAAA,MACd,QAAQ;AAAA;AAAA,MACR,aAAa;AAAA,IACf,CAAC;AAEH,QAAI,aAAa;AAGf,MAAAA,KAAI,MAAM,oEAAoE,YAAY,OAAO,EAAE;AACnG,aAAO;AAAA,IACT;AAGA,WAAO;AAAA,EACT,SAAS,OAAO;AAGd,IAAAA,KAAI,MAAM,0DAA0D,iBAAiB,QAAQ,MAAM,UAAU,eAAe,EAAE;AAC9H,WAAO;AAAA,EACT;AACF;AAKA,eAAsB,WACpB,UACA,MACA,SAC8B;AAC9B,MAAI;AAEF,UAAM,iBAAiB,iBAAiB,IAAI;AAC5C,QAAI,CAAC,eAAe,SAAS;AAC3B,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,eAAe;AAAA,MACxB;AAAA,IACF;AAGA,UAAM,iBAAiB,uBAAuB,KAAK,IAAI;AACvD,UAAM,WAAW,iBAAiB,SAAS,cAAc;AAGzD,UAAM,aAAa,SAAS,UAAU,GAAG,SAAS,YAAY,GAAG,CAAC;AAGlE,UAAM,WAAW,MAAM,oBAAoB,MAAM,SAAS,cAAc;AAGxE,UAAM,aAAa,cAAc,QAAQ,YAAY,KAAK;AAI1D,UAAM,mBAAmB,UAAU,YAAY,UAAU;AAIzD,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,QACpC,KAAK,UAAU,EACf,OAAO,UAAU,MAAM;AAAA,MACtB,cAAc;AAAA,MACd,QAAQ;AAAA,MACR,aAAa,KAAK;AAAA,IACpB,CAAC;AAEH,QAAI,OAAO;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,kBAAkB,MAAM,OAAO;AAAA,MACxC;AAAA,IACF;AAGA,QAAI;AACJ,QAAI,QAAQ,UAAU;AACpB,YAAM,EAAE,MAAM,QAAQ,IAAI,SAAS,QAChC,KAAK,UAAU,EACf,aAAa,QAAQ;AACxB,kBAAY,QAAQ;AAAA,IACtB;AAEA,WAAO;AAAA,MACL,SAAS;AAAA,MACT,MAAM;AAAA,MACN;AAAA,MACA;AAAA,IACF;AAAA,EAEF,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IACnF;AAAA,EACF;AACF;AASO,SAAS,aACd,UACA,MACA,WAAoB,MACZ;AACR,MAAI,CAAC,UAAU;AACb,UAAM,IAAI,MAAM,sDAAsD;AAAA,EACxE;AAEA,MAAI,CAAC,QAAQ,OAAO,SAAS,UAAU;AACrC,UAAM,IAAI,MAAM,2DAA2D;AAAA,EAC7E;AAGA,MAAI,gBAAgB,KAAK,IAAI,GAAG;AAC9B,WAAO;AAAA,EACT;AAGA,MAAI,iBAAiB,KAAK,KAAK,EAAE,QAAQ,QAAQ,EAAE;AAEnD,MAAI,CAAC,gBAAgB;AACnB,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAEA,QAAM,EAAE,oBAAoB,YAAY,IAAI,kBAAkB,cAAc;AAE5E,QAAM,aAAa,sBAAsB,cAAc,QAAQ;AAE/D,QAAM,EAAE,KAAK,IAAI,SAAS,QACvB,KAAK,UAAU,EACf,aAAa,WAAW;AAE3B,SAAO,KAAK;AACd;AAEA,SAAS,kBAAkB,yBAA6F;AACtH,QAAM,sBAAsB;AAC5B,QAAM,eAAe;AACrB,QAAM,qBAAqB,oBAAI,IAAI,CAAC,SAAS,cAAc,CAAC;AAE5D,QAAM,cAAc,wBAAwB,KAAK;AAEjD,MAAI,CAAC,aAAa;AAChB,UAAM,IAAI,MAAM,kDAAkD;AAAA,EACpE;AAGA,QAAM,mBAAmB,YAAY,QAAQ,IAAI;AACjD,MAAI,mBAAmB,GAAG;AACxB,UAAM,kBAAkB,YAAY,MAAM,GAAG,gBAAgB,EAAE,KAAK;AACpE,UAAM,gBAAgB,YAAY,MAAM,mBAAmB,CAAC,EAAE,QAAQ,QAAQ,EAAE;AAEhF,QAAI,mBAAmB,iBAAiB,oBAAoB,KAAK,eAAe,GAAG;AACjF,aAAO;AAAA,QACL,oBAAoB;AAAA,QACpB,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAGA,QAAM,kBAAkB,YAAY,QAAQ,GAAG;AAC/C,MAAI,kBAAkB,GAAG;AACvB,UAAM,kBAAkB,YAAY,MAAM,GAAG,eAAe,EAAE,KAAK;AACnE,UAAM,gBAAgB,YAAY,MAAM,kBAAkB,CAAC,EAAE,QAAQ,QAAQ,EAAE;AAE/E,QACE,mBACA,iBACA,oBAAoB,KAAK,eAAe,KACxC,CAAC,aAAa,KAAK,eAAe,MACjC,mBAAmB,IAAI,eAAe,KAAK,CAAC,gBAAgB,SAAS,GAAG,MACzE,CAAC,QAAQ,KAAK,eAAe,GAC7B;AACA,aAAO;AAAA,QACL,oBAAoB;AAAA,QACpB,aAAa;AAAA,MACf;AAAA,IACF;AAAA,EACF;AAEA,SAAO;AAAA,IACL,oBAAoB;AAAA,IACpB,aAAa;AAAA,EACf;AACF;AAUA,eAAsB,aACpB,UACA,MACA,SACoD;AACpD,MAAI;AAEF,UAAM,aAAa,cAAc,KAAK;AAEtC,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,QACpC,KAAK,UAAU,EACf,gBAAgB,MAAM,QAAQ,aAAa,IAAI;AAElD,QAAI,OAAO;AACT,MAAAA,KAAI,MAAM,gCAAgC,KAAK;AAC/C,aAAO;AAAA,IACT;AAEA,WAAO;AAAA,MACL,KAAK,KAAK;AAAA,MACV,WAAW,IAAI,KAAK,KAAK,IAAI,KAAK,QAAQ,aAAa,QAAQ,GAAI,EAAE,YAAY;AAAA,IACnF;AAAA,EACF,SAAS,OAAO;AACd,IAAAA,KAAI,MAAM,gCAAgC,KAAK;AAC/C,WAAO;AAAA,EACT;AACF;AAGA,IAAM,iBAAiB,oBAAI,IAAgD;AAC3E,IAAM,iBAAiB;AACvB,IAAM,iBAAiB,OAAO;AAE9B,SAAS,YAAY,QAAgB,UAAkB,UAA2B;AAChF,SAAO,YAAY,MAAM,IAAI,WAAW,WAAW,SAAS;AAC9D;AAEA,SAAS,kBAAwB;AAC/B,QAAM,MAAM,KAAK,IAAI;AAGrB,aAAW,CAAC,KAAK,KAAK,KAAK,eAAe,QAAQ,GAAG;AACnD,QAAI,MAAM,YAAY,KAAK;AACzB,qBAAe,OAAO,GAAG;AAAA,IAC3B;AAAA,EACF;AAGA,MAAI,eAAe,OAAO,gBAAgB;AACxC,UAAM,UAAU,MAAM,KAAK,eAAe,QAAQ,CAAC;AACnD,YAAQ,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,SAAS;AAEtD,UAAM,WAAW,KAAK,MAAM,iBAAiB,GAAG;AAChD,aAAS,IAAI,GAAG,IAAI,YAAY,IAAI,QAAQ,QAAQ,KAAK;AACvD,qBAAe,OAAO,QAAQ,CAAC,EAAE,CAAC,CAAC;AAAA,IACrC;AAAA,EACF;AACF;AAUA,eAAsB,sBACpB,UACA,gBACA,SAC8B;AAC9B,QAAM,SAAS,oBAAI,IAAoB;AAEvC,MAAI,eAAe,WAAW,GAAG;AAC/B,WAAO;AAAA,EACT;AAEA,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,OAAO,QAAQ,aAAa,QAAQ;AAG1C,QAAM,cAAwD,CAAC;AAC/D,QAAM,eAAyD,CAAC;AAChE,QAAM,gBAA8E,CAAC;AAErF,aAAW,WAAW,gBAAgB;AACpC,UAAM,WAAW,YAAY,QAAQ,IAAI,QAAQ,WAAW,QAAQ,SAAS;AAC7E,UAAM,SAAS,eAAe,IAAI,QAAQ;AAG1C,QAAI,UAAU,OAAO,YAAY,KAAK;AACpC,aAAO,IAAI,QAAQ,IAAI,OAAO,GAAG;AACjC;AAAA,IACF;AAGA,QAAI,QAAQ,WAAW;AACrB,kBAAY,KAAK,EAAE,IAAI,QAAQ,IAAI,WAAW,QAAQ,UAAU,CAAC;AAAA,IACnE,OAAO;AACL,mBAAa,KAAK,EAAE,IAAI,QAAQ,IAAI,WAAW,QAAQ,UAAU,CAAC;AAAA,IACpE;AACA,kBAAc,KAAK,OAAO;AAAA,EAC5B;AAGA,aAAW,QAAQ,aAAa;AAC9B,QAAI;AACF,YAAM,MAAM,aAAa,UAAU,KAAK,WAAW,IAAI;AACvD,UAAI,KAAK;AACP,eAAO,IAAI,KAAK,IAAI,GAAG;AAEvB,cAAM,WAAW,YAAY,KAAK,IAAI,KAAK,WAAW,IAAI;AAC1D,uBAAe,IAAI,UAAU;AAAA,UAC3B;AAAA,UACA,WAAW,MAAM;AAAA,QACnB,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,MAAAA,KAAI,MAAM,0CAA0C,KAAK,EAAE,KAAK,GAAG;AAAA,IACrE;AAAA,EACF;AAGA,MAAI,aAAa,SAAS,GAAG;AAC3B,UAAM,oBAAoB,aAAa,IAAI,OAAO,SAAS;AACzD,UAAI;AACF,cAAM,kBAAkB,MAAM,aAAa,UAAU,KAAK,WAAW;AAAA,UACnE,SAAS,QAAQ,WAAW;AAAA,UAC5B,OAAO,QAAQ;AAAA,UACf,WAAW,QAAQ,aAAa;AAAA,QAClC,CAAC;AACD,cAAM,MAAM,iBAAiB,OAAO;AAGpC,YAAI,KAAK;AACP,gBAAM,WAAW,YAAY,KAAK,IAAI,KAAK,WAAW,KAAK;AAC3D,yBAAe,IAAI,UAAU;AAAA,YAC3B;AAAA,YACA,WAAW,MAAM;AAAA,UACnB,CAAC;AAAA,QACH;AAEA,eAAO,EAAE,IAAI,KAAK,IAAI,IAAI;AAAA,MAC5B,SAAS,KAAK;AACZ,QAAAA,KAAI,MAAM,0CAA0C,KAAK,EAAE,KAAK,GAAG;AACnE,eAAO,EAAE,IAAI,KAAK,IAAI,KAAK,KAAK;AAAA,MAClC;AAAA,IACF,CAAC;AAED,UAAM,mBAAmB,MAAM,QAAQ,IAAI,iBAAiB;AAE5D,eAAW,UAAU,kBAAkB;AACrC,UAAI,OAAO,KAAK;AACd,eAAO,IAAI,OAAO,IAAI,OAAO,GAAG;AAAA,MAClC;AAAA,IACF;AAAA,EACF;AAGA,MAAI,cAAc,SAAS,GAAG;AAC5B,oBAAgB;AAAA,EAClB;AAEA,SAAO;AACT;AASA,eAAsB,WACpB,UACA,MACA,WAAoB,OAC2B;AAC/C,MAAI;AACF,UAAM,aAAa,cAAc,QAAQ;AAEzC,UAAM,EAAE,MAAM,IAAI,MAAM,SAAS,QAC9B,KAAK,UAAU,EACf,OAAO,CAAC,IAAI,CAAC;AAEhB,QAAI,OAAO;AACT,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,kBAAkB,MAAM,OAAO;AAAA,MACxC;AAAA,IACF;AAEA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,kBAAkB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IACnF;AAAA,EACF;AACF;AAQA,eAAsB,UACpB,UACA,SAC4B;AAC5B,MAAI;AAEF,UAAM,aAAa,cAAc,QAAQ,YAAY,KAAK;AAG1D,QAAI,CAAC,QAAQ,SAAS,CAAC,QAAQ,QAAQ;AACrC,YAAM,IAAI,MAAM,sDAAsD;AAAA,IACxE;AAGA,UAAM,WAAW,QAAQ,QAAQ,QAAQ,QAAQ,SAAS,QAAQ,MAAM;AACxE,UAAM,aAAa,GAAG,QAAQ;AAC9B,UAAM,aAAa,QAAQ,aAAa,GAAG,UAAU,GAAG,QAAQ,UAAU,KAAK;AAE/E,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,QACpC,KAAK,UAAU,EACf,KAAK,YAAY;AAAA,MAChB,OAAO,QAAQ,SAAS;AAAA,MACxB,QAAQ,QAAQ,UAAU;AAAA,MAC1B,QAAQ,EAAE,QAAQ,cAAc,OAAO,OAAO;AAAA,IAChD,CAAC;AAEH,QAAI,OAAO;AACT,MAAAA,KAAI,MAAM,yBAAyB,KAAK;AACxC,aAAO,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,SAAS,MAAM;AAAA,IACpD;AAEA,UAAM,SAA4B,QAAQ,CAAC,GAAG,IAAI,WAAS;AAAA,MACzD,MAAM,KAAK;AAAA,MACX,MAAM,GAAG,UAAU,GAAG,KAAK,IAAI;AAAA,MAC/B,MAAM,KAAK,UAAU,QAAQ;AAAA,MAC7B,UAAU,KAAK,UAAU,YAAY;AAAA,MACrC,cAAc,KAAK,cAAc,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,MAC3E,UAAU;AAAA,QACR,UAAU,KAAK,UAAU,YAAY;AAAA,QACrC,MAAM,KAAK,UAAU,QAAQ;AAAA,QAC7B,GAAI,QAAQ,SAAS,EAAE,OAAO,QAAQ,MAAM;AAAA,QAC5C,GAAI,QAAQ,UAAU,EAAE,QAAQ,QAAQ,OAAO;AAAA,QAC/C,SAAS,QAAQ;AAAA,QACjB,YAAY;AAAA,QACZ,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,QACtD,UAAU,QAAQ,YAAY;AAAA,MAChC;AAAA,IACF,EAAE;AAEF,WAAO;AAAA,MACL;AAAA,MACA,YAAY,MAAM;AAAA,MAClB,SAAS,MAAM,WAAW,QAAQ,SAAS;AAAA,IAC7C;AAAA,EACF,SAAS,OAAO;AACd,IAAAA,KAAI,MAAM,yBAAyB,KAAK;AACxC,WAAO,EAAE,OAAO,CAAC,GAAG,YAAY,GAAG,SAAS,MAAM;AAAA,EACpD;AACF;AASA,eAAsB,aACpB,UACA,MACA,WAAoB,OACoE;AACxF,MAAI;AACF,UAAM,aAAa,cAAc,QAAQ;AAEzC,UAAM,EAAE,MAAM,MAAM,IAAI,MAAM,SAAS,QACpC,KAAK,UAAU,EACf,SAAS,IAAI;AAEhB,QAAI,OAAO;AACT,MAAAA,KAAI,MAAM,4BAA4B,KAAK;AAC3C,aAAO;AAAA,IACT;AAEA,QAAI,CAAC,MAAM;AACT,aAAO;AAAA,IACT;AAGA,UAAM,WAAW,KAAK,MAAM,GAAG,EAAE,IAAI,KAAK;AAG1C,UAAM,EAAE,MAAM,SAAS,IAAI,MAAM,SAAS,QACvC,KAAK,UAAU,EACf,KAAK,KAAK,MAAM,GAAG,EAAE,MAAM,GAAG,EAAE,EAAE,KAAK,GAAG,GAAG;AAAA,MAC5C,QAAQ;AAAA,IACV,CAAC;AAEH,UAAM,WAAW,WAAW,CAAC,GAAG,YAAY,CAAC;AAE7C,WAAO;AAAA,MACL,MAAM;AAAA,MACN,UAAU;AAAA,QACR,MAAM;AAAA,QACN,MAAM,SAAS,QAAQ,KAAK;AAAA,QAC5B,MAAM,SAAS,YAAY;AAAA,MAC7B;AAAA,IACF;AAAA,EACF,SAAS,OAAO;AACd,IAAAA,KAAI,MAAM,4BAA4B,KAAK;AAC3C,WAAO;AAAA,EACT;AACF;AAQA,eAAsB,YACpB,UACA,MACA,SAC+C;AAC/C,MAAI;AACF,UAAM,aAAa,cAAc,QAAQ,YAAY,KAAK;AAG1D,QAAI;AACJ,QAAI,QAAQ,OAAO;AACjB,qBAAe,KAAK,QAAQ,GAAG,QAAQ,KAAK,KAAK,YAAY,QAAQ,KAAK,GAAG;AAAA,IAC/E,WAAW,QAAQ,QAAQ;AACzB,qBAAe,KAAK,QAAQ,SAAS,QAAQ,MAAM,KAAK,kBAAkB,QAAQ,MAAM,GAAG;AAAA,IAC7F,OAAO;AACL,YAAM,IAAI,MAAM,wDAAwD;AAAA,IAC1E;AAGA,UAAM,EAAE,OAAO,UAAU,IAAI,MAAM,SAAS,QACzC,KAAK,UAAU,EACf,KAAK,MAAM,YAAY;AAE1B,QAAI,WAAW;AACb,aAAO;AAAA,QACL,SAAS;AAAA,QACT,OAAO,mBAAmB,UAAU,OAAO;AAAA,MAC7C;AAAA,IACF;AAGA,UAAM,eAAe,MAAM,WAAW,UAAU,MAAM,QAAQ,YAAY,KAAK;AAC/E,QAAI,CAAC,aAAa,SAAS;AACzB,aAAO;AAAA,IACT;AAEA,WAAO,EAAE,SAAS,KAAK;AAAA,EACzB,SAAS,OAAO;AACd,WAAO;AAAA,MACL,SAAS;AAAA,MACT,OAAO,mBAAmB,iBAAiB,QAAQ,MAAM,UAAU,eAAe;AAAA,IACpF;AAAA,EACF;AACF;;;AC5tBA,SAAS,YAAAC,WAAU,aAAAC,YAAW,eAAAC,oBAAmB;AAQjD,IAAM,kBAAkB,oBAAI,IAA2D;AA+ChF,SAAS,qBACd,YACA,WACA,iBACA,UACA,SAC4B;AAC5B,QAAM;AAAA,IACJ,WAAW,KAAK,KAAK;AAAA;AAAA,IACrB,cAAc;AAAA,IACd;AAAA,EACF,IAAI;AAEJ,QAAM,CAAC,SAAS,UAAU,IAAIC,UAAwB,IAAI;AAC1D,QAAM,CAAC,eAAe,gBAAgB,IAAIA,UAA+B,IAAI;AAC7E,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAA0B,CAAC,CAAC;AACxE,QAAM,CAAC,UAAU,WAAW,IAAIA,UAA8B,oBAAI,IAAI,CAAC;AACvE,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAiB,CAAC;AACpD,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAkB,KAAK;AACzD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAErD,QAAM,aAAaC,aAAY,YAA2B;AACxD,QAAI,CAAC,cAAc,CAAC,aAAa,CAAC,UAAU;AAC1C,iBAAW,IAAI;AACf,uBAAiB,IAAI;AACrB,wBAAkB,CAAC,CAAC;AACpB,kBAAY,oBAAI,IAAI,CAAC;AACrB,mBAAa,CAAC;AACd,mBAAa,KAAK;AAClB;AAAA,IACF;AAGA,QAAI,iBAAiB;AACnB,YAAM,YAAY;AAClB,UAAI,CAAC,UAAU,KAAK,eAAe,GAAG;AACpC,eAAO,KAAK,wBAAwB,oDAAoD,EAAE,gBAAgB,CAAC;AAAA,MAC7G;AAAA,IACF;AAIA,UAAM,WAAW,eAAe,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO,IAAI,YAAY,KAAK;AACzJ,QAAI,aAAa;AACf,YAAM,SAAS,gBAAgB,IAAI,QAAQ;AAC3C,UAAI,UAAU,KAAK,IAAI,IAAI,OAAO,YAAY,OAAO,KAAK;AACxD,cAAM,aAAa,OAAO;AAC1B,mBAAW,WAAW,WAAW,IAAI;AACrC,yBAAiB,WAAW,iBAAiB,IAAI;AACjD,0BAAkB,WAAW,kBAAkB,CAAC,CAAC;AACjD,oBAAY,WAAW,YAAY,oBAAI,IAAI,CAAC;AAC5C,qBAAa,WAAW,aAAa,CAAC;AACtC,qBAAa,KAAK;AAClB,iBAAS,IAAI;AACb;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,IAAI;AACjB,eAAS,IAAI;AAEb,UAAI,QAAe,CAAC;AAIpB,UAAI,oBAAoB,QAAW;AACjC,eAAO,MAAM,wBAAwB,2FAA2F;AAAA,UAC9H;AAAA,UACA;AAAA,UACA;AAAA,QACF,CAAC;AAGD,YAAI,kBAAyB,CAAC;AAC9B,YAAI,iBAAwB,CAAC;AAG7B,YAAI,UAAU;AACZ,gBAAM,EAAE,MAAM,UAAU,OAAO,aAAa,IAAI,MAAO,SACpD,IAAI,wCAAwC;AAAA,YAC3C,cAAc;AAAA,YACd,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,mBAAmB;AAAA,UACrB,CAAC;AAEH,cAAI,CAAC,gBAAgB,UAAU;AAC7B,8BAAkB,SACf,OAAO,CAAC,SAAc,KAAK,cAAc,QAAQ,KAAK,MAAM,KAAK,aAAa,KAAK,aAAa,EAChG,IAAI,CAAC,UAAe;AAAA,cACnB,IAAI,KAAK;AAAA,cACT;AAAA,cACA;AAAA,cACA,WAAW,KAAK;AAAA,cAChB,eAAe,KAAK,iBAAiB,CAAC;AAAA,cACtC,iBAAiB;AAAA,cACjB,QAAQ,KAAK,eAAe,UAAU;AAAA,cACtC,WAAW;AAAA,cACX,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,cACtD,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,YACxD,EAAE;AAAA,UACN;AAAA,QACF,OAAO;AACL,gBAAM,EAAE,MAAM,UAAU,OAAO,aAAa,IAAI,MAAO,SACpD,IAAI,4BAA4B;AAAA,YAC/B,cAAc;AAAA,YACd,aAAa;AAAA,YACb,mBAAmB;AAAA,UACrB,CAAC;AAEH,cAAI,CAAC,gBAAgB,UAAU;AAC7B,kBAAM,MAAM,SAAS,IAAI,CAAC,SAAc,KAAK,EAAE;AAC/C,gBAAI,IAAI,SAAS,GAAG;AAClB,oBAAM,EAAE,MAAM,SAAS,IAAI,MAAM,SAC9B,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,MAAM,GAAG,EACZ,GAAG,aAAa,IAAI;AACvB,gCAAkB,YAAY,CAAC;AAAA,YACjC;AAAA,UACF;AAAA,QACF;AAQA,cAAM,WAAW,CAAC,GAAG,iBAAiB,GAAG,cAAc;AACvD,iBAAS,KAAK,CAAC,GAAG,MAAM;AACtB,gBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,gBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,iBAAO,QAAQ;AAAA,QACjB,CAAC;AAED,gBAAQ;AAER,eAAO,MAAM,wBAAwB,+CAA+C;AAAA,UAClF,iBAAiB,gBAAgB;AAAA,UACjC,gBAAgB,eAAe;AAAA,UAC/B,YAAY,MAAM;AAAA,QACpB,CAAC;AAAA,MACH,OAAO;AAIL,YAAI,UAAU;AAEZ,gBAAM,YAAY;AAAA,YAChB,cAAc;AAAA,YACd,aAAa;AAAA,YACb,YAAY;AAAA,YACZ,mBAAmB,mBAAmB;AAAA,UACxC;AAEA,gBAAM,EAAE,MAAM,OAAO,SAAS,IAAI,MAAO,SACtC,IAAI,wCAAwC,SAAS;AAExD,cAAI,UAAU;AACZ,mBAAO,MAAM,wBAAwB,sBAAsB;AAAA,cACzD,UAAU;AAAA,cACV;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA,cACA,OAAO,SAAS;AAAA,cAChB,WAAW,SAAS;AAAA,cACpB,cAAc,SAAS;AAAA,cACvB,WAAW,SAAS;AAAA,YACtB,CAAC;AACD,kBAAM,IAAI,MAAM,SAAS,WAAW,gCAAgC;AAAA,UACtE;AAKA,cAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,oBAAQ,CAAC;AAAA,UACX,OAAO;AAGL,oBAAQ,KACL,OAAO,CAAC,SAAc;AAErB,qBAAO,KAAK,cAAc,QAAQ,KAAK,MAAM,KAAK,aAAa,KAAK;AAAA,YACtE,CAAC,EACA,IAAI,CAAC,SAAc;AAElB,qBAAO;AAAA,gBACL,IAAI,KAAK;AAAA,gBACT;AAAA,gBACA;AAAA,gBACA,WAAW,KAAK;AAAA,gBAChB,eAAe,KAAK,iBAAiB,CAAC;AAAA,gBACtC,iBAAiB,mBAAmB;AAAA,gBACpC,QAAQ,KAAK,eAAe,UAAU;AAAA,gBACtC,WAAW;AAAA;AAAA,gBACX,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,gBACtD,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,cACxD;AAAA,YACF,CAAC;AAAA,UACL;AAAA,QACF,OAAO;AAEL,gBAAM,EAAE,MAAM,SAAS,OAAO,SAAS,IAAI,MAAO,SAC/C,IAAI,4BAA4B;AAAA,YAC/B,cAAc;AAAA,YACd,aAAa;AAAA,YACb,mBAAmB,mBAAmB;AAAA,UACxC,CAAC;AAEH,cAAI,UAAU;AACZ,mBAAO,MAAM,wBAAwB,sBAAsB;AAAA,cACzD,UAAU;AAAA,cACV;AAAA,cACA;AAAA,cACA;AAAA,cACA,OAAO,SAAS;AAAA,cAChB,WAAW,SAAS;AAAA,cACpB,cAAc,SAAS;AAAA,cACvB,WAAW,SAAS;AAAA,YACtB,CAAC;AACD,kBAAM,IAAI,MAAM,SAAS,WAAW,iCAAiC;AAAA,UACvE;AAEA,cAAI,CAAC,WAAW,QAAQ,WAAW,GAAG;AACpC,oBAAQ,CAAC;AAAA,UACX,OAAO;AAEL,kBAAM,MAAM,QAAQ,IAAI,CAAC,SAAc,KAAK,EAAE;AAC9C,kBAAM,EAAE,MAAM,UAAU,OAAO,WAAW,IAAI,MAAM,SACjD,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,MAAM,GAAG,EACZ,GAAG,aAAa,IAAI;AAEvB,gBAAI,YAAY;AACd,oBAAM,IAAI,MAAM,WAAW,WAAW,iCAAiC;AAAA,YACzE;AAEA,oBAAQ,YAAY,CAAC;AAAA,UACvB;AAAA,QACF;AAAA,MACF;AAGA,YAAM,cAAc,MAAM,OAAO,CAAC,MAAW,EAAE,cAAc,IAAI;AAEjE,UAAI,YAAY,WAAW,GAAG;AAC5B,mBAAW,IAAI;AACf,yBAAiB,IAAI;AACrB,0BAAkB,CAAC,CAAC;AACpB,oBAAY,oBAAI,IAAI,CAAC;AACrB,qBAAa,CAAC;AAGd,YAAI,aAAa;AACf,0BAAgB,IAAI,UAAU;AAAA,YAC5B,MAAM,EAAE,SAAS,MAAM,eAAe,MAAM,gBAAgB,CAAC,GAAG,UAAU,oBAAI,IAAI,GAAG,WAAW,EAAE;AAAA,YAClG,WAAW,KAAK,IAAI;AAAA,YACpB,KAAK;AAAA,UACP,CAAC;AAAA,QACH;AACA;AAAA,MACF;AAGA,YAAM,WAA4B,YAAY,IAAI,CAAC,OAAY;AAAA,QAC7D,IAAI,EAAE;AAAA,QACN,YAAY,EAAE;AAAA,QACd,WAAW,EAAE;AAAA,QACb,WAAW,EAAE;AAAA,QACb,eAAe,EAAE,iBAAiB,CAAC;AAAA,QACnC,iBAAiB,EAAE;AAAA,QACnB,QAAQ,EAAE;AAAA,QACV,WAAW,EAAE,aAAa;AAAA,QAC1B,YAAY,EAAE;AAAA,QACd,YAAY,EAAE;AAAA,MAChB,EAAE;AAEF,wBAAkB,QAAQ;AAC1B,mBAAa,SAAS,MAAM;AAE5B,UAAI,YAAY,SAAS,SAAS,GAAG;AAEnC,cAAM,YAAY,SAAS,CAAC;AAC5B,yBAAiB,SAAS;AAG1B,cAAM,MAAM,aAAa,UAAU,UAAU,WAAW,IAAI;AAC5D,mBAAW,GAAG;AAAA,MAChB,OAAO;AAEL,cAAM,SAAS,MAAM,sBAAsB,UAAU,UAAU;AAAA,UAC7D,SAAS;AAAA,UACT,OAAO;AAAA,UACP,WAAW;AAAA,QACb,CAAC;AACD,oBAAY,MAAM;AAClB,yBAAiB,IAAI;AACrB,mBAAW,IAAI;AAAA,MACjB;AAGA,UAAI,aAAa;AACf,wBAAgB,IAAI,UAAU;AAAA,UAC5B,MAAM;AAAA,YACJ,SAAS,WAAY,SAAS,SAAS,IAAI,aAAa,UAAU,SAAS,CAAC,EAAE,WAAW,IAAI,IAAI,OAAQ;AAAA,YACzG,eAAe,YAAY,SAAS,SAAS,IAAI,SAAS,CAAC,IAAI;AAAA,YAC/D,gBAAgB;AAAA,YAChB,UAAU,WAAW,oBAAI,IAAI,KAAK,MAAM;AACtC,oBAAM,SAAS,oBAAI,IAAoB;AACvC,yBAAW,WAAW,UAAU;AAC9B,sBAAM,MAAM,aAAa,UAAU,QAAQ,WAAW,IAAI;AAC1D,oBAAI,KAAK;AACP,yBAAO,IAAI,QAAQ,IAAI,GAAG;AAAA,gBAC5B;AAAA,cACF;AACA,qBAAO;AAAA,YACT,GAAG;AAAA,YACH,WAAW,SAAS;AAAA,UACtB;AAAA,UACA,WAAW,KAAK,IAAI;AAAA,UACpB,KAAK;AAAA,QACP,CAAC;AAAA,MACH;AAAA,IAEF,SAAS,KAAK;AACZ,aAAO,MAAM,wBAAwB,wBAAwB;AAAA,QAC3D;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,OAAO,eAAe,QAAQ,IAAI,UAAU;AAAA,QAC5C,cAAc,eAAe,QAAQ,IAAI,QAAQ,OAAO,GAAG;AAAA,MAC7D,CAAC;AACD,YAAMC,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,wBAAwB;AAC7E,eAASA,MAAK;AACd,iBAAW,IAAI;AACf,uBAAiB,IAAI;AACrB,wBAAkB,CAAC,CAAC;AACpB,kBAAY,oBAAI,IAAI,CAAC;AACrB,mBAAa,CAAC;AAAA,IAChB,UAAE;AACA,mBAAa,KAAK;AAAA,IACpB;AAAA,EACF,GAAG,CAAC,YAAY,WAAW,iBAAiB,UAAU,UAAU,UAAU,WAAW,CAAC;AAGtF,EAAAC,WAAU,MAAM;AACd,QAAI,cAAc,WAAW;AAC3B,iBAAW;AAAA,IACb,OAAO;AACL,iBAAW,IAAI;AACf,uBAAiB,IAAI;AACrB,wBAAkB,CAAC,CAAC;AACpB,kBAAY,oBAAI,IAAI,CAAC;AACrB,mBAAa,CAAC;AACd,mBAAa,KAAK;AAClB,eAAS,IAAI;AAAA,IACf;AAAA,EACF,GAAG,CAAC,YAAY,YAAY,WAAW,eAAe,CAAC;AAEvD,QAAM,UAAUF,aAAY,YAA2B;AACrD,QAAI,CAAC,cAAc,CAAC,UAAW;AAG/B,QAAI,aAAa;AACf,YAAM,WAAW,eAAe,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO,IAAI,YAAY,KAAK;AACzJ,sBAAgB,OAAO,QAAQ;AAAA,IACjC;AACA,UAAM,WAAW;AAAA,EACnB,GAAG,CAAC,YAAY,YAAY,WAAW,iBAAiB,UAAU,WAAW,CAAC;AAE9E,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,8BAAoC;AAClD,aAAW,CAAC,GAAG,KAAK,iBAAiB;AACnC,QAAI,IAAI,WAAW,cAAc,GAAG;AAClC,sBAAgB,OAAO,GAAG;AAAA,IAC5B;AAAA,EACF;AACF;AAKO,SAAS,iCAAmE;AACjF,QAAM,OAAO,MAAM,KAAK,gBAAgB,KAAK,CAAC,EAAE,OAAO,SAAO,IAAI,WAAW,cAAc,CAAC;AAC5F,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX;AAAA,EACF;AACF;;;ACldA,SAAS,YAAAG,WAAU,aAAAC,YAAW,eAAAC,cAAa,UAAAC,eAAc;;;ACjBzD,IAAMC,OAAM,aAAa,sBAAsB;AAExC,IAAM,2BAAN,MAA+D;AAAA,EACpE,YAAoB,UAA0B;AAA1B;AAAA,EAA2B;AAAA;AAAA;AAAA;AAAA,EAK/C,MAAc,WAAW,OAAgC;AACvD,QAAI;AACF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,KAAK,WAAW,EAChB,OAAO,MAAM,EACb,GAAG,MAAM,KAAK,EACd,OAAO;AAEV,UAAI,SAAS,CAAC,MAAM;AAClB,eAAO;AAAA,MACT;AAEA,aAAO,KAAK,QAAQ;AAAA,IACtB,QAAQ;AACN,aAAO;AAAA,IACT;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBA,MAAM,oBAAoB,SAA4B,MAAoC;AACxF,QAAI;AAGF,YAAM,eAAe,CAAC,QAAQ,mBAAmB,QAAQ;AACzD,UAAI,CAAC,gBAAgB,CAAC,QAAQ,iBAAiB;AAC7C,cAAM,IAAI,MAAM,+FAA+F;AAAA,MACjH;AACA,UAAI,CAAC,QAAQ,YAAY;AACvB,cAAM,IAAI,MAAM,wCAAwC;AAAA,MAC1D;AACA,UAAI,CAAC,QAAQ,WAAW;AACtB,cAAM,IAAI,MAAM,uCAAuC;AAAA,MACzD;AACA,UAAI,CAAC,QAAQ,QAAQ;AACnB,cAAM,IAAI,MAAM,kFAAkF;AAAA,MACpG;AAIA,UAAI,sBAA0C;AAC9C,UAAI,cAAc;AAChB,cAAM,EAAE,MAAM,EAAE,MAAM,SAAS,GAAG,OAAO,UAAU,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ;AACxF,YAAI,aAAa,CAAC,UAAU;AAC1B,gBAAM,IAAI,MAAM,wDAAwD;AAAA,QAC1E;AACA,8BAAsB,SAAS;AAC/B,QAAAA,KAAI,MAAM,2DAA2D,EAAE,QAAQ,oBAAoB,CAAC;AAAA,MACtG;AAKA,YAAM,eAAe,MAAM,WAAW,KAAK,UAAU,MAAM;AAAA,QACzD,SAAS;AAAA,QACT,OAAO,QAAQ,mBAAmB;AAAA,QAClC,QAAQ,wBAAwB,eAAe,SAAY,QAAQ;AAAA;AAAA,QACnE,UAAU,QAAQ,aAAa;AAAA,QAC/B,YAAY,QAAQ;AAAA;AAAA,MACtB,CAAC;AACD,UAAI,CAAC,aAAa,SAAS;AACzB,cAAM,IAAI,MAAM,0BAA0B,aAAa,KAAK,EAAE;AAAA,MAChE;AAEA,UAAI,CAAC,aAAa,MAAM;AACtB,cAAM,IAAI,MAAM,mCAAmC;AAAA,MACrD;AAEA,YAAM,WAAW,aAAa;AAG9B,YAAM,WAAW,MAAM,oBAAoB,MAAM;AAAA,QAC/C,SAAS;AAAA,QACT,OAAO,QAAQ,mBAAmB;AAAA,QAClC,QAAQ,wBAAwB,eAAe,SAAY,QAAQ;AAAA;AAAA,QACnE,UAAU,QAAQ,aAAa;AAAA,MACjC,GAAG,QAAQ;AAIX,UAAI,CAAC,gBAAgB,QAAQ,iBAAiB;AAC5C,cAAM,uBAAuB,KAAK,UAAU,QAAQ,eAAe;AAAA,MACrE;AAIA,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,IAAI,8BAA8B;AAAA,QACjC,cAAc,QAAQ;AAAA,QACtB,aAAa,QAAQ;AAAA,QACrB,aAAa;AAAA;AAAA,QACb,mBAAmB,QAAQ,mBAAmB;AAAA,QAC9C,UAAU,QAAQ;AAAA,QAClB,gBAAgB,QAAQ;AAAA,QACxB,YAAY,QAAQ,YAAY;AAAA;AAAA,QAChC,iBAAiB;AAAA,UACf,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,UAAU,KAAK;AAAA,UACf,UAAU,QAAQ;AAAA,UAClB,GAAG;AAAA,UACH,GAAG,QAAQ;AAAA,QACb;AAAA,QACA,aAAa,QAAQ,aAAa;AAAA,QAClC,WAAW,uBAAuB,QAAQ,UAAU;AAAA;AAAA,MACtD,CAAC;AAGH,UAAI,OAAO;AACT,cAAM,WAAW,KAAK,UAAU,UAAU,QAAQ,aAAa,KAAK;AACpE,cAAM,IAAI,MAAM,oCAAoC,MAAM,OAAO,EAAE;AAAA,MACrE;AAGA,UAAI,CAAC,QAAQ,SAAS,MAAM;AAG1B,YAAI,mBAAmB;AACvB,YAAI;AACF,gBAAM,EAAE,MAAM,EAAE,MAAM,SAAS,EAAE,IAAI,MAAM,KAAK,SAAS,KAAK,QAAQ;AACtE,cAAI,UAAU;AACZ,+BAAmB,MAAM,aAAa,SAAS,EAAE;AAAA,UACnD;AAAA,QACF,SAAS,sBAAsB;AAE7B,UAAAA,KAAI,KAAK,sCAAsC,oBAAoB;AAAA,QACrE;AAGA,cAAM,WAAW,KAAK,UAAU,UAAU,QAAQ,aAAa,KAAK;AAGpE,cAAM,UAAU,MAAM,KAAK,WAAW,QAAQ,MAAM,EAAE,MAAM,MAAM,SAAS;AAC3E,cAAM,qBAAqB,QAAQ,eAAe;AAElD,YAAI,kBAAkB;AAEpB,gBAAM,IAAI;AAAA,YACR,+FACkB,kBAAkB,YAAY,OAAO,kCACxB,kBAAkB,6CAA6C,OAAO;AAAA,UACvG;AAAA,QACF,OAAO;AACL,gBAAM,IAAI;AAAA,YACR,uEAAuE,kBAAkB,qBAAqB,kBAAkB,yBAAyB,kBAAkB,sEAC7G,OAAO;AAAA,UACvE;AAAA,QACF;AAAA,MACF;AAGA,YAAM,EAAE,MAAM,SAAS,OAAO,WAAW,IAAI,MAAM,KAAK,SACrD,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,MAAM,IAAI,EACb,OAAO;AAEV,UAAI,cAAc,CAAC,SAAS;AAE1B,cAAM,WAAW,KAAK,UAAU,UAAU,QAAQ,aAAa,KAAK;AACpE,cAAM,IAAI,MAAM,2CAA2C,YAAY,OAAO,EAAE;AAAA,MAClF;AAIA;AAAA,QACE,QAAQ;AAAA,QACR,QAAQ;AAAA,QACR,QAAQ,mBAAmB;AAAA,QAC3B,QAAQ;AAAA,MACV;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,kCAAkC,KAAK;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,iBAAiB,YAAoB,WAAmB,iBAAyD;AACrH,QAAI;AACF,UAAI,QAAQ,KAAK,SACd,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,cAAc,UAAU,EAC3B,GAAG,aAAa,SAAS;AAG5B,UAAI,oBAAoB,QAAQ,oBAAoB,QAAW;AAC7D,gBAAQ,MAAM,GAAG,mBAAmB,IAAI;AAAA,MAC1C,OAAO;AACL,gBAAQ,MAAM,GAAG,mBAAmB,eAAe;AAAA,MACrD;AAEA,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,MAAM,OAAO;AAE3C,UAAI,OAAO;AACT,YAAI,MAAM,SAAS,YAAY;AAC7B,iBAAO;AAAA,QACT;AACA,cAAM,IAAI,MAAM,iCAAiC,MAAM,OAAO,EAAE;AAAA,MAClE;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,iCAAiC,KAAK;AAChD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,WAAW,YAAoB,WAAmB,iBAAkD;AACxG,QAAI;AAEF,YAAM,UAAU,MAAM,KAAK,iBAAiB,YAAY,WAAW,eAAe;AAClF,UAAI,CAAC,SAAS;AACZ,eAAO;AAAA,MACT;AAGA,UAAI,QAAQ,WAAW;AACrB,cAAM,EAAE,MAAM,SAAS,IAAI,MAAM,KAAK,SACnC,IAAI,+BAA+B;AAAA,UAClC,cAAc;AAAA,UACd,aAAa;AAAA,UACb,mBAAmB;AAAA,QACrB,CAAC;AAEH,YAAI,CAAC,UAAU;AACb,iBAAO;AAAA,QACT;AAGA,eAAO,aAAa,KAAK,UAAU,UAAU,IAAI;AAAA,MACnD,OAAO;AAEL,eAAO,MAAM,KAAK,aAAa,YAAY,WAAW,eAAe;AAAA,MACvE;AAAA,IACF,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,2BAA2B,KAAK;AAC1C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,YAAoB,WAAmB,iBAA0B,aAAqB,MAA8B;AACrI,QAAI;AAEF,YAAM,EAAE,MAAM,UAAU,MAAM,IAAI,MAAM,KAAK,SAC1C,IAAI,sCAAsC;AAAA,QACzC,cAAc;AAAA,QACd,aAAa;AAAA,QACb,mBAAmB,mBAAmB;AAAA,QACtC,cAAc;AAAA,MAChB,CAAC;AAEH,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,6BAA6B,MAAM,OAAO,EAAE;AAAA,MAC9D;AAEA,UAAI,CAAC,UAAU;AACb,eAAO;AAAA,MACT;AAGA,YAAM,kBAAkB,MAAM,aAAa,KAAK,UAAU,UAAU;AAAA,QAClE,SAAS;AAAA,QACT,OAAO;AAAA,QACP,QAAQ,kBAAkB,SAAY;AAAA,QACtC,WAAW;AAAA,MACb,CAAC;AAED,aAAO,iBAAiB,OAAO;AAAA,IACjC,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,6BAA6B,KAAK;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,oBAAoB,IAAY,SAAyD;AAC7F,QAAI;AACF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,KAAK,sBAAsB,EAC3B,OAAO,OAAO,EACd,GAAG,MAAM,EAAE,EACX,OAAO,EACP,OAAO;AAEV,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,oCAAoC,MAAM,OAAO,EAAE;AAAA,MACrE;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,kCAAkC,KAAK;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,oBAAoB,YAAoB,WAAmB,iBAA0B,cAAuB,OAAyB;AACzI,QAAI;AAEF,YAAM,UAAU,MAAM,KAAK,iBAAiB,YAAY,WAAW,eAAe;AAElF,YAAM,EAAE,MAAM,IAAI,MAAM,KAAK,SAC1B,IAAI,8BAA8B;AAAA,QACjC,cAAc;AAAA,QACd,aAAa;AAAA,QACb,mBAAmB,mBAAmB;AAAA,QACtC,eAAe;AAAA,MACjB,CAAC;AAEH,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,oCAAoC,MAAM,OAAO,EAAE;AAAA,MACrE;AAGA,UAAI,eAAe,SAAS;AAC1B,cAAM,WAAW,KAAK,UAAU,QAAQ,WAAW,QAAQ,aAAa,KAAK;AAAA,MAC/E;AAEA,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,kCAAkC,KAAK;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,mBAAmB,YAAoB,WAAmB,iBAAoD;AAClH,QAAI;AACF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,IAAI,4BAA4B;AAAA,QAC/B,cAAc;AAAA,QACd,aAAa;AAAA,QACb,mBAAmB,mBAAmB;AAAA,MACxC,CAAC;AAEH,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,mCAAmC,MAAM,OAAO,EAAE;AAAA,MACpE;AAKA,UAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,eAAO,CAAC;AAAA,MACV;AAWA,YAAM,iBAAkC,KACrC,OAAO,CAAC,SAAsB,KAAK,MAAM,KAAK,aAAa,KAAK,aAAa,EAC7E,IAAI,CAAC,SAAsB;AAE1B,cAAM,WAAW,KAAK,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK;AACpD,cAAM,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAG9C,cAAM,UAAyB;AAAA,UAC7B,IAAI,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA,WAAW,KAAK;AAAA,UAChB,eAAe;AAAA,YACb;AAAA,YACA;AAAA,YACA,GAAI,KAAK,iBAAiB,CAAC;AAAA,UAC7B;AAAA,UACA,iBAAiB,mBAAmB;AAAA,UACpC,QAAQ,KAAK,eAAe,SAAS,YAAY,KAAK,cAAc,MAAM,IAAI,YAAY,EAAE;AAAA;AAAA,UAC5F,WAAW,KAAK,aAAa;AAAA,UAC7B,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,UACtD,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA;AAAA,QACxD;AACA,eAAO;AAAA,MACT,CAAC;AAEH,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,kCAAkC,KAAK;AACjD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,aAAa,YAAoB,WAAmB,iBAA2C;AACnG,QAAI;AACF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,IAAI,iCAAiC;AAAA,QACpC,cAAc;AAAA,QACd,aAAa;AAAA,QACb,mBAAmB,mBAAmB;AAAA,MACxC,CAAC;AAEH,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,6BAA6B,MAAM,OAAO,EAAE;AAAA,MAC9D;AAEA,aAAO,QAAQ;AAAA,IACjB,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,6BAA6B,KAAK;AAC5C,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,qBAAqB,IAAY,iBAAyD;AAC9F,QAAI;AACF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,IAAI,2BAA2B;AAAA,QAC9B,qBAAqB;AAAA,QACrB,mBAAmB,mBAAmB;AAAA,MACxC,CAAC;AAEH,UAAI,OAAO;AACT,cAAM,IAAI,MAAM,uCAAuC,MAAM,OAAO,EAAE;AAAA,MACxE;AAEA,UAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,eAAO;AAAA,MACT;AAEA,aAAO,KAAK,CAAC;AAAA,IACf,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,uCAAuC,KAAK;AACtD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,mBACJ,YACA,WACA,UACA,iBAC0B;AAC1B,QAAI;AAIF,YAAM,EAAE,MAAM,MAAM,IAAI,MAAM,KAAK,SAChC,IAAI,wCAAwC;AAAA,QAC3C,cAAc;AAAA,QACd,aAAa;AAAA,QACb,YAAY;AAAA,QACZ,mBAAmB,mBAAmB;AAAA,MACxC,CAAC;AAEH,UAAI,OAAO;AAET,QAAAA,KAAI,MAAM,wCAAwC;AAAA,UAChD;AAAA,UACA,WAAW,MAAM;AAAA,UACjB,cAAc,MAAM;AAAA,UACpB,cAAc,MAAM;AAAA,UACpB;AAAA,UACA;AAAA,UACA;AAAA,UACA;AAAA,UACA,SAAS;AAAA,QACX,CAAC;AACD,cAAM,IAAI,MAAM,oCAAoC,MAAM,OAAO,kGAAkG;AAAA,MACrK;AAKA,UAAI,CAAC,QAAQ,KAAK,WAAW,GAAG;AAC9B,eAAO,CAAC;AAAA,MACV;AAWA,YAAM,iBAAkC,KACrC,OAAO,CAAC,SAAsB;AAE7B,cAAM,eAAe,KAAK,eAAe;AACzC,cAAM,UAAU,iBAAiB;AACjC,YAAI,CAAC,SAAS;AACZ,UAAAA,KAAI,KAAK,2CAA2C;AAAA,YAClD,QAAQ,KAAK;AAAA,YACb,kBAAkB;AAAA,YAClB,gBAAgB;AAAA,UAClB,CAAC;AAAA,QACH;AACA,eAAO,WAAW,KAAK,MAAM,KAAK,aAAa,KAAK;AAAA,MACtD,CAAC,EACA,IAAI,CAAC,SAAsB;AAE1B,cAAM,WAAW,KAAK,UAAU,MAAM,GAAG,EAAE,IAAI,KAAK;AACpD,cAAM,WAAW,SAAS,MAAM,GAAG,EAAE,IAAI,KAAK;AAG9C,cAAM,UAAyB;AAAA,UAC7B,IAAI,KAAK;AAAA,UACT;AAAA,UACA;AAAA,UACA,WAAW,KAAK;AAAA,UAChB,eAAe;AAAA,YACb;AAAA,YACA;AAAA,YACA,UAAW,KAAK,eAAe;AAAA,YAC/B,GAAI,KAAK,iBAAiB,CAAC;AAAA,UAC7B;AAAA,UACA,iBAAiB,mBAAmB;AAAA,UACpC,QAAQ,KAAK,eAAe,SAAS,YAAY,KAAK,cAAc,MAAM,IAAI,YAAY,EAAE;AAAA;AAAA,UAC5F,WAAW,KAAK,aAAa;AAAA,UAC7B,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA,UACtD,YAAY,KAAK,eAAc,oBAAI,KAAK,GAAE,YAAY;AAAA;AAAA,QACxD;AACA,eAAO;AAAA,MACT,CAAC;AAEH,aAAO;AAAA,IACT,SAAS,OAAO;AACd,MAAAA,KAAI,MAAM,oCAAoC,KAAK;AACnD,YAAM;AAAA,IACR;AAAA,EACF;AAAA,EAEA,MAAM,oBACJ,SACA,OACgE;AAChE,UAAM,UAA2B,CAAC;AAClC,UAAM,SAA0C,CAAC;AACjD,UAAM,UAAkF,CAAC;AAGzF,eAAW,QAAQ,OAAO;AACxB,UAAI;AACF,cAAM,gBAAgB,MAAM,KAAK,oBAAoB,SAAS,IAAI;AAElE,gBAAQ,KAAK,aAAa;AAC1B,gBAAQ,KAAK;AAAA,UACX;AAAA,UACA,QAAQ;AAAA,YACN,gBAAgB;AAAA,YAChB,UAAU,cAAc,YACpB,aAAa,KAAK,UAAU,cAAc,WAAW,IAAI,IACzD;AAAA,UACN;AAAA,QACF,CAAC;AAAA,MACH,SAAS,OAAO;AACd,cAAM,UAAU,iBAAiB,QAAQ,MAAM,UAAU;AACzD,eAAO,KAAK,EAAE,MAAM,OAAO,QAAQ,CAAC;AACpC,gBAAQ,KAAK,EAAE,MAAM,QAAQ,MAAM,OAAO,QAAQ,CAAC;AAAA,MACrD;AAAA,IACF;AAEA,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA,OAAO,QAAQ;AAAA,MACf,YAAY,QAAQ;AAAA,MACpB;AAAA,IACF;AAAA,EACF;AACF;AAGO,SAAS,2BAA2B,UAAgD;AACzF,SAAO,IAAI,yBAAyB,QAAQ;AAC9C;AAGA,eAAsB,wBACpB,UACA,SACA,MAC2B;AAE3B,QAAM,UAAU,2BAA2B,QAAQ;AACnD,QAAM,gBAAgB,MAAM,QAAQ,oBAAoB,SAAS,IAAI;AAEnE,QAAM,UAAU,QAAQ,YACtB,aAAa,UAAU,cAAc,WAAW,IAAI,IACpD,MAAM,aAAa,UAAU,cAAc,WAAW;AAAA,IACpD,SAAS;AAAA,IACT,OAAO,QAAQ,mBAAmB;AAAA,IAClC,QAAQ,QAAQ,UAAU;AAAA,IAC1B,WAAW;AAAA,EACb,CAAC;AAEL,QAAM,YAAY,OAAO,YAAY,WAAW,UAAU,SAAS,OAAO;AAE1E,SAAO;AAAA,IACL,gBAAgB;AAAA,IAChB,UAAU;AAAA,IACV,YAAY,QAAQ,YAAY,SAAY,aAAa;AAAA,EAC3D;AACF;;;ADnlBA,IAAM,yBAAyB,oBAAI,IAA2D;AAG9F,IAAMC,kBAAiB;AAGvB,SAAS,eAAe;AACtB,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,UAAU,MAAM,KAAK,uBAAuB,QAAQ,CAAC;AAG3D,QAAM,cAAwB,CAAC;AAC/B,UAAQ,QAAQ,CAAC,CAAC,KAAK,KAAK,MAAM;AAChC,QAAI,MAAM,MAAM,aAAa,MAAM,KAAK;AACtC,kBAAY,KAAK,GAAG;AAAA,IACtB;AAAA,EACF,CAAC;AACD,cAAY,QAAQ,SAAO,uBAAuB,OAAO,GAAG,CAAC;AAG7D,MAAI,uBAAuB,OAAOA,iBAAgB;AAChD,UAAM,SAAS,QACZ,OAAO,CAAC,CAAC,GAAG,MAAM,CAAC,YAAY,SAAS,GAAG,CAAC,EAC5C,KAAK,CAAC,GAAG,MAAM,EAAE,CAAC,EAAE,YAAY,EAAE,CAAC,EAAE,SAAS;AACjD,UAAM,WAAW,OAAO,MAAM,GAAG,uBAAuB,OAAOA,eAAc;AAC7E,aAAS,QAAQ,CAAC,CAAC,GAAG,MAAM,uBAAuB,OAAO,GAAG,CAAC;AAAA,EAChE;AACF;AAgDO,SAAS,eACd,YACA,WACA,iBACA,UACA,SACsB;AACtB,QAAM;AAAA,IACJ,WAAW,KAAK,KAAK;AAAA;AAAA,IACrB,cAAc;AAAA,IACd;AAAA,EACF,IAAI;AAEJ,QAAM,CAAC,SAAS,UAAU,IAAIC,UAAwB,IAAI;AAC1D,QAAM,CAAC,eAAe,gBAAgB,IAAIA,UAA+B,IAAI;AAC7E,QAAM,CAAC,gBAAgB,iBAAiB,IAAIA,UAA0B,CAAC,CAAC;AACxE,QAAM,CAAC,UAAU,WAAW,IAAIA,UAA8B,oBAAI,IAAI,CAAC;AACvE,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAiB,CAAC;AACpD,QAAM,CAAC,WAAW,YAAY,IAAIA,UAAkB,KAAK;AACzD,QAAM,CAAC,OAAO,QAAQ,IAAIA,UAAuB,IAAI;AAIrD,QAAM,eAAeC,QAAO,IAAI;AAGhC,QAAM,eAAe,CAAK,QAA4B,UAAa;AACjE,QAAI,aAAa,SAAS;AACxB,aAAO,KAAK;AAAA,IACd;AAAA,EACF;AAEA,QAAM,aAAaC,aAAY,YAA2B;AACxD,QAAI,CAAC,cAAc,CAAC,aAAa,CAAC,UAAU;AAC1C,mBAAa,YAAY,IAAI;AAC7B,mBAAa,kBAAkB,IAAI;AACnC,mBAAa,mBAAmB,CAAC,CAAC;AAClC,mBAAa,aAAa,oBAAI,IAAI,CAAC;AACnC,mBAAa,cAAc,CAAC;AAC5B,mBAAa,cAAc,KAAK;AAChC;AAAA,IACF;AAGA,QAAI,iBAAiB;AACnB,YAAM,YAAY;AAClB,UAAI,CAAC,UAAU,KAAK,eAAe,GAAG;AACpC,eAAO,KAAK,kBAAkB,qDAAqD,eAAe;AAAA,MACpG;AAAA,IACF;AAIA,UAAM,WAAW,QAAQ,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO,IAAI,YAAY,KAAK;AAClJ,QAAI,aAAa;AACf,YAAM,SAAS,uBAAuB,IAAI,QAAQ;AAClD,UAAI,UAAU,KAAK,IAAI,IAAI,OAAO,YAAY,OAAO,KAAK;AACxD,cAAM,aAAa,OAAO;AAG1B,YAAI,WAAW,iBAAiB,CAAC,WAAW,WACxC,WAAW,cAAc,cAAc,SAAS,UAAU;AAE5D,cAAI;AACF,kBAAM,kBAAkB,MAAM,aAAa,UAAU,WAAW,cAAc,WAAW;AAAA,cACvF,SAAS;AAAA,cACT,OAAO;AAAA,cACP,QAAQ,kBAAkB,SAAY;AAAA,cACtC,WAAW;AAAA,YACb,CAAC;AACD,kBAAM,iBAAiB,iBAAiB,OAAO;AAC/C,yBAAa,YAAY,cAAc;AACvC,yBAAa,kBAAkB,WAAW,aAAa;AACvD,yBAAa,mBAAmB,WAAW,kBAAkB,CAAC,CAAC;AAC/D,yBAAa,aAAa,WAAW,YAAY,oBAAI,IAAI,CAAC;AAC1D,yBAAa,cAAc,WAAW,aAAa,CAAC;AACpD,yBAAa,cAAc,KAAK;AAChC,yBAAa,UAAU,IAAI;AAC3B;AAAA,UACF,SAAS,KAAK;AAEZ,mBAAO,KAAK,kBAAkB,sEAAsE,GAAG;AAAA,UACzG;AAAA,QACF;AAGA,qBAAa,YAAY,WAAW,WAAW,IAAI;AACnD,qBAAa,kBAAkB,WAAW,iBAAiB,IAAI;AAC/D,qBAAa,mBAAmB,WAAW,kBAAkB,CAAC,CAAC;AAC/D,qBAAa,aAAa,WAAW,YAAY,oBAAI,IAAI,CAAC;AAC1D,qBAAa,cAAc,WAAW,aAAa,CAAC;AACpD,qBAAa,cAAc,KAAK;AAChC,qBAAa,UAAU,IAAI;AAC3B;AAAA,MACF;AAAA,IACF;AAEA,QAAI;AACF,mBAAa,cAAc,IAAI;AAC/B,mBAAa,UAAU,IAAI;AAE3B,YAAM,UAAU,2BAA2B,QAAQ;AACnD,UAAI,QAAyB,CAAC;AAK9B,YAAM,yBAAyB,oBAAoB;AAEnD,UAAI,wBAAwB;AAE1B,YAAI,kBAAmC,CAAC;AACxC,YAAI,iBAAkC,CAAC;AAEvC,YAAI;AACF,cAAI,UAAU;AACZ,8BAAkB,MAAM,QAAQ;AAAA,cAC9B;AAAA,cACA;AAAA,cACA;AAAA,cACA;AAAA;AAAA,YACF;AAAA,UACF,OAAO;AACL,8BAAkB,MAAM,QAAQ;AAAA,cAC9B;AAAA,cACA;AAAA,cACA;AAAA;AAAA,YACF;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,iBAAO,KAAK,kBAAkB,qCAAqC,GAAG;AACtE,4BAAkB,CAAC;AAAA,QACrB;AAIA,YAAI;AACF,gBAAM,EAAE,MAAM,EAAE,KAAK,GAAG,OAAO,UAAU,IAAI,MAAM,SAAS,KAAK,QAAQ;AACzE,cAAI,WAAW;AACb,mBAAO,KAAK,kBAAkB,uBAAuB,SAAS;AAAA,UAChE;AAEA,cAAI,MAAM;AAER,kBAAM,EAAE,MAAM,aAAa,OAAO,gBAAgB,IAAI,MAAM,SACzD,KAAK,+BAA+B,EACpC,OAAO,iBAAiB,EACxB,GAAG,WAAW,KAAK,EAAE;AAExB,gBAAI,iBAAiB;AACnB,qBAAO,KAAK,kBAAkB,4CAA4C,eAAe;AAAA,YAC3F;AAEA,gBAAI,eAAe,YAAY,SAAS,GAAG;AAEzC,oBAAM,SAAS,YAAY,IAAI,OAAK,EAAE,eAAe,EAAE,OAAO,OAAO;AAGrE,oBAAM,aAAa,OAAO,IAAI,OAAO,UAAU;AAC7C,oBAAI;AACF,sBAAI,UAAU;AACZ,2BAAO,MAAM,QAAQ;AAAA,sBACnB;AAAA,sBACA;AAAA,sBACA;AAAA,sBACA;AAAA,oBACF;AAAA,kBACF,OAAO;AACL,2BAAO,MAAM,QAAQ;AAAA,sBACnB;AAAA,sBACA;AAAA,sBACA;AAAA,oBACF;AAAA,kBACF;AAAA,gBACF,SAAS,KAAK;AAEZ,yBAAO,CAAC;AAAA,gBACV;AAAA,cACF,CAAC;AAED,oBAAM,aAAa,MAAM,QAAQ,IAAI,UAAU;AAC/C,+BAAiB,WAAW,KAAK;AAAA,YACnC,OAAO;AAIL,kBAAI;AAEF,oBAAI,gBAAgB,SACjB,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,cAAc,UAAU,EAC3B,GAAG,aAAa,SAAS,EACzB,MAAM,cAAc,EAAE,WAAW,MAAM,CAAC;AAE3C,oBAAI,UAAU;AACZ,kCAAgB,cAAc,GAAG,4BAA4B,QAAQ;AAAA,gBACvE;AAEA,sBAAM,EAAE,MAAM,cAAc,IAAI,MAAM;AAEtC,oBAAI,iBAAiB,cAAc,SAAS,GAAG;AAE7C,mCAAiB,cAAc,IAAI,CAAC,OAAY;AAAA,oBAC9C,IAAI,EAAE;AAAA,oBACN,YAAY,EAAE;AAAA,oBACd,WAAW,EAAE;AAAA,oBACb,WAAW,EAAE;AAAA,oBACb,eAAe,EAAE,iBAAiB,CAAC;AAAA,oBACnC,iBAAiB,EAAE;AAAA,oBACnB,QAAQ,EAAE;AAAA,oBACV,WAAW,EAAE,aAAa;AAAA,oBAC1B,YAAY,EAAE;AAAA,oBACd,YAAY,EAAE;AAAA,kBAChB,EAAE;AAAA,gBACJ;AAAA,cACF,SAAS,KAAK;AAAA,cAEd;AAAA,YACF;AAAA,UACF;AAAA,QACF,SAAS,KAAK;AACZ,iBAAO,KAAK,kBAAkB,6CAA6C,GAAG;AAC9E,2BAAiB,CAAC;AAAA,QACpB;AAIA,cAAM,WAAW,CAAC,GAAG,iBAAiB,GAAG,cAAc;AACvD,iBAAS,KAAK,CAAC,GAAG,MAAM;AACtB,gBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,gBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,iBAAO,QAAQ;AAAA,QACjB,CAAC;AAID,YAAI,eAAe,SAAS,KAAK,gBAAgB,SAAS,GAAG;AAE3D,kBAAQ,SAAS,OAAO,OAAK,EAAE,oBAAoB,IAAI;AAAA,QACzD,OAAO;AAEL,kBAAQ;AAAA,QACV;AAKA,YAAI,MAAM,WAAW,GAAG;AACtB,cAAI;AACF,gBAAI,cAAc,SACf,KAAK,sBAAsB,EAC3B,OAAO,iHAAiH,EACxH,GAAG,cAAc,UAAU,EAC3B,GAAG,aAAa,SAAS,EACzB,MAAM,cAAc,EAAE,WAAW,MAAM,CAAC;AAG3C,gBAAI,UAAU;AACZ,4BAAc,YAAY,GAAG,4BAA4B,QAAQ;AAAA,YACnE;AAEA,kBAAM,EAAE,MAAM,YAAY,IAAI,MAAM;AAEpC,gBAAI,eAAe,YAAY,SAAS,GAAG;AAEzC,sBAAQ,YAAY,IAAI,CAAC,OAAY;AAAA,gBACnC,IAAI,EAAE;AAAA,gBACN,YAAY,EAAE;AAAA,gBACd,WAAW,EAAE;AAAA,gBACb,WAAW,EAAE;AAAA,gBACb,eAAe,EAAE,iBAAiB,CAAC;AAAA,gBACnC,iBAAiB,EAAE;AAAA,gBACnB,QAAQ,EAAE;AAAA,gBACV,WAAW,EAAE,aAAa;AAAA,gBAC1B,YAAY,EAAE;AAAA,gBACd,YAAY,EAAE;AAAA,cAChB,EAAE;AAAA,YACJ;AAAA,UACF,SAAS,KAAK;AAAA,UAEd;AAAA,QACF;AAAA,MACF,OAAO;AAIL,YAAI,UAAU;AAGZ,kBAAQ,MAAM,QAAQ;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF,OAAO;AAEL,kBAAQ,MAAM,QAAQ;AAAA,YACpB;AAAA,YACA;AAAA,YACA;AAAA,UACF;AAAA,QACF;AAKA,YAAI,MAAM,WAAW,MAAM,CAAC,mBAAmB,oBAAoB,KAAK;AAEtE,cAAI,kBAAmC,CAAC;AACxC,cAAI,iBAAkC,CAAC;AAEvC,cAAI;AACF,gBAAI,UAAU;AACZ,gCAAkB,MAAM,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAAA,YACF,OAAO;AACL,gCAAkB,MAAM,QAAQ;AAAA,gBAC9B;AAAA,gBACA;AAAA,gBACA;AAAA,cACF;AAAA,YACF;AAAA,UACF,SAAS,KAAK;AAAA,UAEd;AAEA,cAAI;AACF,kBAAM,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,MAAM,SAAS,KAAK,QAAQ;AACvD,gBAAI,MAAM;AACR,oBAAM,EAAE,MAAM,YAAY,IAAI,MAAM,SACjC,KAAK,+BAA+B,EACpC,OAAO,iBAAiB,EACxB,GAAG,WAAW,KAAK,EAAE,EACrB,GAAG,iCAAiC;AAEvC,kBAAI,eAAe,YAAY,SAAS,GAAG;AACzC,sBAAM,SAAS,YAAY,IAAI,OAAK,EAAE,eAAe,EAAE,OAAO,OAAO;AACrE,sBAAM,aAAa,OAAO,IAAI,OAAO,UAAU;AAC7C,sBAAI;AACF,wBAAI,UAAU;AACZ,6BAAO,MAAM,QAAQ,mBAAmB,YAAY,WAAW,UAAU,KAAK;AAAA,oBAChF,OAAO;AACL,6BAAO,MAAM,QAAQ,mBAAmB,YAAY,WAAW,KAAK;AAAA,oBACtE;AAAA,kBACF,SAAS,KAAK;AACZ,2BAAO,CAAC;AAAA,kBACV;AAAA,gBACF,CAAC;AACD,sBAAM,aAAa,MAAM,QAAQ,IAAI,UAAU;AAC/C,iCAAiB,WAAW,KAAK;AAAA,cACnC;AAAA,YACF;AAAA,UACF,SAAS,KAAK;AAAA,UAEd;AAGA,gBAAM,WAAW,CAAC,GAAG,iBAAiB,GAAG,cAAc;AACvD,mBAAS,KAAK,CAAC,GAAG,MAAM;AACtB,kBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,kBAAM,QAAQ,IAAI,KAAK,EAAE,UAAU,EAAE,QAAQ;AAC7C,mBAAO,QAAQ;AAAA,UACjB,CAAC;AAED,cAAI,eAAe,SAAS,KAAK,gBAAgB,SAAS,GAAG;AAC3D,oBAAQ,SAAS,OAAO,OAAK,EAAE,oBAAoB,IAAI;AAAA,UACzD,OAAO;AACL,oBAAQ;AAAA,UACV;AAAA,QACF;AAAA,MACF;AAEA,UAAI,MAAM,WAAW,GAAG;AACtB,qBAAa,YAAY,IAAI;AAC7B,qBAAa,kBAAkB,IAAI;AACnC,qBAAa,mBAAmB,CAAC,CAAC;AAClC,qBAAa,aAAa,oBAAI,IAAI,CAAC;AACnC,qBAAa,cAAc,CAAC;AAG5B,YAAI,aAAa;AACf,iCAAuB,IAAI,UAAU;AAAA,YACnC,MAAM,EAAE,SAAS,MAAM,eAAe,MAAM,gBAAgB,CAAC,GAAG,UAAU,oBAAI,IAAI,GAAG,WAAW,EAAE;AAAA,YAClG,WAAW,KAAK,IAAI;AAAA,YACpB,KAAK;AAAA,UACP,CAAC;AACD,uBAAa;AAAA,QACf;AACA;AAAA,MACF;AAEA,mBAAa,mBAAmB,KAAK;AACrC,mBAAa,cAAc,MAAM,MAAM;AAEvC,UAAI,YAAY,MAAM,SAAS,GAAG;AAEhC,cAAM,YAAY,MAAM,CAAC;AAEzB,qBAAa,kBAAkB,SAAS;AAGxC,YAAI,MAAqB;AACzB,YAAI,UAAU,WAAW;AACvB,gBAAM,aAAa,UAAU,UAAU,WAAW,IAAI;AAAA,QACxD,OAAO;AACL,gBAAM,kBAAkB,MAAM,aAAa,UAAU,UAAU,WAAW;AAAA,YACxE,SAAS;AAAA,YACT,OAAO;AAAA,YACP,QAAQ,kBAAkB,SAAY;AAAA,YACtC,WAAW;AAAA,UACb,CAAC;AACD,gBAAM,iBAAiB,OAAO;AAE9B,cAAI,CAAC,KAAK;AACR,mBAAO,KAAK,kBAAkB,2CAA2C;AAAA,cACvE,WAAW,UAAU;AAAA,cACrB;AAAA,cACA;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AACA,qBAAa,YAAY,GAAG;AAAA,MAC9B,OAAO;AAEL,cAAM,SAAS,MAAM,sBAAsB,UAAU,OAAO;AAAA,UACtD,SAAS;AAAA,UACT,OAAO;AAAA,UACP,QAAQ,kBAAkB,SAAY;AAAA,UACtC,WAAW;AAAA,QACb,CAAC;AACL,qBAAa,aAAa,MAAM;AAChC,qBAAa,kBAAkB,IAAI;AACnC,qBAAa,YAAY,IAAI;AAAA,MAC/B;AAGA,UAAI,aAAa;AAEf,YAAI,YAAiB;AAAA,UACnB,eAAe,YAAY,MAAM,SAAS,IAAI,MAAM,CAAC,IAAI;AAAA,UACzD,gBAAgB;AAAA,UAChB,UAAU,oBAAI,IAAI;AAAA,UAClB,WAAW,MAAM;AAAA,QACnB;AAEA,YAAI,YAAY,MAAM,SAAS,GAAG;AAChC,gBAAM,YAAY,MAAM,CAAC;AACzB,cAAI,MAAqB;AACzB,cAAI,UAAU,WAAW;AACvB,kBAAM,aAAa,UAAU,UAAU,WAAW,IAAI;AAAA,UACxD;AACA,oBAAU,UAAU;AAAA,QACtB,OAAO;AACL,gBAAM,SAAS,oBAAI,IAAoB;AACvC,qBAAW,WAAW,OAAO;AAC3B,gBAAI,QAAQ,WAAW;AACrB,oBAAM,MAAM,aAAa,UAAU,QAAQ,WAAW,IAAI;AAC1D,kBAAI,KAAK;AACP,uBAAO,IAAI,QAAQ,IAAI,GAAG;AAAA,cAC5B;AAAA,YACF;AAAA,UACF;AACA,oBAAU,WAAW;AAAA,QACvB;AAEA,+BAAuB,IAAI,UAAU;AAAA,UACnC,MAAM;AAAA,UACN,WAAW,KAAK,IAAI;AAAA,UACpB,KAAK;AAAA,QACP,CAAC;AACD,qBAAa;AAAA,MACf;AAAA,IAEF,SAAS,KAAK;AACZ,aAAO,MAAM,kBAAkB,yBAAyB,GAAG;AAC3D,YAAMC,SAAQ,eAAe,QAAQ,MAAM,IAAI,MAAM,wBAAwB;AAC7E,mBAAa,UAAUA,MAAK;AAC5B,mBAAa,YAAY,IAAI;AAC7B,mBAAa,kBAAkB,IAAI;AACnC,mBAAa,mBAAmB,CAAC,CAAC;AAClC,mBAAa,aAAa,oBAAI,IAAI,CAAC;AACnC,mBAAa,cAAc,CAAC;AAAA,IAC9B,UAAE;AACA,mBAAa,cAAc,KAAK;AAAA,IAClC;AAAA,EACF,GAAG,CAAC,YAAY,WAAW,iBAAiB,UAAU,UAAU,UAAU,WAAW,CAAC;AAGtF,EAAAC,WAAU,MAAM;AAEd,iBAAa,UAAU;AAEvB,QAAI,cAAc,aAAa,UAAU;AAEvC,iBAAW,EAAE,MAAM,CAAC,QAAQ;AAE1B,YAAI,aAAa,SAAS;AACxB,uBAAa,UAAU,eAAe,QAAQ,MAAM,IAAI,MAAM,eAAe,CAAC;AAAA,QAChF;AAAA,MACF,CAAC;AAAA,IACH,OAAO;AAEL,mBAAa,YAAY,IAAI;AAC7B,mBAAa,kBAAkB,IAAI;AACnC,mBAAa,mBAAmB,CAAC,CAAC;AAClC,mBAAa,aAAa,oBAAI,IAAI,CAAC;AACnC,mBAAa,cAAc,CAAC;AAC5B,mBAAa,cAAc,KAAK;AAChC,mBAAa,UAAU,IAAI;AAAA,IAC7B;AAIA,WAAO,MAAM;AACX,mBAAa,UAAU;AAAA,IACzB;AAAA,EAGF,GAAG,CAAC,YAAY,WAAW,iBAAiB,QAAQ,CAAC;AAErD,QAAM,UAAUF,aAAY,YAA2B;AACrD,QAAI,CAAC,cAAc,CAAC,aAAa,CAAC,SAAU;AAG5C,QAAI,aAAa;AACf,YAAM,WAAW,QAAQ,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO,IAAI,YAAY,KAAK;AAClJ,6BAAuB,OAAO,QAAQ;AAAA,IACxC;AACA,UAAM,WAAW;AAAA,EACnB,GAAG,CAAC,YAAY,YAAY,WAAW,iBAAiB,UAAU,UAAU,WAAW,CAAC;AAExF,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAMO,SAAS,wBAA8B;AAC5C,aAAW,CAAC,GAAG,KAAK,wBAAwB;AAC1C,QAAI,IAAI,WAAW,OAAO,GAAG;AAC3B,6BAAuB,OAAO,GAAG;AAAA,IACnC;AAAA,EACF;AACF;AAKO,SAAS,2BAA6D;AAC3E,QAAM,OAAO,MAAM,KAAK,uBAAuB,KAAK,CAAC,EAAE,OAAO,SAAO,IAAI,WAAW,OAAO,CAAC;AAC5F,SAAO;AAAA,IACL,MAAM,KAAK;AAAA,IACX;AAAA,EACF;AACF;AAWO,SAAS,2BACd,YACA,WACA,iBACA,UACM;AACN,QAAM,WAAW,QAAQ,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO,IAAI,YAAY,KAAK;AAClJ,yBAAuB,OAAO,QAAQ;AAEtC,MAAI,UAAU;AACZ,UAAM,iBAAiB,QAAQ,UAAU,IAAI,SAAS,IAAI,oBAAoB,SAAY,cAAe,mBAAmB,MAAO;AACnI,2BAAuB,OAAO,cAAc;AAAA,EAC9C;AACF;;;AEjqBA,SAAS,aAAAG,kBAAiB;AAC1B,SAAS,mBAAmB;AAO5B,IAAMC,OAAM,aAAa,eAAe;AAoDjC,SAAS,cAAc,OAA4B;AACxD,QAAM,WAAW,YAAY;AAI7B,MAAI;AACJ,MAAI;AACF,QAAI,UAAU,QAAW;AAEvB,YAAM,gBAAgB,UAAU;AAChC,sBAAgB,cAAc;AAAA,IAChC,OAAO;AAEL,sBAAgB;AAAA,IAClB;AAAA,EACF,SAAS,OAAO;AAGd,QAAI,UAAU,QAAW;AACvB,sBAAgB;AAAA,IAClB,OAAO;AAEL,sBAAgB;AAAA,IAClB;AAAA,EACF;AAEA,EAAAC,WAAU,MAAM;AAGd,UAAM,iBAAiB,SAAS,aAAa,YAAY,SAAS,SAAS,WAAW,QAAQ;AAE9F,QAAI,gBAAgB;AAElB,mBAAa;AACb;AAAA,IACF;AAGA,QAAI,CAAC,eAAe;AAClB,mBAAa;AACb;AAAA,IACF;AAGA,UAAM,eAAe,cAAc;AAGnC,UAAM,aAAa,8BAA8B,YAAY;AAE7D,QAAI,CAAC,YAAY;AACf,mBAAa;AACb;AAAA,IACF;AAGA,QAAI;AACF,mBAAa,UAAU;AAAA,IACzB,SAAS,OAAO;AACd,MAAAD,KAAI,MAAM,kCAAkC,KAAK;AAAA,IACnD;AAGA,WAAO,MAAM;AAAA,IAGb;AAAA,EACF,GAAG,CAAC,eAAe,SAAS,QAAQ,CAAC;AACvC;;;AC7JA,SAAS,aAAAE,YAAW,UAAAC,eAAc;AAqC3B,SAAS,oBAAoB,UAAsC,CAAC,GAAS;AAClF,QAAM,EAAE,UAAU,MAAM,gBAAgB,IAAK,IAAI;AACjD,QAAM,0BAA0BA,QAAO,KAAK;AAC5C,QAAM,wBAAwBA,QAA6C,IAAI;AAE/E,EAAAD,WAAU,MAAM;AACd,QAAI,CAAC,WAAW,OAAO,WAAW,YAAa;AAG/C,UAAM,iBAAiB,CAAC,UAA+B;AAErD,UAAI,MAAM,WAAW;AACnB,gCAAwB,UAAU;AAGlC,YAAI,sBAAsB,SAAS;AACjC,uBAAa,sBAAsB,OAAO;AAAA,QAC5C;AAGA,8BAAsB,UAAU,WAAW,MAAM;AAC/C,kCAAwB,UAAU;AAAA,QACpC,GAAG,aAAa;AAAA,MAClB;AAAA,IACF;AAGA,UAAM,yBAAyB,MAAM;AACnC,UAAI,CAAC,SAAS,QAAQ;AAEpB,gCAAwB,UAAU;AAGlC,YAAI,sBAAsB,SAAS;AACjC,uBAAa,sBAAsB,OAAO;AAAA,QAC5C;AAGA,8BAAsB,UAAU,WAAW,MAAM;AAC/C,kCAAwB,UAAU;AAAA,QACpC,GAAG,aAAa;AAAA,MAClB;AAAA,IACF;AAGA,WAAO,iBAAiB,YAAY,cAAc;AAClD,aAAS,iBAAiB,oBAAoB,sBAAsB;AAEpE,WAAO,MAAM;AACX,aAAO,oBAAoB,YAAY,cAAc;AACrD,eAAS,oBAAoB,oBAAoB,sBAAsB;AAEvE,UAAI,sBAAsB,SAAS;AACjC,qBAAa,sBAAsB,OAAO;AAAA,MAC5C;AAAA,IACF;AAAA,EACF,GAAG,CAAC,SAAS,aAAa,CAAC;AAC7B;","names":["useState","useEffect","useCallback","useRef","useState","useRef","useEffect","error","useCallback","log","useState","useEffect","useCallback","useState","useCallback","error","useEffect","useState","useEffect","useCallback","useRef","log","MAX_CACHE_SIZE","useState","useRef","useCallback","error","useEffect","useEffect","log","useEffect","useEffect","useRef"]}
|