@camstack/addon-provider-reolink 0.1.1

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.
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/addon.ts","../src/reolink-camera.ts","../src/accessories/index.ts","../src/accessories/siren.ts","../src/accessories/base.ts","../src/accessories/floodlight.ts","../src/accessories/pir.ts","../src/metadata-populator.ts","../src/schema.ts","../src/stream-routing.ts","../src/error-classifier.ts","../src/intercom-encoder.ts","../src/intercom-session.ts","../src/intercom-orchestrator.ts","../src/intercom-webrtc-peer.ts","../src/sessions-snapshot.ts","../src/synthetic-sdp.ts","../src/reolink-hub.ts","../src/creation-schema.ts","../src/autodetect-cache.ts"],"sourcesContent":["import type {\n ConfigUISchema,\n CreateDeviceSpec,\n DeviceConstructor,\n FieldProbeResult,\n IDevice,\n IScopedLogger,\n ProviderRegistration,\n SystemEvent,\n} from '@camstack/types'\nimport { BaseDeviceProvider, DeviceType, EventCategory } from '@camstack/types'\nimport { autoDetectDeviceType, type AutoDetectResult } from '@apocaliss92/nodelink-js'\nimport { ReolinkCamera, reolinkCameraSchema, REOLINK_ADDON_ID } from './reolink-camera.js'\nimport { ReolinkHub } from './reolink-hub.js'\nimport { reolinkHubSchema } from './schema.js'\nimport { buildCreationFormSchema } from './creation-schema.js'\nimport { AutodetectCache } from './autodetect-cache.js'\n\n/**\n * Adapt a camstack scoped logger to the lib's `Logger` shape (just\n * `log(...)`). Lib lines are prefixed with `[AutoDetect]` /\n * `[BaichuanClient]` / etc. — we forward them at info level on the\n * caller's scope so any tags bound on `scopedLogger` (`addonId`,\n * `requestId` for the Add-Device modal) propagate to every emitted\n * entry. Mirrors `ReolinkCamera.libLoggerAdapter` but with structured\n * forwarding instead of stringifying everything as one info line.\n */\nfunction buildAutodetectLibLogger(scopedLogger: IScopedLogger): {\n log: (...args: unknown[]) => void\n info: (...args: unknown[]) => void\n warn: (...args: unknown[]) => void\n error: (...args: unknown[]) => void\n debug: (...args: unknown[]) => void\n} {\n const fwd = (level: 'info' | 'warn' | 'error' | 'debug') => (...args: unknown[]) => {\n const message = args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')\n scopedLogger[level](message)\n }\n return {\n log: fwd('info'),\n info: fwd('info'),\n warn: fwd('warn'),\n error: fwd('error'),\n debug: fwd('debug'),\n }\n}\n\nfunction getString(obj: Record<string, unknown>, key: string): string {\n const v = obj[key]\n return typeof v === 'string' ? v : ''\n}\n\nfunction getNumber(obj: Record<string, unknown>, key: string, fallback: number): number {\n const v = obj[key]\n return typeof v === 'number' && Number.isFinite(v) ? v : fallback\n}\n\ntype TransportMode = 'auto' | 'tcp' | 'udp'\n\nfunction getTransportMode(obj: Record<string, unknown>): TransportMode {\n const v = obj['transport']\n return v === 'tcp' || v === 'udp' || v === 'auto' ? v : 'auto'\n}\n\nfunction buildAutodetectInputs(form: Record<string, unknown>): {\n readonly host: string\n readonly username: string\n readonly password: string\n readonly uid: string | undefined\n readonly transport: TransportMode\n readonly port: number\n} {\n const host = getString(form, 'host').trim()\n const username = (getString(form, 'username') || 'admin').trim()\n const password = getString(form, 'password')\n const rawUid = getString(form, 'uid').trim()\n const uid = rawUid.length > 0 ? rawUid : undefined\n const transport = getTransportMode(form)\n const port = getNumber(form, 'port', 9000)\n return { host, username, password, uid, transport, port }\n}\n\nfunction buildAutodetectLabels(result: AutoDetectResult): string[] {\n const labels: string[] = []\n const model = result.deviceInfo?.type\n if (model && model.length > 0) labels.push(model)\n labels.push(formatDeviceTypeLabel(result.type))\n if (result.type === 'nvr' && typeof result.channelNum === 'number' && result.channelNum > 0) {\n labels.push(`${result.channelNum} channels`)\n }\n labels.push(`Transport: ${result.transport.toUpperCase()}`)\n if (result.transport === 'udp' && result.udpDiscoveryMethod) {\n labels.push(`UDP via ${result.udpDiscoveryMethod}`)\n }\n if (result.uid && result.uid.length > 0 && result.transport === 'udp') {\n labels.push(`UID ${result.uid.slice(0, 4)}…${result.uid.slice(-4)}`)\n }\n return labels\n}\n\nfunction formatDeviceTypeLabel(type: AutoDetectResult['type']): string {\n switch (type) {\n case 'camera': return 'Camera (TCP)'\n case 'udp-camera': return 'Camera (UDP)'\n case 'battery-cam': return 'Battery camera'\n case 'nvr': return 'NVR / Hub'\n case 'multifocal': return 'Multi-focal camera'\n }\n}\n\nfunction isStreamBrokerReady(event: SystemEvent): boolean {\n if (event.category !== 'system.ready-state') return false\n const data = event.data as Record<string, unknown>\n return data['capName'] === 'stream-broker' && data['state'] === 'ready'\n}\n\n/** True when the string looks like a real hardware identifier — not a\n * placeholder. Reolink firmware sometimes reports `\"00000000000000\"`\n * as serialNumber on Home Hub, or all-`F` macs when the network\n * interface isn't probed yet. Generic guard: ≥6 chars AND ≥2 distinct\n * non-zero characters (filters out '00000000000000', 'ffffffffffff',\n * empty strings, single-char strings, …). */\nfunction isMeaningfulIdentifier(s: string): boolean {\n if (!s || s.length < 6) return false\n const distinct = new Set(s.toLowerCase().split('').filter((c) => c !== '0' && c !== 'f'))\n return distinct.size >= 1\n}\n\n/**\n * Patch `detection.deviceInfo` + `hostNetworkInfo` in-place with the\n * post-login HOST identifiers. Two distinct gaps to fill:\n *\n * 1. The lib's TCP autodetect path tries `getInfo` (cmd 80, host) and\n * `getSupportInfo` (cmd 199, host) on a fresh socket. When these\n * hit ECONNRESET (frequent on Reolink Home Hub firmwares whose\n * pre-login Baichuan stack flaps mid-handshake), the lib falls\n * back to `getInfo cmd 318 ch=0` — which returns the FIRST\n * sub-channel's info, NOT the host's. Result: `detection.deviceInfo`\n * ends up with `type: \"Argus 3E\"` / `serialNumber: <camera serial>`\n * for a Reolink Home Hub. We MUST re-fetch the host probe\n * post-login (channel=undefined ⇒ cmd 80) and overwrite to get\n * the actual host's identity.\n *\n * 2. The lib's TCP autodetect path leaves `hostNetworkInfo` undefined\n * (the UDP path fetches it but TCP comments are explicit at\n * `autodetect.ts:723/740/754`). Post-login `getNetworkInfo()`\n * (cmd 76 + 93, no channel) gives the host's `mac` reliably for\n * both hubs and cameras.\n *\n * Both calls pass `channel = undefined` — that's the HOST. Passing `0`\n * would target sub-channel zero (a camera attached to the hub), which\n * is exactly the bug that polluted `detection.deviceInfo` upstream.\n *\n * Best-effort: failure on either call leaves whatever autodetect wrote\n * in place. `generateStableId` still throws cleanly when truly nothing\n * usable is on record.\n */\nasync function enrichHostIdentifiersFromLiveApi(\n detection: AutoDetectResult,\n logger: import('@camstack/types').IScopedLogger,\n): Promise<void> {\n const api = detection.api as unknown as {\n getInfo?: (channel?: number, opts?: { timeoutMs?: number }) => Promise<{ type?: string; serialNumber?: string; firmwareVersion?: string; hardwareVersion?: string; itemNo?: string; name?: string } | undefined>\n getNetworkInfo?: (channel?: number, opts?: { timeoutMs?: number }) => Promise<{ ip?: string; mac?: string; activeLink?: string } | undefined>\n }\n\n // Post-login HOST getInfo (cmd 80, no channel). ALWAYS overwrite —\n // even if autodetect populated `deviceInfo`, we can't trust it on the\n // TCP path (may be channel-0 fallback data). Post-login on a stable\n // socket the host probe consistently succeeds.\n try {\n const hostInfo = await api.getInfo?.(undefined, { timeoutMs: 4000 })\n if (hostInfo && (hostInfo.type || hostInfo.serialNumber)) {\n const previousType = detection.deviceInfo?.type ?? '(unset)'\n const previousSerial = detection.deviceInfo?.serialNumber ?? '(unset)'\n detection.deviceInfo = { ...(detection.deviceInfo ?? {}), ...hostInfo }\n if (previousType !== hostInfo.type || previousSerial !== hostInfo.serialNumber) {\n logger.info('autodetect: deviceInfo refreshed from post-login host probe (cmd 80)', {\n meta: {\n previousType,\n resolvedType: hostInfo.type ?? '(unset)',\n previousSerial,\n resolvedSerial: hostInfo.serialNumber ?? '(unset)',\n },\n })\n }\n }\n } catch (err) {\n logger.debug('autodetect: post-login host getInfo probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Post-login HOST getNetworkInfo (cmd 76 + 93, no channel). Autodetect's\n // TCP path doesn't fetch this — `hostNetworkInfo` is undefined.\n if (!detection.hostNetworkInfo?.mac) {\n try {\n const net = await api.getNetworkInfo?.(undefined, { timeoutMs: 3000 })\n if (net?.mac || net?.ip) {\n detection.hostNetworkInfo = { ...(detection.hostNetworkInfo ?? {}), ...net }\n logger.info('autodetect: hostNetworkInfo resolved via post-login probe (cmd 76+93)', {\n meta: { ip: net.ip ?? '(unset)', mac: net.mac ?? '(unset)' },\n })\n }\n } catch (err) {\n logger.debug('autodetect: post-login getNetworkInfo probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n}\n\nexport class ReolinkProviderAddon extends BaseDeviceProvider {\n protected readonly addonId = REOLINK_ADDON_ID\n protected readonly providerName = 'Reolink'\n protected readonly deviceClasses: Partial<Record<DeviceType, DeviceConstructor<IDevice>>> = {\n [DeviceType.Camera]: ReolinkCamera,\n [DeviceType.Hub]: ReolinkHub,\n }\n\n /**\n * Short-lived cache that bridges `testCreationField` and\n * `onCreateDevice` — Slice 15 socket reuse. Both code paths key off\n * `AutodetectCache.keyFor({host, username, password, uid, transport})`\n * so a Test click and the subsequent Save share one Baichuan login.\n */\n private readonly autodetectCache = new AutodetectCache()\n\n constructor() { super({}) }\n\n /**\n * Reolink stableId derivation — uniform `mac → host(IP)` chain for\n * every device type (camera, NVR, Home Hub).\n *\n * Priority:\n * 1. mac — hardware MAC from post-login `getNetworkInfo` (cmd 76+93,\n * no channel → host's interface). Durable across firmware\n * updates + DHCP renewals. Persisted in `deviceCache.mac`\n * by `onCreateDevice` AFTER `enrichHostIdentifiersFromLiveApi`.\n * 2. host — operator-typed IP/hostname (DHCP-reservable). Always\n * present on Reolink adds (`onCreateDevice` validates\n * `persistHost` is non-empty before this code path).\n *\n * The legacy uid / serialNumber / autodetect deviceInfo data are NOT\n * used for the stable key — they're often unreliable on the lib's\n * TCP path (e.g. Home Hub returns `serialNumber: \"00000000000000\"`\n * sentinel + the lib substitutes channel-0 data into `deviceInfo`\n * when host probes flap mid-handshake). The mac/host chain is uniform\n * and works on every Reolink device the lib supports.\n *\n * Re-adopt safety: re-adding the same physical device with its mac\n * resolved reuses the same row. If the mac wasn't resolved (rare —\n * `getNetworkInfo` works on every Reolink firmware we've tested),\n * the host fallback at least gives a stable row per IP — operator\n * can DHCP-reserve to make this durable.\n */\n protected override generateStableId(_type: DeviceType, config?: Record<string, unknown>): string {\n const cfg = config ?? {}\n const cache = (cfg['deviceCache'] as Record<string, unknown> | undefined) ?? {}\n const macRaw = typeof cache['mac'] === 'string' ? cache['mac'].trim() : ''\n const mac = macRaw.replace(/[^a-zA-Z0-9]/g, '').toLowerCase()\n // No addon-id prefix — the deviceMeta row key already namespaces by\n // addon (`<addonId>:<stableId>`), so prepending the addon id inside\n // the stable id duplicates it.\n if (isMeaningfulIdentifier(mac)) return `mac-${mac}`\n const host = typeof cfg['host'] === 'string' ? cfg['host'].trim() : ''\n if (host.length > 0) {\n // Sanitize host (replace dots/colons with dashes) so the row key\n // stays a flat slug — keeps DB inspection + log scans readable.\n const slug = host.toLowerCase().replace(/[^a-z0-9]+/g, '-').replace(/^-+|-+$/g, '')\n if (slug.length > 0) return `host-${slug}`\n }\n // Both mac and host missing — happens only when upstream validation\n // skipped (shouldn't reach here in normal flows). Emit alert so the\n // operator sees the failure mode + throw to refuse the row.\n const hostLabel = host || '(unknown host)'\n const reason = `Reolink autodetect on ${hostLabel} resolved neither mac nor host address — cannot persist a stable row key. Verify network reachability + credentials, then retry.`\n try {\n const now = Date.now()\n void this.ctx.api?.alerts?.emit?.mutate?.({\n id: `reolink-no-stableid-${now}`,\n category: 'addon.error',\n severity: 'error',\n title: `Reolink: missing identifier on ${hostLabel}`,\n message: reason,\n status: 'active',\n read: false,\n createdAt: now,\n updatedAt: now,\n source: { type: 'addon', id: this.addonId },\n metadata: { host: hostLabel },\n })\n } catch { /* alerts cap optional — never block the throw */ }\n throw new Error(`Reolink: ${reason}`)\n }\n\n protected override async onShutdown(): Promise<void> {\n await this.autodetectCache.dispose()\n }\n\n protected override async onInitialize(): Promise<ProviderRegistration[]> {\n const regs = await super.onInitialize()\n\n // Re-publish on broker ready (handles boot-order races + broker restart).\n this.subscribe(\n { category: EventCategory.SystemReadyState },\n (event) => {\n if (!isStreamBrokerReady(event)) return\n void this.republishAll().catch((err: unknown) => {\n this.ctx.logger.warn('Failed to re-publish Reolink streams after broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n },\n )\n\n // Native streams are exposed as `pull-rfc4571` — the broker pulls\n // from a loopback TCP server the camera spawns at publish time,\n // and the lib's RFC 4571 server manages its own upstream Baichuan\n // socket lifecycle (idle teardown after ~15s with no clients).\n //\n // On strict on-demand cycles the broker's loopback dial may hit\n // ECONNREFUSED — the lib's TCP listener idle-tore-down between\n // dials. The broker then emits `onRequestStreamSourceRefresh`;\n // we re-run `publishToBroker` (idempotent — `ensureRfc4571Server`\n // self-heals if the cached entry's `server.listening === false`,\n // recreates on a fresh ephemeral port, and re-publishes the new\n // URL via `streamBroker.publishCameraStream`). The broker's\n // `sourceProvider` resolver picks the fresh URL up on the next\n // reconnect tick.\n this.subscribe(\n { category: EventCategory.StreamBrokerOnRequestStreamSourceRefresh },\n (event) => {\n const data = event.data as { deviceId?: number; camStreamId?: string }\n const deviceId = typeof data.deviceId === 'number' ? data.deviceId : null\n if (deviceId === null) return\n void this.refreshDeviceStreams(deviceId, data.camStreamId).catch((err: unknown) => {\n this.ctx.logger.warn('refresh of Reolink stream failed', {\n tags: { deviceId },\n meta: {\n camStreamId: data.camStreamId ?? null,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n })\n },\n )\n\n return regs\n }\n\n /**\n * Handle a broker-issued source-refresh request. With the lazy-publish\n * model the broker always emits this on first dial of a\n * `lazy:rfc4571:` placeholder URL — and re-emits it whenever the\n * upstream rfc4571 socket idle-tears-down between consumer windows.\n * We respond by materializing ONLY the requested camStream's upstream\n * Baichuan session (`materializeStreamSocket`) — never republishing\n * the full stream list. That keeps the firmware-side session count at\n * \"one per actively-watched (channel, profile) tuple\" and avoids\n * burning sessions on unrelated streams.\n *\n * The legacy fallback (when `camStreamId` is missing) re-publishes\n * the full lazy stream list — the broker can then re-issue refresh\n * for whichever stream needs it.\n */\n private async refreshDeviceStreams(deviceId: number, camStreamId: string | undefined): Promise<void> {\n const dev = this.ctx.kernel.deviceRegistry?.getById(deviceId)\n if (!dev || !(dev instanceof ReolinkCamera)) return\n if (camStreamId) {\n this.ctx.logger.info('broker requested rfc4571 source refresh — materializing on demand', {\n tags: { deviceId },\n meta: { camStreamId },\n })\n await dev.materializeStreamSocket(camStreamId)\n return\n }\n this.ctx.logger.info('broker requested full source refresh — republishing lazy entries', {\n tags: { deviceId },\n })\n await dev.publishToBroker()\n }\n\n // ── Creation ─────────────────────────────────────────────────────────\n\n protected async onGetCreationSchema(type: DeviceType): Promise<ConfigUISchema | null> {\n // Camera + Hub share the same creation form: same fields\n // (host/port/credentials/UID/transport/debug). The autodetect\n // probe inside `onCreateDevice` is what decides which kind we\n // actually instantiate from the form values.\n if (type !== DeviceType.Camera && type !== DeviceType.Hub) return null\n return buildCreationFormSchema()\n }\n\n protected async onCreateDevice(type: DeviceType, config: Record<string, unknown>): Promise<CreateDeviceSpec> {\n if (type !== DeviceType.Camera && type !== DeviceType.Hub) {\n throw new Error(`Reolink provider does not support device type: ${type}`)\n }\n\n const name = getString(config, 'name').trim()\n const inputs = buildAutodetectInputs(config)\n\n if (!name) throw new Error('Camera name is required')\n if (!inputs.password) throw new Error('Password is required')\n if (!inputs.host && inputs.transport === 'tcp') {\n throw new Error('Camera IP is required for TCP mode')\n }\n if (!inputs.host && !inputs.uid) {\n throw new Error('Either a host or a UID is required')\n }\n\n // Reuse the autodetect result from a recent Test click when possible —\n // a fresh Baichuan login on every Save adds 1-3s of latency for no\n // benefit. Cache miss falls through to a server-side autodetect.\n const cacheKey = AutodetectCache.keyFor(inputs)\n let detection = this.autodetectCache.take(cacheKey)\n if (!detection) {\n this.ctx.logger.info('autodetect cache miss — running server-side', {\n tags: { host: inputs.host || '(uid-only)' },\n meta: { transport: inputs.transport, hasUid: Boolean(inputs.uid) },\n })\n detection = await autoDetectDeviceType({\n host: inputs.host,\n username: inputs.username,\n password: inputs.password,\n ...(inputs.uid ? { uid: inputs.uid } : {}),\n mode: inputs.transport,\n })\n } else {\n this.ctx.logger.info('autodetect cache hit — reusing api', {\n tags: { host: inputs.host || '(uid-only)' },\n meta: { type: detection.type, transport: detection.transport },\n })\n }\n\n // Surface the autodetect result so operators can diagnose why a\n // multi-channel device is being created as a single Camera (or\n // vice-versa). Critical when the autodetect runs on the Save\n // path with no probe logger wired — without this line there's\n // no record of what `detection.type` resolved to.\n this.ctx.logger.info('autodetect resolved', {\n tags: { host: inputs.host || '(uid-only)' },\n meta: {\n type: detection.type,\n transport: detection.transport,\n channelNum: detection.channelNum ?? null,\n model: detection.deviceInfo?.type ?? null,\n hasUid: Boolean(detection.uid),\n },\n })\n\n if (detection.type === 'multifocal') {\n try { await detection.api.close({ reason: 'unsupported-type' }) } catch { /* ignore */ }\n throw new Error(\n 'Multi-focal cameras (autotrack / dual-lens) are not yet supported. The current build creates one device per camera; multi-focal needs per-lens device split (planned for a later slice).',\n )\n }\n\n await this.applyNvrCgiSanityCheck(detection, inputs.host || '(uid-only)')\n\n // ── Host identifier enrichment ─────────────────────────────────────\n // The lib's TCP autodetect can mis-attribute channel-0 data to the\n // host (when host cmd 80 + cmd 199 hit ECONNRESET on Reolink Home\n // Hub firmwares, the lib falls back to cmd 318 ch=0). It also\n // doesn't populate `hostNetworkInfo` on the TCP path. Re-probe the\n // HOST (channel=undefined ⇒ cmd 80, getNetworkInfo) post-login on\n // the stable socket so generateStableId reads the actual host's\n // identifiers, not a sub-channel's. See the function docblock for\n // the complete failure-mode breakdown.\n await enrichHostIdentifiersFromLiveApi(detection, this.ctx.logger)\n\n const transport: 'tcp' | 'udp' = detection.transport\n const detectedUid = detection.uid && detection.uid.length > 0 ? detection.uid : inputs.uid\n const channelCount = detection.type === 'nvr' && typeof detection.channelNum === 'number'\n ? Math.max(1, detection.channelNum)\n : 1\n\n const persistHost = (inputs.host || detection.hostNetworkInfo?.ip || '').trim()\n if (!persistHost) {\n try { await detection.api.close({ reason: 'no-host-resolvable' }) } catch { /* ignore */ }\n throw new Error('autodetect resolved the device but did not return a host address')\n }\n\n // ── NVR / Hub branch ───────────────────────────────────────────────\n // Autodetect saw a multi-channel device. Persist as DeviceType.Hub\n // (ReolinkHub class) regardless of the operator's type pick — the\n // Hub class is the only one that knows how to manage child\n // discovery + channel routing. The form is shared so both type\n // selections land here without UI duplication.\n if (detection.type === 'nvr') {\n const hubParsed = reolinkHubSchema.parse({\n host: persistHost,\n port: inputs.port,\n username: inputs.username,\n password: inputs.password,\n transport,\n ...(detectedUid ? { uid: detectedUid } : {}),\n deviceCache: {\n deviceType: 'nvr' as const,\n channelCount,\n ...(detection.deviceInfo?.type ? { model: detection.deviceInfo.type } : {}),\n ...(detection.deviceInfo?.serialNumber ? { serialNumber: detection.deviceInfo.serialNumber } : {}),\n ...(detection.deviceInfo?.firmwareVersion ? { firmwareVersion: detection.deviceInfo.firmwareVersion } : {}),\n ...(detection.deviceInfo?.hardwareVersion ? { hardwareVersion: detection.deviceInfo.hardwareVersion } : {}),\n ...(detection.hostNetworkInfo?.mac ? { mac: detection.hostNetworkInfo.mac } : {}),\n probedAt: Date.now(),\n },\n })\n const detectedApi = detection.api\n return {\n meta: { type: DeviceType.Hub, name },\n config: hubParsed,\n onAfterCreate: async (device: IDevice) => {\n if (device instanceof ReolinkHub) {\n device.adoptApi(detectedApi)\n }\n },\n }\n }\n\n const parsed = reolinkCameraSchema.parse({\n host: persistHost,\n port: inputs.port,\n username: inputs.username,\n password: inputs.password,\n transport,\n ...(detectedUid ? { uid: detectedUid } : {}),\n ...(detection.udpDiscoveryMethod ? { udpDiscoveryMethod: detection.udpDiscoveryMethod } : {}),\n deviceCache: {\n deviceType: detection.type,\n channelCount,\n hasBattery: detection.hasBattery === true,\n ...(detection.deviceInfo?.type ? { model: detection.deviceInfo.type } : {}),\n ...(detection.deviceInfo?.serialNumber ? { serialNumber: detection.deviceInfo.serialNumber } : {}),\n ...(detection.deviceInfo?.firmwareVersion ? { firmwareVersion: detection.deviceInfo.firmwareVersion } : {}),\n ...(detection.deviceInfo?.hardwareVersion ? { hardwareVersion: detection.deviceInfo.hardwareVersion } : {}),\n ...(detection.hostNetworkInfo?.mac ? { mac: detection.hostNetworkInfo.mac } : {}),\n // NOTE: do NOT set `probedAt` here. `probedAt` is the gate\n // `ensureFeaturesProbed()` uses to decide whether the\n // post-login features probe has actually completed. Setting\n // it from the autodetect cache (which only knows the static\n // `deviceType` / `channelCount` and never resolves\n // `hasPtz` / `hasIntercom` / etc) locks out hub-children\n // forever — their `ensureApi()` short-circuits to the\n // parent's socket so the per-login probe path never fires,\n // and the Hub's adopt flow's `ensureFeaturesProbed()` then\n // early-returns. Only `probeAndPersistFeatures` should\n // write `probedAt`, and only on a successful probe.\n },\n })\n\n // Capture the autodetect API + ctx in closure so the\n // `onAfterCreate` hook (fired by BaseDeviceProvider after the\n // device is fully registered + accessories spawned) can adopt\n // the live socket and seed the orchestrator. publishToBroker\n // moves into the camera's own `onCreated` lifecycle hook —\n // see `ReolinkCamera.onCreated()`.\n const detectedApi = detection.api\n const ctx = this.ctx\n\n return {\n meta: { type: DeviceType.Camera, name },\n config: parsed,\n onAfterCreate: async (device: IDevice) => {\n if (device instanceof ReolinkCamera) {\n device.adoptApi(detectedApi)\n }\n // Seed the orchestrator's per-device settings with\n // `motionSources: ['onboard']`. Best-effort.\n void ctx.api?.pipelineOrchestrator?.applyDeviceSettingsPatch?.mutate({\n deviceId: device.id,\n patch: { motionSources: ['onboard'] },\n }).catch((err: unknown) => {\n ctx.logger.debug('applyDeviceSettingsPatch motionSources seed failed', {\n tags: { deviceId: device.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n },\n }\n }\n\n // ── NVR / Hub CGI sanity check ───────────────────────────────────────\n //\n // Lib-side autodetect reads `support.channelNum` from\n // `getSupportInfo` (Baichuan cmd 199). On Reolink Home Hub firmwares\n // — and on any device whose Baichuan socket flaps mid-probe (the\n // ECONNRESET storm we sometimes see during `getSupportInfo` /\n // `getInfo cmd80`) — the lib falls back to channelNum=1 and\n // classifies as a \"regular camera\". The CGI `GetChannelstatus` is\n // the authoritative channel count and survives socket flap because\n // it's HTTP, not Baichuan. When the lib classified as\n // `camera` / `battery-cam` but CGI reports >1 channel, override\n // to NVR so the device is created as a Hub with discovery instead\n // of as a single-stream camera locked on channel 0.\n //\n // Used by both `onCreateDevice` (Save path) AND `testCreationField`\n // (Test button) so the modal preview matches the Save outcome.\n private async applyNvrCgiSanityCheck(\n detection: AutoDetectResult,\n hostLabel: string,\n ): Promise<void> {\n if (detection.type !== 'camera' && detection.type !== 'battery-cam') return\n try {\n const summary = await detection.api.getNvrChannelsSummary({ source: 'cgi', timeoutMs: 4000 })\n if (summary.channels.length > 1) {\n this.ctx.logger.info('autodetect: CGI override → NVR (multi-channel)', {\n tags: { host: hostLabel },\n meta: {\n libType: detection.type,\n libChannelNum: detection.channelNum ?? null,\n cgiChannels: summary.channels,\n cgiDeviceCount: summary.devices.length,\n },\n })\n ;(detection as { type: string; channelNum?: number }).type = 'nvr'\n ;(detection as { type: string; channelNum?: number }).channelNum = summary.channels.length\n }\n } catch (err) {\n this.ctx.logger.debug('autodetect CGI sanity-check failed (keeping lib type)', {\n tags: { host: hostLabel },\n meta: { libType: detection.type, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // ── Field probing ────────────────────────────────────────────────────\n\n override async testCreationField(input: {\n type: DeviceType\n key: string\n value: unknown\n formValues?: Record<string, unknown>\n }): Promise<FieldProbeResult> {\n if (input.key !== 'host') {\n return { status: 'ok', labels: ['no probe available'] }\n }\n\n const form = input.formValues ?? { host: typeof input.value === 'string' ? input.value : '' }\n const inputs = buildAutodetectInputs(form)\n\n if (!inputs.password) {\n return { status: 'error', error: 'Enter a password before running the test' }\n }\n if (!inputs.host && !inputs.uid) {\n return { status: 'error', error: 'Provide a host or a UID before running the test' }\n }\n\n // The Add-Device modal generates a `_probeRequestId` per dialog\n // open and tucks it into formValues for every test call. We pass\n // it through to a child logger so the lib's diagnostic lines\n // (Baichuan socket connect, login, profile probe) flow into the\n // log stream the modal is subscribed to. Operators see live socket\n // logs as the test runs instead of staring at a spinner.\n const probeRequestId = typeof form['_probeRequestId'] === 'string'\n ? (form['_probeRequestId'] as string)\n : undefined\n\n const probeLogger = probeRequestId\n ? this.ctx.logger.child('autodetect').withTags({ requestId: probeRequestId })\n : this.ctx.logger.child('autodetect')\n\n try {\n const result = await autoDetectDeviceType({\n host: inputs.host,\n username: inputs.username,\n password: inputs.password,\n ...(inputs.uid ? { uid: inputs.uid } : {}),\n mode: inputs.transport,\n logger: buildAutodetectLibLogger(probeLogger),\n })\n // Mirror onCreateDevice's CGI sanity check so the operator\n // sees the same classification in the modal preview as they'd\n // get on Save. Without this, `getSupportInfo` flakiness\n // (ECONNRESET mid-probe on some hub firmwares) shows the\n // device as a regular camera even when CGI clearly reports\n // multi-channel.\n await this.applyNvrCgiSanityCheck(result, inputs.host || '(uid-only)')\n const cacheKey = AutodetectCache.keyFor(inputs)\n this.autodetectCache.set(cacheKey, result)\n return { status: 'ok', labels: buildAutodetectLabels(result) }\n } catch (err) {\n return {\n status: 'error',\n error: err instanceof Error ? err.message : String(err),\n }\n }\n }\n\n // ── Restore ──────────────────────────────────────────────────────────\n //\n // Default `BaseDeviceProvider.onRestoreDevices` impl:\n // 1. Iterates `savedDevices`, skips children (parentDeviceId !==\n // null) — those are spawned by their parent's\n // `getAccessoryChildren()` after the parent's `register`.\n // 2. For each parent, looks up the class in `deviceClasses`\n // (`{Camera: ReolinkCamera}`) and calls `kernel.devices.create`.\n // 3. Inside `create` → `register` → `device.onCreated()` →\n // `device.getAccessoryChildren()` → kernel auto-spawns each\n // accessory child.\n //\n // The accessory factory closure (`new SirenAccessory(ctx, this)`)\n // lives in `ReolinkCamera.getAccessoryChildren()`, which captures\n // the parent reference for the child's `(ctx, parent)` constructor.\n // Provider has zero accessory boilerplate.\n\n // ── Internal ─────────────────────────────────────────────────────────\n\n private async republishAll(): Promise<void> {\n const own = this.ctx.kernel.deviceRegistry?.getAllForAddon(this.addonId) ?? []\n let published = 0\n for (const dev of own) {\n if (!(dev instanceof ReolinkCamera)) continue\n try {\n await dev.publishToBroker()\n published++\n } catch (err) {\n this.ctx.logger.debug('publishToBroker threw during republish', {\n tags: { deviceId: dev.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n if (published > 0) {\n this.ctx.logger.info('Re-published Reolink streams to stream-broker', {\n meta: { published, total: own.length },\n })\n }\n }\n}\n","import {\n BaseDevice,\n DeviceType,\n DeviceFeature,\n EventCategory,\n AccessoryKind,\n ACCESSORY_LABEL,\n createEvent,\n hydrateSchema,\n snapshotCapability,\n ptzCapability,\n ptzAutotrackCapability,\n type PtzAutotrackSettings,\n type PtzAutotrackStatus,\n type PtzAutotrackRuntimeState,\n createRuntimeStateBridge,\n rebootCapability,\n batteryCapability,\n doorbellCapability,\n motionCapability,\n intercomCapability,\n type AccessoryKindValue,\n type BatteryStatus,\n} from '@camstack/types'\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n ICameraDevice,\n StreamSourceEntry,\n CamProfile,\n InferNativeProvider,\n FeatureProbeStatus,\n} from '@camstack/types'\nimport { createAccessoryDevice } from './accessories/index.js'\nimport { populateReolinkMetadata } from './metadata-populator.js'\nimport { randomBytes } from 'node:crypto'\nimport {\n createRfc4571TcpServer,\n ReolinkBaichuanApi,\n type AiConfig,\n type Rfc4571TcpServer,\n} from '@apocaliss92/nodelink-js'\n\n/** Generate a short random hex token for per-stream RTSP-style credentials. */\nfunction randomToken(bytes: number): string {\n return randomBytes(bytes).toString('hex')\n}\n\n/**\n * Coerce the lib-reported codec string to the canonical broker form.\n * Reolink firmwares vary: some report `\"H.265\"` (with dot), some\n * `\"H265\"`, some `\"hevc\"`, some `\"H.264\"`. The broker registry and\n * WebRTC session-negotiation expect strict `\"h265\"` / `\"h264\"` —\n * anything else falls through to the H.264 branch and the dotted\n * `h.265` was opening Chrome with the wrong decoder. Strip every\n * non-alphanumeric and lowercase, then map the known synonyms.\n *\n * Returns `undefined` when the input is empty so callers can fall\n * back to their own default branch (e.g. `server.videoType`).\n */\nfunction normalizeCodecName(raw: string | undefined | null): string | undefined {\n if (!raw) return undefined\n const collapsed = raw.toLowerCase().replace(/[^a-z0-9]/g, '')\n if (!collapsed) return undefined\n if (collapsed === 'hevc') return 'h265'\n if (collapsed === 'avc') return 'h264'\n return collapsed\n}\nimport {\n reolinkCameraSchema,\n REOLINK_ADDON_ID,\n DEFAULT_CHANNEL,\n AI_CLASS_MAP,\n type ReolinkSocketDebugFlag,\n type ReolinkCameraConfig,\n} from './schema.js'\nimport {\n streamLabel,\n buildStreamIds,\n buildCamStreamId,\n parseCamStreamId,\n} from './stream-routing.js'\nimport { isRecoverableBaichuanError } from './error-classifier.js'\nimport {\n IntercomOrchestrator,\n type IntercomAudioCodecApi,\n} from './intercom-orchestrator.js'\nimport { WeriftIntercomPeer } from './intercom-webrtc-peer.js'\nimport {\n captureSessionsSnapshot,\n buildSessionsTabSections as buildSessionsTabSectionsShared,\n type SessionsSnapshot,\n} from './sessions-snapshot.js'\nimport {\n buildSyntheticRfc4571Sdp,\n buildLazyRfc4571Url,\n} from './synthetic-sdp.js'\n\n/**\n * Narrow shape of the `audio-codec` cap router on `ctx.api`. We type\n * only the four methods the intercom orchestrator drives (decode +\n * push + pull + close) — the full router carries encode + listing\n * methods we don't need here. Mirrors the broker's\n * `audio-codec-proxy.ts` pattern: keep the consumer-facing surface\n * narrow rather than coupling to the generated tRPC router type.\n */\ninterface AudioCodecRouterShape {\n createDecodeSession: { mutate: (input: {\n codec: string\n sourceSampleRate: number\n sourceChannels: number\n targetSampleRate: number\n targetChannels: number\n tag?: string\n }) => Promise<{ sessionId: string; nodeId: string }> }\n pushEncodedFrame: { mutate: (input: {\n sessionId: string\n nodeId?: string\n data: Uint8Array\n pts?: number\n }) => Promise<void> }\n pullPcm: { query: (input: {\n sessionId: string\n nodeId?: string\n maxCount: number\n }) => Promise<readonly { data: Uint8Array; sampleRate: number; channels: number; pts: number }[]> }\n closeSession: { mutate: (input: { sessionId: string; nodeId?: string }) => Promise<void> }\n}\n\nexport {\n reolinkCameraSchema,\n REOLINK_ADDON_ID,\n ReolinkDeviceTypeSchema,\n ReolinkUdpDiscoveryMethodSchema,\n} from './schema.js'\n\n/**\n * One running RFC 4571 TCP server per native (channel, profile) tuple,\n * keyed by camStreamId (e.g. `native:main`). The lib spins up a loopback\n * TCP server that emits framed RTP; the broker pulls from it as a\n * `pull-rfc4571` source. The lib internally manages the underlying\n * Baichuan socket — opening it on first client connect, closing on\n * idle — so battery cams stay asleep when nobody's watching.\n */\ninterface ActiveStream {\n readonly camStreamId: string\n readonly profile: CamProfile\n readonly server: Rfc4571TcpServer\n}\n\n/**\n * Per-device transient diagnostics blob populated from the lib's\n * `getOnlineUserSessionsForUi` + `getSocketPoolSummary` +\n * `getSocketPoolCooldownStatus` calls. NOT persisted — recomputed on\n * demand and shown in the device's \"Sessions\" tab. The aggregator UI\n * polls the device aggregate every ~2.5s and a stale snapshot triggers\n * a background refresh; the operator can also force one via the\n * tab's Refresh button (`_refreshSessions` patch sentinel).\n */\n// Sessions diagnostic types + helpers live in `sessions-snapshot.ts`\n// (shared with `ReolinkHub`).\n\n/**\n * Reolink-specific flag bag stored in the `feature-probe` runtime-state\n * slice. Drivers' `getProbeFlags<T>()` casts the open record to this\n * shape — undefined means \"the probe didn't fill it\" (firmware quirk\n * or pre-probe read).\n */\ntype ReolinkProbeFlags = {\n hasBattery?: boolean\n hasPtz?: boolean\n hasIntercom?: boolean\n hasDoorbell?: boolean\n hasFloodlight?: boolean\n hasSiren?: boolean\n hasPirSensor?: boolean\n hasAutotrack?: boolean\n} & Record<string, unknown>\n\n/**\n * Reolink camera device — connects via Baichuan protocol and pushes\n * Annex-B H.264/H.265 directly to the stream broker.\n *\n * Lifecycle (Slice 1):\n * 1. `publishToBroker()` registers `main` + `sub` cam streams as\n * `kind: 'push-annexb'` so the broker emits demand events.\n * 2. The owning addon listens for `stream-broker.onCamStreamDemand`\n * events and forwards them to `onCamStreamDemand()` here.\n * 3. We login (lazy), open a `BaichuanVideoStream` for the requested\n * stream, fetch the broker handle, and forward every\n * `videoAccessUnit` event as `pushEncodedPacket`.\n * 4. On `onCamStreamIdle()` we stop the stream. The Baichuan socket\n * stays alive while at least one stream is active — battery-aware\n * sleep handling lands in Slice 3.\n */\nexport class ReolinkCamera\n extends BaseDevice<typeof reolinkCameraSchema>\n implements ICameraDevice\n{\n readonly type = DeviceType.Camera as const\n\n /**\n * Features derived from the post-probe `feature-probe` runtime-state\n * slice. Surfaced via `device-manager.getDevice` so any service in\n * the cluster (stream-broker, snapshot orchestrator, pipeline-runner)\n * can derive policy from a single source.\n *\n * Returns a fresh array on each read so consumers can't mutate the\n * underlying state. The set is small (≤6 entries) so allocation cost\n * is negligible vs the staleness of caching.\n */\n get features(): readonly DeviceFeature[] {\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n const out: DeviceFeature[] = [DeviceFeature.NativeSnapshot, DeviceFeature.Rebootable]\n if (probe.hasBattery === true) out.push(DeviceFeature.BatteryOperated)\n if (probe.hasPtz === true) out.push(DeviceFeature.PanTiltZoom)\n if (probe.hasAutotrack === true) out.push(DeviceFeature.PtzAutotrack)\n if (probe.hasIntercom === true) out.push(DeviceFeature.TwoWayAudio)\n if (probe.hasDoorbell === true) out.push(DeviceFeature.DoorbellButton)\n return out\n }\n\n /** Lazy-connected Baichuan API. Spans the lifetime of every active stream. */\n private api: ReolinkBaichuanApi | null = null\n /**\n * Stable bound handler for `api.onSimpleEvent` — created ONCE in\n * the class so every `onSimpleEvent` / `offSimpleEvent` call uses\n * the SAME function reference. The lib's `simpleEventListeners`\n * is a `Set` and dedupes by reference, so passing inline arrows\n * `(e) => this.handleSimpleEvent(e)` on each subscribe call (as\n * the previous code did across login / adoptApi / watchdog\n * paths) silently accumulated multiple listeners — same event\n * dispatched N times on the camstack side, and `offSimpleEvent`\n * couldn't remove the old one without the original ref. Net\n * effect was that subscriptions drifted out of sync with the\n * camera's TCP push channel: device 15 (Daniel) went silent for\n * 2+ hours while device 8 (battery doorbell) kept pushing\n * normally because its short connection lifecycle naturally\n * reset the listener set.\n */\n private readonly handleSimpleEventBound = (event: import('@apocaliss92/nodelink-js').ReolinkSimpleEvent): void => {\n this.handleSimpleEvent(event)\n }\n /**\n * Plugin-level event-health interval — fallback layer over the\n * lib's internal 5-minute watchdog. Mirrors Scrypted's\n * `startEventCheck` (`baichuan-base.ts:719-783`): every 60 s,\n * if `eventSubscriptionActive` is false on a live socket OR no\n * event has arrived in 10 min, force a full unsub/resub cycle.\n */\n private eventHealthCheckTimer: ReturnType<typeof setInterval> | null = null\n /**\n * Counter of consecutive event-health re-subscribes that did NOT\n * recover an event. Drives the log-level degradation (first one is\n * `info` so operators see something; the long tail drops to\n * `debug`) and the polling backoff (60s → 5min → 15min so a\n * chronically-silent camera doesn't spam the logs every minute).\n * Reset to 0 the moment a simpleEvent arrives.\n */\n private consecutiveStaleHealthChecks = 0\n /** Wall-clock ms when the next health check is allowed to fire.\n * Used by `runEventHealthCheckTick` to short-circuit between\n * backoff windows without changing the underlying interval. */\n private nextEventHealthCheckAt = 0\n /** Active video streams keyed by camStreamId. */\n private readonly active = new Map<string, ActiveStream>()\n /** Login lock — concurrent demand events must not race the login flow. */\n private loginPromise: Promise<ReolinkBaichuanApi> | null = null\n /** True once we've discovered (via getBatteryInfo) that this is a battery cam. */\n private isBattery = false\n /** Reconnect attempt counter — drives exponential backoff. */\n private reconnectAttempts = 0\n /** Pending reconnect timer; cleared on successful reconnect or removeDevice. */\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n /**\n * Read-through to the `battery.sleeping` runtime-state slice with\n * a `false` default for non-battery cams (no slice). Internal\n * shorthand — driver code uses `this.state.battery.sleeping = b` to write\n * (proxy on `BaseDevice`); reads via this getter to avoid sprinkling\n * `?? false` everywhere conditional logic checks the flag.\n */\n private get sleeping(): boolean {\n return this.state.battery.sleeping ?? false\n }\n /**\n * Wall-clock ms when `this.sleeping` last flipped. Drives a\n * hysteresis window that filters out the lib's UDP sleep-inference\n * flapping. The lib runs an internal `getSleepStatus` every 2s on\n * UDP/battery cameras and emits awake/sleeping events from socket\n * I/O patterns alone — those events do NOT reflect real firmware\n * state, especially on battery cams with idle-disconnect where\n * socket activity drops periodically. Without this window every\n * inferred flip would close active streams + emit a cap event.\n */\n private sleepStateChangedAt = 0\n /**\n * Min time between observed sleep transitions before we honour\n * another flip. Picked >= the lib's full UDP inference cycle\n * (~12s on battery cams with idle-disconnect) so a single\n * inference flap doesn't pass through. 30s is also longer than\n * `getIdleDisconnectTimeoutMs` (default 30s in the lib), which\n * means a transient socket close → reopen pattern can't drive\n * us through a full state cycle either.\n */\n private static readonly SLEEP_HYSTERESIS_MS = 30_000\n /** Background timer that runs the passive sleep poll (battery cams only). */\n private sleepPollTimer: ReturnType<typeof setInterval> | null = null\n\n /** Periodic timer driving `alignAuxDevicesState()` on wired cams.\n * Battery cams use the wake transition path instead. */\n private alignAuxTimer: ReturnType<typeof setInterval> | null = null\n\n /** Aux accessory references registered by spawned children\n * (siren / floodlight / pir). The parent's centralised align\n * method iterates these to refresh their slices on a single\n * cadence — replaces the old per-accessory setInterval. Mirrors\n * scrypted-reolink-native's `motionSiren` / `floodlight` / `pir`\n * field refs + their `alignAuxDevicesState` method. */\n private readonly auxAccessoryRefs = new Map<string, import('./accessories/base.js').ReolinkAccessoryRef>()\n /**\n * Reliability watchdogs (Slice 10). Both timers are armed after a\n * successful login (`ensureApi` / `adoptApi`) and torn down on\n * `disconnectAll` / `removeDevice`.\n *\n * - `pingWatchdogTimer` — every 30s `api.ping()`s the live socket;\n * 3 consecutive failures tear down the api so the next demand\n * triggers a fresh login (recovers from D2C_DISC / ECONNRESET\n * storms that would otherwise leave a half-open socket).\n * - `eventWatchdogTimer` — every 60s checks how long since the last\n * simple event; 10 minutes silent → unsubscribe + resubscribe\n * (plugin-level fallback to the library's own watchdog).\n */\n private pingWatchdogTimer: ReturnType<typeof setInterval> | null = null\n private consecutivePingFailures = 0\n private eventWatchdogTimer: ReturnType<typeof setInterval> | null = null\n /** Wall-clock ms when the most-recent simple event arrived; 0 = none yet. */\n private lastEventAt = 0\n /** Wall-clock ms when the watchdogs first armed (used as the no-event baseline). */\n private watchdogStartedAt = 0\n /** True once we've registered the battery capability provider. */\n private batteryRegistered = false\n /**\n * Cam-stream ids we've successfully published to the broker. Used by\n * `publishToBroker` to retract entries that fall out of the camera's\n * offer between probes, and by `removeDevice` to clean up exhaustively\n * (instead of guessing from a synthetic id list).\n */\n private readonly publishedStreamIds = new Set<string>()\n /**\n * Transient diagnostics for the device's \"Sessions\" settings tab.\n * `null` until the first refresh completes. Re-populated ONLY by\n * `refreshSessionsSnapshot()` triggered by the operator clicking\n * the tab's Refresh button (`_refreshSessions` sentinel patch in\n * `applySettingsPatch`). The earlier auto-fetch on stale was\n * removed because `getOnlineUserList` (cmd 120) wakes a sleeping\n * battery cam — and the Settings panel polls every ~2.5s, which\n * was keeping the doorbell awake just by leaving the tab open.\n */\n private sessionsSnapshot: SessionsSnapshot | null = null\n /** Single-flight guard so concurrent reads share one round-trip. */\n private sessionsRefreshInFlight: Promise<void> | null = null\n // Snapshot caching, rate-limiting, and sleep-aware fallback live in\n // the builtin `snapshot.addon.ts` wrapper (`packages/core/src/builtins/\n // snapshot`). The wrapper already consults the device's `battery` cap\n // for sleep state and serves cached frames instead of waking the\n // camera. The provider here only exposes a raw JPEG fetch — no\n // duplication of cache or rate-limit logic at the device level.\n //\n // FOLLOW-UP (Slice 13 quick wins): register the `battery` native cap\n // for battery-flagged Reolink devices so the wrapper's sleep gate\n // actually fires (today `isDeviceSleeping` returns false for Reolink\n // cams because the cap is missing).\n\n constructor(ctx: DeviceContext) {\n super(ctx, reolinkCameraSchema, { type: DeviceType.Camera })\n // BaseDevice constructor seeds `device-status` slice with online=false;\n // no need to re-set here.\n this.registerNativeCapabilities()\n }\n\n /**\n * Resolved Baichuan channel for per-channel cmd_ids. Children\n * under a Hub carry their channel in the persisted config (set\n * during `adoptDevice`). Standalone cameras default to 0 so the\n * pre-Hub behavior is preserved verbatim.\n */\n private getChannel(): number {\n const ch = this.config.get('channel')\n if (typeof ch === 'number' && ch >= 0) return ch\n return DEFAULT_CHANNEL\n }\n\n /**\n * `true` iff this camera is a child of a Hub. Drives the\n * connection delegation in `ensureApi` and gates the standalone\n * lifecycle (own subscription, watchdog, reconnect loop). The\n * persisted `channel` field is the discriminator — adoption sets\n * it; standalone autodetect never touches it.\n */\n private isHubChild(): boolean {\n return this.parentDeviceId !== null && typeof this.config.get('channel') === 'number'\n }\n\n /**\n * Resolve the parent Hub via the addon's device manager. Cached\n * lazily — the lookup is async but cheap, and the parent device\n * doesn't change over the camera's lifetime. Returns `null` for\n * standalone cameras.\n */\n private async getParentHub(): Promise<ReolinkHubLike | null> {\n if (!this.isHubChild()) return null\n if (this.cachedParentHub !== undefined) return this.cachedParentHub\n const all = await this.ctx.devices.getAll()\n const parent = all.find((d: { id: number }) => d.id === this.parentDeviceId)\n this.cachedParentHub = isHubLike(parent) ? parent : null\n return this.cachedParentHub\n }\n private cachedParentHub: ReolinkHubLike | null | undefined = undefined\n\n // ── Lifecycle ────────────────────────────────────────────────────────\n\n /**\n * Phase 3 (kernel-driven) — populate the `feature-probe` runtime\n * state slice. Runs ONCE after `register` and BEFORE\n * `getAccessoryChildren()`, so siren / floodlight / PIR accessory\n * spawn sees the post-probe firmware truth. Failures are swallowed\n * (transient login race / battery cam asleep); next `reprobe()` call\n * retries.\n */\n override async onProbe(): Promise<void> {\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.ctx.logger.debug('onProbe: ensureApi failed — leaving feature-probe slice unseeded', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n await this.probeAndPersistFeatures(api)\n // Hardware metadata refresh — runs once per kernel-driven probe\n // (every addon startup / restart). Covers BOTH top-level cameras\n // and hub-adopted children: the latter's `ensureApi()` short-\n // circuits to the parent hub's socket so the metadata refresh\n // wired into `ensureApi`'s post-login branch never fires for them.\n // Doing it here at the lifecycle entry point makes the refresh\n // contract uniform across both paths.\n await this.populateMetadataFromFirmware(api).catch((err: unknown) => {\n this.ctx.logger.debug('Reolink metadata populate (onProbe) failed (non-fatal)', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n\n /**\n * Phase 5 (kernel-driven) — fired after `onProbe()` + accessory\n * reconciliation. Publishes streams to the broker. Best-effort; the\n * provider's `system.ready-state` listener re-publishes if the broker\n * isn't ready yet. Skipped when the device is soft-disabled.\n */\n override async onActivate(): Promise<void> {\n if (this.disabled) {\n this.ctx.logger.info('Reolink camera disabled — skipping initial broker publish')\n return\n }\n await this.publishToBroker().catch((err: unknown) => {\n this.ctx.logger.warn('publishToBroker on activate failed — will retry on broker ready', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n // Best-effort image + motion snapshot for the parent settings\n // tabs. Each lib call is gated on a live Baichuan login; failure\n // (camera offline / 4xx) leaves the snapshot empty and the form\n // hydrates from defaults.\n void this.refreshParentSettingsSnapshot()\n }\n\n /** Debounce timestamp for the on-demand snapshot retry kicked from\n * `getSettingsUISchema`. Prevents the form open from spamming\n * refresh calls on incomplete caches (battery cam still sleeping,\n * legacy firmware that doesn't support some endpoints). */\n private lastSettingsSnapshotRetryAt = 0\n private static readonly SETTINGS_SNAPSHOT_RETRY_MIN_MS = 60_000\n\n /** True when any settings-snapshot field that drives a UI section\n * is missing from the persisted cache. Drives the on-demand retry\n * in `getSettingsUISchema`. */\n private hasIncompleteSettingsCache(): boolean {\n const cache = this.config.get('deviceCache')\n if (!cache) return true\n return cache.encSnapshot === undefined\n || cache.maskSnapshot === undefined\n || cache.audioNoiseSnapshot === undefined\n || cache.autoFocusSnapshot === undefined\n }\n\n /**\n * Probe `getVideoInput` + `getMotionAlarm` and persist into the\n * `deviceCache` snapshots. Fires once on `onCreated`; future\n * settings opens read straight from the persisted snapshot. Image\n * is readonly in the UI (lib lacks `setVideoInput`); motion is\n * writable via `setMotionAlarm` so its snapshot also drives the\n * dispatch's known-good baseline.\n */\n private async refreshParentSettingsSnapshot(): Promise<void> {\n // Battery cam sleep gate — refusing to call `ensureApi()` while\n // the cam is asleep is the difference between observing the\n // natural wake/idle cycle and forcibly waking it ourselves. Login\n // on a sleeping UDP/battery cam is itself a wake event (the lib's\n // discovery + handshake flow nudges the cam awake), so a settings\n // probe on a known-sleeping cam triggers the exact \"wake → fetch\n // → fail-some-probes → idle_disconnect → reconnect 25s later\"\n // loop seen on the doorbell. The wake-transition path\n // (`handleSimpleEvent('awake')` → `onWakeTransition`) re-runs\n // this method as soon as the cam comes up naturally.\n if (this.isBattery && this.sleeping) {\n this.ctx.logger.debug('refreshParentSettingsSnapshot skipped — battery cam is sleeping', {\n tags: { deviceId: this.id },\n })\n return\n }\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch {\n return\n }\n const channel = this.getChannel()\n const cacheUpdate: Record<string, unknown> = {}\n try {\n const video = await api.getVideoInput(channel)\n const vi = video?.body?.VideoInput\n if (vi) {\n cacheUpdate.imageSnapshot = {\n bright: typeof vi.bright === 'number' ? vi.bright : null,\n contrast: typeof vi.contrast === 'number' ? vi.contrast : null,\n saturation: typeof vi.saturation === 'number' ? vi.saturation : null,\n hue: typeof vi.hue === 'number' ? vi.hue : null,\n irCutSwap: typeof vi.irCutSwap === 'number' ? vi.irCutSwap : null,\n dayNight: typeof vi.dayNight === 'string' ? vi.dayNight : undefined,\n }\n }\n } catch (err) {\n this.ctx.logger.debug('reolink getVideoInput probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n try {\n const motion = await api.getMotionAlarm(channel)\n const md = motion?.body?.MdAlarm?.sensInfoNew\n if (md) {\n // The camera-side `sensitivity` in MdAlarm is the INVERSE\n // of the user-facing slider (Reolink mobile app + web UI\n // show 1..50; camera stores `51 - userValue`). Same as\n // reolink_aio's `set_md_sensitivity` comment. Convert\n // here so the UI slider and the persisted field always\n // hold the user-facing value.\n const rawSens = typeof md.sensitivity === 'number' ? md.sensitivity : null\n cacheUpdate.motionSnapshot = {\n enabled: md.enable === 1,\n sensitivity: rawSens != null ? 51 - rawSens : null,\n }\n }\n } catch (err) {\n this.ctx.logger.debug('reolink getMotionAlarm probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Per-AI-class sensitivity snapshot. Probes the supported\n // detect types via `getAiDetectTypes` once, then fans out\n // `getAiAlarmRaw` per type to read the current\n // sensitivity/stayTime. Each call is scoped (cmdId=342) so\n // one missing type doesn't take down the whole probe.\n try {\n const detectTypes = await api.getAiDetectTypes(channel, { timeoutMs: 1500 })\n if (detectTypes && detectTypes.length > 0) {\n cacheUpdate.aiDetectTypes = detectTypes\n const aiSnap: Record<string, { sensitivity?: number | null; stayTime?: number | null }> = {}\n for (const aiType of detectTypes) {\n try {\n const raw = await api.getAiAlarmRaw(channel, aiType, { timeoutMs: 1500 })\n const cfg = (raw as { body?: { AiDetectCfg?: { sensitivity?: unknown; stayTime?: unknown } } } | undefined)?.body?.AiDetectCfg\n aiSnap[aiType] = {\n sensitivity: typeof cfg?.sensitivity === 'number' ? cfg.sensitivity : null,\n stayTime: typeof cfg?.stayTime === 'number' ? cfg.stayTime : null,\n }\n } catch {\n // Type not actually supported on this firmware — skip.\n }\n }\n if (Object.keys(aiSnap).length > 0) cacheUpdate.aiSensitivitySnapshot = aiSnap\n }\n } catch (err) {\n this.ctx.logger.debug('reolink AI sensitivity probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Stream/Encoder snapshot (cmd_id 56). Drives Stream tab\n // defaults so the operator sees current bitrate/framerate\n // when first opening the form.\n //\n // Wire shape (verified live): `{body: {Compression: {channelId,\n // mainStream, subStream, thirdStream, ...}}}`. Each stream entry\n // carries `audio` (0/1), `width`, `height`, `frame` (NOT\n // `frameRate`), `bitRate`, `videoEncType` (0=h264, 1=h265),\n // `encoderProfile`, `gop`. The `audio` toggle is per-stream,\n // not a root-level flag — we read it from `mainStream` since\n // that's the canonical capture stream.\n try {\n const enc = await api.getEnc(channel)\n const compression = enc?.body?.Compression\n if (compression) {\n const pickStream = (s?: NonNullable<typeof compression.mainStream>): {\n bitRate: number | null\n frameRate: number | null\n videoEncType: string | null\n width: number | null\n height: number | null\n } | undefined => {\n if (!s) return undefined\n // `videoEncType` is a numeric enum on Reolink Baichuan\n // (0 = h264, 1 = h265). Map to the lowercase strings the\n // UI labels expect.\n let codec: string | null = null\n if (typeof s.videoEncType === 'number') {\n codec = s.videoEncType === 1 ? 'h265' : 'h264'\n } else if (typeof s.videoEncType === 'string') {\n codec = s.videoEncType\n }\n return {\n bitRate: typeof s.bitRate === 'number' ? s.bitRate : null,\n // Wire field is `frame`, not `frameRate`.\n frameRate: typeof s.frame === 'number' ? s.frame : null,\n videoEncType: codec,\n width: typeof s.width === 'number' ? s.width : null,\n height: typeof s.height === 'number' ? s.height : null,\n }\n }\n const mainStream = pickStream(compression.mainStream)\n const subStream = pickStream(compression.subStream)\n // Audio capture flag is per-stream on this firmware (carried on\n // each `mainStream` / `subStream` entry rather than at the\n // Compression root). Treat the main stream's flag as the\n // device-level toggle for the Settings UI.\n const audioFlag = typeof compression.mainStream?.audio === 'number'\n ? compression.mainStream.audio\n : null\n cacheUpdate.encSnapshot = {\n audio: audioFlag,\n mainStream,\n subStream,\n }\n } else {\n this.ctx.logger.warn('reolink getEnc returned no Compression block — Streams tab will hide', {\n tags: { deviceId: this.id },\n })\n }\n } catch (err) {\n this.ctx.logger.warn('reolink getEnc probe failed — Streams tab will hide', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Privacy mask snapshot (cmd_id 52). Master enable only.\n try {\n const mask = await api.getMask(channel)\n const m = mask?.body?.Shelter\n if (m) {\n cacheUpdate.maskSnapshot = {\n enabled: typeof m.enable === 'number' ? m.enable === 1 : null,\n }\n }\n } catch (err) {\n this.ctx.logger.debug('reolink getMask probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Audio noise reduction snapshot (cmd_id 439). Drives the\n // Audio noise slider default — camera reports current level.\n // Wire tag is lowercase `aiDenoise` (cf. types `AudioNoiseConfig`).\n try {\n const noise = await api.getAudioNoise(channel)\n const n = noise?.body?.aiDenoise\n if (n) {\n cacheUpdate.audioNoiseSnapshot = {\n enabled: typeof n.enable === 'number' ? n.enable === 1 : null,\n level: typeof n.level === 'number' ? n.level : null,\n }\n }\n } catch (err) {\n this.ctx.logger.debug('reolink getAudioNoise probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Auto-focus snapshot (cmd_id 224). Failure marks\n // `supported=false` so the UI section is hidden on cams\n // without focus controls. The camera-side field is `disable`\n // — invert here so the snapshot stores user-facing\n // \"enabled\" semantics.\n //\n // Sticky-skip: once a probe attempt has resolved `supported=false`\n // (fixed-focus cams like doorbells / battery bullets always 400\n // on cmd 224), don't re-issue the call on every wake. The snapshot\n // is already correct, the firmware behaviour won't change without\n // a model swap, and the firmware-400 stamp re-emits a debug log\n // every cycle. Cams that may have AF added by future firmware\n // would need an explicit re-probe trigger anyway.\n const afPrev = this.config.get('deviceCache')?.autoFocusSnapshot\n if (afPrev?.supported === false) {\n this.ctx.logger.debug('reolink getAutoFocus probe skipped — cam advertised supported=false on prior probe', {\n tags: { deviceId: this.id },\n })\n } else {\n try {\n const af = await api.getAutoFocus(channel, { timeoutMs: 1500 })\n const a = af?.body?.AutoFocus\n if (a) {\n cacheUpdate.autoFocusSnapshot = {\n enabled: typeof a.disable === 'number' ? a.disable === 0 : null,\n supported: true,\n }\n }\n } catch (err) {\n this.ctx.logger.debug('reolink getAutoFocus probe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n // Persist `supported=false` on the FIRST probe failure so\n // future calls take the sticky-skip path above. The previous\n // value is preserved on subsequent transient failures.\n if (!afPrev) {\n cacheUpdate.autoFocusSnapshot = { enabled: null, supported: false }\n }\n }\n }\n\n if (Object.keys(cacheUpdate).length === 0) return\n const current = this.config.get('deviceCache') ?? {}\n try {\n // NOTE: do NOT touch `probedAt` here. This helper persists\n // settings-tab snapshots (image, motion, AI, enc, mask,\n // audioNoise, AF) — none of which constitute the features\n // probe (`hasPtz` / `hasIntercom` / etc). Conflating the two\n // would lock out `ensureFeaturesProbed()` after the first\n // settings refresh and leave hub-children with empty feature\n // sets.\n await this.config.setAll({\n deviceCache: { ...current, ...cacheUpdate },\n })\n } catch (err) {\n this.ctx.logger.debug('reolink deviceCache snapshot persist failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Declare on-camera accessory child devices the kernel should\n * auto-spawn after `onCreated`. Each entry maps directly to a\n * concrete accessory class via the existing `createAccessoryDevice`\n * factory; the closure captures `this` for the child's\n * `(ctx, parent)` constructor — accessories share the parent's\n * Baichuan socket via `parent.ensureApi()`.\n *\n * Restoring an existing accessory is automatic: the kernel detects\n * the persisted row by the deterministic stableId and skips the\n * pre-persist step. Adding a new accessory between boots (operator\n * enabled `hasFloodlight` via re-detection) appears on the next\n * parent register.\n */\n override getAccessoryChildren(): readonly import('@camstack/types').AccessoryChildSpec[] {\n // Pre-probe → no accessory specs. Spawning before the firmware's\n // ability table is read would land orphans on a re-probe that\n // drops a flag (e.g. a firmware downgrade that removes siren).\n if (!this.hasProbed()) return []\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n const out: import('@camstack/types').AccessoryChildSpec[] = []\n const push = (kind: AccessoryKindValue, type: DeviceType, label: string): void => {\n out.push({\n stableIdSuffix: `acc-${kind}`,\n meta: {\n type,\n name: `${this.name} ${label}`,\n },\n config: { kind, channel: 0 },\n factory: (ctx) => createAccessoryDevice(kind, ctx, this),\n })\n }\n if (probe.hasSiren === true) push(AccessoryKind.Siren, DeviceType.Siren, ACCESSORY_LABEL[AccessoryKind.Siren])\n if (probe.hasFloodlight === true) push(AccessoryKind.Floodlight, DeviceType.Light, ACCESSORY_LABEL[AccessoryKind.Floodlight])\n if (probe.hasPirSensor === true) push(AccessoryKind.PirSensor, DeviceType.Switch, ACCESSORY_LABEL[AccessoryKind.PirSensor])\n // Autotrack is NOT a child accessory — it's an extension of PTZ\n // exposed via the `ptz-autotrack` cap on the parent camera.\n return out\n }\n\n /**\n * Register per-device native capability providers (Slice 5+).\n * Currently exposes `snapshot` via the Baichuan getSnapshot command;\n * PTZ + intercom land in Slices 7 + 9 once we know the camera's\n * abilities (see `config.features`).\n */\n private registerNativeCapabilities(): void {\n const snapshotProvider: InferNativeProvider<typeof snapshotCapability> = {\n getSnapshot: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n return this.fetchSnapshotWithSingleFlight()\n },\n invalidateCache: async () => {\n // No local cache — the snapshot wrapper owns it.\n },\n }\n this.ctx.registerNativeCap(snapshotCapability, snapshotProvider)\n\n // Reboot — Baichuan exposes `cmd_id 23` on every Reolink device, so\n // the cap is registered unconditionally. The lib forwards the\n // request and returns once the camera ACKs (camera goes offline\n // shortly after; the device-online indicator drives the UI state,\n // not this method's resolution).\n const rebootProvider: InferNativeProvider<typeof rebootCapability> = {\n reboot: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n const api = await this.ensureApi()\n await api.reboot(this.getChannel())\n return { success: true as const }\n },\n }\n this.ctx.registerNativeCap(rebootCapability, rebootProvider)\n\n // Motion — every Reolink camera has firmware motion (Baichuan push\n // events). Register so the cap's `runtimeState` schema is installed.\n // Reolink does NOT write the slice directly: `handleSimpleEvent`\n // emits `MotionOnMotionChanged` (`source: 'onboard'`) and the\n // pipeline-runner's phase machine is the sole writer of the\n // `motion` slice (`handlePhaseChanged`). Symmetric with the\n // analyzer path. The `isDetected` read-through goes via the same\n // in-process mirror the runner writes through.\n const motionProvider: InferNativeProvider<typeof motionCapability> = {\n isDetected: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n return this.state.motion.detected ?? false\n },\n }\n this.ctx.registerNativeCap(motionCapability, motionProvider)\n // Seed the slice once so the read path returns a populated record\n // before the runner's first phase commit and so partial writes\n // merge cleanly through `patchCapState`.\n this.setCapSlice(motionCapability, {\n detected: false,\n lastDetectedAt: null,\n autoClearAfterMs: null,\n })\n\n // Doorbell — registered only when the abilities probe flagged\n // `hasDoorbell`. Push events arrive via `handleSimpleEvent` and\n // emit on the cap event bus + bridge to `EventCategory.DoorbellOnPressed`.\n this.registerDoorbellIfSupported()\n\n // PTZ — registered only when abilities probe (Slice 4) discovered\n // PTZ support. On first boot before the probe completes, the\n // persisted `deviceCache` may be missing — in that case the\n // post-login `probeAndPersistFeatures()` will call\n // `registerPtzIfSupported()` to wire PTZ retroactively.\n this.registerPtzIfSupported()\n\n // Autotrack — sibling cap of PTZ, registered only when the\n // probe flagged `hasAutotrack`. Autotrack used to be a child\n // accessory device with a `switch` cap; it now lives on the\n // parent as a typed `ptz-autotrack` cap (status + settings) so\n // automations can address it directly without a child-device\n // lookup. Same retroactive-registration story as PTZ.\n this.registerPtzAutotrackIfSupported()\n\n // Intercom — registered only when the abilities probe flagged\n // `hasIntercom`. v0.1 ships the cap binding + the lib bridge layer\n // (PCM-in / ADPCM-out via `ReolinkIntercomSession`); the WebRTC\n // server-side path (Opus RTP receive + ffmpeg decode → PCM →\n // session.feedPcm) is scaffolded as a stub that throws\n // NotImplemented on `startSession`. Operators see the cap in the\n // bindings table and the `audioConfig` ability is reported via the\n // status surface; the actual mic-to-camera flow lands when the\n // werift audio session is wired in (planned next iteration).\n this.registerIntercomIfSupported()\n\n // Battery cap — register eagerly when autodetect already flagged\n // the device as battery-operated. Without this the cap is only\n // registered AFTER the first successful login (`ensureApi` /\n // `adoptApi`), which means a freshly-added (or briefly-offline)\n // battery camera has `DeviceFeature.BatteryOperated` in its\n // `features` (so the UI thinks it's battery-aware) but no\n // `battery.getStatus` provider — `useDeviceBattery` returns null\n // and the BatteryBadge stays invisible. Registering now lets the\n // provider serve the cached snapshot (and a conservative\n // fallback) until `refreshBatteryFromApi` populates real data.\n //\n // Two flag paths to consider:\n // - Standalone autodetect → `deviceType === 'battery-cam'` (the\n // transport-discriminator field; battery cams require UDP).\n // - Hub-adopted discovery → `deviceType === 'camera'` plus\n // `hasBattery: true` from the NVR's GetChannelstatus payload.\n // Hub children always go through the parent's TCP socket so\n // `deviceType` stays `'camera'` even for batteries; the\n // dedicated `hasBattery` flag is the right discriminator here.\n const cache = this.config.get('deviceCache')\n if (cache?.deviceType === 'battery-cam' || cache?.hasBattery === true) {\n this.isBattery = true\n // Register the battery cap so its `runtimeState` schema\n // attaches to `this.runtimeState` and the persisted slice\n // (loaded from the kernel's `__device-state` namespace before\n // the device was constructed) becomes valid. The `sleeping`\n // getter reads straight off that slice — no shadow field\n // to hydrate.\n this.registerBatteryIfSupported()\n }\n }\n\n // Battery slice access goes through `this.state.battery` (proxy from\n // BaseDevice). Field-level reads/writes route through the runtime\n // state; see `registerBatteryIfSupported` for the seed that lets\n // partial writes succeed for battery cams.\n\n /**\n * Idempotent guard for the PTZ provider — registered once per device\n * instance lifetime. `removeDevice` tears down the instance, so a\n * fresh device wires PTZ from scratch. `disconnectAll` does NOT\n * change `hasPtz`, so we don't have to unregister/reregister around\n * the api lifecycle.\n */\n private ptzRegistered = false\n /** Idempotent guard for the `ptz-autotrack` cap. Registered once\n * per device instance once the abilities probe flagged\n * `hasAutotrack`. */\n private ptzAutotrackRegistered = false\n /** Single-flight guard for the autotrack camera refresh — back-to-back\n * `getStatus`/`getSettings` calls within the same tick fold into one\n * Baichuan round-trip. */\n private autotrackRefreshInFlight: Promise<void> | null = null\n\n /**\n * Single-flight guard for snapshot fetches (Slice 10). When two\n * consumers hit `getSnapshot` concurrently we issue ONE Baichuan\n * request and let both await its result — saves a doubled wake on\n * battery cams and halves load for regular ones. Pattern mirrors\n * `takePictureInFlight` in scrypted-reolink-native.\n */\n private snapshotInFlight: Promise<{ base64: string; contentType: string } | null> | null = null\n\n /**\n * Map a nodelink `BatteryInfo` push payload into the camstack\n * `BatteryStatus` shape (Slice 13). `sleeping` honours the runtime\n * `this.sleeping` flag — the BatteryInfo's own `sleeping` field is\n * present on some firmwares but absent on others, so the runtime\n * tracker is more reliable.\n */\n private mapBatteryInfo(info: import('@apocaliss92/nodelink-js').BatteryInfo): BatteryStatus {\n const percentage = typeof info.batteryPercent === 'number'\n ? Math.max(0, Math.min(100, Math.round(info.batteryPercent)))\n : (this.state.battery.percentage ?? 0)\n const adapter = (info.adapterStatus ?? '').toLowerCase()\n const charge = (info.chargeStatus ?? '').toLowerCase()\n const isCharging = charge === 'charging' || charge === 'chargecomplete'\n const charging: BatteryStatus['charging'] = adapter.includes('solar')\n ? 'solar'\n : isCharging ? 'dc' : 'none'\n const sleeping = info.sleeping === true || this.sleeping\n return {\n percentage,\n charging,\n sleeping,\n lastUpdated: Date.now(),\n }\n }\n\n /**\n * Register the battery cap provider on battery-flagged devices\n * (Slice 13). The wrapper `snapshot.addon.ts` calls\n * `batteryCapability.getStatus({deviceId})` to decide whether to\n * serve cached frames instead of waking the camera; without this\n * provider the wrapper assumes \"awake\" and would wake the cam.\n */\n private registerBatteryIfSupported(): void {\n if (this.batteryRegistered) return\n if (!this.isBattery) return\n this.batteryRegistered = true\n\n const provider: InferNativeProvider<typeof batteryCapability> = {\n getStatus: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkCamera: deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n // Always serve the slice — the post-registration seed below\n // guarantees a populated value from this point on. Re-issuing\n // a Baichuan command from the snapshot wrapper's sleep gate\n // would defeat the purpose (and wake the camera).\n return this.state.battery\n },\n }\n this.ctx.registerNativeCap(batteryCapability, provider)\n // Seed the slice once so subsequent field-level proxy writes\n // (e.g. `this.state.battery.sleeping = true`) merge cleanly through\n // `patchCapState` without tripping the schema's required-fields\n // check. Real values land via `battery` push events / post-login\n // `getBatteryInfo`.\n if (this.getCapSlice(batteryCapability) === null) {\n this.setCapSlice(batteryCapability, {\n percentage: 0,\n charging: 'none',\n sleeping: false,\n lastUpdated: Date.now(),\n })\n }\n\n // Bridge runtime-state writes to the cap event bus automatically.\n // Any write that touches the `battery` slice (proxy field-level\n // writes via `this.state.battery.<field>`, full-slice writes via\n // `setCapSlice(batteryCapability, …)`, future paths) re-emits\n // `battery.onStatusChanged` once — no manual `emit` calls scattered\n // across the writers.\n this.runtimeState.subscribeCap<BatteryStatus>('battery', (slice) => {\n if (!slice) return\n this.ctx.eventBus.emit(createEvent(\n EventCategory.BatteryOnStatusChanged,\n this.eventSource(),\n { deviceId: this.id, status: slice },\n ))\n })\n\n // Seed the cache with a one-shot `getBatteryInfo` after registration —\n // this is the same call the legacy battery probe does, so no extra\n // wake event. Steady-state updates arrive via the firmware's\n // `battery` simpleEvent push (handled in `handleSimpleEvent`),\n // routed for hub children by `ReolinkHub.routeSimpleEvent` — no\n // periodic poll needed.\n void this.refreshBatteryFromApi()\n }\n\n private async refreshBatteryFromApi(): Promise<void> {\n // Resolve the api through `ensureApi()` rather than reading\n // `this.api` directly — hub-adopted children never own a socket\n // (they delegate to the parent Hub's long-lived connection), so\n // `this.api` stays null for them and the previous early-return\n // silently skipped every refresh. `ensureApi()` returns the\n // parent's api in that case (no extra wake, no extra login).\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.ctx.logger.debug('battery refresh: ensureApi failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n try {\n const info = await api.getBatteryInfo(this.getChannel())\n this.updateBatteryCache(info)\n } catch (err) {\n this.ctx.logger.debug('battery refresh failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Decide whether to honour a sleep-state transition. Returns\n * `false` when the lib's UDP inference is still inside the\n * hysteresis window (8s by default) so we don't flap. The first\n * transition AFTER an api login / restart is always honoured —\n * no prior-flip timestamp gating it.\n *\n * @param next - the desired next value of `this.sleeping`\n * @returns `true` if the caller should commit the new state\n */\n private acceptSleepingTransition(next: boolean): boolean {\n if (this.sleeping === next) return false\n const now = Date.now()\n if (this.sleepStateChangedAt > 0\n && now - this.sleepStateChangedAt < ReolinkCamera.SLEEP_HYSTERESIS_MS) {\n this.ctx.logger.debug('ignoring sleep inference flap within hysteresis window', {\n tags: { deviceId: this.id },\n meta: { next, sinceLastChangeMs: now - this.sleepStateChangedAt },\n })\n return false\n }\n this.sleepStateChangedAt = now\n return true\n }\n\n private updateBatteryCache(info: import('@apocaliss92/nodelink-js').BatteryInfo): void {\n // Single write through the cap-keyed runtime state — the kernel\n // layer debounces persistence to disk and the\n // `subscribeCap('battery')` bridge in `registerBatteryIfSupported`\n // re-emits `battery.onStatusChanged` automatically. No more\n // shadow `batteryStatusCache` field; no more manual cap event\n // emission scattered across writers.\n this.setCapSlice(batteryCapability, this.mapBatteryInfo(info))\n }\n\n /**\n * Battery cams require an explicit wake before cmd_id 109 will\n * answer reliably. The lib documents this pattern in\n * `predownloadRecordingMp4` (`ensureAwake` → `wakeUp` →\n * operation → retry with longer wait on failure). `getSnapshot`\n * itself only re-`login()`s, which over BCUDP can complete before\n * the camera firmware is fully up — the snapshot then times out\n * because the video subsystem isn't ready yet.\n *\n * Probe live testing (scripts/probe-reolink-snapshot.mts) confirmed:\n * awake state: getSnapshot in ~1s\n * wakeUp + getSnapshot: ~2.7s total (wakeUp 1.7s + snapshot 1s)\n * The added latency is the cost we pay to actually return a frame\n * instead of timing out.\n */\n /**\n * Refresh the diagnostic Sessions snapshot from the live Baichuan\n * api: who's currently logged into the camera, how many sockets we\n * hold open, and whether the camera is currently rate-limiting our\n * reconnect attempts. Single-flight (concurrent calls reuse the same\n * round-trip) and tolerant of partial failures — each lib call's\n * error is recorded but does not poison the rest of the snapshot.\n *\n * Side-effect: writes `this.sessionsSnapshot`. Best-effort: when\n * `ensureApi()` fails (camera offline, sleeping battery cam) the\n * snapshot still updates with a populated `error` field plus a\n * fallback `socketPool` block so the operator sees the failure\n * surface instead of stale data.\n */\n private async refreshSessionsSnapshot(): Promise<void> {\n if (this.sessionsRefreshInFlight) return this.sessionsRefreshInFlight\n const promise = (async (): Promise<void> => {\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.sessionsSnapshot = {\n ts: Date.now(),\n onlineUsers: null,\n socketPool: [],\n dedicatedSessions: [],\n cooldown: null,\n error: err instanceof Error ? err.message : String(err),\n }\n return\n }\n this.sessionsSnapshot = await captureSessionsSnapshot(api, {\n logger: this.ctx.logger,\n deviceId: this.id,\n })\n })().finally(() => {\n this.sessionsRefreshInFlight = null\n })\n this.sessionsRefreshInFlight = promise\n return promise\n }\n\n /**\n * Build the contents of the \"Sessions\" tab from `this.sessionsSnapshot`.\n * Always returns at least the Refresh button so the operator can force\n * a fetch when the snapshot is empty (cold start) or stale beyond the\n * displayed timestamp. Section + tab layout details mirror the rest\n * of the device-settings UI (one card per logical block).\n *\n * NO auto-fetch. `getOnlineUserList` (cmd 120) wakes a sleeping\n * battery cam, and the Settings panel polls every ~2.5s — auto-\n * triggering on stale would keep the doorbell awake indefinitely\n * just because the operator left the tab open. The operator's\n * explicit \"Refresh\" click is the sole trigger; it lands as\n * `_refreshSessions` action sentinel through `applySettingsPatch`.\n */\n private buildSessionsTabSections(): readonly ConfigUISchema['sections'][number][] {\n return buildSessionsTabSectionsShared(this.sessionsSnapshot, { tabId: 'sessions', title: 'Camera Sessions' })\n }\n\n private async fetchSnapshotWithSingleFlight(): Promise<{ base64: string; contentType: string } | null> {\n if (this.snapshotInFlight) return this.snapshotInFlight\n const promise = (async (): Promise<{ base64: string; contentType: string } | null> => {\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.ctx.logger.warn('snapshot: ensureApi failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return null\n }\n\n // Battery-cam pre-wake. `getSleepStatus` is purely passive —\n // inspects the lib's internal command-traffic cache, no network\n // I/O — so it costs nothing and never wakes the camera by\n // itself. We treat anything other than confirmed-awake as\n // \"probably sleeping\" because the user-facing cost of an\n // unnecessary wake (a couple of seconds + a small battery hit)\n // is much smaller than the cost of returning null when a frame\n // was wanted.\n const isSleepingSuspected = this.isBattery && (() => {\n try {\n const status = api.getSleepStatus({ channel: this.getChannel() })\n return status.state !== 'awake'\n } catch {\n return true\n }\n })()\n if (isSleepingSuspected) {\n try {\n await api.wakeUp(this.getChannel(), { waitAfterWakeMs: 1500, attempts: 2 })\n } catch (err) {\n this.ctx.logger.debug('snapshot: pre-wake failed (will still try getSnapshot)', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n const tryOnce = async (timeoutMs: number): Promise<Buffer | null> => {\n const buf = await api.getSnapshot(this.getChannel(), { streamType: 'main', timeoutMs })\n return buf && buf.length > 0 ? buf : null\n }\n\n const formatResult = (buf: Buffer): { base64: string; contentType: string } => ({\n base64: buf.toString('base64'),\n contentType: 'image/jpeg',\n })\n\n // Battery cams: 25 s on the first attempt to absorb wake-up\n // latency variance; non-battery cams keep the lib's 15 s default.\n const firstTimeout = this.isBattery ? 25_000 : 15_000\n try {\n const buf = await tryOnce(firstTimeout)\n if (buf) return formatResult(buf)\n } catch (err) {\n const recoverable = isRecoverableBaichuanError(err)\n const meta = {\n channel: this.getChannel(),\n recoverable,\n attempt: 1,\n error: err instanceof Error ? err.message : String(err),\n }\n if (recoverable) {\n this.ctx.logger.debug('snapshot: getSnapshot transient error', { tags: { deviceId: this.id }, meta })\n } else {\n this.ctx.logger.warn('snapshot: getSnapshot failed (attempt 1)', { tags: { deviceId: this.id }, meta })\n }\n // Battery cams get one explicit wake-up + retry. Mirrors the\n // lib's `predownloadRecordingMp4` retry pattern. Skip the\n // retry for non-battery cams — there the failure is genuinely\n // a network/auth issue and re-trying the same call yields the\n // same result.\n if (this.isBattery) {\n try {\n await api.wakeUp(this.getChannel(), { waitAfterWakeMs: 3000, attempts: 3, reconnect: true })\n const buf = await tryOnce(25_000)\n if (buf) return formatResult(buf)\n } catch (retryErr) {\n this.ctx.logger.warn('snapshot: getSnapshot failed (attempt 2 after wake)', {\n tags: { deviceId: this.id },\n meta: {\n channel: this.getChannel(),\n error: retryErr instanceof Error ? retryErr.message : String(retryErr),\n },\n })\n }\n }\n }\n return null\n })()\n this.snapshotInFlight = promise\n try {\n return await promise\n } finally {\n this.snapshotInFlight = null\n }\n }\n\n /**\n * Idempotency for the doorbell provider. The cap is registered once\n * per device-instance lifetime — `removeDevice` rebuilds the device\n * from scratch.\n */\n private doorbellRegistered = false\n\n /** Push counter mirrored into the doorbell cap status. */\n private doorbellPressCountSinceStart = 0\n private doorbellLastPressedAt: number | null = null\n\n private registerDoorbellIfSupported(): void {\n if (this.doorbellRegistered) return\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n if (!probe.hasDoorbell) return\n this.doorbellRegistered = true\n const doorbellProvider: InferNativeProvider<typeof doorbellCapability> = {\n getStatus: async () => ({\n lastPressedAt: this.doorbellLastPressedAt,\n pressCountSinceStart: this.doorbellPressCountSinceStart,\n }),\n }\n this.ctx.registerNativeCap(doorbellCapability, doorbellProvider)\n }\n\n /** One-time registration guard for `intercom` cap. */\n private intercomRegistered = false\n /**\n * Active intercom orchestrator. The `intercom` cap is\n * `singleton`-mode device-scoped — at most one talk session is\n * open at a time per camera (mirrors how Reolink's own UI\n * behaves). The orchestrator owns the WebRTC peer + audio-codec\n * decode session + Reolink talk channel for that one active\n * session; `null` when idle.\n */\n private intercomOrchestrator: IntercomOrchestrator | null = null\n\n /**\n * Register the `intercom` cap when the camera advertises two-way\n * audio support. Server-WebRTC-shaped: `startSession` returns an\n * SDP offer, `handleAnswer` applies the browser's response, the\n * orchestrator pumps Opus RTP through the `audio-codec` cap (Opus\n * → PCM s16le @ camera rate, with resampling) and feeds the PCM\n * into `ReolinkIntercomSession` for IMA ADPCM encoding + transport\n * onto the camera's dedicated talk channel.\n *\n * The `audio-codec` cap is REQUIRED — without it (no audio-codec\n * addon installed) intercom can't function. We register the cap\n * regardless and surface the missing dep as a clear error from\n * `startSession`; this preserves the \"cap visible in bindings/docs\"\n * UX without falsely claiming readiness when audio decode isn't\n * available.\n */\n private registerIntercomIfSupported(): void {\n if (this.intercomRegistered) return\n // `hasIntercom` lives in the `feature-probe` runtime-state slice\n // post-migration (see schema comment). The legacy\n // `deviceCache.hasIntercom` is stripped by self-healing on first\n // read, so reading it would silently miss every device added after\n // the migration landed. `getProbeFlags<ReolinkProbeFlags>()` returns\n // the typed shape — same helper the `features` getter uses.\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n if (probe.hasIntercom !== true) return\n this.intercomRegistered = true\n\n const provider: InferNativeProvider<typeof intercomCapability> = {\n startSession: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkCamera: intercom deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n if (this.disabled) {\n throw new Error('Reolink intercom: device is disabled — re-enable it before opening a talk session')\n }\n const audioCodec = this.resolveAudioCodecApi()\n const api = await this.ensureApi()\n // Lazy-create the orchestrator on first session and keep it\n // around for the device's lifetime. The orchestrator's own\n // single-active-session guard handles repeated\n // `startSession` calls (closes the stale one first).\n //\n // Per-session settings are read fresh at start time so the\n // operator can tweak them between sessions without restarting\n // the device — the new orchestrator instance picks them up\n // (or, on subsequent starts on the same orchestrator, the\n // talkSession opened inside `start()` reads the live opts via\n // closure).\n if (!this.intercomOrchestrator) {\n this.intercomOrchestrator = new IntercomOrchestrator({\n deviceTag: `camera-${this.id}`,\n channel: this.getChannel(),\n api,\n logger: this.ctx.logger.withTags({ deviceId: this.id, scope: 'intercom' }),\n audioCodec,\n peerFactory: ({ logger }) => new WeriftIntercomPeer({ logger }),\n // Battery-cam pre-wake (Scrypted parity). Skip the wake on\n // wired cams so the handshake doesn't pay an extra\n // `getSleepStatus` round-trip for nothing.\n wakeBeforeStart: this.isBattery\n ? async () => { await this.wakeForIntercom(api) }\n : undefined,\n ...(this.config.get('intercomBlocksPerPayload') !== undefined\n ? { blocksPerPayload: this.config.get('intercomBlocksPerPayload') }\n : {}),\n ...(this.config.get('intercomMaxBacklogMs') !== undefined\n ? { maxBacklogMs: this.config.get('intercomMaxBacklogMs') }\n : {}),\n ...(this.config.get('intercomGain') !== undefined\n ? { outputGain: this.config.get('intercomGain') }\n : {}),\n })\n }\n return this.intercomOrchestrator.start()\n },\n handleAnswer: async ({ deviceId, sessionId, sdpAnswer }) => {\n if (deviceId !== this.id) return\n if (!this.intercomOrchestrator) {\n throw new Error(`Reolink intercom: handleAnswer with no active orchestrator (sessionId=${sessionId})`)\n }\n await this.intercomOrchestrator.handleAnswer(sessionId, sdpAnswer)\n },\n stopSession: async ({ deviceId, sessionId }) => {\n if (deviceId !== this.id) return\n if (!this.intercomOrchestrator) return\n await this.intercomOrchestrator.stop(sessionId)\n },\n }\n this.ctx.registerNativeCap(intercomCapability, provider)\n this.ctx.logger.info('intercom cap registered (WebRTC + audio-codec wiring active)', {\n tags: { deviceId: this.id },\n })\n }\n\n /**\n * Resolve the `audio-codec` cap router off `ctx.api`. Throws a\n * clear error when the cap isn't mounted (no audio-codec addon\n * installed) so the operator gets actionable feedback at\n * `startSession` time rather than a generic dispatch failure.\n *\n * The shape we surface is the narrow `IntercomAudioCodecApi` used\n * by the orchestrator — we don't expose the full router because\n * intercom only needs decode + push + pull + close.\n */\n private resolveAudioCodecApi(): IntercomAudioCodecApi {\n const root = this.ctx.api as unknown as { audioCodec?: AudioCodecRouterShape }\n const router = root.audioCodec\n if (!router) {\n throw new Error(\n 'Reolink intercom: `audio-codec` capability is not mounted. ' +\n 'Install + enable the `addon-audio-codec-nodeav` addon (or any other ' +\n 'addon that provides the `audio-codec` capability) before opening ' +\n 'a talk session.',\n )\n }\n return {\n createDecodeSession: (input) => router.createDecodeSession.mutate(input),\n pushEncodedFrame: (input) => router.pushEncodedFrame.mutate(input),\n pullPcm: (input) => router.pullPcm.query(input),\n closeSession: (input) => router.closeSession.mutate(input),\n }\n }\n\n /**\n * Battery-cam pre-wake hook for the intercom orchestrator. Mirrors\n * scrypted-reolink-native's `intercom.ts:90-106`: ask the lib for\n * the current sleep status (passive — no traffic), and if the cam\n * is asleep issue an explicit `wakeUp(channel, {waitAfterWakeMs:2000})`\n * + a 1s settle delay before letting the talk-session handshake\n * fire. Without this, the handshake against a sleeping UDP/battery\n * cam silently times out and the operator gets a generic \"talk\n * session open failed\" error.\n */\n private async wakeForIntercom(api: ReolinkBaichuanApi): Promise<void> {\n const channel = this.getChannel()\n let sleepStatus\n try {\n sleepStatus = api.getSleepStatus({ channel })\n } catch (err) {\n this.ctx.logger.debug('intercom: getSleepStatus failed — proceeding without explicit wake', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n if (sleepStatus.state !== 'sleeping') return\n this.ctx.logger.info('intercom: cam sleeping — waking up before talk session', {\n tags: { deviceId: this.id },\n })\n try {\n await api.wakeUp(channel, { waitAfterWakeMs: 2000 })\n // Settle delay matches Scrypted — UDP wake completion lags the\n // ack by ~1s on Argus / battery doorbell firmwares. Skipping\n // this wait produces a flaky `createDedicatedTalkSession` even\n // though `getSleepStatus` reports awake.\n await new Promise<void>((resolve) => setTimeout(resolve, 1000))\n } catch (err) {\n this.ctx.logger.warn('intercom: wakeUp failed — proceeding anyway', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Translate a Reolink Baichuan `doorbell` simple-event into a\n * camstack-side doorbell press: bump the counters, emit the cap\n * event, and bridge to the typed `EventCategory.DoorbellOnPressed`\n * so non-cap consumers (UI toast, notifier) can subscribe without\n * holding a cap reference.\n */\n private emitDoorbellPressed(timestamp: number): void {\n this.doorbellLastPressedAt = timestamp\n this.doorbellPressCountSinceStart += 1\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DoorbellOnPressed,\n this.eventSource(),\n { deviceId: this.id, timestamp },\n ))\n }\n\n /**\n * Register the `ptz-autotrack` cap when the abilities probe flagged\n * `hasAutotrack`. The cap surface (`getStatus` / `setEnabled` /\n * `getSettings` / `setSettings`) maps directly onto the lib's\n * `getAutotracking` / `setAutotracking` / `setAutotrackingSettings`\n * Baichuan commands.\n *\n * Source of truth is `runtimeState['ptz-autotrack']` — the kernel\n * persists this slice across restarts and validates it against the\n * cap's runtimeState schema. The four cap methods become trampolines:\n * read from the slice, refresh from the camera when stale, write\n * back to the slice. No private cache fields, no config-blob dance.\n */\n private registerPtzAutotrackIfSupported(): void {\n if (this.ptzAutotrackRegistered) return\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n if (!probe.hasAutotrack) return\n this.ptzAutotrackRegistered = true\n\n const channel = this.getChannel()\n const CAP_NAME = 'ptz-autotrack'\n /** Stale-after window for served-from-cache reads. ~10s is short\n * enough that the UI's 5s poll always sees a fresh round-trip,\n * long enough that two consumers in the same tick fold into one. */\n const STALE_MS = 10_000\n\n // Static for Reolink — the Baichuan `smartTrackType` field accepts\n // these well-known tokens (verified via the lib's example payloads).\n // Compound strings like `\"people,vehicle\"` work on multi-class\n // firmwares; we expose the canonical subset and let the operator\n // type compound values manually if needed.\n const SUPPORTED_TARGET_TYPES: PtzAutotrackStatus['supportedTargetTypes'] = [\n { value: 'people', label: 'People' },\n { value: 'vehicle', label: 'Vehicle' },\n { value: 'pet', label: 'Pet' },\n { value: 'people,vehicle', label: 'People + Vehicle' },\n ]\n\n const extractSettingsFromAiCfg = (raw: AiConfig | undefined): PtzAutotrackSettings | null => {\n const cfg = raw?.body?.AiCfg\n if (!cfg) return null\n return {\n targetType: typeof cfg.smartTrackType === 'string' ? cfg.smartTrackType : '',\n stopDelaySeconds: Number(cfg.smartTrackObjectStopDelay ?? 20),\n disappearDelaySeconds: Number(cfg.smartTrackObjectDisappearDelay ?? 10),\n }\n }\n\n const readSlice = (): PtzAutotrackRuntimeState | undefined =>\n this.runtimeState.getCapState<PtzAutotrackRuntimeState>(CAP_NAME)\n\n /** Round-trip the camera, mirror everything into runtimeState.\n * Single-flight: concurrent callers within one tick await the\n * same promise. Failure leaves the existing slice untouched. */\n const refreshFromCamera = async (): Promise<void> => {\n if (this.autotrackRefreshInFlight) return this.autotrackRefreshInFlight\n const promise = (async () => {\n try {\n const api = await this.ensureApi()\n const state = await api.getAutotracking(channel)\n const enabled = Boolean(state?.enabled)\n const fromCamera = extractSettingsFromAiCfg(state?.raw)\n const prev = readSlice()\n this.runtimeState.setCapState(CAP_NAME, {\n enabled,\n lastChangedAt: prev?.enabled === enabled ? (prev?.lastChangedAt ?? 0) : Date.now(),\n currentSettings: fromCamera ?? prev?.currentSettings ?? null,\n supportedTargetTypes: SUPPORTED_TARGET_TYPES,\n lastFetchedAt: Date.now(),\n })\n } catch (err) {\n this.ctx.logger.debug('autotrack refresh failed — keeping last slice', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n })()\n this.autotrackRefreshInFlight = promise\n try { await promise } finally { this.autotrackRefreshInFlight = null }\n }\n\n const bridge = createRuntimeStateBridge({\n runtimeState: this.runtimeState,\n cap: ptzAutotrackCapability,\n ownDeviceId: this.id,\n refresh: refreshFromCamera,\n staleMs: STALE_MS,\n empty: (): PtzAutotrackStatus => ({\n enabled: false,\n lastChangedAt: 0,\n currentSettings: null,\n supportedTargetTypes: SUPPORTED_TARGET_TYPES,\n }),\n })\n\n const provider: InferNativeProvider<typeof ptzAutotrackCapability> = {\n getStatus: bridge.getStatus,\n setEnabled: async ({ deviceId, enabled }) => {\n if (deviceId !== this.id) return\n const api = await this.ensureApi()\n await api.setAutotracking(enabled, channel)\n const prev = readSlice()\n this.runtimeState.patchCapState(CAP_NAME, {\n enabled,\n lastChangedAt: prev?.enabled === enabled ? (prev?.lastChangedAt ?? 0) : Date.now(),\n lastFetchedAt: Date.now(),\n })\n },\n getSettings: async ({ deviceId }) => {\n if (deviceId !== this.id) return null\n await bridge.ensureFresh()\n return readSlice()?.currentSettings ?? null\n },\n setSettings: async ({ deviceId, settings }) => {\n if (deviceId !== this.id) return\n // Merge the patch onto whatever the slice currently holds so\n // partial UI updates don't clobber unspecified fields. Empty\n // slice (cold-start, camera never reached) → lib defaults.\n const current = readSlice()?.currentSettings ?? {\n targetType: '',\n stopDelaySeconds: 20,\n disappearDelaySeconds: 10,\n }\n const merged: PtzAutotrackSettings = {\n targetType: settings.targetType ?? current.targetType,\n stopDelaySeconds: settings.stopDelaySeconds ?? current.stopDelaySeconds,\n disappearDelaySeconds: settings.disappearDelaySeconds ?? current.disappearDelaySeconds,\n }\n // Push first — on failure, the slice stays untouched so the\n // UI matches the camera. Empty targetType means \"camera\n // default\" → drop the wire field (some firmwares reject \"\").\n const api = await this.ensureApi()\n const fwSettings: Parameters<typeof api.setAutotrackingSettings>[1] = {\n smartTrackObjectStopDelay: merged.stopDelaySeconds,\n smartTrackObjectDisappearDelay: merged.disappearDelaySeconds,\n }\n if (merged.targetType.length > 0) fwSettings.smartTrackType = merged.targetType\n await api.setAutotrackingSettings(channel, fwSettings)\n // Re-read so we reflect any clamping (some firmwares round\n // delays to multiples of 5).\n await refreshFromCamera()\n },\n }\n this.ctx.registerNativeCap(ptzAutotrackCapability, provider)\n this.ctx.logger.info('Reolink ptz-autotrack cap registered', {\n tags: { deviceId: this.id },\n })\n }\n\n private registerPtzIfSupported(): void {\n if (this.ptzRegistered) return\n const probe = this.getProbeFlags<ReolinkProbeFlags>()\n if (!probe.hasPtz) return\n this.ptzRegistered = true\n {\n const ptzProvider: InferNativeProvider<typeof ptzCapability> = {\n move: async ({ deviceId, pan, tilt, zoom, speed }) => {\n if (deviceId !== this.id) return\n await this.runPtz(pan, tilt, zoom, speed, /* continuous */ false)\n },\n continuousMove: async ({ deviceId, pan, tilt, zoom, speed }) => {\n if (deviceId !== this.id) return\n await this.runPtz(pan, tilt, zoom, speed, /* continuous */ true)\n },\n stop: async ({ deviceId }) => {\n if (deviceId !== this.id) return\n const api = await this.ensureApi()\n const channel = this.getChannel()\n // Sending 'stop' on every direction is wasteful; nodelink's\n // ptz already sends a stop on whichever command was last\n // active, so issue a no-op stop on Up — the camera ignores\n // stops for inactive directions.\n try { await api.ptz(channel, { action: 'stop', command: 'Up' }) } catch { /* ignore */ }\n },\n getPresets: async ({ deviceId }) => {\n if (deviceId !== this.id) return []\n try {\n const api = await this.ensureApi()\n const channel = this.getChannel()\n const presets = await api.getPtzPresets(channel)\n return presets.map((p) => ({ id: String(p.id), name: p.name }))\n } catch { return [] }\n },\n goToPreset: async ({ deviceId, presetId }) => {\n if (deviceId !== this.id) return\n const api = await this.ensureApi()\n const channel = this.getChannel()\n const id = Number(presetId)\n if (!Number.isFinite(id)) throw new Error(`Invalid presetId: ${presetId}`)\n await api.moveToPtzPreset(channel, id)\n },\n goHome: async ({ deviceId }) => {\n if (deviceId !== this.id) return\n // Reolink Baichuan exposes \"home\" as preset id 0 on most\n // firmwares. Best-effort — fail silently if the camera\n // refuses (preset not configured).\n try {\n const api = await this.ensureApi()\n const channel = this.getChannel()\n await api.moveToPtzPreset(channel, 0)\n } catch { /* ignore */ }\n },\n getPosition: async ({ deviceId }) => {\n if (deviceId !== this.id) return { pan: 0, tilt: 0, zoom: 0 }\n // Baichuan does not expose live PTZ position on most cameras;\n // return zeros until a position-query path lands upstream.\n return { pan: 0, tilt: 0, zoom: 0 }\n },\n }\n this.ctx.registerNativeCap(ptzCapability, ptzProvider)\n }\n }\n\n /**\n * Translate normalized (-1..1) pan/tilt/zoom to one or more discrete\n * Baichuan PTZ commands. Camstack ptz uses ONVIF-style continuous\n * motion vectors; Reolink Baichuan needs discrete directional\n * commands (Left/Right/Up/Down/ZoomIn/ZoomOut). Magnitudes map to\n * speed (0–63 typical). Continuous=true issues 'start' (camera moves\n * until next 'stop'); continuous=false issues 'start' followed by an\n * autoStop after a short window so a single tap of an arrow key\n * results in a tiny nudge.\n */\n private async runPtz(\n pan: number | undefined,\n tilt: number | undefined,\n zoom: number | undefined,\n speed: number | undefined,\n continuous: boolean,\n ): Promise<void> {\n const api = await this.ensureApi()\n const channel = this.getChannel()\n const cmds: Array<'Left' | 'Right' | 'Up' | 'Down' | 'ZoomIn' | 'ZoomOut'> = []\n if (pan !== undefined && Math.abs(pan) > 0.01) cmds.push(pan > 0 ? 'Right' : 'Left')\n if (tilt !== undefined && Math.abs(tilt) > 0.01) cmds.push(tilt > 0 ? 'Up' : 'Down')\n if (zoom !== undefined && Math.abs(zoom) > 0.01) cmds.push(zoom > 0 ? 'ZoomIn' : 'ZoomOut')\n if (cmds.length === 0) return\n\n const baichuanSpeed = speed === undefined\n ? 32\n : Math.max(1, Math.min(63, Math.round(speed * 63)))\n for (const command of cmds) {\n try {\n await api.ptz(channel, {\n action: 'start',\n command,\n speed: baichuanSpeed,\n ...(continuous ? {} : { autoStopMs: 200 }),\n })\n } catch (err) {\n this.ctx.logger.warn('Baichuan ptz command failed', {\n tags: { deviceId: this.id, command },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n // ── Stream metadata ───────────────────────────────────────────────────\n\n async getStreamSources(): Promise<readonly StreamSourceEntry[]> {\n const streams = buildStreamIds(this.channelCount())\n return streams.map((s, i): StreamSourceEntry => ({\n id: s.id,\n label: s.label,\n protocol: 'annexb',\n profileHint: i % 2 === 0 ? 'high' : 'mid',\n }))\n }\n\n private channelCount(): number {\n return this.config.get('deviceCache')?.channelCount ?? 1\n }\n\n /**\n * Publish Reolink streams to the stream-broker. Reolink cameras\n * typically expose the same logical channel over THREE containers —\n * native Baichuan push, RTSP, RTMP. We publish all of them so the\n * operator can pick any in the assignment UI; native streams are\n * `autoEligible: true` (the recommended path) and RTSP / RTMP mirrors\n * are `autoEligible: false` (selectable, but never the default).\n *\n * Stream IDs are kind-prefixed (`native:main`, `rtsp:main`, …) so the\n * broker treats them as distinct rows even though they share the\n * same camera profile.\n *\n * The lib's `buildVideoStreamOptions()` is the single source of truth\n * for the camera's offer — it returns nativeStreams + rtspStreams +\n * rtmpStreams with full metadata (width/height/frameRate/bitRate/\n * videoEncType) in a single call. We resolve it lazily on every\n * publish instead of caching it in `deviceCache`: the cache layer\n * was a constant source of bugs (transient probe failures wiped\n * the persisted entry, leaving the device stuck with `desired.size === 0`\n * until manual recovery). Calling the lib directly is cheap (it\n * caches internally) and self-healing across firmware upgrades.\n *\n * Re-publish is idempotent — the broker keys entries by\n * `(deviceId, camStreamId)`. Streams from a previous publish that no\n * longer appear in the latest snapshot are explicitly retracted so\n * the broker registry stays in sync.\n *\n * Native streams need a per-(channel, profile) RFC 4571 TCP server on\n * loopback — handed off to `ensureRfc4571Server`, which uses the lib's\n * shared-server pool (`sharedRfc4571Servers` keyed by `(deviceId,\n * channel, profile, variant, …)`). Without `deviceId` the lib falls\n * back to the unshared path: every call opens a fresh\n * BaichuanVideoStream on the same control socket, and 3 concurrent\n * stream probes overload the camera's session table → ECONNRESET.\n * With it, main + sub + ext coexist freely.\n */\n async publishToBroker(): Promise<void> {\n // Battery cam sleep gate. `ensureApi()` on a sleeping UDP/battery\n // cam force-wakes it via the lib's discovery handshake, which is\n // exactly what we want to avoid. The wake-transition path\n // (`handleSimpleEvent('awake')`) re-runs publishToBroker as soon\n // as the cam comes up naturally — see the `publishedStreamIds.size\n // === 0` retry block in `handleSimpleEvent('awake')`. Skipping\n // here at boot leaves zero streams registered until first wake,\n // which is fine: the broker holds nothing to dial, no consumer\n // can demand frames, no spurious reconnect cycle.\n if (this.isBattery && this.sleeping) {\n this.ctx.logger.debug('publishToBroker skipped — battery cam is sleeping', {\n tags: { deviceId: this.id },\n })\n return\n }\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.ctx.logger.debug('publishToBroker: ensureApi failed — will retry on reconnect', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n let streamOptions: import('@apocaliss92/nodelink-js').ReolinkVideoStreamOptionsResult\n // Hub-child cams share the parent's Baichuan socket. Pass our\n // channel + `onNvr: true` so the lib's stream enumeration runs\n // against the correct channel index — otherwise we get channel\n // 0's streams while addressing channel N's RFC 4571 server,\n // which produces black frames + the misleading\n // `[native-rfc4571 ch=0 profile=main]` log on a child whose\n // actual channel is 3.\n const isChild = this.isHubChild()\n const ownChannel = this.getChannel()\n const buildOptions = isChild ? { channel: ownChannel, onNvr: true } : undefined\n try {\n streamOptions = buildOptions\n ? await api.buildVideoStreamOptions(buildOptions)\n : await api.buildVideoStreamOptions()\n } catch (err) {\n this.ctx.logger.warn('publishToBroker: buildVideoStreamOptions failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n const channelCount = this.channelCount()\n const desired = new Map<string, {\n readonly camStreamId: string\n readonly kind: 'pull-rfc4571' | 'pull-rtsp' | 'pull-rtmp'\n readonly label: string\n readonly url?: string\n readonly autoEligible: boolean\n readonly resolution?: { width: number; height: number }\n readonly fps?: number\n readonly codec?: string\n readonly metadata?: Record<string, unknown>\n }>()\n\n // Native streams: LAZY PUBLISH. We build the broker entries from\n // device-side metadata (`buildVideoStreamOptions` → cmd_id 56\n // GetEnc) without opening the upstream Baichuan streaming session.\n // The lib's `createRfc4571TcpServer` would normally extract SDP\n // from a live IDR frame — that requires opening a dedicated TCP\n // session on the NVR for the channel, which then squats in the\n // firmware's session table for minutes after release.\n //\n // Instead we publish each entry with:\n // - a synthetic SDP built from `videoEncType` + audio metadata\n // (`buildSyntheticRfc4571Sdp`). SPS/PPS are omitted; the\n // muxer's `sendCachedVideoConfigToClient` primes downstream\n // consumers from in-band keyframe NAL units once the upstream\n // session is active.\n // - a `lazy:rfc4571:` sentinel URL. The broker detects this on\n // dial and emits `StreamBrokerOnRequestStreamSourceRefresh`\n // instead of attempting to connect. The\n // `addon.onStreamBrokerOnRequestStreamSourceRefresh` handler\n // responds by calling `materializeStreamSocket(camStreamId)`,\n // which is the only path that actually opens the upstream\n // Baichuan session via `ensureRfc4571Server`.\n //\n // Net effect: zero new sockets to the NVR until a consumer\n // (UI viewer, pipeline runner, restreamer) actually demands the\n // stream. Idle hub-children = idle session table firmware-side.\n for (const s of streamOptions.nativeStreams) {\n const fallbackChannel = isChild ? ownChannel : DEFAULT_CHANNEL\n const id = buildCamStreamId('native', s.channel ?? fallbackChannel, s.profile, channelCount)\n const m = s.metadata\n // Some Reolink firmwares report `videoEncType` as `\"H.265\"` /\n // `\"H.264\"` (with a dot), others as `\"H265\"` / `\"H264\"`. Strip\n // non-alphanumerics + lowercase so the broker registry stores\n // the canonical `\"h265\"` / `\"h264\"` form.\n const codec = normalizeCodecName(m?.videoEncType) ?? 'h264'\n const sdp = buildSyntheticRfc4571Sdp({\n videoEncType: m?.videoEncType ?? 'H.264',\n audioEnabled: (m?.audio ?? 0) === 1,\n })\n const entry: {\n readonly camStreamId: string\n readonly kind: 'pull-rfc4571' | 'pull-rtsp' | 'pull-rtmp'\n readonly label: string\n readonly url: string\n readonly autoEligible: boolean\n readonly resolution?: { width: number; height: number }\n readonly fps?: number\n readonly codec: string\n readonly metadata: Record<string, unknown>\n } = {\n camStreamId: id,\n kind: 'pull-rfc4571',\n label: s.name && s.name.length > 0 ? s.name : streamLabel(s, 'native'),\n url: buildLazyRfc4571Url(id),\n autoEligible: true,\n ...(m && m.width > 0 && m.height > 0 ? { resolution: { width: m.width, height: m.height } } : {}),\n ...(m && m.frameRate > 0 ? { fps: m.frameRate } : {}),\n codec,\n metadata: { sdp },\n }\n desired.set(id, entry)\n }\n\n // RTSP and RTMP — selectable secondary options, broker pulls directly\n // from the camera's own URLs. No local server needed.\n const enqueueAlt = (\n kind: 'rtsp' | 'rtmp',\n streams: ReadonlyArray<import('@apocaliss92/nodelink-js').ReolinkSupportedStream>,\n ): void => {\n if (streams.length === 0) return\n const brokerKind = kind === 'rtsp' ? 'pull-rtsp' : 'pull-rtmp'\n const fallbackChannel = isChild ? ownChannel : DEFAULT_CHANNEL\n for (const s of streams) {\n const id = buildCamStreamId(kind, s.channel ?? fallbackChannel, s.profile, channelCount)\n if (desired.has(id)) continue\n const m = s.metadata\n desired.set(id, {\n camStreamId: id,\n kind: brokerKind,\n label: s.name && s.name.length > 0 ? s.name : streamLabel(s, kind),\n url: s.urlWithAuth,\n autoEligible: false,\n ...(m && m.width > 0 && m.height > 0 ? { resolution: { width: m.width, height: m.height } } : {}),\n ...(m && m.frameRate > 0 ? { fps: m.frameRate } : {}),\n ...(normalizeCodecName(m?.videoEncType) ? { codec: normalizeCodecName(m?.videoEncType)! } : {}),\n })\n }\n }\n enqueueAlt('rtsp', streamOptions.rtspStreams)\n enqueueAlt('rtmp', streamOptions.rtmpStreams)\n\n if (desired.size === 0) {\n this.ctx.logger.warn('publishToBroker: camera reports no streams — nothing to publish', {\n tags: { deviceId: this.id },\n })\n return\n }\n\n // Retract any streams we previously published that aren't in the\n // new desired set — keeps the broker registry truthful when the\n // probe discovers the camera no longer offers a given container.\n const retract: string[] = []\n for (const id of this.publishedStreamIds) {\n if (!desired.has(id)) retract.push(id)\n }\n for (const id of retract) {\n try {\n await this.ctx.api.streamBroker.retractCameraStream.mutate({ deviceId: this.id, camStreamId: id })\n } catch (err) {\n this.ctx.logger.debug('retractCameraStream (stale) failed', {\n tags: { deviceId: this.id, camStreamId: id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n this.publishedStreamIds.delete(id)\n }\n\n for (const e of desired.values()) {\n await this.publishOne(e)\n }\n }\n\n private async publishOne(entry: {\n readonly camStreamId: string\n readonly kind: 'pull-rfc4571' | 'pull-rtsp' | 'pull-rtmp'\n readonly label: string\n readonly url?: string\n readonly autoEligible: boolean\n readonly resolution?: { width: number; height: number }\n readonly fps?: number\n readonly codec?: string\n readonly metadata?: Record<string, unknown>\n }): Promise<void> {\n try {\n await this.ctx.api.streamBroker.publishCameraStream.mutate({\n deviceId: this.id,\n camStreamId: entry.camStreamId,\n kind: entry.kind,\n label: entry.label,\n ...(entry.url ? { url: entry.url } : {}),\n ...(entry.resolution ? { resolution: entry.resolution } : {}),\n ...(entry.fps !== undefined ? { fps: entry.fps } : {}),\n ...(entry.codec !== undefined ? { codec: entry.codec } : {}),\n ...(entry.metadata ? { metadata: entry.metadata } : {}),\n deviceFeatures: [...this.features],\n autoEligible: entry.autoEligible,\n })\n this.publishedStreamIds.add(entry.camStreamId)\n } catch (err) {\n this.ctx.logger.warn('publishCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: entry.camStreamId },\n meta: { kind: entry.kind, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Materialize the upstream rfc4571 streaming session for one\n * camStreamId on demand. Called from the\n * `StreamBrokerOnRequestStreamSourceRefresh` listener when the broker\n * is about to dial a `lazy:rfc4571:` placeholder URL (or when an\n * established session went stale and the lib's loopback teardown ran).\n *\n * The flow:\n * 1. Parse camStreamId → (channel, profile)\n * 2. Open the rfc4571 server via `ensureRfc4571Server`. THIS is the\n * single point where a new TCP session to the NVR is opened —\n * `publishToBroker` no longer triggers it.\n * 3. Re-publish the broker entry with the real `tcp://...` URL +\n * the lib's accurate SDP (the lib has SPS/PPS now). The broker\n * picks up the fresh URL on its next `sourceProvider()` call.\n *\n * Idempotent: subsequent calls reuse the cached server when still\n * listening; if the lib idle-tore-down between calls we recreate.\n */\n async materializeStreamSocket(camStreamId: string): Promise<void> {\n const parts = parseCamStreamId(camStreamId, this.getChannel())\n if (!parts || parts.kind !== 'native') {\n // Non-native streams (rtsp/rtmp) carry their own URL upfront;\n // refresh requests for them are a no-op.\n return\n }\n const server = await this.ensureRfc4571Server(camStreamId, parts.channel, parts.profile)\n if (!server) {\n this.ctx.logger.warn('materializeStreamSocket: ensureRfc4571Server returned null', {\n tags: { deviceId: this.id, camStreamId },\n })\n return\n }\n const auth = `${encodeURIComponent(server.username)}:${encodeURIComponent(server.password)}`\n const url = `tcp://${auth}@${server.host}:${server.port}`\n // The lib's SDP carries SPS/PPS extracted from the live IDR — use\n // it (instead of the synthetic SDP) for the materialized entry so\n // downstream consumers get the most accurate decoder config. Both\n // the synthetic and the lib's SDP describe the same stream; only\n // the parameter sets differ.\n const codec = server.videoType === 'H265' ? 'h265' : 'h264'\n await this.publishOne({\n camStreamId,\n kind: 'pull-rfc4571',\n label: camStreamId,\n url,\n codec,\n autoEligible: true,\n metadata: { sdp: server.sdp },\n })\n this.ctx.logger.info('materialized rfc4571 stream socket on broker demand', {\n tags: { deviceId: this.id, camStreamId },\n meta: { url: `tcp://${server.host}:${server.port}` },\n })\n }\n\n override async removeDevice(): Promise<void> {\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n this.ctx.logger.info('Removing Reolink camera', { meta: { stableId: this.stableId } })\n const ids = [...this.publishedStreamIds]\n await Promise.all(\n ids.map((id) =>\n this.ctx.api.streamBroker.retractCameraStream\n .mutate({ deviceId: this.id, camStreamId: id })\n .catch((err: unknown) => {\n this.ctx.logger.debug('retractCameraStream failed', {\n tags: { deviceId: this.id, camStreamId: id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }),\n ),\n )\n this.publishedStreamIds.clear()\n await this.disconnectAll()\n }\n\n // ── Native-stream RFC 4571 servers ───────────────────────────────────\n\n /**\n * Spin up a loopback RFC 4571 TCP server for one (channel, profile)\n * tuple if we don't already have one. Returns the running server with\n * its `host`/`port`/`sdp`/`username`/`password`. Idempotent — repeat\n * calls return the existing instance.\n *\n * The lib's server manages the underlying Baichuan socket lifecycle\n * (open on first client connect, close on idle teardown), which keeps\n * battery cams asleep until a real consumer pulls the stream.\n */\n async ensureRfc4571Server(camStreamId: string, channel: number, profile: 'main' | 'sub' | 'ext'): Promise<Rfc4571TcpServer | null> {\n // Self-healing: the lib's rfc4571 servers idle-teardown after\n // ~15s with no TCP client. If a previous `ensureRfc4571Server`\n // call cached a server that has since been torn down,\n // `existing.server.server.listening` is false. We close the\n // cached entry and fall through to recreate, mirroring the\n // pattern Scrypted uses in `scrypted-reolink-native/src/stream-\n // utils.ts:ensureRfcServer`.\n const existing = this.active.get(camStreamId)\n if (existing?.server?.server?.listening) return existing.server\n if (existing) {\n this.ctx.logger.info('rfc4571: cached server is down — recreating', {\n tags: { deviceId: this.id, camStreamId },\n })\n try { await existing.server.close('recreate') } catch { /* already closed */ }\n this.active.delete(camStreamId)\n }\n\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.ctx.logger.error('rfc4571: Baichuan login failed', {\n tags: { deviceId: this.id, camStreamId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n // Online state is firmware-push-driven only (mirrors Scrypted) —\n // RFC4571 / login failures don't flip it; the next firmware\n // 'offline' push (or absence of an 'online' push) carries the\n // semantic transition.\n return null\n }\n\n // Use a per-stream username/password so the broker's pull URL stays\n // self-contained. Random suffix avoids leaking any device-level\n // secret onto the loopback socket. Auth is required to keep an\n // accidental scan on a free port from grabbing the stream.\n const username = `cam${this.id}`\n const password = randomToken(16)\n\n let server: Rfc4571TcpServer\n try {\n server = await createRfc4571TcpServer({\n api,\n // Keep our shared base api alive when the lib tears down a single\n // RFC 4571 server — the `disconnectAll` path handles api closure.\n closeApiOnTeardown: false,\n channel,\n profile,\n host: '127.0.0.1',\n username,\n password,\n requireAuth: true,\n // `deviceId` enables the lib's shared-server pool (see\n // `buildSharedRfc4571Key` in nodelink-js) — keyed by\n // `(deviceId, channel, profile, variant, …)`. Without it the\n // lib's \"unshared\" path opens a fresh BaichuanVideoStream on\n // the same control socket on EVERY call, and 3 concurrent\n // stream probes overload the camera (ECONNRESET). The pool\n // also handles in-flight dedup + refcount, so concurrent\n // assignProfile / publish races don't double-create.\n deviceId: this.stableId,\n // `requestedId` is used by the lib for composite-stream\n // pairing inference and shows up in shared-pool logs — pass\n // our broker camStreamId so log lines can be cross-referenced\n // with the broker's profile slots.\n requestedId: camStreamId,\n // Pass a console-shaped logger; the lib only calls `.log`/`.error`/\n // `.warn`/`.info`/`.debug` and we want them on our scoped logger.\n logger: this.libLoggerAdapter(),\n })\n } catch (err) {\n this.ctx.logger.error('rfc4571: createRfc4571TcpServer failed', {\n tags: { deviceId: this.id, camStreamId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return null\n }\n\n const entry = { camStreamId, profile: this.profileForCamStream(camStreamId), server }\n this.active.set(camStreamId, entry)\n\n // Mirror scrypted-reolink-native (`stream-utils.ts:459-463`):\n // the lib's idle-teardown closes its TCP listener after ~15s with\n // no clients. Without this listener the cache would survive the\n // teardown and the next `ensureRfc4571Server` call would still see\n // a stale entry until the explicit liveness check at the top of\n // the function. Evicting on `'close'` keeps the cache truthful\n // even when nobody asks for a while.\n server.server.once('close', () => {\n const current = this.active.get(camStreamId)\n if (current?.server === server) {\n this.active.delete(camStreamId)\n this.ctx.logger.debug('rfc4571: server closed — evicting cache entry', {\n tags: { deviceId: this.id, camStreamId },\n })\n }\n })\n\n this.ctx.logger.info('rfc4571: server listening', {\n tags: { deviceId: this.id, camStreamId },\n meta: { host: server.host, port: server.port, videoType: server.videoType, audio: server.audio?.codec ?? null },\n })\n\n return server\n }\n\n /** Map a camStreamId back to its broker profile (for `ActiveStream.profile`). */\n private profileForCamStream(camStreamId: string): CamProfile {\n // Best-effort. For NVR per-channel streams the camStreamId is `native:ch{N}-main`,\n // but the broker profile is what assignment chose. Default to 'high' if unknown —\n // this field is informational only (logging).\n if (camStreamId.endsWith(':sub') || camStreamId.endsWith('-sub')) return 'low'\n if (camStreamId.endsWith(':ext') || camStreamId.endsWith('-ext')) return 'mid'\n return 'high'\n }\n\n /**\n * Adapt the camstack scoped logger to the `Console` shape the lib expects.\n * Drops the structured `meta` object — the lib only formats the message,\n * not our hierarchical log fields.\n */\n private libLoggerAdapter(): Console {\n const log = this.ctx.logger.child('nodelink')\n const wrap = (level: 'info' | 'warn' | 'error' | 'debug') => (...args: unknown[]) => {\n const message = args.map((a) => (typeof a === 'string' ? a : JSON.stringify(a))).join(' ')\n log[level](message, {})\n }\n // Console is a class — coerce structurally; we only fill the methods the lib uses.\n return { log: wrap('info'), info: wrap('info'), warn: wrap('warn'), error: wrap('error'), debug: wrap('debug') } as unknown as Console\n }\n\n\n /**\n * Close every running RFC 4571 server (Slice 9). Used when the camera\n * reports sleep / device removal. Keeps the api alive so we still\n * receive `awake` events; the servers will be re-spawned the next\n * time `publishToBroker` runs (after restore / reconnect).\n */\n private async closeActiveStreams(reason: string): Promise<void> {\n const entries = [...this.active.values()]\n this.active.clear()\n if (entries.length === 0) return\n this.ctx.logger.debug('Closing active RFC 4571 servers', {\n tags: { deviceId: this.id },\n meta: { count: entries.length, reason },\n })\n for (const entry of entries) {\n try { await entry.server.close('teardown') } catch { /* best-effort */ }\n }\n }\n\n /**\n * Periodic passive sleep-status poll (Slice 9, battery cams only).\n * Calls `api.getSleepStatus()` which inspects recent socket I/O —\n * no network traffic emitted, no risk of waking the camera. Used to\n * reconcile our `this.sleeping` flag when push events miss the\n * `sleeping`/`awake` transition (firmware quirks, NAT timeouts).\n */\n private startSleepPoll(): void {\n if (this.sleepPollTimer) return\n if (!this.isBattery) return\n // Hub-child battery cams: skip the poll. Mirrors Scrypted's\n // `nvr.ts` pattern (camera.ts:3768 — `if (!this.nvrDevice && !this.multiFocalDevice)`).\n // The Hub holds the single Baichuan subscription and forwards\n // `awake`/`sleeping` simpleEvents into the child's\n // `handleSimpleEvent`, which is the canonical writer of the\n // `battery.sleeping` slice. A second poll on the child would\n // duplicate the work without adding signal.\n if (this.isHubChild()) return\n // 5 s — same cadence Scrypted's reolink-native uses. Pure passive\n // call (`api.getSleepStatus` inspects internal socket-I/O cache,\n // emits no network traffic), so a tight cadence is cheap.\n const POLL_MS = 5_000\n this.sleepPollTimer = setInterval(() => {\n const api = this.api\n if (!api) return\n try {\n const status = api.getSleepStatus({ channel: this.getChannel() })\n if (status.state === 'sleeping' && !this.sleeping) {\n this.state.battery.sleeping = true\n this.ctx.logger.info('Sleep poll detected sleep — closing active streams', {\n tags: { deviceId: this.id },\n meta: { idleMs: status.idleMs },\n })\n void this.closeActiveStreams('sleep-poll').catch(() => { /* best-effort */ })\n } else if (status.state === 'awake' && this.sleeping) {\n this.state.battery.sleeping = false\n this.ctx.logger.debug('Sleep poll detected awake', { tags: { deviceId: this.id } })\n // Backstop for a missed `awake` push event — drive the\n // same refresh contract the simpleEvent handler uses so\n // both paths converge to identical post-wake state.\n void this.onWakeTransition('sleep-poll').catch(() => { /* best-effort */ })\n }\n } catch (err) {\n this.ctx.logger.debug('sleep poll error', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }, POLL_MS)\n if (typeof this.sleepPollTimer.unref === 'function') this.sleepPollTimer.unref()\n }\n\n private stopSleepPoll(): void {\n if (this.sleepPollTimer) {\n clearInterval(this.sleepPollTimer)\n this.sleepPollTimer = null\n }\n }\n\n // ── Aux device align (Scrypted alignAuxDevicesState pattern) ─────────\n //\n // Spawned accessory children (siren, floodlight, pir) hold their own\n // runtime-state slice but don't poll. The parent runs a single\n // periodic align that refreshes every child's slice from firmware,\n // respecting:\n // - per-accessory cooldowns (15s window after operator setState\n // so a stale firmware read doesn't clobber the just-applied\n // value — Reolink's GET endpoints lag the SET by 5-10s).\n // - the parent's sleep state (battery cams: skip while sleeping;\n // run once on the awake transition).\n // Wired cams: setInterval at 10s, mirroring Scrypted's cadence.\n\n /** Implements `ReolinkAccessoryHost.registerAccessoryChild`. Called\n * from each accessory's constructor. Idempotent — re-registering the\n * same kind overwrites the prior ref (covers a replay-on-restart\n * scenario where the parent ctor lands before the child ctor of an\n * already-spawned accessory). */\n registerAccessoryChild(ref: import('./accessories/base.js').ReolinkAccessoryRef): void {\n this.auxAccessoryRefs.set(ref.kind, ref)\n }\n\n /** Pull-once refresh of every registered aux accessory's slice.\n * Best-effort: per-accessory failures are logged + swallowed by the\n * individual `refreshFromAlign` impls; this loop never throws. */\n private async alignAuxDevicesState(reason: 'periodic' | 'wake' | 'init'): Promise<void> {\n if (this.auxAccessoryRefs.size === 0) return\n if (this.isBattery && this.sleeping && reason === 'periodic') {\n // Battery cam asleep → skip (would 400 + drain). The wake\n // transition path will fire `alignAuxDevicesState('wake')` when\n // the cam comes back up.\n return\n }\n const refs = [...this.auxAccessoryRefs.values()]\n const skipped: string[] = []\n const promises: Promise<void>[] = []\n for (const ref of refs) {\n if (ref.isInCooldown()) {\n skipped.push(ref.kind)\n continue\n }\n promises.push(ref.refreshFromAlign().catch(() => { /* per-accessory swallowed */ }))\n }\n await Promise.allSettled(promises)\n if (skipped.length > 0) {\n this.ctx.logger.debug('alignAuxDevicesState: skipped (cooldown)', {\n tags: { deviceId: this.id },\n meta: { reason, skipped },\n })\n }\n }\n\n /** Start periodic align for wired cams. No-op for battery cams (wake\n * path handles it). Idempotent. */\n private startAlignAuxPolling(): void {\n if (this.alignAuxTimer) return\n if (this.isBattery) return\n const POLL_MS = 10_000\n this.alignAuxTimer = setInterval(() => {\n void this.alignAuxDevicesState('periodic')\n }, POLL_MS)\n if (typeof this.alignAuxTimer.unref === 'function') this.alignAuxTimer.unref()\n }\n\n private stopAlignAuxPolling(): void {\n if (this.alignAuxTimer) {\n clearInterval(this.alignAuxTimer)\n this.alignAuxTimer = null\n }\n }\n\n /**\n * Shared wake-transition handler invoked by both the simpleEvent\n * `awake` push (canonical fast path) and the sleep poll's\n * `sleeping → awake` flip (backstop). Mirrors Scrypted's\n * `updateSleepingState`'s wake branch (camera.ts:3543-3560) — fan\n * out the refreshes that depend on the camera being responsive:\n * - aux accessories (siren/floodlight/PIR) re-read their\n * firmware state via `alignAuxDevicesState('wake')`\n * - parent settings snapshot (image / motion / AI / enc / mask /\n * audioNoise / autoFocus) re-reads via\n * `refreshParentSettingsSnapshot()` so the Settings UI shows\n * camera-current values instead of the snapshot taken before\n * the cam went to sleep.\n * Both calls are best-effort; per-call failures land in their own\n * debug logs and never break the wake-transition flow.\n */\n private async onWakeTransition(reason: 'simple-event' | 'sleep-poll'): Promise<void> {\n this.ctx.logger.debug('reolink wake transition — fanning out refresh', {\n tags: { deviceId: this.id },\n meta: { reason },\n })\n await Promise.allSettled([\n this.alignAuxDevicesState('wake'),\n this.refreshParentSettingsSnapshot(),\n ])\n }\n\n // ── Reliability watchdogs (Slice 10) ─────────────────────────────────\n\n private static readonly PING_INTERVAL_MS = 30_000\n private static readonly PING_MAX_FAILURES = 3\n private static readonly EVENT_CHECK_INTERVAL_MS = 60_000\n private static readonly EVENT_STALE_THRESHOLD_MS = 10 * 60_000\n\n /**\n * Arm the connection-liveness ping watchdog and the event-watchdog.\n * Battery cams skip the ping watchdog — they keep `setIdleDisconnect=true`\n * so the api intentionally goes silent and a ping would just wake the\n * camera and burn battery for no signal.\n *\n * Idempotent: re-arming clears prior timers first so we don't end up\n * with parallel watchdogs after a reconnect cycle.\n */\n private startWatchdogs(): void {\n this.stopWatchdogs()\n this.watchdogStartedAt = Date.now()\n this.consecutivePingFailures = 0\n\n if (!this.isBattery) {\n this.pingWatchdogTimer = setInterval(() => {\n void this.runPingWatchdog().catch(() => { /* logged inside */ })\n }, ReolinkCamera.PING_INTERVAL_MS)\n if (typeof this.pingWatchdogTimer.unref === 'function') this.pingWatchdogTimer.unref()\n }\n\n // Event watchdog also skipped for battery cams — Scrypted's\n // `eventCheckInterval` is double-gated on `eventSubscriptionActive\n // === false && socket connected` (`baichuan-base.ts:735-746`),\n // which is effectively never true on UDP/battery during idle.\n // Running our equivalent here just risks waking the camera the\n // moment our 10-min stale threshold trips. The lib has its own\n // `simpleEventWatchdog` (`ReolinkBaichuanApi.ts:2854`) that\n // re-subscribes via `subscribeEvents` after exponential backoff,\n // and it short-circuits on `!isSocketConnected` so battery cams\n // are protected by it too.\n if (!this.isBattery) {\n this.eventWatchdogTimer = setInterval(() => {\n void this.runEventWatchdog().catch(() => { /* logged inside */ })\n }, ReolinkCamera.EVENT_CHECK_INTERVAL_MS)\n if (typeof this.eventWatchdogTimer.unref === 'function') this.eventWatchdogTimer.unref()\n }\n }\n\n private stopWatchdogs(): void {\n if (this.pingWatchdogTimer) {\n clearInterval(this.pingWatchdogTimer)\n this.pingWatchdogTimer = null\n }\n if (this.eventWatchdogTimer) {\n clearInterval(this.eventWatchdogTimer)\n this.eventWatchdogTimer = null\n }\n this.stopEventHealthCheck()\n this.consecutivePingFailures = 0\n }\n\n private async runPingWatchdog(): Promise<void> {\n const api = this.api\n if (!api) return\n if (!api.client.isSocketConnected()) {\n // Socket is already gone — a reconnect is presumably in flight.\n // Don't pile on; reset the counter and let scheduleReconnect run.\n this.consecutivePingFailures = 0\n return\n }\n try {\n await api.ping()\n this.consecutivePingFailures = 0\n } catch (err) {\n this.consecutivePingFailures++\n const recoverable = isRecoverableBaichuanError(err)\n this.ctx.logger.debug('Reolink ping failed', {\n tags: { deviceId: this.id },\n meta: {\n failures: this.consecutivePingFailures,\n recoverable,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n if (this.consecutivePingFailures >= ReolinkCamera.PING_MAX_FAILURES) {\n this.ctx.logger.warn('Reolink ping watchdog: too many failures — recycling api', {\n tags: { deviceId: this.id },\n meta: { failures: this.consecutivePingFailures },\n })\n this.consecutivePingFailures = 0\n this.scheduleReconnect('ping-watchdog')\n }\n }\n }\n\n private async runEventWatchdog(): Promise<void> {\n const api = this.api\n if (!api) return\n if (!api.client.isSocketConnected() || !api.client.loggedIn) return\n\n const now = Date.now()\n const reference = this.lastEventAt > 0 ? this.lastEventAt : this.watchdogStartedAt\n const idleMs = now - reference\n if (idleMs < ReolinkCamera.EVENT_STALE_THRESHOLD_MS) return\n\n this.ctx.logger.warn('Reolink event watchdog: re-subscribing after idle window', {\n meta: { idleMs, lastEventAt: this.lastEventAt },\n })\n try {\n await this.resubscribeSimpleEvents(api, 'watchdog')\n // Reset the baseline so a fresh idle window starts now — no\n // events in the next 10 min will trigger another resubscribe.\n this.watchdogStartedAt = Date.now()\n } catch (err) {\n this.ctx.logger.debug('Reolink event watchdog re-subscribe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Subscribe (or re-subscribe) `handleSimpleEventBound` against\n * the Baichuan API. Single-source-of-truth for the listener\n * lifecycle:\n *\n * 1. Always `offSimpleEvent(handleSimpleEventBound)` first —\n * Set-dedup is a no-op on the first call (handler isn't\n * registered yet) but on re-subscribes it cleanly removes\n * the prior registration so the lib's listener Set never\n * grows past one entry per device.\n * 2. Verify the connection is ready (socket + login). The\n * lib's own `ensureSimpleEventSubscribed` checks this too,\n * but failing fast at the camstack layer keeps the\n * re-subscribe attempts in lock-step with the connection\n * state we observe — important for the `event-health`\n * interval that fires every 60s.\n * 3. Register via `onSimpleEvent(handleSimpleEventBound)`.\n * Lib starts/refreshes its own subscribe + 5min watchdog.\n *\n * Mirrors `subscribeToEventsInternal` in Scrypted's\n * `baichuan-base.ts:837-888` — same pattern, same rationale.\n */\n private async resubscribeSimpleEvents(\n api: ReolinkBaichuanApi,\n reason: string,\n ): Promise<void> {\n try {\n await api.offSimpleEvent(this.handleSimpleEventBound)\n } catch {\n // First subscribe (handler not yet registered) — `Set.delete`\n // returns false but doesn't throw. Any other error here\n // shouldn't block the re-subscribe.\n }\n if (!api.client.isSocketConnected() || !api.client.loggedIn) {\n this.ctx.logger.debug('Reolink simpleEvent subscribe skipped — socket not ready', {\n meta: { reason, connected: api.client.isSocketConnected(), loggedIn: api.client.loggedIn },\n })\n return\n }\n await api.onSimpleEvent(this.handleSimpleEventBound)\n this.ctx.logger.debug('Reolink simpleEvent subscribed', { meta: { reason } })\n }\n\n /**\n * Plugin-level event-health watchdog. Fires every 60s while the\n * api is alive. Two recovery paths:\n *\n * - If we've never received an event AND the watchdog baseline\n * is older than EVENT_STALE_THRESHOLD_MS, force a full\n * re-subscribe. Catches \"subscribe call succeeded but the\n * camera silently rejected the registration\" — the lib's\n * own watchdog uses the same trigger but a re-arm at the\n * plugin level recovers from cases where the lib's internal\n * state has drifted.\n * - If the connection went down between our `runEventWatchdog`\n * ticks (every 30s by default), the next tick of THIS check\n * re-subscribes the moment the socket comes back up.\n */\n private startEventHealthCheck(): void {\n this.stopEventHealthCheck()\n this.consecutiveStaleHealthChecks = 0\n this.nextEventHealthCheckAt = 0\n this.eventHealthCheckTimer = setInterval(() => {\n void (async () => {\n const api = this.api\n if (!api) return\n if (!api.client.isSocketConnected() || !api.client.loggedIn) return\n // Backoff gate: chronically-silent cameras (subnet quirks,\n // firmware that only emits events on motion in an empty room)\n // used to log every 60s for hours. Skip ticks until the\n // backoff window elapses.\n const now = Date.now()\n if (now < this.nextEventHealthCheckAt) return\n const idleMs = now - (this.lastEventAt > 0 ? this.lastEventAt : this.watchdogStartedAt)\n if (idleMs < ReolinkCamera.EVENT_STALE_THRESHOLD_MS) return\n // Log degradation: first re-subscribe in a stale streak is\n // `info` so operators see we noticed; subsequent ones drop\n // to `debug` to stop spamming. Counter is reset to 0 by\n // `handleSimpleEvent` on the first successful event delivery.\n const meta = {\n idleMs,\n lastEventAt: this.lastEventAt,\n consecutive: this.consecutiveStaleHealthChecks + 1,\n }\n if (this.consecutiveStaleHealthChecks === 0) {\n this.ctx.logger.info('Reolink event-health check forcing full re-subscribe', { meta })\n } else {\n this.ctx.logger.debug('Reolink event-health check forcing full re-subscribe', { meta })\n }\n try {\n await this.resubscribeSimpleEvents(api, 'event-health-check')\n this.watchdogStartedAt = Date.now()\n } catch (err) {\n this.ctx.logger.debug('Reolink event-health re-subscribe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n // Schedule the next allowed check using exponential backoff\n // (60s → 2min → 5min → 15min capped). Mirrors common\n // long-poll backoff patterns; a real event arriving resets\n // the counter and the next stale streak starts fresh at 60s.\n this.consecutiveStaleHealthChecks += 1\n const backoffMs = computeHealthCheckBackoffMs(this.consecutiveStaleHealthChecks)\n this.nextEventHealthCheckAt = Date.now() + backoffMs\n })()\n }, 60_000)\n if (typeof this.eventHealthCheckTimer.unref === 'function') {\n this.eventHealthCheckTimer.unref()\n }\n }\n\n private stopEventHealthCheck(): void {\n if (this.eventHealthCheckTimer) {\n clearInterval(this.eventHealthCheckTimer)\n this.eventHealthCheckTimer = null\n }\n }\n\n /** Stop every active stream and close the Baichuan API. */\n async disconnectAll(): Promise<void> {\n this.stopSleepPoll()\n this.stopAlignAuxPolling()\n this.stopWatchdogs()\n const entries = [...this.active.values()]\n this.active.clear()\n for (const entry of entries) {\n try { await entry.server.close('disconnect-all') } catch { /* best-effort */ }\n }\n if (this.api) {\n try { await this.api.close({ reason: 'device-shutdown' }) } catch { /* best-effort */ }\n this.api = null\n }\n this.markOnline(false)\n }\n\n // ── Settings UI ───────────────────────────────────────────────────────\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const cache = this.config.get('deviceCache')\n const motionSnap = cache?.motionSnapshot ?? {}\n const imgSnap = cache?.imageSnapshot ?? {}\n // On-demand retry — if any settings-snapshot is missing from the\n // persisted cache, kick a background refresh. Debounced so opening\n // the form repeatedly during a sleep window doesn't spam the lib.\n // Battery cams that were sleeping at the time of the initial probe\n // (`onCreated` → `refreshParentSettingsSnapshot`) hit this path the\n // first time the operator opens settings post-wake.\n if (this.hasIncompleteSettingsCache()) {\n const now = Date.now()\n if (now - this.lastSettingsSnapshotRetryAt > ReolinkCamera.SETTINGS_SNAPSHOT_RETRY_MIN_MS) {\n this.lastSettingsSnapshotRetryAt = now\n void this.refreshParentSettingsSnapshot().catch(() => { /* best-effort */ })\n }\n }\n const schema: ConfigUISchema = {\n tabs: [\n // Same taxonomy as Hikvision — `general` (connection /\n // credentials / debug-as-advanced) + per-concern tabs. The\n // `image` / `audio` ids are well-known across the admin UI\n // so the tab labels + icons come from the registry; we just\n // declare the order here so Image lands before Audio.\n // NOTE: `streams` tab disabled until we have a reliable way\n // to seed bitrate / fps / codec / resolution from camera\n // truth. Showing zod defaults (4096 / 25 / etc) misleads\n // operators into thinking the form reflects firmware state.\n // Re-enable once `getEnc` snapshot is fully wired into the\n // form's `default:` values for every field.\n { id: 'general', label: 'General', icon: 'settings', order: 0 },\n { id: 'image', label: 'Image', icon: 'image', order: 30 },\n { id: 'audio', label: 'Audio', icon: 'volume', order: 40 },\n { id: 'sessions', label: 'Sessions', icon: 'users', order: 80 },\n ],\n sections: [\n // Identity (`name`, `location`) is base meta — rendered by the\n // device-manager's contribution, not the driver schema.\n {\n id: 'connection',\n tab: 'general',\n title: 'Connection',\n description: 'Baichuan protocol connection settings.',\n columns: 2,\n fields: [\n { type: 'text', key: 'host', label: 'Camera IP', required: true, placeholder: '192.168.1.100' },\n { type: 'number', key: 'port', label: 'Port', default: 9000, min: 1, max: 65535, step: 1 },\n { type: 'text', key: 'username', label: 'Username', default: 'admin' },\n { type: 'password', key: 'password', label: 'Password', required: true, showToggle: true },\n ],\n },\n // ── Motion (writable via setMotionAlarm cmd_id=47) ──────────\n {\n id: 'motion',\n tab: 'motion',\n title: 'On-camera motion (MdAlarm)',\n description:\n 'Camera-side motion detection master switch + sensitivity. Pushed via Baichuan `SetMdAlarm` (cmd_id 47). Range 1..50 matches the Reolink mobile app + web UI; the driver inverts to the camera-side raw value (`51 - value`) before push.',\n columns: 2,\n fields: [\n {\n type: 'boolean',\n key: 'motionEnabled',\n label: 'Motion detection',\n description: 'Master switch for the camera\\'s on-board motion detector. Disabling stops the camera from emitting motion events on the alarm channel.',\n default: motionSnap.enabled ?? true,\n style: 'switch',\n span: 2,\n },\n {\n type: 'slider',\n key: 'motionSensitivity',\n label: 'Sensitivity',\n description: 'Higher = triggers on smaller motion. Reolink user-facing range 1..50.',\n min: 1,\n max: 50,\n step: 1,\n default: motionSnap.sensitivity ?? 25,\n showValue: true,\n showStepper: true,\n span: 2,\n },\n ],\n },\n // ── AI sensitivity (writable via setAiDetection cmd_id=343) ──\n // Per-class sliders. Each one only renders when the camera's\n // `getAiDetectTypes` advertised the matching detector — the\n // Reolink mobile app uses the same gating so unsupported\n // models (e.g. fixed cams without AI) get a clean Motion tab.\n ...((cache?.aiDetectTypes && cache.aiDetectTypes.length > 0)\n ? [{\n id: 'ai-sensitivity',\n tab: 'motion' as const,\n title: 'AI detection sensitivity',\n description:\n 'Per-class on-camera AI sensitivity. Pushed via `SetAiAlarm` (cmd_id 343). Reolink range 0..100. Each section appears only when the camera advertises the corresponding detector.',\n columns: 2 as const,\n fields: ([\n ['people', 'aiPersonSensitivity', 'Person'],\n ['vehicle', 'aiVehicleSensitivity', 'Vehicle'],\n ['dog_cat', 'aiAnimalSensitivity', 'Animal'],\n ['face', 'aiFaceSensitivity', 'Face'],\n ['package', 'aiPackageSensitivity', 'Package'],\n ] as const)\n .filter(([type]) => cache.aiDetectTypes!.includes(type))\n .map(([type, key, label]) => ({\n type: 'slider' as const,\n key,\n label: `${label} sensitivity`,\n min: 0,\n max: 100,\n step: 1,\n default: cache.aiSensitivitySnapshot?.[type]?.sensitivity ?? 50,\n showValue: true,\n showStepper: true,\n })),\n }]\n : []),\n // ── Image (writable via setImage / setIsp on cmd_id 25) ─────\n // Snapshot from `GetVideoInput` (cmd_id 26) seeds the\n // defaults so the operator sees the current value when they\n // first open the tab. Edits are pushed to the camera in the\n // applySettingsPatch dispatcher below — no round-trip to the\n // mobile app or on-device menu required anymore.\n {\n id: 'image',\n tab: 'image',\n title: 'Image',\n description:\n 'Core image settings pushed via Baichuan `SetImage` (cmd_id 25). Each slider rearms the camera on save; defaults seed from a `GetVideoInput` probe at boot (refreshed on every `applySettingsPatch`). Range 0..255 — most Reolink models clamp to 0..100 internally.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'imgBright',\n label: 'Brightness',\n min: 0,\n max: 255,\n step: 1,\n default: imgSnap.bright ?? 128,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'imgContrast',\n label: 'Contrast',\n min: 0,\n max: 255,\n step: 1,\n default: imgSnap.contrast ?? 128,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'imgSaturation',\n label: 'Saturation',\n min: 0,\n max: 255,\n step: 1,\n default: imgSnap.saturation ?? 128,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'imgHue',\n label: 'Hue',\n min: 0,\n max: 255,\n step: 1,\n default: imgSnap.hue ?? 128,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'imgSharpen',\n label: 'Sharpness',\n min: 0,\n max: 255,\n step: 1,\n default: 128,\n showValue: true,\n showStepper: true,\n span: 2,\n },\n ],\n },\n // ── ISP (writable — same cmd_id 25 + 297 for dayNightThreshold) ─\n ...(imgSnap.dayNight !== undefined\n ? [{\n id: 'isp',\n tab: 'image' as const,\n title: 'Advanced (ISP)',\n description:\n 'Day/Night + exposure + HDR. Pushed via `SetIsp` (cmd_id 25). The day/night threshold uses cmd_id 297 (DayNightThreshold).',\n columns: 2 as const,\n fields: [\n {\n type: 'text' as const,\n key: 'ispDayNight',\n label: 'Day/Night mode',\n description: 'Camera-specific values — common: `auto`, `color`, `blackAndWhite`. The driver normalizes case before push.',\n default: imgSnap.dayNight ?? 'auto',\n },\n {\n type: 'text' as const,\n key: 'ispExposure',\n label: 'Exposure',\n description: 'Common values: `auto`, `manual`. Lowercased before push.',\n default: 'auto',\n },\n ],\n }]\n : []),\n // ── Stream / Encoder (writable via setEnc cmd_id 57) ─────────\n // Bitrate (kbps) + framerate per main/sub stream + global\n // audio capture toggle. Defaults seed from the snapshot\n // (`getEnc`) when present so the operator sees the camera-\n // current value the first time the form opens.\n //\n // Codec + resolution are NOT exposed as inputs:\n // - codec needs a per-camera capability check (older\n // firmware ships h264-only) which we don't have yet;\n // - resolution is firmware-fixed per stream slot (the\n // lib's `setEnc` patch surface only accepts `bitRate /\n // frameRate / videoEncType`).\n // Both are surfaced read-only in each slider's description\n // (`Codec: ...; resolution: ...`) so the operator sees the\n // camera-current value without ambiguity.\n //\n // STREAM-ENCODER SECTION DISABLED. Was rendering with zod\n // defaults (4096 / 25 / 512 / 15) when the snapshot probe\n // hadn't completed, misleading operators about firmware\n // state. Re-enable in a follow-up once we wire `getEnc`\n // snapshot end-to-end (currently `body.Compression` parsing\n // works but the form's `default:` values aren't sourced from\n // firmware reliably).\n // The tab is also disabled in `tabs:` above. Both will be\n // re-enabled together. See git blame for the prior layout.\n // ── IR + status LED (writable via setIrLights cmd_id 209) ────\n {\n id: 'ir-lights',\n tab: 'image',\n title: 'IR + status LED',\n description:\n 'IR LED control + per-camera IR brightness. Pushed via `SetIrLights` (cmd_id 209). State `Auto` lets the camera switch IR on automatically at low light; `Off` forces colour-only mode.',\n columns: 2,\n fields: [\n {\n type: 'select',\n key: 'irLightsState',\n label: 'IR LED',\n default: 'Auto',\n options: [\n { value: 'Auto', label: 'Auto (camera decides)' },\n { value: 'On', label: 'Always on' },\n { value: 'Off', label: 'Always off' },\n ],\n },\n {\n type: 'slider',\n key: 'irLightsBrightness',\n label: 'IR brightness',\n description: 'Only applied when the camera advertises programmable IR brightness. 0..255 raw range; many models clamp to 0..100.',\n min: 0,\n max: 255,\n step: 5,\n default: 128,\n showValue: true,\n showStepper: true,\n },\n ],\n },\n // ── Privacy mask master switch (writable via setMask cmd_id 53) ─\n // Only the global enable flag — zone editing is a separate\n // flow not exposed by this driver. Renders in the image tab\n // so the operator finds it next to the other camera-frame\n // controls. Always rendered: every Reolink model accepts the\n // setter (silently no-ops if no zones are configured).\n {\n id: 'privacy-mask',\n tab: 'image',\n title: 'Privacy mask',\n description:\n 'Master enable for the privacy zones configured on the camera (set up via the Reolink mobile app + camera UI). Pushed via `SetMask` (cmd_id 53). Disabling shows the full frame; enabling re-applies the persisted zones.',\n columns: 1,\n fields: [\n {\n type: 'boolean',\n key: 'privacyMaskEnabled',\n label: 'Privacy mask enabled',\n default: cache?.maskSnapshot?.enabled ?? false,\n style: 'switch',\n },\n ],\n },\n // ── Auto-focus (writable via setAutoFocus cmd_id 225) ────────\n // Only rendered when the snapshot probe flagged\n // `supported=true`. Fixed-focus cameras (most Reolink bullet\n // models) don't expose AF and would round-trip an error\n // every save — gating keeps the form clean.\n ...(cache?.autoFocusSnapshot?.supported === true\n ? [{\n id: 'auto-focus',\n tab: 'image' as const,\n title: 'Auto focus',\n description:\n 'Toggles the lens auto-focus subsystem. Pushed via `SetAutoFocus` (cmd_id 225). Disabling locks the lens at its current focal position — useful for fixed-distance scenes where AF would hunt.',\n columns: 1 as const,\n fields: [\n {\n type: 'boolean' as const,\n key: 'autoFocusEnabled',\n label: 'Auto focus',\n default: cache.autoFocusSnapshot?.enabled ?? true,\n style: 'switch' as const,\n },\n ],\n }]\n : []),\n // ── Audio (writable via setAudioCfg cmd_id 265) ─────────────\n {\n id: 'audio',\n tab: 'audio',\n title: 'Audio output',\n description:\n 'Speaker volumes pushed via `SetAudioCfg` (cmd_id 265). All ranges 0..100. Doorbell models expose the visitor / talkAndReply channels — non-doorbell cameras ignore those without erroring.',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'audioVolume',\n label: 'Speaker volume',\n min: 0,\n max: 100,\n step: 5,\n default: 50,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'audioTalkAndReplyVolume',\n label: 'Talk + reply volume',\n description: 'Two-way audio + canned reply playback. Doorbell-only on most firmwares.',\n min: 0,\n max: 100,\n step: 5,\n default: 50,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider',\n key: 'audioVisitorVolume',\n label: 'Visitor volume',\n description: 'Visitor / chime playback channel. Doorbell-only on most firmwares.',\n min: 0,\n max: 100,\n step: 5,\n default: 50,\n showValue: true,\n showStepper: true,\n span: 2,\n },\n ],\n },\n // ── Audio noise reduction (writable via setAudioNoise cmd_id 440) ─\n // Single integer level: 0 = off; 1..3 = low / mid / high.\n // Lib treats `level <= 0` as the off path, so a value of 0\n // reliably disables denoise without needing a separate\n // boolean. Renders unconditionally — the camera silently\n // accepts the setter even when the firmware doesn't expose\n // a denoise UI in the mobile app.\n {\n id: 'audio-noise',\n tab: 'audio',\n title: 'Audio noise reduction',\n description:\n 'On-camera AI denoise applied to the encoded audio track. Pushed via `SetAudioNoise` (cmd_id 440). 0 disables; 1..3 maps to the Reolink mobile app levels (low / mid / high).',\n columns: 1,\n fields: [\n {\n type: 'slider',\n key: 'audioNoiseLevel',\n label: 'Noise reduction level',\n description: '0 = off · 1 = low · 2 = medium · 3 = high.',\n min: 0,\n max: 3,\n step: 1,\n default: cache?.audioNoiseSnapshot?.enabled\n ? (cache.audioNoiseSnapshot?.level ?? 1)\n : 0,\n showValue: true,\n showStepper: true,\n },\n ],\n },\n // ── Intercom tuning ─────────────────────────────────────────\n // Mirrors scrypted-reolink-native's `intercom-mixin.ts` storage\n // settings: same names, same ranges. Only renders when the cam\n // advertises two-way audio so non-intercom models don't show\n // controls that have no effect. Source-of-truth is the\n // `feature-probe` runtime-state slice (the canonical post-\n // migration store; the legacy `deviceCache.hasIntercom` was\n // stripped by `DeviceConfig.fromSchema` self-healing).\n ...(this.getProbeFlags<ReolinkProbeFlags>().hasIntercom === true\n ? [{\n id: 'intercom',\n tab: 'audio' as const,\n title: 'Intercom',\n description:\n 'Push-to-talk pipeline tuning. Settings take effect on the next talk session — restart any active session for changes to apply.',\n columns: 2 as const,\n fields: [\n {\n type: 'slider' as const,\n key: 'intercomBlocksPerPayload',\n label: 'Blocks per payload',\n description: 'Lower = lower latency (more sendAudio calls/sec). Higher = fewer calls, less per-call socket overhead. Range 1..8. Default 1 (Scrypted-parity, lowest latency).',\n min: 1,\n max: 8,\n step: 1,\n default: this.config.get('intercomBlocksPerPayload') ?? 1,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider' as const,\n key: 'intercomMaxBacklogMs',\n label: 'Max backlog (ms)',\n description: 'Maximum PCM backlog before dropping oldest samples to cap latency. Higher = more stable on slow systems but more latency. Range 20..5000. Default 120 (Scrypted-parity).',\n min: 20,\n max: 5000,\n step: 20,\n default: this.config.get('intercomMaxBacklogMs') ?? 120,\n showValue: true,\n showStepper: true,\n },\n {\n type: 'slider' as const,\n key: 'intercomGain',\n label: 'Output gain',\n description: 'Multiplier applied to PCM samples before IMA-ADPCM encode. 1.0 = neutral, 2.0 ≈ +6dB, 0.5 ≈ -6dB. Hard-clipped at int16 bounds.',\n min: 0.1,\n max: 10,\n step: 0.1,\n default: this.config.get('intercomGain') ?? 1.0,\n showValue: true,\n showStepper: true,\n span: 2 as const,\n },\n ],\n }]\n : []),\n {\n id: 'debug',\n tab: 'advanced',\n title: 'Debug',\n description:\n 'Lib-side Baichuan tracing. Mirrors scrypted-reolink-native. ' +\n 'Changing these resets the control-channel client (active streams keep running) ' +\n 'so new debug options take effect on the next dial.',\n columns: 1,\n fields: [\n {\n type: 'boolean',\n key: 'debugGeneral',\n label: 'General debug logs',\n description: 'Forwards `DebugOptions.general=true` — verbose lib protocol logs.',\n default: false,\n style: 'switch',\n },\n {\n type: 'multiselect',\n key: 'debugSocketLogs',\n label: 'Socket trace categories',\n description: 'Per-area lib tracing. Pick only what you need; each adds log volume.',\n default: [],\n options: [\n { value: 'debugRtsp', label: 'RTSP (proxy/server traffic)' },\n { value: 'traceNativeStream', label: 'Native stream (cmd 3/4 + H.264/H.265 + param sets)' },\n { value: 'traceRecordings', label: 'Recordings (FileInfoList, findAlarmVideo, download)' },\n { value: 'traceTalk', label: 'Talkback (cmd 10/11/201/202)' },\n { value: 'traceEvents', label: 'Events (cmd 33 — AlarmEventList push)' },\n ],\n },\n ],\n },\n ...this.buildSessionsTabSections(),\n ],\n }\n const values: Record<string, unknown> = {\n host: this.config.get('host'),\n port: this.config.get('port'),\n username: this.config.get('username'),\n password: this.config.get('password'),\n debugGeneral: this.config.get('debugGeneral') ?? false,\n debugSocketLogs: this.config.get('debugSocketLogs') ?? [],\n motionEnabled: this.config.get('motionEnabled') ?? motionSnap.enabled ?? true,\n motionSensitivity: this.config.get('motionSensitivity') ?? motionSnap.sensitivity ?? 25,\n // Per-AI-class sensitivities — operator override → camera\n // snapshot → schema default 50. Sliders that aren't rendered\n // (camera doesn't advertise the detector) round-trip the\n // value back as undefined and Zod's `.optional()` lets it\n // pass through `setAll` without trampling.\n aiPersonSensitivity: this.config.get('aiPersonSensitivity') ?? cache?.aiSensitivitySnapshot?.['people']?.sensitivity ?? 50,\n aiVehicleSensitivity: this.config.get('aiVehicleSensitivity') ?? cache?.aiSensitivitySnapshot?.['vehicle']?.sensitivity ?? 50,\n aiAnimalSensitivity: this.config.get('aiAnimalSensitivity') ?? cache?.aiSensitivitySnapshot?.['dog_cat']?.sensitivity ?? 50,\n aiFaceSensitivity: this.config.get('aiFaceSensitivity') ?? cache?.aiSensitivitySnapshot?.['face']?.sensitivity ?? 50,\n aiPackageSensitivity: this.config.get('aiPackageSensitivity') ?? cache?.aiSensitivitySnapshot?.['package']?.sensitivity ?? 50,\n // Image controls — operator override on `this.config.get`,\n // probed snapshot fallback (so the slider lands on the\n // current camera value the first time the panel is opened),\n // schema default last.\n imgBright: this.config.get('imgBright') ?? imgSnap.bright ?? 128,\n imgContrast: this.config.get('imgContrast') ?? imgSnap.contrast ?? 128,\n imgSaturation: this.config.get('imgSaturation') ?? imgSnap.saturation ?? 128,\n imgHue: this.config.get('imgHue') ?? imgSnap.hue ?? 128,\n imgSharpen: this.config.get('imgSharpen') ?? 128,\n ispDayNight: this.config.get('ispDayNight') ?? imgSnap.dayNight ?? 'auto',\n ispExposure: this.config.get('ispExposure') ?? 'auto',\n irLightsState: this.config.get('irLightsState') ?? 'Auto',\n irLightsBrightness: this.config.get('irLightsBrightness') ?? 128,\n audioVolume: this.config.get('audioVolume') ?? 50,\n audioTalkAndReplyVolume: this.config.get('audioTalkAndReplyVolume') ?? 50,\n audioVisitorVolume: this.config.get('audioVisitorVolume') ?? 50,\n // Stream/Encoder — operator override → encSnapshot fallback\n // → conservative defaults. The slider only renders when the\n // snapshot is present, but hydration covers the value-side\n // even when the section is gated out.\n streamMainBitRate: this.config.get('streamMainBitRate') ?? cache?.encSnapshot?.mainStream?.bitRate ?? 4096,\n streamMainFrameRate: this.config.get('streamMainFrameRate') ?? cache?.encSnapshot?.mainStream?.frameRate ?? 25,\n streamSubBitRate: this.config.get('streamSubBitRate') ?? cache?.encSnapshot?.subStream?.bitRate ?? 512,\n streamSubFrameRate: this.config.get('streamSubFrameRate') ?? cache?.encSnapshot?.subStream?.frameRate ?? 15,\n streamAudioEnabled: this.config.get('streamAudioEnabled') ?? cache?.encSnapshot?.audio === 1,\n // Privacy mask master switch + Audio noise level + AutoFocus\n privacyMaskEnabled: this.config.get('privacyMaskEnabled') ?? cache?.maskSnapshot?.enabled ?? false,\n audioNoiseLevel: this.config.get('audioNoiseLevel')\n ?? (cache?.audioNoiseSnapshot?.enabled ? (cache.audioNoiseSnapshot?.level ?? 1) : 0),\n autoFocusEnabled: this.config.get('autoFocusEnabled') ?? cache?.autoFocusSnapshot?.enabled ?? true,\n // Intercom tuning — operator override → Scrypted-parity defaults.\n // Hydration covers the value side even when the section is gated\n // out (non-intercom cams), keeping `setAll` round-trips clean.\n intercomBlocksPerPayload: this.config.get('intercomBlocksPerPayload') ?? 1,\n intercomMaxBacklogMs: this.config.get('intercomMaxBacklogMs') ?? 120,\n intercomGain: this.config.get('intercomGain') ?? 1.0,\n }\n return hydrateSchema(schema, values)\n }\n\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n // Action sentinels are not persisted — they trigger a side-effect\n // and are stripped before storage. Today only `_refreshSessions`\n // (forced refresh via the Sessions tab Refresh button) lands here;\n // the same mechanism is reusable for any future \"click button →\n // run side-effect\" UI without inventing a new cap surface.\n const { _refreshSessions, ...rest } = patch\n if (_refreshSessions !== undefined) {\n await this.refreshSessionsSnapshot().catch((err) => {\n this.ctx.logger.warn('forced sessions refresh failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n if (Object.keys(rest).length === 0) return\n\n // setAll() already shallow-merges over the current data and\n // re-parses against the schema; no manual snapshot required.\n await this.config.setAll(rest)\n const typedPatch = patch as Partial<ReolinkCameraConfig>\n if (typedPatch.host || typedPatch.port || typedPatch.username || typedPatch.password) {\n await this.disconnectAll()\n return\n }\n // Debug-flag changes need a fresh `new ReolinkBaichuanApi` so the\n // lib picks the new options up. Don't tear down active streams —\n // each rfc4571 server holds its own dedicated socket and survives\n // a control-channel reset (see scheduleReconnect comment). Just\n // drop the cached api so the next demand rebuilds with the new\n // debug blob. Mirrors Scrypted's deferred `resetBaichuanClient`\n // at `camera.ts:265-291`.\n if ('debugGeneral' in patch || 'debugSocketLogs' in patch) {\n if (this.api) {\n try { await this.api.close({ reason: 'debug-options-changed' }) } catch { /* best-effort */ }\n this.api = null\n }\n }\n // Motion alarm push (cmdId=47) — fires on either field change.\n // The schema field is the user-facing 1..50 (Reolink mobile-app\n // semantics); the camera-side raw value is the INVERSE\n // (`raw = 51 - userValue`) — same as reolink_aio's\n // `set_md_sensitivity`. We invert here on push so the operator\n // never sees the raw value. Reads current state into the wire\n // signature so the camera doesn't lose the un-touched dimension\n // (sensitivity stays put when only the master switch flips).\n if ('motionEnabled' in patch || 'motionSensitivity' in patch) {\n try {\n const api = await this.ensureApi()\n const cur = this.config.get('deviceCache')?.motionSnapshot ?? {}\n const enabled = typedPatch.motionEnabled ?? cur.enabled ?? true\n const userSensitivity = typedPatch.motionSensitivity ?? cur.sensitivity ?? undefined\n const rawSensitivity = typeof userSensitivity === 'number' ? 51 - userSensitivity : undefined\n const channel = this.getChannel()\n await api.setMotionAlarm(enabled, rawSensitivity, channel)\n // Refresh so the inverted snapshot lands back as the\n // user-facing value the next form render shows.\n void this.refreshParentSettingsSnapshot().catch(() => {})\n } catch (err) {\n this.ctx.logger.warn('motion alarm push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Per-AI-class sensitivity (cmdId=343 via setAiDetection). One\n // call per touched class — the lib does its own GET-then-patch\n // so unrelated dimensions (stayTime) stay where they were. We\n // refresh the snapshot at the end so the next form render\n // shows the values the camera actually accepted (Reolink\n // clamps internally on a few firmwares).\n const aiPushes: Array<[keyof ReolinkCameraConfig, string]> = [\n ['aiPersonSensitivity', 'people'],\n ['aiVehicleSensitivity', 'vehicle'],\n ['aiAnimalSensitivity', 'dog_cat'],\n ['aiFaceSensitivity', 'face'],\n ['aiPackageSensitivity', 'package'],\n ]\n let didAiPush = false\n for (const [field, aiType] of aiPushes) {\n if (!(field in patch)) continue\n const v = (typedPatch as Record<string, unknown>)[field]\n if (typeof v !== 'number') continue\n try {\n const api = await this.ensureApi()\n await api.setAiDetection(this.getChannel(), aiType, v)\n didAiPush = true\n } catch (err) {\n this.ctx.logger.warn('ai detection push failed', {\n meta: { aiType, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n if (didAiPush) {\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n\n // Image controls (cmdId=25 via setImage). Build a partial patch\n // covering only the keys the operator actually touched — the lib\n // does a read-modify-write at cmdId=26 and only patches the tags\n // we pass, so untouched values stay at their camera-current value\n // even if the operator hasn't seen the latest probe yet.\n const imageKeys = ['imgBright', 'imgContrast', 'imgSaturation', 'imgHue', 'imgSharpen'] as const\n if (imageKeys.some((k) => k in patch)) {\n try {\n const api = await this.ensureApi()\n const imagePatch: Parameters<typeof api.setImage>[1] = {}\n if (typeof typedPatch.imgBright === 'number') imagePatch.bright = typedPatch.imgBright\n if (typeof typedPatch.imgContrast === 'number') imagePatch.contrast = typedPatch.imgContrast\n if (typeof typedPatch.imgSaturation === 'number') imagePatch.saturation = typedPatch.imgSaturation\n if (typeof typedPatch.imgHue === 'number') imagePatch.hue = typedPatch.imgHue\n if (typeof typedPatch.imgSharpen === 'number') imagePatch.sharpen = typedPatch.imgSharpen\n if (Object.keys(imagePatch).length > 0) {\n await api.setImage(this.getChannel(), imagePatch)\n // Refresh the snapshot so the next form render shows the\n // pushed values without the operator having to reload.\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('image push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // ISP (DayNight / Exposure) — same cmdId=25 path with a different\n // section of the InputAdvanceCfg block. The lib's setIsp method\n // covers dayNight + exposure + binningMode + hdr + the threshold\n // (cmd_id 297) in one call.\n const ispKeys = ['ispDayNight', 'ispExposure', 'ispHdr', 'ispBinningMode', 'ispDayNightThreshold'] as const\n if (ispKeys.some((k) => k in patch)) {\n try {\n const api = await this.ensureApi()\n const ispPatch: Parameters<typeof api.setIsp>[1] = {}\n if (typeof typedPatch.ispDayNight === 'string' && typedPatch.ispDayNight) ispPatch.dayNight = typedPatch.ispDayNight\n if (typeof typedPatch.ispExposure === 'string' && typedPatch.ispExposure) ispPatch.exposure = typedPatch.ispExposure\n if (typeof typedPatch.ispBinningMode === 'number') ispPatch.binningMode = typedPatch.ispBinningMode\n if (typedPatch.ispHdr === 0 || typedPatch.ispHdr === 1) ispPatch.hdr = typedPatch.ispHdr\n if (typeof typedPatch.ispDayNightThreshold === 'number') ispPatch.dayNightThreshold = typedPatch.ispDayNightThreshold\n if (Object.keys(ispPatch).length > 0) {\n await api.setIsp(this.getChannel(), ispPatch)\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('isp push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // IR + status LED (cmdId=209). Translate the operator-friendly\n // enums to the lib's already-normalized keys (`irState`,\n // `irBrightness`, `lightState`, `doorbellLightState`).\n if ('irLightsState' in patch || 'irLightsBrightness' in patch) {\n try {\n const api = await this.ensureApi()\n const irPatch: Parameters<typeof api.setIrLights>[1] = {}\n if (typedPatch.irLightsState !== undefined) irPatch.irState = typedPatch.irLightsState\n if (typeof typedPatch.irLightsBrightness === 'number') irPatch.irBrightness = typedPatch.irLightsBrightness\n if (Object.keys(irPatch).length > 0) await api.setIrLights(this.getChannel(), irPatch)\n } catch (err) {\n this.ctx.logger.warn('ir-lights push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Audio output (cmdId=265). Doorbell-only fields silently no-op\n // on cameras without the talk/visitor channels — cleaner than\n // gating the UI on the abilities probe.\n const audioKeys = ['audioVolume', 'audioTalkAndReplyVolume', 'audioVisitorVolume'] as const\n if (audioKeys.some((k) => k in patch)) {\n try {\n const api = await this.ensureApi()\n const audioPatch: Parameters<typeof api.setAudioCfg>[1] = {}\n if (typeof typedPatch.audioVolume === 'number') audioPatch.volume = typedPatch.audioVolume\n if (typeof typedPatch.audioTalkAndReplyVolume === 'number') audioPatch.talkAndReplyVolume = typedPatch.audioTalkAndReplyVolume\n if (typeof typedPatch.audioVisitorVolume === 'number') audioPatch.visitorVolume = typedPatch.audioVisitorVolume\n if (Object.keys(audioPatch).length > 0) await api.setAudioCfg(this.getChannel(), audioPatch)\n } catch (err) {\n this.ctx.logger.warn('audio-cfg push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Stream / Encoder (cmdId=57 via setEnc). Build the per-stream\n // patch covering only the keys the operator actually touched —\n // the lib's read-modify-write keeps untouched dimensions\n // (resolution / videoEncType) at their camera-current value.\n // Codec is intentionally read-only in the UI — flipping it\n // would need a per-camera capability check that we don't have\n // yet (older firmwares only support h264). Surfaced as a\n // readonly display field instead of a dropdown.\n const encKeys = [\n 'streamMainBitRate', 'streamMainFrameRate',\n 'streamSubBitRate', 'streamSubFrameRate',\n 'streamAudioEnabled',\n ] as const\n if (encKeys.some((k) => k in patch)) {\n try {\n const api = await this.ensureApi()\n const encPatch: Parameters<typeof api.setEnc>[1] = {}\n if (typeof typedPatch.streamAudioEnabled === 'boolean') {\n encPatch.audio = typedPatch.streamAudioEnabled ? 1 : 0\n }\n const mainPatch: NonNullable<Parameters<typeof api.setEnc>[1]['mainStream']> = {}\n if (typeof typedPatch.streamMainBitRate === 'number') mainPatch.bitRate = typedPatch.streamMainBitRate\n if (typeof typedPatch.streamMainFrameRate === 'number') mainPatch.frameRate = typedPatch.streamMainFrameRate\n if (Object.keys(mainPatch).length > 0) encPatch.mainStream = mainPatch\n const subPatch: NonNullable<Parameters<typeof api.setEnc>[1]['subStream']> = {}\n if (typeof typedPatch.streamSubBitRate === 'number') subPatch.bitRate = typedPatch.streamSubBitRate\n if (typeof typedPatch.streamSubFrameRate === 'number') subPatch.frameRate = typedPatch.streamSubFrameRate\n if (Object.keys(subPatch).length > 0) encPatch.subStream = subPatch\n if (Object.keys(encPatch).length > 0) {\n await api.setEnc(this.getChannel(), encPatch)\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('enc push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Privacy mask master switch (cmdId=53 via setMask).\n if ('privacyMaskEnabled' in patch) {\n try {\n const api = await this.ensureApi()\n const v = typedPatch.privacyMaskEnabled\n if (typeof v === 'boolean') {\n await api.setMask(this.getChannel(), { enable: v })\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('mask push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Audio noise reduction (cmdId=440 via setAudioNoise). Single\n // integer level: 0 = off; >0 = on with that level. The lib does\n // the off-path conversion when level <= 0.\n if ('audioNoiseLevel' in patch) {\n try {\n const api = await this.ensureApi()\n const v = typedPatch.audioNoiseLevel\n if (typeof v === 'number') {\n await api.setAudioNoise(this.getChannel(), v)\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('audio-noise push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n // Auto-focus (cmdId=225 via setAutoFocus). UI-friendly polarity\n // (\"autoFocusEnabled\" = AF on) — invert before push because the\n // camera-side field is named `disable`.\n if ('autoFocusEnabled' in patch) {\n try {\n const api = await this.ensureApi()\n const v = typedPatch.autoFocusEnabled\n if (typeof v === 'boolean') {\n await api.setAutoFocus(this.getChannel(), v ? 0 : 1)\n void this.refreshParentSettingsSnapshot().catch(() => {})\n }\n } catch (err) {\n this.ctx.logger.warn('auto-focus push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n\n // ── Internals ─────────────────────────────────────────────────────────\n\n /**\n * Optionally inject an already-logged-in Baichuan api — used by\n * `onCreateDevice` to reuse the api instance returned by\n * `autoDetectDeviceType` (Slice 15 socket reuse). Skips the next\n * connect cycle entirely.\n */\n adoptApi(api: ReolinkBaichuanApi): void {\n this.api = api\n this.reconnectAttempts = 0\n api.client.on('close', () => this.scheduleReconnect('close'))\n api.client.on('error', (err) => {\n this.ctx.logger.warn('Baichuan client error — scheduling reconnect', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n this.scheduleReconnect('error')\n })\n // Slice 9: kick off sleep-status poll if this turns out to be a\n // battery cam (autodetect flagged the deviceType).\n const persistedDeviceType = this.config.get('deviceCache')?.deviceType\n if (persistedDeviceType === 'battery-cam') {\n this.isBattery = true\n api.setIdleDisconnect(true)\n this.startSleepPoll()\n this.registerBatteryIfSupported()\n // Eager registration in the constructor seeds with an api-less\n // refresh (no-op). Now that we have a live api, populate the\n // cache so the badge moves off the conservative 0% fallback.\n void this.refreshBatteryFromApi()\n } else {\n // Wired cam — drive aux device alignment on a 10s periodic\n // timer (Scrypted parity). Battery cams use the wake transition\n // path inside startSleepPoll instead.\n this.startAlignAuxPolling()\n }\n // Subscribe simple events so awake/sleeping/motion/AI flows work\n // even when we skipped the regular login path. `resubscribeSimpleEvents`\n // does an `offSimpleEvent` first so re-running adoptApi against\n // the same api instance doesn't accumulate listeners.\n void this.resubscribeSimpleEvents(api, 'adoptApi').catch((err: unknown) => {\n this.ctx.logger.debug('Reolink adoptApi: simple-event subscribe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n // Slice 10: arm watchdogs after adopting an api too.\n this.startWatchdogs()\n this.startEventHealthCheck()\n // Run the abilities probe on the adopted api too — the socket-\n // reuse path otherwise skips it, leaving `hasPtz`/`hasIntercom`\n // unset (PTZ controls hidden in the UI). Stream availability is\n // resolved runtime by `publishToBroker`, so we only re-run the\n // probe when the static abilities haven't been recorded yet.\n if (!this.config.get('deviceCache')?.probedAt) {\n void this.probeAndPersistFeatures(api).catch((err: unknown) => {\n this.ctx.logger.debug('Reolink feature probe (adoptApi) failed (non-fatal)', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n // Hardware metadata refresh lives in `onProbe` (kernel-driven\n // entry point). adoptApi runs out-of-band — when the provider\n // hands us the autodetect-time socket — and the kernel always\n // fires onProbe right after, so duplicating the populator here\n // would just churn the bus.\n }\n\n /**\n * Expose the lazy-login Baichuan API to accessory children. The\n * children share the parent's socket and credentials — there's no\n * separate auth flow per accessory. `ReolinkAccessory` calls this\n * inside its `applyOn` / `fetchOn` to reach the camera firmware.\n */\n /** Implements `ReolinkAccessoryHost.isBatteryCam` so spawned\n * accessory children skip background polling on a sleeping\n * battery cam (Baichuan polls on a sleeping camera return 400 +\n * drain the battery). Reads from the runtime feature-probe slice\n * populated during `onProbe()`; falls back to false when the probe\n * hasn't completed yet. */\n isBatteryCam(): boolean {\n const fp = this.runtimeState.getCapState<{ flags?: Record<string, unknown> }>('feature-probe')\n return fp?.flags?.['hasBattery'] === true\n }\n\n /** Implements `ReolinkAccessoryHost.isSleeping`. Reads the canonical\n * source of truth — the `battery.sleeping` runtime-state slice — so\n * hub-children (whose simpleEvents are routed in by the Hub) and\n * top-level battery cams (whose own subscription / sleep poll write\n * the slice) share one signal. Always returns `false` for non-\n * battery cams (`this.sleeping` getter falls back to `false` when\n * the slice is absent). */\n isSleeping(): boolean {\n return this.sleeping\n }\n\n async ensureApi(): Promise<ReolinkBaichuanApi> {\n // Hub-child fast path — never log in independently. The parent\n // Hub keeps a single long-lived Baichuan socket per device; we\n // borrow its api and inherit credentials, channel routing\n // (`this.getChannel()`), and event subscription (the Hub\n // dispatches simpleEvents into our `handleSimpleEvent` directly,\n // see `ReolinkHub.routeSimpleEvent`). Skipping login means\n // battery cams under a Hub never get woken by an \"online\" check\n // — exactly the behavior the user asked for.\n if (this.isHubChild()) {\n const parent = await this.getParentHub()\n if (!parent) {\n throw new Error(`ReolinkCamera ${this.id}: parent Hub ${this.parentDeviceId} not registered yet`)\n }\n return parent.getApi()\n }\n\n if (this.api) return this.api\n if (this.loginPromise) return this.loginPromise\n\n const host = this.config.get('host')\n const port = this.config.get('port')\n const username = this.config.get('username')\n const password = this.config.get('password')\n const transport = this.config.get('transport')\n const uid = this.config.get('uid')\n const udpDiscoveryMethod = this.config.get('udpDiscoveryMethod')\n\n this.ctx.logger.info('Connecting to Reolink', {\n tags: { deviceId: this.id, host: String(host) },\n meta: { port, transport, hasUid: Boolean(uid) },\n })\n\n const debugOptions = this.getBaichuanDebugOptions()\n\n this.loginPromise = (async () => {\n const api = new ReolinkBaichuanApi({\n host,\n port,\n username,\n password,\n transport,\n ...(uid ? { uid } : {}),\n ...(udpDiscoveryMethod ? { udpDiscoveryMethod } : {}),\n ...(debugOptions ? { debugOptions } : {}),\n // Wire our scoped logger as the lib's `Console` sink. Without\n // this, the main control-channel client's protocol traces\n // (`[BaichuanClient] tx/rx`, `udp_keepalive_*`, sleep\n // inference ticks) fall through to the lib's default sink and\n // bypass our deviceId/addonId tagging — making it impossible\n // to correlate the wake/sleep flap events with what was on\n // the wire. Mirrors `scrypted-reolink-native/connect.ts:33-39`.\n logger: this.libLoggerAdapter(),\n })\n try {\n await api.login()\n } catch (err) {\n this.loginPromise = null\n throw err\n }\n this.api = api\n this.loginPromise = null\n this.reconnectAttempts = 0\n\n // Battery probe — autodetect already flagged battery cams at\n // creation time (Slice 15 features.deviceType==='battery-cam').\n // Honour the persisted flag first; only fall back to the live\n // probe when features hasn't been populated yet (older devices\n // created before Slice 15).\n const persistedDeviceType = this.config.get('deviceCache')?.deviceType\n if (persistedDeviceType === 'battery-cam') {\n this.isBattery = true\n api.setIdleDisconnect(true)\n this.ctx.logger.info('Reolink camera flagged as battery (from autodetect) — idle disconnect enabled', {\n tags: { deviceId: this.id },\n })\n } else if (persistedDeviceType === undefined) {\n try {\n await api.getBatteryInfo(0)\n this.isBattery = true\n api.setIdleDisconnect(true)\n this.ctx.logger.info('Reolink camera flagged as battery (live probe) — idle disconnect enabled', {\n tags: { deviceId: this.id },\n })\n // Battery flag changed → re-derive `this.features` and propagate\n // the new BatteryOperated entry to the broker.\n void this.publishToBroker().catch(() => { /* best-effort */ })\n } catch {\n this.isBattery = false\n }\n } else {\n this.isBattery = false\n }\n\n // Slice 9: start the periodic sleep-status poll for battery cams\n // so we catch missed sleeping/awake transitions from push events.\n if (this.isBattery) {\n this.startSleepPoll()\n // Slice 13: register the battery cap so the snapshot wrapper\n // can consult sleep state without waking the camera.\n this.registerBatteryIfSupported()\n // Refresh the cache post-login. When the cap was already\n // registered eagerly at construction (battery-cam in\n // deviceCache), the seed inside `registerBatteryIfSupported`\n // ran without an api and was a no-op — without this explicit\n // refresh the badge would keep serving the conservative\n // fallback (0%) until the first push event arrives.\n void this.refreshBatteryFromApi()\n }\n\n // Slice 10: arm reliability watchdogs (ping + event freshness).\n this.startWatchdogs()\n\n // Abilities + channel count probe (Slice 4). Persisted into config\n // so the UI / later slices can consult device capabilities without\n // forcing a fresh round-trip to the camera.\n void this.probeAndPersistFeatures(api).catch((err: unknown) => {\n this.ctx.logger.debug('Reolink feature probe failed (non-fatal)', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n // Hardware metadata refresh lives in `onProbe` — the kernel\n // calls onProbe right after construction (and after each addon\n // restart), and onProbe re-enters here via ensureApi. Putting\n // the populator there keeps a single refresh per probe cycle.\n\n // Reconnect on socket loss / D2C disconnect.\n api.client.on('close', () => this.scheduleReconnect('close'))\n api.client.on('error', (err) => {\n this.ctx.logger.warn('Baichuan client error — scheduling reconnect', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n this.scheduleReconnect('error')\n })\n\n // Hardware motion / AI / lifecycle events (Slice 6). Routes\n // through `resubscribeSimpleEvents` so the bound handler ref\n // is reused across watchdog re-arms — see the field comment\n // for the listener-accumulation regression this prevents.\n void this.resubscribeSimpleEvents(api, 'login').catch((err: unknown) => {\n this.ctx.logger.debug('Baichuan onSimpleEvent subscribe failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n this.startEventHealthCheck()\n\n return api\n })()\n\n return this.loginPromise\n }\n\n /**\n * Bridge Reolink hardware events to the camstack event bus. Mirrors\n * the dispatch shape of scrypted-reolink-native's `onSimpleEvent`\n * (`camera.ts:1979-2033`): online and sleeping are TWO ORTHOGONAL\n * axes, each driven exclusively by their own firmware push events.\n *\n * - 'motion' + AI types (people, vehicle, animal, face, package):\n * emit `EventCategory.MotionOnMotionChanged` (`source: 'onboard'`)\n * so the runner's phase machine reacts via `reportMotion`. The\n * runner is the sole writer of the `motion` runtime-state slice\n * (no direct slice writes here — keeps onboard symmetric with the\n * analyzer path). AI subtype also lands on\n * `EventCategory.DetectionCameraNative`.\n * - 'awake' / 'sleeping': emit DeviceAwake / DeviceSleeping. Battery\n * power-state only — does NOT touch online state.\n * - 'online' / 'offline': emit DeviceOnline / DeviceOffline + flip\n * `this.online`. The ONLY emit site for these — socket close,\n * reconnect, RFC4571 idle teardown all leave online state alone.\n * - 'battery': refresh cache, emit cap event.\n * - 'doorbell', 'daynight', 'other': informational debug log only.\n */\n /** Reusable event-source identity for every emit on this device. */\n private eventSource(): { type: 'device'; id: number; addonId: string; deviceId: number } {\n return { type: 'device', id: this.id, addonId: REOLINK_ADDON_ID, deviceId: this.id }\n }\n\n /**\n * Build the `DebugOptions` blob forwarded to `ReolinkBaichuanApi`'s\n * constructor. Mirrors Scrypted's `getBaichuanDebugOptions` at\n * `camera.ts:1778-1786`. Returns `undefined` when no flags are set\n * so the lib falls back to its own defaults instead of an empty\n * object override.\n */\n private getBaichuanDebugOptions(): Record<string, boolean> | undefined {\n const general = this.config.get('debugGeneral') === true\n const socketFlags = (this.config.get('debugSocketLogs') ?? []) as readonly ReolinkSocketDebugFlag[]\n const opts: Record<string, boolean> = {}\n if (general) opts.general = true\n for (const flag of socketFlags) opts[flag] = true\n return Object.keys(opts).length > 0 ? opts : undefined\n }\n\n /**\n * Public entry point for simpleEvent dispatch. Used both by the\n * camera's own subscription (`handleSimpleEventBound`) when the\n * camera holds its own Baichuan socket AND by `ReolinkHub.routeSimpleEvent`\n * when the camera lives under a Hub and inherits the parent's\n * single subscription.\n */\n handleSimpleEvent(event: import('@apocaliss92/nodelink-js').ReolinkSimpleEvent): void {\n // Slice 10: stamp the watchdog so it knows the subscription is live.\n this.lastEventAt = Date.now()\n // Reset the health-check stale streak so the next quiet window\n // logs `info` again instead of staying on `debug` forever.\n this.consecutiveStaleHealthChecks = 0\n this.nextEventHealthCheckAt = 0\n const eventSource = this.eventSource()\n\n // Trace every Baichuan simple-event we receive — operator\n // diagnostics for the motion / AI / lifecycle pipeline. Logged\n // at info level (low volume — bounded by the camera's push\n // cadence, not by frame rate) so it surfaces in the Logs tab\n // without enabling debug. Excludes only the high-frequency\n // `battery` push, which already has dedicated dedup +\n // periodic emit elsewhere; keeping it here would double-log.\n //\n // `this.ctx.logger` is the per-device logger built by\n // device-cap-proxy.ts:317 — already scoped to this device's\n // stableId with `deviceId` baked into its tags. No need to\n // re-tag it here.\n if (event.type !== 'battery') {\n this.ctx.logger.info('Reolink simpleEvent received', {\n meta: {\n type: event.type,\n channel: event.channel,\n timestamp: event.timestamp,\n },\n })\n }\n\n // Bare motion. Single unified output:\n //\n // - `MotionOnMotionChanged` event — `source: 'onboard'`. The runner\n // subscribes, drives its phase machine via `reportMotion`, and is\n // the sole writer of the `motion` runtime-state slice (in\n // `handlePhaseChanged`). Mirrors the analyzer path: providers\n // emit, the runner owns slice writes — symmetric across sources.\n if (event.type === 'motion') {\n const now = Date.now()\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n eventSource,\n { deviceId: this.id, detected: true, timestamp: now, source: 'onboard' },\n ))\n return\n }\n\n // AI-classified events → detection.camera-native with structured shape.\n // This is the canonical channel for hardware-AI pushes (people / vehicle\n // / animal / face / package). Motion is handled separately above; we\n // ALSO mirror an onboard motion tick on the unified event channel so\n // the runner's phase machine treats AI pushes as motion.\n const aiClass = AI_CLASS_MAP[event.type]\n if (aiClass !== undefined) {\n const now = Date.now()\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DetectionCameraNative,\n eventSource,\n {\n cameraId: this.id,\n source: 'onboard',\n detections: [{ class: aiClass, timestamp: now }],\n },\n ))\n this.ctx.eventBus.emit(createEvent(\n EventCategory.MotionOnMotionChanged,\n eventSource,\n { deviceId: this.id, detected: true, timestamp: now, source: 'onboard' },\n ))\n return\n }\n\n // Power-state transitions (battery cams). DeviceAwake / DeviceSleeping\n // are the canonical events for power state — orthogonal to\n // DeviceOnline / DeviceOffline (firmware liveness, handled above).\n // BatteryBadge listens to DeviceAwake/Sleeping; connectivity\n // indicators listen to DeviceOnline/Offline.\n if (event.type === 'awake') {\n const wasSleeping = this.sleeping\n // Hysteresis: the lib runs an internal UDP \"sleep inference\"\n // every 2s that emits awake/sleeping events based on socket\n // I/O patterns, not actual camera firmware state. With idle\n // disconnect enabled (battery cams) the inference flaps every\n // ~12s during steady-state idle. Suppress flips that land\n // inside the hysteresis window so we don't churn UI / cache /\n // active-stream teardowns over an inferred state.\n if (this.acceptSleepingTransition(false)) {\n // Battery slice exists ONLY when registerBatteryIfSupported\n // ran post-login. simpleEvents can fire BEFORE that (e.g.\n // initial 'awake' burst on Hub login arriving for non-battery\n // children, or pre-flight events on a regular cam). Guard the\n // slice write so we don't trip the runtime-state validator\n // with \"no schema registered for cap 'battery'\".\n if (this.isBattery) this.state.battery.sleeping = false\n if (wasSleeping) {\n this.ctx.logger.info('Reolink camera woke up', { tags: { deviceId: this.id } })\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DeviceAwake,\n eventSource,\n { deviceId: this.id, providerId: REOLINK_ADDON_ID, reason: 'awake' },\n ))\n // If we never managed to publish streams to the broker (e.g.\n // boot-time `publishToBroker` ran while the camera was still\n // asleep, `ensureApi` timed out, no streams were registered),\n // run it now. The api is live by the time the firmware emits\n // 'awake', so this is the natural retry point and doesn't\n // wake the camera — it's already awake.\n if (this.publishedStreamIds.size === 0) {\n void this.publishToBroker().catch((err: unknown) => {\n this.ctx.logger.debug('publishToBroker on wake failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n // Wake-transition refresh: now that the camera is up,\n // re-read aux accessory state (siren / floodlight / PIR)\n // and the parent settings snapshot (image / motion / AI /\n // enc / mask / audioNoise / autoFocus). Both are\n // read-only Baichuan calls on an already-awake socket —\n // they don't re-wake the cam. Mirrors Scrypted's\n // `updateSleepingState` wake branch (camera.ts:3543-3560)\n // which runs `alignAuxDevicesState()` + battery/snapshot\n // on the same transition.\n // NOTE: `getBatteryInfo` is intentionally NOT called here.\n // The camera's own `battery` simpleEvent push arrives\n // shortly after wake on Argus firmware; the cache refresh\n // happens without us polling.\n void this.onWakeTransition('simple-event').catch(() => { /* best-effort */ })\n }\n }\n return\n }\n\n // Firmware connectivity transitions (mirrors Scrypted reolink-native\n // `updateOnlineState` at `camera.ts:2001-2003`). DeviceOnline /\n // DeviceOffline are emitted ONLY from these explicit Baichuan push\n // events — never from socket close / RFC4571 idle teardown / reconnect.\n if (event.type === 'online' || event.type === 'offline') {\n const next = event.type === 'online'\n if (next !== this.online) {\n this.markOnline(next)\n this.ctx.eventBus.emit(createEvent(\n next ? EventCategory.DeviceOnline : EventCategory.DeviceOffline,\n eventSource,\n next\n ? { deviceId: this.id, providerId: REOLINK_ADDON_ID, profileCount: 0, reason: 'firmware-push' }\n : { deviceId: this.id, providerId: REOLINK_ADDON_ID, reason: 'firmware-push' },\n ))\n }\n return\n }\n\n if (event.type === 'sleeping') {\n const wasSleeping = this.sleeping\n if (this.acceptSleepingTransition(true)) {\n if (this.isBattery) this.state.battery.sleeping = true\n if (!wasSleeping) {\n this.ctx.eventBus.emit(createEvent(\n EventCategory.DeviceSleeping,\n eventSource,\n { deviceId: this.id, providerId: REOLINK_ADDON_ID, reason: 'sleeping' },\n ))\n }\n // Only escalate to info + active-stream teardown when the\n // camera was actually serving streams. With zero active\n // streams the \"going to sleep\" transition has no operational\n // impact — log at debug to keep the channel quiet during\n // idle-state lib inference flaps.\n if (this.active.size > 0) {\n this.ctx.logger.info('Reolink camera went to sleep — closing active streams', {\n tags: { deviceId: this.id },\n meta: { activeStreams: this.active.size },\n })\n // Close every BaichuanVideoStream so the broker doesn't\n // keep pushing into a dead session and so we release sockets\n // cleanly. Do NOT close the api itself — we still want\n // awake events.\n void this.closeActiveStreams('sleeping').catch(() => { /* best-effort */ })\n } else {\n this.ctx.logger.debug('Reolink camera sleep transition (no active streams)', {\n tags: { deviceId: this.id },\n })\n }\n // `this.state.battery.sleeping = true` already updated the slice;\n // the `subscribeCap('battery')` bridge above re-emits\n // `BatteryOnStatusChanged` so the BatteryBadge picks the\n // moon icon up the moment the cam actually went down.\n }\n return\n }\n\n // Slice 13: battery push → refresh cache + emit cap event so the\n // snapshot wrapper's sleep gate sees fresh data without polling.\n if (event.type === 'battery' && event.battery) {\n this.updateBatteryCache(event.battery as import('@apocaliss92/nodelink-js').BatteryInfo)\n return\n }\n\n // Doorbell ring — bridge to the doorbell cap + EventCategory so\n // toasts/alerts react without polling.\n if (event.type === 'doorbell') {\n this.emitDoorbellPressed(Date.now())\n return\n }\n\n // daynight, other — log only; consumers can subscribe to\n // provider-reolink-specific events later if needed.\n this.ctx.logger.debug('Reolink simple-event (informational)', {\n tags: { deviceId: this.id },\n meta: { type: event.type, channel: event.channel, ...(event.battery ? { battery: event.battery } : {}) },\n })\n }\n\n /**\n * Probe device abilities + channel count and persist into the camera\n * config. Best-effort — unsupported camera firmwares may return\n * partial data; we record what we can and skip the rest.\n *\n * Writes the result into the `feature-probe` runtime-state slice\n * (the canonical store for hasPtz/hasIntercom/etc) and persists\n * channelCount + deviceType in the config blob (so the camera\n * lifecycle has them before next-boot rehydrate completes).\n *\n * Called by the kernel via `onProbe()` once after register, and by\n * `reprobe()` on demand.\n */\n private async probeAndPersistFeatures(api: ReolinkBaichuanApi): Promise<void> {\n let channelCount: number | undefined\n let hasPtz: boolean | undefined\n let hasIntercom: boolean | undefined\n let hasDoorbell: boolean | undefined\n let hasFloodlight: boolean | undefined\n let hasSiren: boolean | undefined\n let hasBattery: boolean | undefined\n let hasPirSensor: boolean | undefined\n let hasAutotrack: boolean | undefined\n\n try { channelCount = await api.getChannelCount() } catch { /* ignore */ }\n\n // Use the lib's pre-parsed `getDeviceCapabilities()` for every\n // hasX flag. The lib handles the firmware-specific quirks\n // (ptzMode='none' overriding individual ability flags, supportItem\n // vs ability-table differences, dingDong vs doorbellVersion vs\n // doorbell, lightType >= 2 for floodlight, etc).\n try {\n const { capabilities } = await api.getDeviceCapabilities(this.getChannel())\n hasPtz = capabilities.hasPtz\n hasIntercom = capabilities.hasIntercom\n hasDoorbell = capabilities.isDoorbell\n hasFloodlight = capabilities.hasFloodlight\n hasSiren = capabilities.hasSiren\n hasBattery = capabilities.hasBattery\n hasPirSensor = capabilities.hasPir\n hasAutotrack = capabilities.hasAutotracking\n } catch (err) {\n this.ctx.logger.debug('getDeviceCapabilities failed — leaving flags unset', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Only persist when the round-trip actually returned data.\n // Failures (transient login race / partial firmware response /\n // sleeping cam) leave every local `undefined`; persisting in that\n // case would mark `lastProbedAt` and trick downstream consumers\n // into thinking the probe completed.\n const probeReturnedData = (\n hasPtz !== undefined\n || hasIntercom !== undefined\n || hasDoorbell !== undefined\n || hasFloodlight !== undefined\n || hasSiren !== undefined\n || hasBattery !== undefined\n || hasPirSensor !== undefined\n || hasAutotrack !== undefined\n || channelCount !== undefined\n )\n\n if (!probeReturnedData) {\n this.ctx.logger.debug('Reolink features probe carried no data — slice unchanged', {\n tags: { deviceId: this.id },\n })\n return\n }\n\n // Preserve previously-known flags when this probe didn't reach a\n // particular field. Feature-probe slice is the canonical store\n // now — config blob's deviceCache is for snapshots only.\n const prev = this.runtimeState.getCapState<FeatureProbeStatus>('feature-probe')\n const prevFlags = (prev?.flags ?? {}) as ReolinkProbeFlags\n const persistedDeviceType = this.config.get('deviceCache')?.deviceType\n const inferredDeviceType: string | null = persistedDeviceType\n ?? (this.isBattery ? 'battery-cam' : null)\n const persistedModel = this.config.get('deviceCache')?.model ?? null\n\n const flags: ReolinkProbeFlags = {\n ...prevFlags,\n // Trust the lib's `hasBattery` when it's defined; fall back to\n // `this.isBattery` (autodetect + live `getBatteryInfo` probe) so\n // the flag is always coherent even when the abilities probe\n // hasn't completed.\n hasBattery: hasBattery ?? prevFlags.hasBattery ?? this.isBattery,\n ...(hasPtz !== undefined ? { hasPtz } : {}),\n ...(hasIntercom !== undefined ? { hasIntercom } : {}),\n ...(hasDoorbell !== undefined ? { hasDoorbell } : {}),\n ...(hasFloodlight !== undefined ? { hasFloodlight } : {}),\n ...(hasSiren !== undefined ? { hasSiren } : {}),\n ...(hasPirSensor !== undefined ? { hasPirSensor } : {}),\n ...(hasAutotrack !== undefined ? { hasAutotrack } : {}),\n }\n\n const now = Date.now()\n const next: FeatureProbeStatus = {\n flags: flags as Record<string, unknown>,\n deviceType: inferredDeviceType,\n model: persistedModel,\n channelCount: channelCount ?? prev?.channelCount ?? null,\n lastProbedAt: now,\n lastFetchedAt: now,\n }\n this.runtimeState.setCapState('feature-probe', next)\n\n // Persist channelCount + deviceType in the config blob too — they\n // drive the camera lifecycle (channel routing, battery sleep\n // handling) BEFORE the runtime-state slice rehydrates on next boot.\n try {\n const previous = this.config.get('deviceCache') ?? {}\n await this.config.setAll({\n deviceCache: {\n ...previous,\n ...(inferredDeviceType ? { deviceType: inferredDeviceType } : {}),\n ...(channelCount !== undefined ? { channelCount } : {}),\n },\n })\n } catch (err) {\n this.ctx.logger.debug('deviceCache identity persist failed (non-fatal)', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Now that features are known, register optional caps retroactively.\n this.registerPtzIfSupported()\n this.registerPtzAutotrackIfSupported()\n this.registerDoorbellIfSupported()\n\n // NOTE: hardware metadata refresh is NOT gated on the feature\n // probe — `adoptApi` / `ensureApi` already trigger it on every\n // post-login transition so a firmware update or a hardware swap\n // is reflected on the next addon restart even when `probedAt`\n // is set (which short-circuits this method).\n\n this.ctx.logger.info('Reolink features probed + persisted', {\n tags: { deviceId: this.id },\n meta: { flags, channelCount, deviceType: inferredDeviceType },\n })\n }\n\n /**\n * Pull `getInfo` and patch the device-meta `metadata` blob with the\n * resolved manufacturer / model / firmware / hardware / serial / uid.\n * Idempotent — `setMetadata` shallow-merges and emits a change event\n * only when at least one field flipped, so re-running the probe on\n * every reconnect doesn't churn the bus.\n */\n private async populateMetadataFromFirmware(api: ReolinkBaichuanApi): Promise<void> {\n const cfgUid = this.config.get('uid')\n // Top-level cameras are themselves the host — pass `undefined` so\n // the populator hits cmd 80 (host getInfo) + cmd 76 host. For\n // hub-adopted children, route via their channel index so\n // `getInfo cmd 318(ch=N)` returns the per-channel identity rather\n // than the hub's host data.\n const channel = this.parentDeviceId !== null ? this.getChannel() : undefined\n await populateReolinkMetadata(api, channel, {\n id: this.id,\n ctx: this.ctx,\n uid: typeof cfgUid === 'string' && cfgUid.length > 0 ? cfgUid : undefined,\n setDeviceCachePatch: async (cachePatch) => {\n const previous = this.config.get('deviceCache') ?? {}\n await this.config.setAll({ deviceCache: { ...previous, ...cachePatch } })\n },\n })\n }\n\n /**\n * Tear down the current API and schedule a reconnect with exponential\n * backoff. Active streams are cleared — when the broker re-issues\n * demand events on next consumer activity, ensureApi() reconnects\n * lazily and the demand path re-arms BaichuanVideoStream instances.\n *\n * **Battery-camera path**: when the camera is sleeping (or already\n * believed to be sleeping), a `close`/`error` from the Baichuan\n * client is the EXPECTED steady state — the lib disconnects the\n * idle UDP socket on its own, the camera is asleep, and there is\n * nothing to reconnect to. Aggressively reconnecting in that state\n * is what was waking up the doorbell every ~60 s in production\n * logs. Mirror Scrypted's reolink-native handler: drop the api\n * reference, but DO NOT schedule a reconnect timer. The next\n * `awake` push (or a snapshot demand from the operator) will\n * lazily call `ensureApi()` and re-establish the client.\n */\n private scheduleReconnect(reason: string): void {\n if (this.reconnectTimer) return\n if (this.isBattery) {\n // Battery cam: never proactively reconnect. Drop the stale api\n // reference so demand-driven `ensureApi` rebuilds it on next\n // call. Stop short of scheduling a timer — the next `awake`\n // event re-arms it. Online state is firmware-push-driven only\n // (mirrors Scrypted reolink-native): socket close does NOT\n // flip `this.online`; the firmware emits its own 'offline' push\n // when the camera state actually transitions.\n this.api = null\n this.ctx.logger.debug('Battery cam disconnect — deferring reconnect to next demand', {\n tags: { deviceId: this.id },\n meta: { reason, sleeping: this.sleeping },\n })\n return\n }\n const attempt = ++this.reconnectAttempts\n const backoffMs = Math.min(30_000, 1_000 * 2 ** Math.min(attempt - 1, 5))\n this.ctx.logger.info('Baichuan reconnect scheduled', {\n tags: { deviceId: this.id },\n meta: { reason, attempt, backoffMs },\n })\n\n // RFC 4571 servers each own a dedicated Baichuan socket (via the\n // `deviceId` option), so a control-channel reconnect on the main api\n // doesn't invalidate them. Leave them running — the lib's per-server\n // socket survives independently and self-heals on the next client\n // connection.\n this.api = null\n\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null\n // Trigger ensureApi indirectly — the next demand event will. We\n // don't pre-emptively reconnect because the broker may already\n // have torn down its end (no active subscribers → no demand).\n this.ctx.logger.debug('Baichuan reconnect window elapsed — ready for next demand', {\n tags: { deviceId: this.id },\n })\n }, backoffMs)\n if (typeof this.reconnectTimer.unref === 'function') this.reconnectTimer.unref()\n }\n}\n\n// ── Hub coupling helpers ───────────────────────────────────────────────\n//\n// Structural type for the parent Hub: kept here (instead of imported\n// from `reolink-hub.ts`) to avoid the camera ↔ hub circular import.\n// The Hub class IS structurally compatible — it exposes `id`, `getApi()`,\n// and the deviceClass marker on its prototype. The duck-test below\n// catches both the live class and the Moleculer-deserialized facade.\n\nexport interface ReolinkHubLike {\n readonly id: number\n getApi(): Promise<ReolinkBaichuanApi>\n}\n\nfunction isHubLike(d: unknown): d is ReolinkHubLike {\n if (typeof d !== 'object' || d === null) return false\n const obj = d as { getApi?: unknown; id?: unknown }\n return typeof obj.id === 'number' && typeof obj.getApi === 'function'\n}\n\n/**\n * Exponential backoff schedule for the event-health watchdog. The\n * camera's silent-event behaviour is bimodal: either there's a real\n * event flow (counter resets to 0 on every event → next stale streak\n * starts at 60s) OR the subscription is genuinely broken / nothing is\n * happening in front of the cam (counter rises → backoff lengthens).\n *\n * Schedule: 60s → 2min → 5min → 15min capped. Mirrors the long-poll\n * backoff curves in scrypted-reolink-native + reolink_aio.\n */\nfunction computeHealthCheckBackoffMs(consecutive: number): number {\n if (consecutive <= 1) return 60_000 // 1st: 60s\n if (consecutive === 2) return 2 * 60_000 // 2nd: 2min\n if (consecutive === 3) return 5 * 60_000 // 3rd: 5min\n return 15 * 60_000 // 4th+: 15min\n}\n\n","/**\n * Reolink accessory subsystem barrel export — kind-keyed factory +\n * concrete classes. The provider addon imports `createAccessoryDevice`\n * to instantiate a child by `kind`, and never has to know which\n * concrete class implements each kind.\n *\n * Autotrack is NOT modelled here — it's an extension of PTZ exposed\n * via the `ptz-autotrack` cap on the parent camera, not a child\n * device. See `ReolinkCamera.registerPtzAutotrackIfSupported()`.\n */\n\nimport type { DeviceContext, IDevice, AccessoryKindValue } from '@camstack/types'\nimport { AccessoryKind } from '@camstack/types'\nimport { type ReolinkAccessoryHost } from './base.js'\nimport { SirenAccessory } from './siren.js'\nimport { FloodlightAccessory } from './floodlight.js'\nimport { PirAccessory } from './pir.js'\n\nexport {\n SirenAccessory,\n sirenAccessorySchema,\n type SirenAccessoryConfig,\n} from './siren.js'\nexport {\n FloodlightAccessory,\n floodlightAccessorySchema,\n type FloodlightAccessoryConfig,\n} from './floodlight.js'\nexport {\n PirAccessory,\n pirAccessorySchema,\n type PirAccessoryConfig,\n} from './pir.js'\nexport {\n isReolinkSupportedAccessoryKind,\n reolinkAccessoryBaseSchema,\n type ReolinkAccessoryHost,\n} from './base.js'\n\n/**\n * Factory — pick the right concrete class for an `AccessoryKindValue`\n * and instantiate it with the shared `(ctx, parent)` pair. The\n * accessory's persisted config has already been written to the DB\n * by `kernel.devices.persistInitialConfig` (for create) or is\n * already there (for restore); the constructor reads it via\n * `ctx.persistedConfig`.\n *\n * Throws on `AccessoryKindValue`s Reolink doesn't model as accessories\n * (Autotrack lives on the parent via `ptz-autotrack` cap; Spotlight /\n * Chime / Nightvision / PrivacyMask aren't implemented yet) — callers\n * should gate with `isReolinkSupportedAccessoryKind` before invoking\n * the factory in restore paths where saved rows might carry stale or\n * cross-vendor kinds.\n */\nexport function createAccessoryDevice(\n kind: AccessoryKindValue,\n ctx: DeviceContext,\n parent: ReolinkAccessoryHost,\n): IDevice {\n switch (kind) {\n case AccessoryKind.Siren:\n return new SirenAccessory(ctx, parent)\n case AccessoryKind.Floodlight:\n return new FloodlightAccessory(ctx, parent)\n case AccessoryKind.PirSensor:\n return new PirAccessory(ctx, parent)\n default:\n throw new Error(`Reolink provider does not implement accessory kind \"${String(kind)}\"`)\n }\n}\n","/**\n * SirenAccessory — child IDevice for the on-camera siren endpoint.\n *\n * Caps:\n * - `switch` — discrete on/off via cmd_id 30 (SetSiren)\n * - `motion-trigger` — toggle \"siren on motion\" via cmd_id 231 (SetSirenOnMotion)\n *\n * Settings:\n * - `sirenDurationS` — passed to `setSiren(on=true, duration)` on\n * the next switch flip (0 = play indefinitely)\n *\n * State model: every cap's last-known state lives in the runtime-state\n * slice. A 30s background poll keeps switch + motion-trigger aligned\n * with whatever the camera reports — covers Reolink-app-driven flips\n * and firmware reverts without operator action.\n */\n\nimport { z } from 'zod'\nimport {\n BaseDevice,\n DeviceType,\n DeviceFeature,\n hydrateSchema,\n switchCapability,\n motionTriggerCapability,\n createRuntimeStateBridge,\n type SwitchStatus,\n type MotionTriggerStatus,\n type MotionTriggerRuntimeState,\n} from '@camstack/types'\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n InferNativeProvider,\n} from '@camstack/types'\nimport { reolinkAccessoryBaseSchema, type ReolinkAccessoryHost, AccessoryKind } from './base.js'\nimport type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\n\n/** Detection-class combinations the Reolink firmware accepts for the\n * siren-on-motion `typeScheduleList`. Mirrors `motion-floodlight`\n * detect-type choices and Scrypted's `motion-siren.detectTypes`. */\nexport const SIREN_DETECT_TYPE_CHOICES = [\n 'MD',\n 'people',\n 'vehicle',\n 'dog_cat',\n 'MD,people',\n 'MD,vehicle',\n 'MD,dog_cat',\n 'people,vehicle',\n 'people,dog_cat',\n 'vehicle,dog_cat',\n 'MD,people,vehicle',\n 'MD,people,dog_cat',\n 'MD,vehicle,dog_cat',\n 'people,vehicle,dog_cat',\n 'MD,people,vehicle,dog_cat',\n] as const\n\nexport const sirenAccessorySchema = reolinkAccessoryBaseSchema.extend({\n kind: z.literal(AccessoryKind.Siren),\n /**\n * Default duration (seconds) passed to `setSiren(on=true, duration)`\n * when the operator flips the switch via the cap. `0` = play\n * indefinitely until turned off; positive value = one-shot pulse\n * for that many seconds.\n */\n sirenDurationS: z.number().int().min(0).max(300).default(0),\n /**\n * Detection classes that fire the siren when \"Trigger on motion\" is\n * armed. Forwarded as a `typeScheduleList` to `setSirenOnMotion`\n * (cmd_id 231). Mirrors Scrypted's `motion-siren.detectTypes` field\n * + the parallel `motionLightDetectType` on Floodlight.\n * 168 = 24×7 hours per row; we set every hour to ON for the chosen\n * classes and every hour to OFF for the rest.\n */\n motionSirenDetectType: z.enum(SIREN_DETECT_TYPE_CHOICES).default('MD,people,vehicle,dog_cat'),\n})\n\nexport type SirenAccessoryConfig = z.infer<typeof sirenAccessorySchema>\n\nconst STALE_MS = 30_000\n\n/** Cooldown after operator-driven setMotionTrigger — parent's\n * alignAuxDevicesState skips this accessory inside the window. */\nconst COOLDOWN_MS = 15_000\n\nexport class SirenAccessory extends BaseDevice<typeof sirenAccessorySchema> {\n readonly features = [DeviceFeature.MotionTrigger]\n\n private lastSetAtMs = 0\n /** Becomes `true` after the first successful\n * `refreshMotionTriggerFromFirmware` round-trip. Used by `onProbe`\n * to decide whether the boot-time sleep-gate should force a wake\n * (no cached state) or skip (already-have-state path). Mirrors the\n * Reolink mobile app's one-shot wake-and-fetch on launch. */\n private hasFetchedFromFirmware = false\n\n constructor(ctx: DeviceContext, private readonly parent: ReolinkAccessoryHost) {\n super(ctx, sirenAccessorySchema, {\n type: DeviceType.Siren,\n role: AccessoryKind.Siren,\n })\n this.registerSwitchCap()\n this.registerMotionTriggerCap()\n // Per scrypted-reolink-native (camera.ts:2798-2801): siren switch\n // direct control is fire-and-forget — `getSiren` returns OFF\n // immediately after triggering, so polling would clobber the UI\n // back to OFF. We only align motion-trigger; switch state is\n // operator-driven via setState writes (the slice update inside\n // setState IS the source of truth).\n this.parent.registerAccessoryChild?.({\n kind: AccessoryKind.Siren,\n id: this.id,\n refreshFromAlign: () => this.refreshMotionTriggerFromFirmware(),\n isInCooldown: () => this.lastSetAtMs > 0 && Date.now() - this.lastSetAtMs < COOLDOWN_MS,\n })\n }\n\n private get channel(): number { return this.config.get('channel') ?? 0 }\n private get api(): Promise<ReolinkBaichuanApi> { return this.parent.ensureApi() as Promise<ReolinkBaichuanApi> }\n\n private switchView(): SwitchStatus {\n return this.getCapSlice(switchCapability) ?? { on: false, lastChangedAt: 0 }\n }\n\n // Note: no `refreshSwitchFromFirmware` — see comment in constructor.\n // Scrypted-reolink-native explicitly excludes siren from align\n // because `getSiren` returns OFF immediately after triggering.\n\n /**\n * Kernel-driven probe (Phase 4). Runs on every device construction\n * + addon restart. Pulls the actual motion-trigger enable state\n * from the camera so the slice reflects firmware truth. The switch\n * (manual fire) cap is intentionally NOT refreshed — Reolink's\n * `getSiren` returns OFF as soon as the audio task ends.\n *\n * Sleeping battery cam:\n * 1. **First-time probe (no cached state yet)** — force a one-shot\n * wake + fetch so the operator sees a populated UI at launch.\n * Mirrors the Reolink mobile app: at app launch it wakes the\n * cam once to fetch initial state. Without this, an aggressive\n * sleep timeout (~14s) can return the cam to sleep before the\n * kernel finishes its initial probe pass — the retry chain\n * exhausts and the slice never populates.\n * 2. **Subsequent probes (slice already populated)** — skip. A\n * poll on a sleeping camera returns 400 and drains the\n * battery. The parent's `alignAuxDevicesState('wake')` path\n * (simpleEvent `awake` push or sleep-poll backstop) refreshes\n * us when the cam wakes naturally.\n */\n override async onProbe(): Promise<void> {\n const isSleepingBattery = this.parent.isBatteryCam?.() === true && this.parent.isSleeping?.() === true\n if (isSleepingBattery) {\n if (this.hasFetchedFromFirmware) {\n this.ctx.logger.debug('siren onProbe skipped — parent battery cam is sleeping (have cached state)', {\n tags: { deviceId: this.id },\n })\n return\n }\n // First-time probe: wake the cam to fetch initial state.\n // Best-effort — if wake fails we still proceed with the fetch.\n // Mirrors `wakeForIntercom` in the parent camera class\n // (waitAfterWakeMs:2000 + 1s settle).\n this.ctx.logger.info('siren onProbe forcing wake — initial probe with no cached state', {\n tags: { deviceId: this.id },\n })\n try {\n const api = await this.api\n await api.wakeUp(this.channel, { waitAfterWakeMs: 2000 })\n await new Promise<void>((resolve) => setTimeout(resolve, 1000))\n } catch (err) {\n this.ctx.logger.warn('siren wake before initial probe failed — proceeding anyway', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n await this.refreshMotionTriggerFromFirmware()\n }\n\n private async refreshMotionTriggerFromFirmware(): Promise<void> {\n try {\n const api = await this.api\n const result = await api.getSirenOnMotion(this.channel)\n // Lib emits the AudioTask block under `body.AudioTask` per the\n // typed `AudioTaskConfig`. Older builds shimmed it as either a\n // root-level `enable` or `audioTask.enable`; keep the cascading\n // probe so we work across versions without forcing a lockstep\n // upgrade.\n const audioTask = (result as { body?: { AudioTask?: { enable?: number; typeScheduleList?: { item?: Array<{ type?: string; valueTable?: string }> } } } } | undefined)?.body?.AudioTask\n const enable = audioTask?.enable\n ?? (result as { enable?: number; audioTask?: { enable?: number } } | undefined)?.enable\n ?? (result as { audioTask?: { enable?: number } } | undefined)?.audioTask?.enable\n const enabled = enable === 1\n const previous = this.getCapSlice(motionTriggerCapability)\n const lastChangedAt = previous && previous.enabled === enabled\n ? previous.lastChangedAt\n : Date.now()\n this.setCapSlice(motionTriggerCapability, {\n enabled,\n lastChangedAt,\n lastFetchedAt: Date.now(),\n })\n this.hasFetchedFromFirmware = true\n\n // Mirror `typeScheduleList` → `motionSirenDetectType` so the\n // Settings form reflects firmware truth (operator changed\n // detect classes from the Reolink app → next align refresh\n // updates camstack's UI). Inverse of `buildSirenTypeScheduleList`:\n // for each item, the type is \"active\" when ANY hour in its 168-\n // char `valueTable` is `'1'`. Active types ordered canonically\n // (MD,people,vehicle,dog_cat) and joined CSV.\n const items = audioTask?.typeScheduleList?.item ?? []\n if (items.length > 0) {\n const recovered = recoverSirenDetectTypeFromItems(items)\n if (recovered !== undefined && (SIREN_DETECT_TYPE_CHOICES as readonly string[]).includes(recovered)) {\n await this.config.setAll({ motionSirenDetectType: recovered }).catch((err: unknown) => {\n this.ctx.logger.debug('siren detectType mirror failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n } else if (recovered !== undefined) {\n this.ctx.logger.debug('siren detectType outside enum — skipping config mirror', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { recovered },\n })\n }\n }\n } catch (err) {\n this.ctx.logger.warn('siren motion-trigger fetch failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n\n private registerSwitchCap(): void {\n const provider: InferNativeProvider<typeof switchCapability> = {\n // Slice-driven read — mirrors scrypted-reolink-native pattern.\n // setState updates the slice on every write; no firmware refresh.\n getStatus: async (): Promise<SwitchStatus> => this.switchView(),\n setState: async ({ deviceId, on }) => {\n if (deviceId !== this.id) return\n const api = await this.api\n // Lib ignores duration on OFF; explicit `undefined` keeps the\n // call self-documenting.\n const duration = on ? (this.config.get('sirenDurationS') ?? 0) : undefined\n await api.setSiren(on, duration, this.channel)\n this.setCapSlice(switchCapability, { on, lastChangedAt: Date.now() })\n // No-op for the cooldown — siren switch is never aligned, so\n // there's nothing to skip. Marking `lastSetAtMs` here would\n // also pointlessly delay the motion-trigger align.\n },\n }\n this.ctx.registerNativeCap(switchCapability, provider)\n this.setCapSlice(switchCapability, { on: false, lastChangedAt: 0 })\n }\n\n private registerMotionTriggerCap(): void {\n const bridge = createRuntimeStateBridge({\n runtimeState: this.runtimeState,\n cap: motionTriggerCapability,\n ownDeviceId: this.id,\n refresh: () => this.refreshMotionTriggerFromFirmware(),\n staleMs: STALE_MS,\n empty: (): MotionTriggerStatus => ({ enabled: false, lastChangedAt: 0 }),\n })\n\n const provider: InferNativeProvider<typeof motionTriggerCapability> = {\n getStatus: bridge.getStatus,\n setMotionTrigger: async ({ deviceId, enabled }) => {\n if (deviceId !== this.id) return\n const detectType = this.config.get('motionSirenDetectType') ?? 'MD,people,vehicle,dog_cat'\n this.ctx.logger.info('siren motion-trigger setMotionTrigger', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { enabled, detectType },\n })\n try {\n const api = await this.api\n if (enabled) {\n const typeScheduleList = buildSirenTypeScheduleList(detectType)\n await api.setSirenOnMotion({ enable: 1, typeScheduleList }, this.channel)\n } else {\n await api.setSirenOnMotion({ enable: 0 }, this.channel)\n }\n this.setCapSlice(motionTriggerCapability, {\n enabled,\n lastChangedAt: Date.now(),\n lastFetchedAt: Date.now(),\n })\n this.lastSetAtMs = Date.now()\n } catch (err) {\n this.ctx.logger.warn('siren motion-trigger set failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { enabled, error: err instanceof Error ? err.message : String(err) },\n })\n throw err\n }\n },\n }\n this.ctx.registerNativeCap(motionTriggerCapability, provider)\n const seed: MotionTriggerRuntimeState = { enabled: false, lastChangedAt: 0, lastFetchedAt: 0 }\n this.setCapSlice(motionTriggerCapability, seed)\n }\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const schema: ConfigUISchema = {\n sections: [\n {\n id: 'siren',\n title: 'Siren',\n description: 'Tone duration when triggered. Set to 0 for \"play until turned off\"; positive values pulse for that many seconds.',\n columns: 1,\n fields: [{\n type: 'number',\n key: 'sirenDurationS',\n label: 'Default duration (seconds)',\n description: 'Forwarded to `setSiren(on=true, duration)` (cmd_id 30) when the operator flips the switch.',\n default: 0,\n min: 0,\n max: 300,\n step: 1,\n }],\n },\n {\n id: 'motion-siren',\n title: 'Motion-triggered siren',\n description:\n 'Detection classes that fire the siren when \"Trigger on motion\" is armed. ' +\n 'Forwarded as a `typeScheduleList` to `setSirenOnMotion` (cmd_id 231). ' +\n 'Saved values re-apply on every motion-trigger flip.',\n columns: 1,\n fields: [{\n type: 'select',\n key: 'motionSirenDetectType',\n label: 'Detection classes',\n description: 'Object/motion classes that arm the siren. `MD` = generic motion detection.',\n options: SIREN_DETECT_TYPE_CHOICES.map((v) => ({ value: v, label: v.replace(/,/g, ' + ').replace(/_/g, '+') })),\n default: 'MD,people,vehicle,dog_cat',\n }],\n },\n ],\n }\n return hydrateSchema(schema, {\n sirenDurationS: this.config.get('sirenDurationS') ?? 0,\n motionSirenDetectType: this.config.get('motionSirenDetectType') ?? 'MD,people,vehicle,dog_cat',\n })\n }\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n await this.config.setAll(patch)\n // If detect-type changed AND motion-trigger is currently armed,\n // re-push to the firmware so the new filter takes effect immediately.\n if ('motionSirenDetectType' in patch) {\n const slice = this.runtimeState.getCapState<MotionTriggerRuntimeState>('motion-trigger')\n if (slice?.enabled === true) {\n const detectType = (patch.motionSirenDetectType as string | undefined) ?? this.config.get('motionSirenDetectType') ?? 'MD,people,vehicle,dog_cat'\n try {\n const api = await this.api\n const typeScheduleList = buildSirenTypeScheduleList(detectType)\n await api.setSirenOnMotion({ enable: 1, typeScheduleList }, this.channel)\n } catch (err) {\n this.ctx.logger.warn('siren motion-trigger re-push after settings change failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { detectType, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n }\n }\n}\n\n/** Build the `typeScheduleList` payload Reolink expects:\n * one entry per known detection class with a 168-char (24×7) ON/OFF\n * bitmap. Mirrors Scrypted's `buildTypeScheduleList`. */\nfunction buildSirenTypeScheduleList(detectType: string): Array<{ type: string; valueTable: string }> {\n const types = detectType.split(',').map((t) => t.trim())\n const allOn = '1'.repeat(168)\n const allOff = '0'.repeat(168)\n return [\n { type: 'MD', valueTable: types.includes('MD') ? allOn : allOff },\n { type: 'people', valueTable: types.includes('people') ? allOn : allOff },\n { type: 'vehicle', valueTable: types.includes('vehicle') ? allOn : allOff },\n { type: 'dog_cat', valueTable: types.includes('dog_cat') ? allOn : allOff },\n ]\n}\n\n/** Inverse of `buildSirenTypeScheduleList`. Reads the camera-reported\n * `typeScheduleList.item[]` array (cmd_id 232 GetSirenOnMotion) and\n * recovers the canonical CSV. A type is considered ACTIVE when its\n * 168-char `valueTable` carries at least one `'1'` — `buildSirenTypeScheduleList`\n * always writes either all-1s or all-0s, so partial rows are unexpected\n * but treated as active (any active hour counts). Active types are\n * emitted in canonical order (MD → people → vehicle → dog_cat) so the\n * recovered string lines up with `SIREN_DETECT_TYPE_CHOICES`. */\nfunction recoverSirenDetectTypeFromItems(\n items: ReadonlyArray<{ type?: string; valueTable?: string }>,\n): string | undefined {\n const active = new Set<string>()\n for (const item of items) {\n if (!item.type || typeof item.valueTable !== 'string') continue\n if (item.valueTable.includes('1')) active.add(item.type)\n }\n const canonicalOrder = ['MD', 'people', 'vehicle', 'dog_cat']\n const ordered = canonicalOrder.filter((t) => active.has(t))\n return ordered.length > 0 ? ordered.join(',') : undefined\n}\n","/**\n * Reolink accessory base layer — kind enum, parent-host contract,\n * shared `BaseDevice` glue every kind-specific subclass extends.\n *\n * Each accessory kind (Siren, Floodlight, PIR, Autotrack) lives in its\n * own file (`./siren.ts`, `./floodlight.ts`, …) with its own Zod\n * schema, settings UI, and cap-registration logic — they're\n * independent device classes that share only the parent reference and\n * the `channel` field.\n *\n * Why split per file:\n * - Each kind owns a different subset of the lib's Baichuan\n * surface (`setSiren`, `setWhiteLedState`, `setPirInfo`,\n * `setAutotracking{,Settings}`) — keeping the dispatch in one\n * monolithic switch obscures the per-kind contract.\n * - Per-accessory settings UIs are different schemas; co-locating\n * each schema with its consumer makes the kind self-contained\n * (the file you read to understand \"what does the siren expose\"\n * is `siren.ts`, full stop).\n * - Adding a new kind (e.g. a future SpotlightAccessory) is one\n * new file + one factory case, no edits to existing kinds.\n */\n\nimport { z } from 'zod'\nimport {\n AccessoryKind,\n type AccessoryKindValue,\n} from '@camstack/types'\n\n/**\n * Shared base fields every accessory subclass extends with its own\n * kind-specific knobs. The `channel` mirror lets multi-channel NVRs\n * route per-channel commands; single-camera devices stay on `0`.\n *\n * The `kind` field uses the **system-wide** `AccessoryKind` enum —\n * not a Reolink-specific narrow. Each per-kind subclass schema\n * locks down `kind: z.literal(AccessoryKind.X)` for its own value.\n */\nexport const reolinkAccessoryBaseSchema = z.object({\n channel: z.number().int().nonnegative().default(0),\n kind: z.string(),\n})\n\n/** Runtime guard: the four kinds Reolink models out of the full\n * `AccessoryKind` set. The factory's `switch` is the canonical\n * per-kind dispatch; this helper exists for the restore path,\n * where saved rows can carry stale string values from older\n * revisions and the addon needs a quick \"do we still know how\n * to spawn this?\" check before attempting instantiation. */\nexport function isReolinkSupportedAccessoryKind(value: string): value is AccessoryKindValue {\n // `Autotrack` is intentionally NOT here — it lives on the parent\n // camera as the `ptz-autotrack` cap (PTZ extension), never as a\n // child device. A saved row carrying `kind: 'autotrack'` from a\n // pre-migration build would fall through this guard and the\n // restore path would skip it; the next `setSettings`/`setEnabled`\n // call on the parent's cap rebuilds the runtime state.\n return (\n value === AccessoryKind.Siren ||\n value === AccessoryKind.Floodlight ||\n value === AccessoryKind.PirSensor\n )\n}\n\n/**\n * Anything an accessory subclass needs from the parent camera. The\n * subclass receives this rather than a direct `ReolinkCamera`\n * reference so:\n * - The accessory file doesn't import the camera class (avoids\n * circular import the camera class doesn't need).\n * - Tests can stub the host with a tiny fake instead of standing\n * up a full camera.\n * - The contract is explicit: subclasses use the parent's\n * Baichuan socket via `ensureApi()` and read its display name\n * for naming.\n */\n/**\n * Aux-device reference exposed by each accessory to its parent.\n * Mirrors scrypted-reolink-native's `alignAuxDevicesState` pattern:\n * the parent camera owns the periodic align scheduling (cadence,\n * sleep-gate, cooldown), the accessory owns the slice projection\n * + cooldown timestamp. Single 30s background poll thus replaces N\n * per-accessory timers AND can be sleep-gated centrally.\n */\nexport interface ReolinkAccessoryRef {\n readonly kind: string\n readonly id: number\n /** Read latest state from firmware and write into the slice. Best-\n * effort: per-accessory failures are logged + swallowed by the\n * caller (parent's alignAuxDevicesState). */\n refreshFromAlign(): Promise<void>\n /** True when a recent operator setState/setMotionTrigger call\n * marked the cooldown — caller should skip align this round to\n * avoid clobbering the just-applied state with a stale firmware\n * read (camera takes 10s+ to reflect changes in some endpoints). */\n isInCooldown(): boolean\n}\n\nexport interface ReolinkAccessoryHost {\n readonly name: string\n ensureApi(): Promise<unknown>\n /** True when the parent camera is battery-operated. Battery cams\n * spend most of their time asleep; Baichuan polls on a sleeping\n * camera return 400 (responseCode 400, empty body) AND drain the\n * battery. The parent's `alignAuxDevicesState` skips polling\n * entirely while sleeping and runs once on the sleep→awake\n * transition.\n * Optional for back-compat — host impls that don't expose battery\n * info should return false (the safe default). */\n isBatteryCam?(): boolean\n /** True when the parent camera is currently asleep. Used by the\n * accessory's `onProbe` to gate runtime refresh: at runtime we\n * only fetch firmware state when the cam is already awake (per\n * user policy — never wake a sleeping cam outside the explicit\n * startup probe path). For wired cams this should always return\n * false. Optional for back-compat. */\n isSleeping?(): boolean\n /** Accessories register themselves with the parent at construction\n * so the parent's centralised `alignAuxDevicesState` can iterate\n * them. Optional for back-compat — when absent the accessory\n * silently skips registration (no align happens). */\n registerAccessoryChild?(ref: ReolinkAccessoryRef): void\n}\n\n/** Lib's Baichuan API — typed indirectly to avoid a hard dep on\n * the lib in this file (kind-specific subclasses cast to the\n * concrete shape they need). */\nexport type BaichuanApi = unknown\n\n/** Re-export the system enum + values type so callers (factory)\n * can build their dispatch tables off a single import. */\nexport { AccessoryKind, type AccessoryKindValue }\n","/**\n * FloodlightAccessory — child IDevice for the on-camera white-LED\n * floodlight.\n *\n * Caps:\n * - `switch` — discrete on/off via `setWhiteLedState(on,\n * brightness, channel)`\n * - `brightness` — dim level 0..100 — same lib call, the on/off\n * bit is preserved across brightness changes\n * - `motion-trigger` — toggle \"floodlight on motion\" via\n * `setFloodlightOnMotion`\n *\n * State model: every cap's last-known state lives in the runtime-state\n * slice (per CLAUDE.md). Reads project from the slice, writes update\n * the slice optimistically, and a periodic poll re-syncs from firmware\n * so external changes (Reolink app, motion firing, firmware reverts)\n * surface in the UI without operator action.\n */\n\nimport { z } from 'zod'\nimport {\n BaseDevice,\n DeviceType,\n DeviceFeature,\n hydrateSchema,\n switchCapability,\n brightnessCapability,\n motionTriggerCapability,\n createRuntimeStateBridge,\n type SwitchStatus,\n type BrightnessStatus,\n type MotionTriggerStatus,\n type MotionTriggerRuntimeState,\n} from '@camstack/types'\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n InferNativeProvider,\n} from '@camstack/types'\nimport { reolinkAccessoryBaseSchema, type ReolinkAccessoryHost, AccessoryKind } from './base.js'\nimport type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\n\n/**\n * Detection-class strings the Reolink Baichuan firmware accepts for\n * `setFloodlightSettings({ detectType })`. Mirrors the Scrypted\n * reolink-native motion-floodlight choices verbatim — these are the\n * combos the lib has been observed to accept across firmwares we\n * support. Operators can compose them via the multi-select UI; the\n * driver passes the joined string to the lib unchanged.\n */\nconst FLOODLIGHT_DETECT_TYPE_CHOICES = [\n 'people',\n 'vehicle',\n 'dog_cat',\n 'people,vehicle',\n 'people,dog_cat',\n 'vehicle,dog_cat',\n 'people,vehicle,dog_cat',\n] as const\n\nexport const floodlightAccessorySchema = reolinkAccessoryBaseSchema.extend({\n kind: z.literal(AccessoryKind.Floodlight),\n /**\n * Seconds the floodlight stays lit after a motion event. Forwarded\n * to `api.setFloodlightSettings(channel, { duration, detectType })`\n * (Baichuan FloodlightTask). Range mirrors Scrypted's reference\n * (1..600s) — values outside the firmware-accepted range are\n * silently clamped by the camera. */\n motionLightDurationS: z.number().int().min(1).max(600).default(180)\n .describe('Seconds the floodlight stays lit after motion (default 180)'),\n /**\n * Object classes that trigger the floodlight on motion. Combined as\n * a comma-separated string per Reolink's wire format. */\n motionLightDetectType: z.enum(FLOODLIGHT_DETECT_TYPE_CHOICES).default('people,vehicle,dog_cat')\n .describe('Detection classes that turn the floodlight on'),\n})\n\nexport type FloodlightAccessoryConfig = z.infer<typeof floodlightAccessorySchema>\n\n/** Stale window for the runtime-state bridge — past this age, the next\n * `getStatus()` call refreshes from firmware. Used by the\n * motion-trigger bridge below; switch + brightness slices are written\n * on every setState/setBrightness so they don't need a stale check. */\nconst STALE_MS = 30_000\n\n/** Cooldown after a manual setState/setMotionTrigger — the parent's\n * alignAuxDevicesState skips this accessory inside this window so a\n * stale firmware read doesn't clobber the just-applied value\n * (Reolink's GET endpoints lag the SET by 5-10s). */\nconst COOLDOWN_MS = 15_000\n\nexport class FloodlightAccessory extends BaseDevice<typeof floodlightAccessorySchema> {\n readonly features = [DeviceFeature.MotionTrigger]\n\n /** Wall-clock ms of the last operator-driven setState/setBrightness\n * /setMotionTrigger. Read by the parent's align loop via the\n * `ReolinkAccessoryRef.isInCooldown` impl below. */\n private lastSetAtMs = 0\n /** Becomes `true` once both `refreshLightFromFirmware` and\n * `refreshMotionTriggerFromFirmware` have completed at least one\n * successful round-trip. Used by `onProbe` to decide whether the\n * boot-time sleep-gate should force a wake (no cached state) or\n * skip (already-have-state path). Mirrors the Reolink mobile app's\n * one-shot wake-and-fetch on launch. */\n private hasFetchedFromFirmware = false\n\n constructor(ctx: DeviceContext, private readonly parent: ReolinkAccessoryHost) {\n super(ctx, floodlightAccessorySchema, {\n type: DeviceType.Light,\n role: AccessoryKind.Floodlight,\n })\n this.registerSwitchCap()\n this.registerBrightnessCap()\n this.registerMotionTriggerCap()\n // No per-accessory polling — the parent ReolinkCamera runs a\n // single `alignAuxDevicesState` (Scrypted parity) that drives all\n // aux refreshes. Battery cam: only on wake. Wired cam: every 10s.\n this.parent.registerAccessoryChild?.({\n kind: AccessoryKind.Floodlight,\n id: this.id,\n refreshFromAlign: async () => {\n await Promise.allSettled([\n this.refreshLightFromFirmware(),\n this.refreshMotionTriggerFromFirmware(),\n ])\n },\n isInCooldown: () => this.lastSetAtMs > 0 && Date.now() - this.lastSetAtMs < COOLDOWN_MS,\n })\n }\n\n private get channel(): number { return this.config.get('channel') ?? 0 }\n private get api(): Promise<ReolinkBaichuanApi> { return this.parent.ensureApi() as Promise<ReolinkBaichuanApi> }\n\n /** Refresh on/off + brightness from firmware and write both slices.\n * The lib's `getWhiteLedState` returns both in one call so we\n * coalesce them here. Failure path: log + leave the slices\n * untouched. */\n private async refreshLightFromFirmware(): Promise<void> {\n try {\n const api = await this.api\n const state = await api.getWhiteLedState(this.channel)\n const on = Boolean(state?.enabled)\n const brightness = typeof state?.brightness === 'number' ? state.brightness : undefined\n const ts = Date.now()\n this.setCapSlice(switchCapability, { on, lastChangedAt: ts })\n if (brightness !== undefined) {\n this.setCapSlice(brightnessCapability, { percentage: brightness, lastChangedAt: ts })\n }\n this.hasFetchedFromFirmware = true\n } catch (err) {\n this.ctx.logger.debug('floodlight light refresh failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Kernel-driven probe (Phase 4). Runs on every device construction\n * + addon restart. Pulls the actual on/off + brightness +\n * motion-trigger state from the camera so the slices reflect\n * firmware truth instead of the conservative cold-start seeds\n * written in `registerSwitchCap` / `registerBrightnessCap` /\n * `registerMotionTriggerCap`.\n *\n * Sleeping battery cam:\n * 1. **First-time probe (no cached state yet)** — force a one-shot\n * wake + fetch so the operator sees a populated UI at launch.\n * Mirrors the Reolink mobile app: at app launch it wakes the\n * cam once to fetch initial state. Without this, an aggressive\n * sleep timeout (~14s) can return the cam to sleep before the\n * kernel finishes its initial probe pass — the retry chain\n * exhausts and the slices never populate.\n * 2. **Subsequent probes (slices already populated)** — skip. A\n * poll on a sleeping camera returns 400 and drains the\n * battery. The parent's `alignAuxDevicesState('wake')` runs\n * on the next wake transition (simpleEvent push or sleep-poll\n * backstop) and refreshes both slices then.\n */\n override async onProbe(): Promise<void> {\n const isSleepingBattery = this.parent.isBatteryCam?.() === true && this.parent.isSleeping?.() === true\n if (isSleepingBattery) {\n if (this.hasFetchedFromFirmware) {\n this.ctx.logger.debug('floodlight onProbe skipped — parent battery cam is sleeping (have cached state)', {\n tags: { deviceId: this.id },\n })\n return\n }\n // First-time probe: wake the cam to fetch initial state.\n // Best-effort — if wake fails we still proceed with the fetch.\n // Mirrors `wakeForIntercom` in the parent camera class\n // (waitAfterWakeMs:2000 + 1s settle).\n this.ctx.logger.info('floodlight onProbe forcing wake — initial probe with no cached state', {\n tags: { deviceId: this.id },\n })\n try {\n const api = await this.api\n await api.wakeUp(this.channel, { waitAfterWakeMs: 2000 })\n await new Promise<void>((resolve) => setTimeout(resolve, 1000))\n } catch (err) {\n this.ctx.logger.warn('floodlight wake before initial probe failed — proceeding anyway', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n await Promise.all([\n this.refreshLightFromFirmware(),\n this.refreshMotionTriggerFromFirmware(),\n ])\n }\n\n /** Refresh motion-trigger from firmware and write the slice. Also\n * mirrors the camera-current `duration` + `detectType` into config\n * so the Settings form shows firmware truth instead of stale\n * operator-typed values, and the next motion-trigger arm doesn't\n * clobber the operator's app-side change.\n *\n * Mirror is best-effort: a `detectType` value not in\n * `FLOODLIGHT_DETECT_TYPE_CHOICES` (e.g. firmware advertised a\n * combination we don't list) is logged + skipped — leaves the\n * config field untouched rather than throwing the whole refresh. */\n private async refreshMotionTriggerFromFirmware(): Promise<void> {\n try {\n const api = await this.api\n const result = await api.getFloodlightOnMotion(this.channel)\n const enabled = Boolean(result?.floodlightOnMotion)\n const previous = this.getCapSlice(motionTriggerCapability)\n const lastChangedAt = previous && previous.enabled === enabled\n ? previous.lastChangedAt\n : Date.now()\n this.setCapSlice(motionTriggerCapability, {\n enabled,\n lastChangedAt,\n lastFetchedAt: Date.now(),\n })\n this.hasFetchedFromFirmware = true\n\n // Mirror tuning fields → config. `duration` ranges 1..600s on\n // the schema; firmware-reported value sits well inside. The\n // schema is `z.enum(...)`, so an unrecognised `detectType`\n // (firmware advertising a combo we don't enumerate) would\n // throw — guard with the known-choice check.\n const patch: Record<string, unknown> = {}\n if (typeof result?.duration === 'number' && result.duration > 0) {\n patch['motionLightDurationS'] = result.duration\n }\n if (typeof result?.detectType === 'string') {\n const normalized = (FLOODLIGHT_DETECT_TYPE_CHOICES as readonly string[]).includes(result.detectType)\n ? result.detectType\n : undefined\n if (normalized !== undefined) {\n patch['motionLightDetectType'] = normalized\n } else {\n this.ctx.logger.debug('floodlight detectType outside enum — skipping config mirror', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { detectType: result.detectType },\n })\n }\n }\n if (Object.keys(patch).length > 0) {\n await this.config.setAll(patch).catch((err: unknown) => {\n this.ctx.logger.debug('floodlight config mirror failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n } catch (err) {\n this.ctx.logger.warn('floodlight motion-trigger fetch failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n\n\n // ── Cached projections ──────────────────────────────────────────────\n\n /** Last-known on/off + change timestamp. Defaults match the cold-\n * start seed values written below. */\n private switchView(): SwitchStatus {\n return this.getCapSlice(switchCapability) ?? { on: false, lastChangedAt: 0 }\n }\n private brightnessView(): BrightnessStatus {\n return this.getCapSlice(brightnessCapability) ?? { percentage: 100, lastChangedAt: 0 }\n }\n\n private registerSwitchCap(): void {\n const provider: InferNativeProvider<typeof switchCapability> = {\n // Slice-driven read — the parent's align loop refreshes\n // periodically; per-call firmware reads would defeat the\n // align-cooldown contract.\n getStatus: async (): Promise<SwitchStatus> => this.switchView(),\n setState: async ({ deviceId, on }) => {\n if (deviceId !== this.id) return\n const api = await this.api\n const brightness = this.brightnessView().percentage\n await api.setWhiteLedState(on, brightness, this.channel)\n this.setCapSlice(switchCapability, { on, lastChangedAt: Date.now() })\n this.lastSetAtMs = Date.now()\n },\n }\n this.ctx.registerNativeCap(switchCapability, provider)\n // Cold-start seed — keeps the SwitchHero out of \"Awaiting state…\"\n // until the first refresh writes the firmware-reported value.\n this.setCapSlice(switchCapability, { on: false, lastChangedAt: 0 })\n }\n\n private registerBrightnessCap(): void {\n const provider: InferNativeProvider<typeof brightnessCapability> = {\n getStatus: async (): Promise<BrightnessStatus> => this.brightnessView(),\n setBrightness: async ({ deviceId, percentage }) => {\n if (deviceId !== this.id) return\n const value = Math.max(0, Math.min(100, percentage))\n // Lib accepts brightness only when the LED is on; otherwise we\n // just stash the slice value so the next setState({on:true})\n // re-issues setWhiteLedState with this brightness.\n if (this.switchView().on) {\n const api = await this.api\n await api.setWhiteLedState(true, value, this.channel)\n }\n this.setCapSlice(brightnessCapability, { percentage: value, lastChangedAt: Date.now() })\n this.lastSetAtMs = Date.now()\n },\n }\n this.ctx.registerNativeCap(brightnessCapability, provider)\n this.setCapSlice(brightnessCapability, { percentage: 100, lastChangedAt: 0 })\n }\n\n private registerMotionTriggerCap(): void {\n const bridge = createRuntimeStateBridge({\n runtimeState: this.runtimeState,\n cap: motionTriggerCapability,\n ownDeviceId: this.id,\n refresh: () => this.refreshMotionTriggerFromFirmware(),\n staleMs: STALE_MS,\n empty: (): MotionTriggerStatus => ({ enabled: false, lastChangedAt: 0 }),\n })\n\n const provider: InferNativeProvider<typeof motionTriggerCapability> = {\n getStatus: bridge.getStatus,\n setMotionTrigger: async ({ deviceId, enabled }) => {\n if (deviceId !== this.id) return\n this.ctx.logger.info('floodlight motion-trigger setMotionTrigger', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { enabled },\n })\n try {\n const api = await this.api\n await api.setFloodlightOnMotion(enabled, this.channel)\n // Always re-push duration + detectType when arming the\n // motion-trigger — Scrypted's reolink-native does the same so\n // the camera's auto-light timing matches the operator's saved\n // preference, not whatever the firmware defaults to.\n if (enabled) await this.pushFloodlightSettings()\n // Optimistic write — periodic poll will reconcile if firmware\n // doesn't actually persist (e.g. preconditions missing).\n this.setCapSlice(motionTriggerCapability, {\n enabled,\n lastChangedAt: Date.now(),\n lastFetchedAt: Date.now(),\n })\n this.lastSetAtMs = Date.now()\n } catch (err) {\n this.ctx.logger.warn('floodlight motion-trigger set failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { enabled, error: err instanceof Error ? err.message : String(err) },\n })\n throw err\n }\n },\n }\n this.ctx.registerNativeCap(motionTriggerCapability, provider)\n // Cold-start seed: the slice exists but with stale=true so the\n // first getStatus / poll round-trip is the source of truth.\n const seed: MotionTriggerRuntimeState = { enabled: false, lastChangedAt: 0, lastFetchedAt: 0 }\n this.setCapSlice(motionTriggerCapability, seed)\n }\n\n /**\n * Push the saved `duration` + `detectType` to the firmware via the\n * Baichuan FloodlightTask command. Used both on settings save (via\n * `applySettingsPatch` triggering a reapply) and right after arming\n * motion-trigger so the camera honours the operator-set timing\n * instead of the firmware default.\n */\n private async pushFloodlightSettings(): Promise<void> {\n const duration = this.config.get('motionLightDurationS') ?? 180\n const detectType = this.config.get('motionLightDetectType') ?? 'people,vehicle,dog_cat'\n try {\n const api = await this.api\n await api.setFloodlightSettings(this.channel, { duration, detectType })\n } catch (err) {\n this.ctx.logger.warn('floodlight motion settings push failed', {\n tags: { deviceId: this.id, channel: this.channel },\n meta: { duration, detectType, error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const schema: ConfigUISchema = {\n sections: [{\n id: 'motion-floodlight',\n title: 'Motion-triggered floodlight',\n description:\n 'Pushed to the camera (`FloodlightTask`) when the \"Trigger on motion\" toggle is armed. ' +\n 'Saved values also re-apply on every motion-trigger flip so the firmware always honours your preference.',\n columns: 2,\n fields: [\n {\n type: 'number',\n key: 'motionLightDurationS',\n label: 'Light duration (s)',\n description: 'Seconds the floodlight stays lit after a motion event. Reolink range 1..600s.',\n min: 1,\n max: 600,\n step: 1,\n default: 180,\n },\n {\n type: 'select',\n key: 'motionLightDetectType',\n label: 'Detection classes',\n description: 'Object classes that turn the floodlight on. Composed values (e.g. \"people,vehicle\") fire when ANY listed class detects.',\n options: FLOODLIGHT_DETECT_TYPE_CHOICES.map((v) => ({ value: v, label: v.replace(/,/g, ' + ').replace(/_/g, '+') })),\n default: 'people,vehicle,dog_cat',\n },\n ],\n }],\n }\n return hydrateSchema(schema, {\n motionLightDurationS: this.config.get('motionLightDurationS'),\n motionLightDetectType: this.config.get('motionLightDetectType'),\n })\n }\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n await this.config.setAll(patch)\n if ('motionLightDurationS' in patch || 'motionLightDetectType' in patch) {\n await this.pushFloodlightSettings()\n }\n }\n}\n","/**\n * PirAccessory — child IDevice for the on-camera passive-infrared\n * motion sensor (Argus / Duo battery models).\n *\n * Caps:\n * - `switch` — discrete enable/disable via cmd_id 153 (SetPirInfo)\n *\n * Settings:\n * - `pirSensitive` — 1..100 (firmware tolerance to small motion)\n * - `pirIntervalS` — minimum delay between consecutive triggers\n * - `pirReduceAlarm` — boolean false-positive reduction toggle\n *\n * Switch state lives in the runtime-state slice; a 30s background poll\n * keeps it aligned with whatever the camera reports so external flips\n * (mobile app, motion auto-reset) surface in the UI without operator\n * action. Settings save pushes the FULL `SetPirInfo` payload — the lib\n * doesn't expose per-field mutators.\n */\n\nimport { z } from 'zod'\nimport {\n BaseDevice,\n DeviceType,\n hydrateSchema,\n switchCapability,\n type SwitchStatus,\n} from '@camstack/types'\nimport type {\n ConfigUISchema,\n ConfigUISchemaWithValues,\n DeviceContext,\n InferNativeProvider,\n} from '@camstack/types'\nimport { reolinkAccessoryBaseSchema, type ReolinkAccessoryHost, AccessoryKind } from './base.js'\nimport type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\n\nexport const pirAccessorySchema = reolinkAccessoryBaseSchema.extend({\n kind: z.literal(AccessoryKind.PirSensor),\n // No zod defaults — these mirror firmware-current values and would\n // mislead the UI (showing \"50\" when the camera actually has \"70\").\n // The accessory's `refreshSwitchFromExternal` mirrors `getPirInfo`\n // → config on every align refresh, so the UI form pulls camera\n // truth directly. Optional gates the cold-start window before the\n // first refresh lands; `pushConfigToFirmware` skips undefined\n // fields so the lib's setPirInfo read-modify-write preserves\n // unspecified camera values.\n pirSensitive: z.number().int().min(1).max(100).optional(),\n pirReduceAlarm: z.boolean().optional(),\n // Range matches what the Reolink mobile app exposes for the PIR\n // detection cooldown: 5..120s. Firmware rejects values outside the\n // window with HTTP 400. The per-cam `intervalMax` mirrored from\n // `getPirInfo` (see `pirIntervalMax` below) is informational —\n // every model we support returns 120 — but we don't use it as the\n // schema cap because the firmware-reported value can in principle\n // be lower than 120 on some models, and clamping the schema below\n // 120 would silently reject a fully valid app-side value.\n pirIntervalS: z.number().int().min(5).max(120).optional(),\n /** Firmware-advertised upper bound for `pirIntervalS` — read from\n * `getPirInfo`'s `state.intervalMax` and mirrored on every align\n * refresh. Drives the operator slider's max in\n * `getSettingsUISchema`; absent value falls back to `60`\n * (scrypted-reolink-native default). */\n pirIntervalMax: z.number().int().positive().optional(),\n})\n\nexport type PirAccessoryConfig = z.infer<typeof pirAccessorySchema>\n\n/** Cooldown after operator-driven setState — parent's\n * alignAuxDevicesState skips this accessory inside the window. */\nconst COOLDOWN_MS = 15_000\n\nexport class PirAccessory extends BaseDevice<typeof pirAccessorySchema> {\n readonly features = []\n\n private lastSetAtMs = 0\n /** Becomes `true` after the first successful `refreshFromFirmware`\n * round-trip. Used by `onProbe` to decide whether the boot-time\n * sleep-gate should force a wake (no cached state) or skip\n * (already-have-state path). Mirrors the Reolink mobile app's\n * one-shot wake-and-fetch on launch. */\n private hasFetchedFromFirmware = false\n\n constructor(ctx: DeviceContext, private readonly parent: ReolinkAccessoryHost) {\n super(ctx, pirAccessorySchema, {\n type: DeviceType.Switch,\n role: AccessoryKind.PirSensor,\n })\n this.registerSwitchCap()\n // No per-accessory polling — parent's alignAuxDevicesState drives\n // refresh on a single 10s timer (wired) or wake transition (battery).\n this.parent.registerAccessoryChild?.({\n kind: AccessoryKind.PirSensor,\n id: this.id,\n refreshFromAlign: () => this.refreshFromFirmware(),\n isInCooldown: () => this.lastSetAtMs > 0 && Date.now() - this.lastSetAtMs < COOLDOWN_MS,\n })\n }\n\n private get channel(): number { return this.config.get('channel') ?? 0 }\n private get api(): Promise<ReolinkBaichuanApi> { return this.parent.ensureApi() as Promise<ReolinkBaichuanApi> }\n\n private switchView(): SwitchStatus {\n return this.getCapSlice(switchCapability) ?? { on: false, lastChangedAt: 0 }\n }\n\n /** Refresh switch + tuning slices from a firmware payload. The lib\n * surfaces the camera-current PIR config in `state.{sensitive,\n * reduceAlarm, interval}`; we mirror them into the accessory's\n * config so the UI reflects camera truth instead of stale\n * schema defaults (50 / false / 5). Called by the parent's\n * `alignAuxDevicesState()` — the parent owns the scheduling\n * (poll cadence + sleep gate + cooldown), the accessory just owns\n * the slice projection. */\n refreshSwitchFromExternal(state: { enabled?: boolean; state?: { sensitive?: number; reduceAlarm?: number; interval?: number; intervalMax?: number } }): void {\n const on = Boolean(state?.enabled)\n const previous = this.switchView()\n const lastChangedAt = previous.on === on ? previous.lastChangedAt : Date.now()\n this.setCapSlice(switchCapability, { on, lastChangedAt })\n\n // Mirror tuning fields into config so the Settings tab + the next\n // `pushConfigToFirmware` round-trip don't clobber camera-current\n // state with stale operator-typed values.\n //\n // Sensitivity is INVERSE on the wire: Reolink mobile app shows\n // user-facing 1..100 where higher = more sensitive, but the\n // camera-side `<sensiValue>` is `101 - userValue` (lower raw =\n // more sensitive). Mirrors the same convention as motion\n // sensitivity (where it's `51 - rawValue`, see\n // `refreshParentSettingsSnapshot:motionSnapshot`). Without this\n // inversion the UI shows e.g. 31 when the camera-app shows 70.\n //\n // `intervalMax` is the firmware-advertised upper bound for the\n // `interval` field — mirrored straight through (no inversion) so\n // the UI slider's `max` adapts per-cam.\n const inner = state?.state\n if (inner) {\n const patch: Record<string, unknown> = {}\n if (typeof inner.sensitive === 'number') patch['pirSensitive'] = 101 - inner.sensitive\n if (typeof inner.reduceAlarm === 'number') patch['pirReduceAlarm'] = inner.reduceAlarm === 1\n if (typeof inner.interval === 'number') patch['pirIntervalS'] = inner.interval\n if (typeof inner.intervalMax === 'number') patch['pirIntervalMax'] = inner.intervalMax\n if (Object.keys(patch).length > 0) {\n void this.config.setAll(patch).catch((err: unknown) => {\n this.ctx.logger.debug('pir config mirror failed', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n }\n }\n\n private async refreshFromFirmware(): Promise<void> {\n try {\n const api = await this.api\n const state = await api.getPirInfo(this.channel)\n this.refreshSwitchFromExternal(state)\n this.hasFetchedFromFirmware = true\n } catch (err) {\n this.ctx.logger.debug('pir refresh failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n\n /**\n * Kernel-driven probe — runs at device construction (addon boot,\n * device add). For battery cams currently asleep we have two\n * cases:\n *\n * 1. **First-time probe (no cached state yet)** — force a one-shot\n * wake + fetch. Mirrors the Reolink mobile app's launch\n * behaviour: the app wakes the cam once at startup so the\n * operator sees a populated UI. Without this, an aggressive\n * sleep timeout (~14s after boot on Argus / battery doorbell\n * firmwares) can put the cam back to sleep before the\n * kernel's initial probe pass completes — the slice never\n * gets populated, the kernel's retry chain exhausts after 5\n * attempts, and the operator sees an empty PIR switch state\n * until they manually wake the cam.\n * 2. **Subsequent probes (slice already populated)** — skip the\n * fetch. A Baichuan poll on a sleeping camera returns 400,\n * drains the battery, and would clobber the cached value\n * anyway. The parent's `alignAuxDevicesState('wake')` path\n * (simpleEvent `awake` push or sleep-poll backstop)\n * refreshes us when the cam wakes naturally.\n *\n * Wired cams + already-awake battery cams take the normal path.\n */\n override async onProbe(): Promise<void> {\n const isSleepingBattery = this.parent.isBatteryCam?.() === true && this.parent.isSleeping?.() === true\n if (isSleepingBattery) {\n if (this.hasFetchedFromFirmware) {\n this.ctx.logger.debug('pir onProbe skipped — parent battery cam is sleeping (have cached state)', {\n tags: { deviceId: this.id },\n })\n return\n }\n // First-time probe: wake the cam to fetch initial state.\n // Best-effort — if wake fails we still proceed with the fetch\n // (ensureApi's login flow itself nudges the cam awake on\n // most firmwares). Mirrors `wakeForIntercom` in the parent\n // camera class (waitAfterWakeMs:2000 + 1s settle).\n this.ctx.logger.info('pir onProbe forcing wake — initial probe with no cached state', {\n tags: { deviceId: this.id },\n })\n try {\n const api = await this.api\n await api.wakeUp(this.channel, { waitAfterWakeMs: 2000 })\n await new Promise<void>((resolve) => setTimeout(resolve, 1000))\n } catch (err) {\n this.ctx.logger.warn('pir wake before initial probe failed — proceeding anyway', {\n tags: { deviceId: this.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n await this.refreshFromFirmware()\n }\n\n\n private registerSwitchCap(): void {\n const provider: InferNativeProvider<typeof switchCapability> = {\n // Slice-driven read — mirrors scrypted-reolink-native pattern.\n getStatus: async (): Promise<SwitchStatus> => this.switchView(),\n setState: async ({ deviceId, on }) => {\n if (deviceId !== this.id) return\n // Push the FULL config on every toggle — keeps firmware in sync\n // with operator-edited sensitivity / interval values even when\n // the operator just flips on/off.\n await this.pushConfigToFirmware(on)\n this.setCapSlice(switchCapability, { on, lastChangedAt: Date.now() })\n this.lastSetAtMs = Date.now()\n },\n }\n this.ctx.registerNativeCap(switchCapability, provider)\n this.setCapSlice(switchCapability, { on: false, lastChangedAt: 0 })\n }\n\n /** One-shot push of the `SetPirInfo` payload — used both by the\n * switch flip and the settings save. `enable` is passed explicitly\n * so the caller controls on/off semantics; tuning fields are\n * omitted when undefined so the lib's read-modify-write preserves\n * the camera-current value (no false-default writes back).\n *\n * Sensitivity inversion: config holds the user-facing 1..100\n * (higher = more sensitive). Camera-side `<sensiValue>` is the\n * inverse `101 - userValue` (lower raw = more sensitive).\n * Mirrors `refreshSwitchFromExternal`'s reverse transform. */\n private async pushConfigToFirmware(enable: boolean): Promise<void> {\n const api = await this.api\n const sensitive = this.config.get('pirSensitive')\n const reduceAlarm = this.config.get('pirReduceAlarm')\n const interval = this.config.get('pirIntervalS')\n await api.setPirInfo({\n enable: enable ? 1 : 0,\n ...(typeof sensitive === 'number' ? { sensitive: 101 - sensitive } : {}),\n ...(typeof reduceAlarm === 'boolean' ? { reduceAlarm: reduceAlarm ? 1 : 0 } : {}),\n ...(typeof interval === 'number' ? { interval } : {}),\n }, this.channel)\n }\n\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n // Defaults seed only from config — config is mirrored from\n // firmware on every align refresh (`refreshSwitchFromExternal`)\n // so the form ALWAYS shows camera-current values when populated.\n // Pre-first-refresh window: fields are undefined; the form\n // renders empty and the operator should wait for the cam to\n // wake (battery) or the first probe to land (wired).\n const sensitive = this.config.get('pirSensitive')\n const interval = this.config.get('pirIntervalS')\n const reduceAlarm = this.config.get('pirReduceAlarm')\n // Per-cam upper bound for the cooldown slider. The lib parses\n // `<intervalSecMax>` from the firmware response and surfaces it\n // as `state.intervalMax`; we mirror it into config in\n // `refreshSwitchFromExternal`. Fallback `120` matches the Reolink\n // mobile app's range — every model we've probed returns 120, but\n // the dynamic mirror future-proofs against firmware that\n // advertises a lower cap. Capped at 120 to mirror the schema's\n // `.max(120)` (see `pirAccessorySchema`).\n const intervalMax = Math.min(this.config.get('pirIntervalMax') ?? 120, 120)\n const schema: ConfigUISchema = {\n sections: [{\n id: 'pir',\n title: 'PIR sensor',\n description: 'Hardware passive-infrared trigger configuration. Pushed via cmd_id 153 (SetPirInfo).',\n columns: 2,\n fields: [\n {\n type: 'slider',\n key: 'pirSensitive',\n label: 'Sensitivity',\n description: 'Higher value → triggers on smaller motion. Most cameras max at 100.',\n min: 1,\n max: 100,\n step: 1,\n ...(typeof sensitive === 'number' ? { default: sensitive } : {}),\n showValue: true,\n },\n {\n type: 'slider',\n key: 'pirIntervalS',\n label: 'Cooldown (seconds)',\n description: `Minimum delay between consecutive PIR triggers. Reolink app range 5..${intervalMax}s; firmware clamps on push.`,\n min: 5,\n max: intervalMax,\n step: 1,\n ...(typeof interval === 'number' ? { default: interval } : {}),\n showValue: true,\n showStepper: true,\n },\n {\n type: 'boolean',\n key: 'pirReduceAlarm',\n label: 'Reduce false alarms',\n description: 'Trades detection range for fewer false positives. Maps to firmware `reduceAlarm` flag.',\n ...(typeof reduceAlarm === 'boolean' ? { default: reduceAlarm } : {}),\n style: 'switch',\n span: 2,\n },\n ],\n }],\n }\n return hydrateSchema(schema, {\n ...(typeof sensitive === 'number' ? { pirSensitive: sensitive } : {}),\n ...(typeof interval === 'number' ? { pirIntervalS: interval } : {}),\n ...(typeof reduceAlarm === 'boolean' ? { pirReduceAlarm: reduceAlarm } : {}),\n })\n }\n\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n await this.config.setAll(patch)\n if ('pirSensitive' in patch || 'pirReduceAlarm' in patch || 'pirIntervalS' in patch) {\n try {\n await this.pushConfigToFirmware(this.switchView().on)\n } catch (err) {\n this.ctx.logger.warn('pir firmware push failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n throw err\n }\n }\n }\n}\n","/**\n * Hardware-metadata populator shared by `ReolinkCamera` + `ReolinkHub`.\n *\n * Pulls every host-level identification probe the lib exposes:\n * - `getInfo` (cmd 80 host / cmd 318 channel) — model, hw/fw versions,\n * serial, itemNo, name, plus the extended `detail` hardware code.\n * - `getNetworkInfo` (cmd 76 + 93 host / cmd 76 channel) — ip, mac,\n * activeLink (LAN / PPPOE / WiFi).\n * - `getChannelCount` — total channel count (NVR/Hub diagnostics).\n *\n * Patches two stores:\n * 1. The device-meta `metadata` blob via `device-manager.setMetadata`\n * (consumed by the device-detail Hardware sub-tab — read-only).\n * 2. The driver's persisted `deviceCache` config blob (read by\n * `generateStableId` when computing the row key for re-adding\n * the same physical device).\n *\n * `channel`: pass `undefined` for hubs / top-level cameras (host-level\n * cmd 80 + cmd 76 host). Pass a numeric index for hub-adopted child\n * cameras (cmd 318 ch=N + cmd 76 ch=N — the sub-channel's own data).\n *\n * Passing `0` for a hub returns sub-channel-0's data (i.e. one of the\n * attached cameras) — an upstream lib quirk that historically polluted\n * the hub's metadata blob with a sibling camera's identity. The\n * `undefined` branch in the lib's `getInfo` / `getNetworkInfo` is the\n * correct host-level path.\n *\n * Both writes are idempotent — `setMetadata` short-circuits on a\n * no-diff patch + `DeviceConfig.setAll` shallow-merges over the\n * current cache. Failure paths are best-effort: a transient lib\n * error leaves whatever fields the previous probe wrote in place.\n */\n\nimport type { ReolinkBaichuanApi, ReolinkDeviceInfo } from '@apocaliss92/nodelink-js'\nimport type { DeviceContext } from '@camstack/types'\n\nexport interface MetadataPopulatorTarget {\n readonly id: number\n readonly ctx: DeviceContext\n /** Existing deviceCache patch helper. Both classes use BaseDevice's\n * `this.config.setAll(partial)` API. */\n readonly setDeviceCachePatch: (cachePatch: Record<string, unknown>) => Promise<void>\n /** Persisted `uid` field — surfaced in the metadata blob for parity\n * with the deviceCache. Pass `undefined` when the schema doesn't\n * carry uid (e.g. Hub schema doesn't always require it). */\n readonly uid: string | undefined\n}\n\ninterface ExtendedDeviceInfo extends Partial<ReolinkDeviceInfo> {\n /** Extended hardware code (concatenation of board/lens/sensor codes).\n * Not in the canonical `ReolinkDeviceInfo` shape but returned by\n * cmd 80 / cmd 318 when explicitly requested via the `tags` option. */\n readonly detail?: string\n}\n\ninterface NetworkInfo {\n readonly ip?: string\n readonly mac?: string\n readonly activeLink?: string\n}\n\ninterface ApiSurface {\n getInfo: (\n channel?: number,\n opts?: { timeoutMs?: number; tags?: readonly string[] },\n ) => Promise<ExtendedDeviceInfo | undefined>\n getNetworkInfo: (\n channel?: number,\n opts?: { timeoutMs?: number },\n ) => Promise<NetworkInfo | undefined>\n getChannelCount?: () => Promise<number>\n}\n\nconst EXTENDED_INFO_TAGS: readonly string[] = [\n 'type',\n 'hardwareVersion',\n 'firmwareVersion',\n 'itemNo',\n 'serialNumber',\n 'name',\n 'detail',\n]\n\nexport async function populateReolinkMetadata(\n api: ReolinkBaichuanApi,\n channel: number | undefined,\n target: MetadataPopulatorTarget,\n): Promise<void> {\n const apiX = api as unknown as ApiSurface\n\n let info: ExtendedDeviceInfo | undefined\n try {\n info = await apiX.getInfo(channel, { timeoutMs: 4000, tags: EXTENDED_INFO_TAGS })\n } catch (err) {\n target.ctx.logger.info('populateReolinkMetadata: getInfo failed — skipping populate', {\n tags: { deviceId: target.id },\n meta: { channel: channel ?? 'host', error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n if (!info) {\n target.ctx.logger.info('populateReolinkMetadata: getInfo returned empty — skipping populate', {\n tags: { deviceId: target.id },\n meta: { channel: channel ?? 'host' },\n })\n return\n }\n\n let net: NetworkInfo | undefined\n try {\n net = await apiX.getNetworkInfo(channel, { timeoutMs: 2000 })\n } catch (err) {\n target.ctx.logger.debug('populateReolinkMetadata: getNetworkInfo failed (non-fatal)', {\n tags: { deviceId: target.id },\n meta: { channel: channel ?? 'host', error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // Channel count is host-level metadata — only meaningful when\n // probing the host (channel === undefined).\n let channelCount: number | undefined\n if (channel === undefined && typeof apiX.getChannelCount === 'function') {\n try {\n const c = await apiX.getChannelCount()\n if (typeof c === 'number' && c > 0) channelCount = c\n } catch {\n /* swallow */\n }\n }\n\n const mac = net?.mac\n const ip = net?.ip\n const activeLink = net?.activeLink\n\n // ── 1. device-meta `metadata` blob ───────────────────────────────\n const patch: Record<string, unknown> = {\n manufacturer: 'Reolink',\n ...(info.type ? { model: info.type } : {}),\n ...(info.firmwareVersion ? { firmware: info.firmwareVersion } : {}),\n ...(info.hardwareVersion ? { hardware: info.hardwareVersion } : {}),\n ...(info.serialNumber ? { serialNumber: info.serialNumber } : {}),\n ...(info.itemNo ? { itemNo: info.itemNo } : {}),\n ...(info.detail ? { hardwareDetail: info.detail } : {}),\n ...(info.name ? { reportedName: info.name } : {}),\n ...(mac ? { mac } : {}),\n ...(ip ? { ip } : {}),\n ...(activeLink ? { networkLink: activeLink } : {}),\n ...(target.uid ? { uid: target.uid } : {}),\n ...(channelCount !== undefined ? { channelCount } : {}),\n }\n try {\n await (target.ctx.api as { deviceManager?: { setMetadata?: { mutate?: (input: { deviceId: number; patch: Record<string, unknown> }) => Promise<void> } } } | undefined)\n ?.deviceManager?.setMetadata?.mutate?.({ deviceId: target.id, patch })\n target.ctx.logger.info('populateReolinkMetadata: metadata patched', {\n tags: { deviceId: target.id },\n meta: {\n channel: channel ?? 'host',\n keys: Object.keys(patch),\n },\n })\n } catch (err) {\n target.ctx.logger.warn('populateReolinkMetadata: setMetadata failed', {\n tags: { deviceId: target.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n\n // ── 2. deviceCache config blob (durable identifiers for stableId)\n const cachePatch: Record<string, unknown> = {}\n if (info.serialNumber) cachePatch['serialNumber'] = info.serialNumber\n if (mac) cachePatch['mac'] = mac\n if (info.firmwareVersion) cachePatch['firmwareVersion'] = info.firmwareVersion\n if (info.hardwareVersion) cachePatch['hardwareVersion'] = info.hardwareVersion\n if (Object.keys(cachePatch).length > 0) {\n try {\n await target.setDeviceCachePatch(cachePatch)\n } catch (err) {\n target.ctx.logger.debug('deviceCache identity persist failed (non-fatal)', {\n tags: { deviceId: target.id },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n}\n","import { z } from 'zod'\n\n/**\n * Single source of truth for the addon id used in event source tags +\n * `device.online`/`device.offline` payloads. Kept in sync with the\n * provider's `addonId` field manually — both must match for the device-\n * manager + alert center to correlate events back to the provider.\n */\nexport const REOLINK_ADDON_ID = 'provider-reolink'\n\n/**\n * Default Baichuan channel for single-camera devices. NVR per-channel\n * routing is handled by the stream-routing helpers from the\n * `ch{N}-main` stream id format (Slice 8). This constant is the\n * fallback for snapshot, sleep poll, abilities probe, etc. — anything\n * that doesn't carry a channel context.\n */\nexport const DEFAULT_CHANNEL = 0\n\n/**\n * Map nodelink ReolinkSimpleEvent.type values to the loose\n * CameraNativeDetection class strings the rest of camstack consumes.\n * Keeping the loose-string contract — face/package etc are valid.\n */\nexport const AI_CLASS_MAP: Readonly<Record<string, string>> = {\n people: 'person',\n vehicle: 'vehicle',\n animal: 'animal',\n face: 'face',\n package: 'package',\n}\n\n/**\n * Detected device class (Slice 15). The creation form never asks the\n * operator to pick this — `autoDetectDeviceType` from nodelink decides\n * at probe time. Persisted on the device so future reconnects don't\n * have to re-run autodetect (it's slower than a direct login).\n */\nexport const ReolinkDeviceTypeSchema = z.enum([\n 'camera',\n 'udp-camera',\n 'battery-cam',\n 'nvr',\n 'multifocal',\n])\n\nexport const ReolinkUdpDiscoveryMethodSchema = z.enum([\n 'local-direct',\n 'local-broadcast',\n 'remote',\n 'relay',\n 'map',\n])\n\n/**\n * Lib-side socket debug categories. Mirrors scrypted-reolink-native's\n * `DebugLogOption` (debug-options.ts) — same names, same semantics, so\n * users coming from Scrypted recognize them. Each flag is forwarded\n * verbatim into the `DebugOptions` blob `ReolinkBaichuanApi` accepts.\n */\nexport const ReolinkSocketDebugFlagSchema = z.enum([\n 'debugRtsp',\n 'traceNativeStream',\n 'traceRecordings',\n 'traceTalk',\n 'traceEvents',\n])\n\nexport type ReolinkSocketDebugFlag = z.infer<typeof ReolinkSocketDebugFlagSchema>\n\nexport const reolinkCameraSchema = z.object({\n // `name` is now base meta (DB column on `device-meta`, not in this\n // hardware-config schema). Read via `this.name` (resolved by\n // `BaseDevice` from `ctx.deviceMeta.name`); mutated via\n // `kernel.devices.setName(id, name)`.\n host: z.string().describe('Camera IP or hostname'),\n port: z.number().default(9000).describe('Baichuan port (default 9000)'),\n username: z.string().default('admin').describe('Username'),\n password: z.string().describe('Password'),\n // NOTE: `channels` removed in the architectural cleanup pass —\n // channel layout is fully determined by `deviceCache.channelCount`\n // (single-channel default 1) plus the lib's `buildVideoStreamOptions()`\n // result fetched runtime by `publishToBroker`. Single-channel devices\n // always run on channel 0; NVR routing carries the channel inside\n // each `ch{N}-main`/`ch{N}-sub` stream id.\n /**\n * Transport mode resolved at creation time by autodetect (Slice 15).\n * - `tcp` : standard TCP-based Baichuan (regular cameras, NVR/Hub)\n * - `udp` : BCUDP-based Baichuan (battery cams, UDP-only models like\n * Elite Floodlight WiFi)\n */\n transport: z.enum(['tcp', 'udp']).default('tcp'),\n /**\n * Camera UID (BCUDP). Required for battery / UDP cameras when the\n * library cannot reach them over local-direct discovery. Empty string\n * for TCP-only devices.\n */\n uid: z.string().optional(),\n /**\n * UDP discovery method selected by autodetect — preserved across\n * reconnects so we don't waste time re-discovering each time.\n */\n udpDiscoveryMethod: ReolinkUdpDiscoveryMethodSchema.optional(),\n /**\n * Channel index for cameras that live behind a Hub / NVR. When\n * present (>= 0), the camera class:\n * 1. Skips its own Baichuan login — `ensureApi()` delegates to\n * the parent Hub's already-warm socket via `parent.getApi()`.\n * 2. Routes per-channel cmd_ids (snapshot / motion / setEnc /\n * status / reboot / ...) at this channel index instead of\n * the default 0. Returned by `this.getChannel()`.\n * 3. Skips its own simpleEvent subscription + watchdog — the\n * Hub owns the subscription and dispatches events into\n * `handleSimpleEvent` via `routeSimpleEvent`.\n *\n * Standalone cameras (no parent) leave this undefined → channel 0\n * fallback, matching the pre-Hub behavior verbatim.\n */\n channel: z.number().int().nonnegative().optional(),\n /**\n * Cached probe results: autodetect-derived class (Slice 15) +\n * abilities/streams probe (Slice 4). NOT to be confused with the\n * top-level `BaseDevice.features: DeviceFeature[]` array — that one\n * is the public capability list surfaced via `device-manager.getDevice`\n * and lives on the class, not in config. This blob is Reolink-specific\n * persistence so we don't re-probe at every boot.\n *\n * Both `deviceType` and the `hasBattery`/`hasPtz`/… booleans are kept\n * for syntactic convenience downstream — they're always consistent\n * (enforced by the writers).\n */\n deviceCache: z\n .object({\n deviceType: ReolinkDeviceTypeSchema.optional(),\n channelCount: z.number().int().positive().optional(),\n model: z.string().optional(),\n /** Globally-unique Reolink serial number (`getInfo` → cmd 80).\n * Used as a fallback stableId discriminator when uid is absent. */\n serialNumber: z.string().optional(),\n /** MAC address from `getNetworkInfo` / autodetect's\n * `hostNetworkInfo.mac`. Used as a stableId discriminator after\n * uid + serialNumber. Stored as raw firmware-reported string;\n * the stableId derivation strips separators. */\n mac: z.string().optional(),\n /** Hardware revision string (e.g. \"IPC_523128M5MP\"). */\n hardwareVersion: z.string().optional(),\n /** Firmware version string. */\n firmwareVersion: z.string().optional(),\n // NOTE: feature flags (hasPtz/hasIntercom/hasDoorbell/hasFloodlight/\n // hasSiren/hasPirSensor/hasAutotrack/hasBattery) used to live here\n // — moved to the `feature-probe` runtime-state slice on BaseDevice.\n // Removed fields are stripped from existing rows by\n // `DeviceConfig.fromSchema` self-healing on first read after this\n // schema lands. The runtime-state slice rehydrates from the kernel\n // store on next worker boot, then `onProbe()` repopulates it.\n /**\n * Snapshot of the current motion-alarm config from\n * `getMotionAlarm` (cmdId=46). Drives the Motion tab's\n * hydrated values + sensitivity-range bounds.\n */\n motionSnapshot: z.object({\n enabled: z.boolean().optional(),\n sensitivity: z.number().nullable().optional(),\n }).optional(),\n /**\n * Per-AI-class sensitivity + stay-time snapshot from\n * `getAiAlarmRaw` (cmdId=342) for every type the camera\n * advertises in its `getAiDetectTypes` response. Drives the\n * \"AI sensitivity\" section on the Motion tab — one\n * sensitivity slider per supported class. Empty / missing\n * keys mean the camera doesn't expose that detector.\n */\n aiSensitivitySnapshot: z.record(\n z.string(),\n z.object({\n sensitivity: z.number().nullable().optional(),\n stayTime: z.number().nullable().optional(),\n }),\n ).optional(),\n /**\n * Cached list of AI types the camera advertises\n * (`getAiDetectTypes`). Keys are the canonical lib names —\n * `people`, `vehicle`, `dog_cat`, `face`, `package`. Used\n * to gate which AI sensitivity sliders render.\n */\n aiDetectTypes: z.array(z.string()).optional(),\n /**\n * Snapshot of the camera's image input config from\n * `getVideoInput` (cmdId=49). Drives the Image tab defaults.\n */\n imageSnapshot: z.object({\n bright: z.number().nullable().optional(),\n contrast: z.number().nullable().optional(),\n saturation: z.number().nullable().optional(),\n hue: z.number().nullable().optional(),\n irCutSwap: z.number().nullable().optional(),\n dayNight: z.string().optional(),\n }).optional(),\n /**\n * Snapshot of the camera's encoder config from `getEnc`\n * (cmdId=56). Drives the Stream/Encoder tab defaults +\n * channel-codec readouts.\n */\n encSnapshot: z.object({\n audio: z.number().nullable().optional(),\n mainStream: z.object({\n bitRate: z.number().nullable().optional(),\n frameRate: z.number().nullable().optional(),\n videoEncType: z.string().nullable().optional(),\n width: z.number().nullable().optional(),\n height: z.number().nullable().optional(),\n }).optional(),\n subStream: z.object({\n bitRate: z.number().nullable().optional(),\n frameRate: z.number().nullable().optional(),\n videoEncType: z.string().nullable().optional(),\n width: z.number().nullable().optional(),\n height: z.number().nullable().optional(),\n }).optional(),\n }).optional(),\n /**\n * Snapshot of the camera's privacy mask master switch from\n * `getMask` (cmdId=52). Zone editing is a separate flow —\n * we only expose the master enable.\n */\n maskSnapshot: z.object({\n enabled: z.boolean().nullable().optional(),\n }).optional(),\n /**\n * Snapshot of the camera's audio noise reduction config\n * from `getAudioNoise` (cmdId=439).\n */\n audioNoiseSnapshot: z.object({\n enabled: z.boolean().nullable().optional(),\n level: z.number().nullable().optional(),\n }).optional(),\n /**\n * Snapshot of the camera's auto-focus state from\n * `getAutoFocus` (cmdId=224). `supported=false` when the\n * probe returns nothing — used to gate the AutoFocus\n * section in the UI so non-zoom cams don't see it.\n */\n autoFocusSnapshot: z.object({\n enabled: z.boolean().nullable().optional(),\n supported: z.boolean().optional(),\n }).optional(),\n })\n .loose()\n .optional(),\n /**\n * Generic Baichuan debug logs. Forwarded as `DebugOptions.general`\n * on the lib's `ReolinkBaichuanApi` constructor. Mirrors Scrypted's\n * `debugLogs` boolean.\n */\n debugGeneral: z.boolean().default(false),\n /**\n * Lib-side socket trace categories. Each flag flips the matching\n * `DebugOptions.<flag>` on the api. Mirrors Scrypted's\n * `socketApiDebugLogs` multi-select.\n */\n debugSocketLogs: z.array(ReolinkSocketDebugFlagSchema).default([]),\n /**\n * On-camera motion detection master switch + sensitivity.\n * Pushed via `setMotionAlarm` (cmdId=47). The Reolink mobile\n * app + camera web UI use 1..50 (1 = least sensitive,\n * 50 = most sensitive). The camera-side raw value is the\n * INVERSE of the displayed value (`raw = 51 - value`) — same\n * convention as reolink_aio's `set_md_sensitivity`. The\n * driver does the conversion both on push (in\n * `applySettingsPatch`) and on read (in\n * `refreshParentSettingsSnapshot`), so this field is always\n * the user-facing value.\n */\n motionEnabled: z.boolean().optional(),\n motionSensitivity: z.number().int().min(1).max(50).optional(),\n /**\n * Per-AI-class sensitivity overrides — pushed via\n * `setAiDetection(aiType, sensitivity)` (cmdId=343). Reolink\n * range 0..100 — same scale the reolink_aio CGI exposes\n * (`set_ai_sensitivity` validates 0..100). Each field gates on\n * the matching `cache.aiDetectTypes` entry. Keys mirror the\n * lib's canonical names (people / vehicle / animal=dog_cat /\n * face / package) so the dispatcher can fan them out via the\n * lib `setAiDetection(aiType, ...)` overload.\n */\n aiPersonSensitivity: z.number().int().min(0).max(100).optional(),\n aiVehicleSensitivity: z.number().int().min(0).max(100).optional(),\n aiAnimalSensitivity: z.number().int().min(0).max(100).optional(),\n aiFaceSensitivity: z.number().int().min(0).max(100).optional(),\n aiPackageSensitivity: z.number().int().min(0).max(100).optional(),\n /**\n * Image controls — pushed via `setImage` (cmd_id=25, read via 26).\n * Reolink ranges are 0..100 for the core sliders. Undefined → the\n * driver doesn't push that slider on save and the camera keeps its\n * existing value (operator never wrote it).\n */\n imgBright: z.number().int().min(0).max(255).optional(),\n imgContrast: z.number().int().min(0).max(255).optional(),\n imgSaturation: z.number().int().min(0).max(255).optional(),\n imgHue: z.number().int().min(0).max(255).optional(),\n imgSharpen: z.number().int().min(0).max(255).optional(),\n /**\n * ISP (advanced image) — pushed via `setIsp` (cmd_id=25 +\n * cmd_id=297 for dayNightThreshold). dayNight values are\n * camera-specific enum strings; the driver normalizes case\n * before push.\n */\n ispDayNight: z.string().optional(),\n ispExposure: z.string().optional(),\n ispHdr: z.union([z.literal(0), z.literal(1)]).optional(),\n ispBinningMode: z.number().int().min(0).optional(),\n ispDayNightThreshold: z.number().int().min(0).max(255).optional(),\n /**\n * IR + status LED — pushed via `setIrLights` (cmd_id=209, read via\n * cmd_id=208). Operator-friendly enums; the driver normalizes to\n * the camera's `open`/`close` lowercase form before push.\n */\n irLightsState: z.enum(['On', 'Off', 'Auto']).optional(),\n irLightsBrightness: z.number().int().min(0).max(255).optional(),\n /**\n * Audio output volume — pushed via `setAudioCfg` (cmd_id=265,\n * read via 264). Reolink-spec range 0..100.\n */\n audioVolume: z.number().int().min(0).max(100).optional(),\n audioTalkAndReplyVolume: z.number().int().min(0).max(100).optional(),\n audioVisitorVolume: z.number().int().min(0).max(100).optional(),\n /**\n * Stream/Encoder (cmd_id 56/57). Bitrate (kbps) + framerate\n * per main/sub stream + global audio capture toggle. Codec\n * (h264/h265) and resolution are intentionally NOT exposed —\n * codec switching needs a per-camera capability check (older\n * firmwares are h264-only) and resolution is firmware-fixed per\n * stream slot. Both are surfaced READ-ONLY in the form via the\n * snapshot description.\n * Numeric ranges are advisory; the camera clamps to the supported\n * set on push.\n */\n streamMainBitRate: z.number().int().min(64).max(20480).optional(),\n streamMainFrameRate: z.number().int().min(1).max(60).optional(),\n streamSubBitRate: z.number().int().min(64).max(8192).optional(),\n streamSubFrameRate: z.number().int().min(1).max(60).optional(),\n streamAudioEnabled: z.boolean().optional(),\n /**\n * Privacy mask master switch — pushed via `setMask` (cmd_id 53,\n * read via 52). Toggles the `<Shelter><enable>` flag. Zone\n * editing is a separate flow not exposed by this driver.\n */\n privacyMaskEnabled: z.boolean().optional(),\n /**\n * Audio noise reduction level — pushed via `setAudioNoise`\n * (cmd_id 440, read via 439). 0 disables; 1..3 maps to\n * Reolink's low/medium/high. Lib treats `level <= 0` as off\n * and any positive value as on with that strength.\n */\n audioNoiseLevel: z.number().int().min(0).max(3).optional(),\n /**\n * Auto-focus master switch — pushed via `setAutoFocus`\n * (cmd_id 225). UI-friendly polarity (true = AF on, false =\n * AF off); the driver inverts before push because the\n * camera-side field is named `disable`. Only rendered when\n * `deviceCache.autoFocusSnapshot.supported === true`.\n */\n autoFocusEnabled: z.boolean().optional(),\n /**\n * Intercom (browser→cam talk channel) tuning. Only consumed\n * when the camera advertises two-way audio (`hasIntercom` in the\n * feature-probe slice). Mirrors scrypted-reolink-native's\n * `intercom-mixin.ts` storage settings — same names, same ranges,\n * same defaults so the user-facing latency matches Scrypted's.\n *\n * `intercomBlocksPerPayload` — number of IMA-ADPCM blocks bundled\n * into each `sendAudio` call against the lib's dedicated talk\n * session. Lower = smaller chunks, more sends/sec, lower latency.\n * Higher = fewer sends, less per-call socket overhead. Default 1\n * = lowest latency, matching Scrypted.\n */\n intercomBlocksPerPayload: z.number().int().min(1).max(8).optional(),\n /**\n * `intercomMaxBacklogMs` — bound on the PCM backlog before the\n * pump drops oldest samples to cap latency. Default 120ms matches\n * Scrypted; raise via the Settings UI on systems where the cross-\n * process Opus→PCM path is bursty enough to trigger frequent\n * `intercom backlog clamped` warnings.\n */\n intercomMaxBacklogMs: z.number().int().min(20).max(5000).optional(),\n /**\n * `intercomGain` — output gain multiplier applied to s16le PCM\n * samples before IMA-ADPCM encode. 1.0 = neutral; 2.0 ≈ +6dB;\n * 0.5 ≈ -6dB. Sample clipping is hard-capped at int16 bounds.\n */\n intercomGain: z.number().min(0.1).max(10).optional(),\n})\n\nexport type ReolinkCameraConfig = z.infer<typeof reolinkCameraSchema>\n\n/**\n * Hub / NVR config schema. The Hub holds the long-lived Baichuan\n * control + event channel (one socket per device, not per child) and\n * acts as the discovery source for the channels populated behind it.\n * Streaming and per-channel settings live on the child `ReolinkCamera`\n * instances; this schema only carries connection + debug + the cached\n * channels summary so we can re-render the Discovery panel offline.\n *\n * Mirrors `scrypted-reolink-native`'s `ReolinkNativeNvrDevice`\n * `storageSettings` (`nvr.ts:49-178`): same scope, same fields, just\n * adapted to the Zod conventions used elsewhere in this package.\n */\nexport const reolinkHubSchema = z.object({\n host: z.string().describe('Hub IP or hostname'),\n port: z.number().default(9000).describe('Baichuan port (default 9000)'),\n username: z.string().default('admin'),\n password: z.string(),\n /** Hubs are TCP-only on supported firmwares — kept here for symmetry. */\n transport: z.enum(['tcp', 'udp']).default('tcp'),\n /** UID is unused on Hubs (no UDP discovery), but persisted for symmetry. */\n uid: z.string().optional(),\n /** Lib-side socket trace categories. Same flag set as the camera schema. */\n debugGeneral: z.boolean().default(false),\n debugSocketLogs: z.array(ReolinkSocketDebugFlagSchema).default([]),\n /**\n * Cached probe results. `channelCount` is authoritative for the\n * Discovery panel's \"X channels\" badge; `model` is what autodetect\n * read from `getInfo` (e.g. \"Reolink Home Hub\").\n */\n deviceCache: z.object({\n deviceType: z.literal('nvr').optional(),\n channelCount: z.number().int().positive().optional(),\n model: z.string().optional(),\n /** Globally-unique Reolink serial; `getInfo` cmd 80. Stable id\n * fallback after uid. Hub firmware sometimes returns a sentinel\n * `00000000000000` — `generateStableId`'s isMeaningfulIdentifier\n * filters that out. */\n serialNumber: z.string().optional(),\n /** MAC address from `getNetworkInfo` / autodetect's\n * `hostNetworkInfo.mac`. Stable id fallback after serial. */\n mac: z.string().optional(),\n hardwareVersion: z.string().optional(),\n firmwareVersion: z.string().optional(),\n probedAt: z.number().optional(),\n }).loose().optional(),\n})\n\nexport type ReolinkHubConfig = z.infer<typeof reolinkHubSchema>\n","/**\n * Camera-stream id format surfaced via `publishCameraStream`.\n *\n * Reolink cameras typically expose the same logical stream (main/sub/ext)\n * over THREE different containers — native Baichuan push, RTSP, RTMP. We\n * publish all available containers so the operator can pick which one the\n * broker should consume; the camStreamId carries the container kind as a\n * prefix to keep them disambiguated:\n *\n * - `native:main`, `native:sub`, `native:ext` — Baichuan RFC 4571 (loopback TCP)\n * - `rtsp:main`, `rtsp:sub`, `rtsp:ext` — RTSP pull (URL on stream)\n * - `rtmp:main`, `rtmp:sub`, `rtmp:ext` — RTMP pull (URL on stream)\n *\n * NVR / Hub devices (multi-channel) interleave the channel after the kind:\n *\n * - `native:ch0-main`, `native:ch0-sub`, `native:ch1-main`, …\n * - `rtsp:ch0-main`, …\n */\nexport type ReolinkStreamProfile = 'main' | 'sub' | 'ext'\nexport type ReolinkStreamKind = 'native' | 'rtsp' | 'rtmp'\n\nexport interface StreamIdParts {\n readonly kind: ReolinkStreamKind\n readonly channel: number\n readonly profile: ReolinkStreamProfile\n}\n\nconst KIND_RE = /^(native|rtsp|rtmp):(.+)$/\nconst CH_PROFILE_RE = /^ch(\\d+)-(main|sub|ext)$/\nconst PROFILE_ONLY_RE = /^(main|sub|ext)$/\n\n/**\n * Build a camStreamId from the (kind, channel, profile) tuple. Single-channel\n * devices omit the `ch{N}-` infix; NVR / Hub include it so per-channel\n * routing survives the round-trip through the broker.\n */\nexport function buildCamStreamId(\n kind: ReolinkStreamKind,\n channel: number,\n profile: ReolinkStreamProfile,\n channelCount: number,\n): string {\n if (channelCount > 1) return `${kind}:ch${channel}-${profile}`\n return `${kind}:${profile}`\n}\n\n/**\n * Parse a Reolink camStreamId back to its (kind, channel, profile) tuple.\n * Returns `null` if the id does not match our format — the demand handler\n * uses this to ignore non-native streams (broker pulls those directly).\n */\nexport function parseCamStreamId(camStreamId: string, defaultChannel: number): StreamIdParts | null {\n const m = KIND_RE.exec(camStreamId)\n if (!m) return null\n const kind = m[1] as ReolinkStreamKind\n const rest = m[2]!\n const ch = CH_PROFILE_RE.exec(rest)\n if (ch) {\n return {\n kind,\n channel: parseInt(ch[1]!, 10),\n profile: ch[2] as ReolinkStreamProfile,\n }\n }\n const p = PROFILE_ONLY_RE.exec(rest)\n if (p) {\n return {\n kind,\n channel: defaultChannel,\n profile: p[1] as ReolinkStreamProfile,\n }\n }\n return null\n}\n\n/**\n * Minimal duck-type for `streamLabel` — accepts any of:\n * - `ReolinkSupportedStream` from the lib (`buildVideoStreamOptions` result)\n * - any object that exposes the same three field names\n *\n * Keeping the parameter loose means the caller can pass the lib's\n * own structures without an adapter. We only read `profile`, `channel`,\n * and `lens`.\n */\nexport interface StreamLabelInput {\n readonly profile: ReolinkStreamProfile\n readonly channel?: number\n readonly lens?: 'wide' | 'telephoto' | 'composite'\n}\n\n/** Render a human-readable label for the device-settings dropdown. */\nexport function streamLabel(s: StreamLabelInput, kind?: ReolinkStreamKind): string {\n const parts: string[] = []\n if (kind) parts.push(kindLabel(kind))\n if (s.channel !== undefined) parts.push(`Ch${s.channel}`)\n parts.push(s.profile.charAt(0).toUpperCase() + s.profile.slice(1))\n if (s.lens && s.lens !== 'wide') parts.push(`(${s.lens})`)\n return parts.join(' ')\n}\n\nfunction kindLabel(kind: ReolinkStreamKind): string {\n switch (kind) {\n case 'native': return 'Native'\n case 'rtsp': return 'RTSP'\n case 'rtmp': return 'RTMP'\n }\n}\n\n/**\n * Synthetic native cam-stream id list used by `getStreamSources()` to\n * advertise the device's expected stream shape before the lib has been\n * called. `publishToBroker` ignores this — it always uses the live\n * result of `buildVideoStreamOptions()` instead.\n */\nexport function buildStreamIds(channelCount: number): ReadonlyArray<{ id: string; label: string }> {\n if (channelCount <= 1) {\n return [\n { id: 'native:main', label: 'Native Main' },\n { id: 'native:sub', label: 'Native Sub' },\n ]\n }\n const out: Array<{ id: string; label: string }> = []\n for (let ch = 0; ch < channelCount; ch++) {\n out.push({ id: `native:ch${ch}-main`, label: `Native Ch${ch} Main` })\n out.push({ id: `native:ch${ch}-sub`, label: `Native Ch${ch} Sub` })\n }\n return out\n}\n","/**\n * Recognise transient Baichuan/socket failures that are expected to\n * recover on the next reconnect cycle. Mirrors\n * `scrypted-reolink-native/src/camera.ts isRecoverableBaichuanError` —\n * keeping parity so the same wire-level conditions are treated the\n * same way across plugins.\n *\n * Ported categories:\n * - Baichuan-specific transport closes (`Baichuan socket closed`,\n * `Baichuan UDP stream closed`, `Baichuan TCP socket is not\n * connected`)\n * - Generic TCP errors that fire as part of the same disconnect\n * storm (`ECONNRESET`, `EPIPE`, `socket hang up`)\n * - D2C disconnects (`D2C_DISC`) — UDP relay path teardown\n *\n * Callers downgrade these to WARN-level logs and trigger a fresh\n * login on the next demand instead of bubbling a hard ERROR.\n */\nconst RECOVERABLE_FRAGMENTS: ReadonlyArray<string> = [\n 'Baichuan socket closed',\n 'Baichuan UDP stream closed',\n 'Baichuan TCP socket is not connected',\n 'socket hang up',\n 'ECONNRESET',\n 'EPIPE',\n 'D2C_DISC',\n]\n\nexport function isRecoverableBaichuanError(err: unknown): boolean {\n const message = err instanceof Error\n ? err.message\n : typeof err === 'string'\n ? err\n : (err as { toString?: () => string })?.toString?.() ?? ''\n return RECOVERABLE_FRAGMENTS.some((fragment) => message.includes(fragment))\n}\n","/**\n * IMA ADPCM (DVI4) encoder — Reolink Baichuan talk-back wire format.\n *\n * Ported from `scrypted-reolink-native/src/intercom.ts` (BSD-2 like the\n * rest of the cross-cam Scrypted infra). Reolink cameras expect ADPCM\n * blocks of (4 + N) bytes:\n * - 2 bytes: little-endian Int16 predictor (first PCM sample of the\n * block, used to seed the decoder state on the camera side)\n * - 1 byte: index into the IMA step table (always 0 — we re-seed\n * the predictor on every block instead of carrying state forward)\n * - 1 byte: padding\n * - N bytes: 2 nibbles per byte, low nibble first, each nibble\n * encodes one PCM sample's delta as `sign | delta3` (4 bits)\n *\n * The block size is camera-firmware specific; the lib reports it as\n * `TalkSessionInfo.blockSize` per session.\n *\n * Why standalone?\n * - Pure function — no I/O, easy to unit-test\n * - Reusable from the (currently stub) WebRTC server-side intercom\n * path AND from any future direct-PCM caller\n * - Decoupled from werift / lib types\n */\n\nconst IMA_INDEX_TABLE = Int8Array.from([\n -1, -1, -1, -1, 2, 4, 6, 8,\n -1, -1, -1, -1, 2, 4, 6, 8,\n])\n\nconst IMA_STEP_TABLE = Int16Array.from([\n 7, 8, 9, 10, 11, 12, 13, 14, 16, 17, 19, 21, 23, 25, 28, 31,\n 34, 37, 41, 45, 50, 55, 60, 66, 73, 80, 88, 97, 107, 118, 130, 143,\n 157, 173, 190, 209, 230, 253, 279, 307, 337, 371, 408, 449, 494, 544, 598, 658,\n 724, 796, 876, 963, 1060, 1166, 1282, 1411, 1552, 1707, 1878, 2066, 2272, 2499, 2749, 3024,\n 3327, 3660, 4026, 4428, 4871, 5358, 5894, 6484, 7132, 7845, 8630, 9493, 10442, 11487, 12635, 13899,\n 15289, 16818, 18500, 20350, 22385, 24623, 27086, 29794, 32767,\n])\n\nfunction clamp16(x: number): number {\n if (x > 32767) return 32767\n if (x < -32768) return -32768\n return x | 0\n}\n\n/**\n * Encode a PCM s16le buffer into IMA ADPCM blocks of size\n * `(4 + blockSizeBytes)` each. The output length is a multiple of\n * `4 + blockSizeBytes`. PCM samples that don't fit a full block at\n * the end get folded into a final partial block (the camera tolerates\n * trailing zeros from the unpopulated nibbles).\n *\n * Block layout per Reolink's wire format:\n * bytes [0..1] = predictor (Int16 LE)\n * byte [2] = step index (always 0 — re-seed each block)\n * byte [3] = padding (0x00)\n * bytes [4..N+3] = packed nibbles (2 samples per byte, low first)\n *\n * Each block carries `blockSizeBytes * 2 + 1` PCM samples (the +1\n * is the predictor sample stored explicitly in the header).\n */\nexport function encodeImaAdpcm(pcm: Int16Array, blockSizeBytes: number): Buffer {\n const samplesPerBlock = blockSizeBytes * 2 + 1\n const totalBlocks = Math.ceil(pcm.length / samplesPerBlock)\n const outBlocks: Buffer[] = []\n\n let sampleIndex = 0\n for (let b = 0; b < totalBlocks; b++) {\n const block = Buffer.alloc(4 + blockSizeBytes)\n const first = pcm[sampleIndex] ?? 0\n let predictor = first\n let index = 0\n block.writeInt16LE(predictor, 0)\n block.writeUInt8(index, 2)\n block.writeUInt8(0, 3)\n sampleIndex++\n\n const codes = new Uint8Array(blockSizeBytes * 2)\n for (let i = 0; i < codes.length; i++) {\n const sample = pcm[sampleIndex] ?? predictor\n sampleIndex++\n let diff = sample - predictor\n let sign = 0\n if (diff < 0) { sign = 8; diff = -diff }\n let step = IMA_STEP_TABLE[index] ?? 7\n let delta = 0\n let vpdiff = step >> 3\n if (diff >= step) { delta |= 4; diff -= step; vpdiff += step }\n step >>= 1\n if (diff >= step) { delta |= 2; diff -= step; vpdiff += step }\n step >>= 1\n if (diff >= step) { delta |= 1; vpdiff += step }\n predictor = sign ? clamp16(predictor - vpdiff) : clamp16(predictor + vpdiff)\n index += IMA_INDEX_TABLE[delta] ?? 0\n if (index < 0) index = 0\n if (index > 88) index = 88\n codes[i] = (delta | sign) & 0x0f\n }\n\n for (let i = 0; i < blockSizeBytes; i++) {\n const lo = codes[i * 2] ?? 0\n const hi = codes[i * 2 + 1] ?? 0\n block[4 + i] = (lo & 0x0f) | ((hi & 0x0f) << 4)\n }\n outBlocks.push(block)\n }\n return Buffer.concat(outBlocks)\n}\n","import type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\nimport type { IScopedLogger } from '@camstack/types'\nimport { encodeImaAdpcm } from './intercom-encoder.js'\n\n/**\n * Wraps `ReolinkBaichuanApi.createDedicatedTalkSession` with a\n * PCM-in / ADPCM-out forwarding pump. Independent of WebRTC — the\n * caller is responsible for producing PCM s16le at the rate the\n * camera reports (`session.info.audioConfig.sampleRate`).\n *\n * Lifecycle:\n * 1. `start()` → opens the lib's dedicated talk socket, fetches\n * the camera's negotiated audio config + block size, primes the\n * backlog cap.\n * 2. `feedPcm(buffer)` (zero or more times) → buffers PCM, splits\n * into block-sized chunks, encodes each chunk to IMA ADPCM,\n * sends to the camera. Backlog clamping drops the oldest\n * samples when the encoder/network can't keep up.\n * 3. `stop()` → closes the talk session. Idempotent.\n *\n * Every method returns / throws synchronously meaningful errors. The\n * caller drives the lifecycle (this class doesn't manage timers,\n * watchdogs, or auto-reconnect).\n */\n\ninterface TalkSession {\n readonly info: {\n readonly audioConfig: { readonly sampleRate: number }\n readonly blockSize: number\n readonly fullBlockSize: number\n }\n sendAudio(adpcm: Buffer): Promise<void>\n stop(): Promise<void>\n}\n\ninterface ReolinkApiWithTalk {\n createDedicatedTalkSession(channel: number, opts: {\n blocksPerPayload?: number\n idleTimeoutMs?: number\n deviceId?: string\n logger?: { log: (msg: string, ...rest: unknown[]) => void }\n }): Promise<TalkSession>\n}\n\n// Scrypted-parity defaults. An earlier iteration bumped these to\n// 500/4 to absorb the cross-process Opus→PCM jitter (`intercom\n// backlog clamped` warnings on stalls), but the trade-off was\n// noticeably more end-to-end latency — at 500ms backlog the operator\n// hears their own voice ~half a second after speaking, which kills\n// the intercom feel. Stay aligned with Scrypted's lower-latency\n// defaults; operators with stable pipelines accept the occasional\n// clamp warning, those willing to trade latency for stability raise\n// the slider via the new Settings UI section.\nconst DEFAULT_BACKLOG_MS = 120\nconst MAX_BACKLOG_MS = 5_000\nconst MIN_BACKLOG_MS = 20\n\nconst DEFAULT_BLOCKS_PER_PAYLOAD = 1\n\nconst DEFAULT_GAIN = 1.0\nconst MIN_GAIN = 0.1\nconst MAX_GAIN = 10\n\nexport interface ReolinkIntercomSessionOptions {\n readonly channel: number\n readonly api: ReolinkBaichuanApi\n readonly logger: IScopedLogger\n /** Stable id for diagnostic logging (\"camera-{deviceId}\"). */\n readonly deviceTag: string\n /** Per-payload block count (1 = lowest latency, more sockets/sec).\n * Default: 4. Range [1, 8] matches the lib + Scrypted UI. */\n readonly blocksPerPayload?: number\n /** Auto-teardown of the lib's talk socket after this many ms with\n * no audio sent. 0 disables. Default: 30s. */\n readonly idleTimeoutMs?: number\n /** Bound on PCM backlog → trades latency for stability. Default: 500ms. */\n readonly maxBacklogMs?: number\n /** Output gain multiplier applied to PCM samples before IMA ADPCM\n * encode. `1.0` = neutral, `2.0` ≈ +6dB. Default: 1.0 (no change). */\n readonly outputGain?: number\n}\n\nexport class ReolinkIntercomSession {\n private session: TalkSession | null = null\n private pcmBuffer: Buffer = Buffer.alloc(0)\n private pumping = false\n private pumpPromise: Promise<void> | null = null\n private maxBacklogBytes = 0\n private bytesPerBlock = 0\n private blockSize = 0\n private lastBacklogClampLogAtMs = 0\n private outputGain = DEFAULT_GAIN\n\n constructor(private readonly opts: ReolinkIntercomSessionOptions) {}\n\n /** True once `start()` has resolved and not yet been `stop()`'d. */\n get isOpen(): boolean { return this.session !== null }\n\n /** Sample rate the camera negotiated. Throws when not started. */\n get sampleRate(): number {\n if (!this.session) throw new Error('ReolinkIntercomSession.sampleRate read before start()')\n return this.session.info.audioConfig.sampleRate\n }\n\n async start(): Promise<void> {\n if (this.session) return\n this.outputGain = clampGain(this.opts.outputGain)\n const apiTalk = this.opts.api as unknown as ReolinkApiWithTalk\n const session = await apiTalk.createDedicatedTalkSession(this.opts.channel, {\n blocksPerPayload: clampBlocks(this.opts.blocksPerPayload),\n idleTimeoutMs: this.opts.idleTimeoutMs ?? 30_000,\n deviceId: this.opts.deviceTag,\n logger: { log: (msg, ...rest) => this.opts.logger.debug(`talk: ${msg}`, { meta: { rest } }) },\n })\n const { blockSize, fullBlockSize } = session.info\n if (!Number.isFinite(blockSize) || blockSize <= 0 || fullBlockSize !== blockSize + 4) {\n try { await session.stop() } catch { /* swallow */ }\n throw new Error(`Reolink talk session reported invalid block sizes: blockSize=${blockSize} fullBlockSize=${fullBlockSize}`)\n }\n const samplesPerBlock = blockSize * 2 + 1\n this.bytesPerBlock = samplesPerBlock * 2 // Int16 PCM\n this.blockSize = blockSize\n const sampleRate = session.info.audioConfig.sampleRate\n if (!Number.isFinite(sampleRate) || sampleRate <= 0) {\n try { await session.stop() } catch { /* swallow */ }\n throw new Error(`Reolink talk session reported invalid sampleRate: ${sampleRate}`)\n }\n const wantedBacklogMs = Math.max(MIN_BACKLOG_MS, Math.min(MAX_BACKLOG_MS, this.opts.maxBacklogMs ?? DEFAULT_BACKLOG_MS))\n // bytes/sec = sampleRate * 1 channel * 2 (s16) → window the buffer accordingly\n this.maxBacklogBytes = Math.max(this.bytesPerBlock, Math.floor(wantedBacklogMs / 1000 * sampleRate * 2))\n this.session = session\n this.pcmBuffer = Buffer.alloc(0)\n this.opts.logger.info('intercom talk session opened', {\n meta: {\n channel: this.opts.channel,\n sampleRate,\n blockSize,\n bytesPerBlock: this.bytesPerBlock,\n backlogMs: wantedBacklogMs,\n maxBacklogBytes: this.maxBacklogBytes,\n blocksPerPayload: clampBlocks(this.opts.blocksPerPayload),\n outputGain: this.outputGain,\n },\n })\n }\n\n /**\n * Feed a chunk of PCM s16le at `this.sampleRate` Hz. Returns\n * immediately after enqueueing — the actual encode + send happens\n * in a background pump. Calls before `start()` (or after `stop()`)\n * silently drop the chunk so callers don't have to gate every\n * push on `isOpen`.\n */\n feedPcm(pcm: Buffer): void {\n if (!this.session) return\n if (pcm.length === 0) return\n this.pcmBuffer = this.pcmBuffer.length ? Buffer.concat([this.pcmBuffer, pcm]) : pcm\n\n // Backlog clamping — drop the OLDEST samples once the buffer\n // exceeds the configured window. Aligned to 16-bit sample size\n // so the next encode pass doesn't get a half-sample.\n if (this.pcmBuffer.length > this.maxBacklogBytes) {\n const keep = this.maxBacklogBytes - (this.maxBacklogBytes % 2)\n const dropped = this.pcmBuffer.length - keep\n this.pcmBuffer = this.pcmBuffer.subarray(this.pcmBuffer.length - keep)\n const now = Date.now()\n if (now - this.lastBacklogClampLogAtMs > 2_000) {\n this.lastBacklogClampLogAtMs = now\n this.opts.logger.warn('intercom backlog clamped (dropping PCM)', {\n meta: { droppedBytes: dropped, keptBytes: keep, maxBytes: this.maxBacklogBytes },\n })\n }\n }\n if (!this.pumping) this.startPump()\n }\n\n private startPump(): void {\n const session = this.session\n if (!session) return\n this.pumping = true\n this.pumpPromise = (async () => {\n try {\n // eslint-disable-next-line no-constant-condition\n while (true) {\n if (this.session !== session) return\n if (this.pcmBuffer.length < this.bytesPerBlock) return\n const chunk = this.pcmBuffer.subarray(0, this.bytesPerBlock)\n this.pcmBuffer = this.pcmBuffer.subarray(this.bytesPerBlock)\n // Subarray points into the same ArrayBuffer — no copy needed.\n const samples = new Int16Array(chunk.buffer, chunk.byteOffset, chunk.length / 2)\n // Apply operator-tunable output gain BEFORE encoding so the\n // ADPCM block predictors see the boosted/attenuated signal\n // (gain applied post-encode would corrupt the ADPCM state).\n // Mirrors scrypted-reolink-native's `volume=N` ffmpeg\n // `-filter:a` arg in `intercom.ts:445-457`. `gain === 1`\n // skips the multiply pass — every other Opus frame avoids\n // a redundant Int16 multiply when the operator hasn't\n // touched the slider.\n const finalSamples = this.outputGain === 1 ? samples : applyGainInt16(samples, this.outputGain)\n const adpcm = encodeImaAdpcm(finalSamples, this.blockSize)\n await session.sendAudio(adpcm)\n }\n } catch (err) {\n this.opts.logger.warn('intercom pump error — stopping', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n } finally {\n this.pumping = false\n }\n })()\n }\n\n async stop(): Promise<void> {\n const session = this.session\n if (!session) return\n this.session = null\n this.pcmBuffer = Buffer.alloc(0)\n if (this.pumpPromise) {\n try { await Promise.race([this.pumpPromise, new Promise<void>((r) => setTimeout(r, 250))]) } catch { /* swallow */ }\n this.pumpPromise = null\n }\n try {\n await Promise.race([\n session.stop(),\n new Promise<void>((_, rej) => setTimeout(() => rej(new Error('talk session stop timeout')), 2_000)),\n ])\n } catch (err) {\n this.opts.logger.warn('intercom session stop error', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n }\n }\n}\n\n/** Clamp `blocksPerPayload` into the lib's accepted range. Mirrors\n * scrypted-reolink-native's `intercom-mixin.ts:96-101` clamp. */\nfunction clampBlocks(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value)) return DEFAULT_BLOCKS_PER_PAYLOAD\n return Math.max(1, Math.min(8, Math.floor(value)))\n}\n\n/** Clamp `outputGain` into the operator-safe range. Same `[0.1, 10]`\n * band as Scrypted (`intercom-mixin.ts:104-107`). */\nfunction clampGain(value: number | undefined): number {\n if (value === undefined || !Number.isFinite(value)) return DEFAULT_GAIN\n return Math.max(MIN_GAIN, Math.min(MAX_GAIN, value))\n}\n\n/** Apply a floating-point gain to each `Int16` sample in-place into\n * a freshly-allocated buffer. The result is hard-clipped to int16\n * bounds — soft saturation isn't worth the tradeoff for an intercom\n * channel where occasional clipping is preferable to a perceived\n * loudness mismatch. */\nfunction applyGainInt16(samples: Int16Array, gain: number): Int16Array {\n const out = new Int16Array(samples.length)\n for (let i = 0; i < samples.length; i++) {\n const scaled = (samples[i] ?? 0) * gain\n out[i] = scaled > 32767 ? 32767 : scaled < -32768 ? -32768 : scaled\n }\n return out\n}\n","/**\n * Intercom orchestrator — wires the three async layers required to\n * push browser-mic audio onto a Reolink camera's talk channel:\n *\n * browser ──── Opus RTP ────► IntercomWebrtcPeer (werift)\n * │\n * ▼ onOpusFrame(payload)\n * audio-codec cap (decode + resample)\n * │\n * ▼ pullPcm(...)\n * ReolinkIntercomSession.feedPcm(buf)\n * │\n * ▼ ADPCM\n * camera dedicated talk channel\n *\n * Lifecycle (driven by `intercom` cap router calls):\n * 1. `start()` — spin up the WebRTC peer (createOffer), open the\n * audio-codec decode session, open the Reolink talk session,\n * wire `onOpusFrame` → push-encoded → pull-PCM → feedPcm.\n * Returns `{sessionId, sdpOffer}` for the cap router to surface\n * to the client.\n * 2. `handleAnswer(sessionId, sdpAnswer)` — set remote description\n * on the peer; once ICE completes, RTP starts flowing.\n * 3. `stop(sessionId)` — close everything in reverse order: WebRTC\n * peer first (so no more pushes hit the codec session after\n * close), then audio-codec session, then talk session. Each\n * teardown is best-effort + idempotent.\n *\n * The WebRTC peer is supplied by the caller (DI) so:\n * - Unit tests can pass a fake peer with manual `emitOpusFrame()`\n * and assert the audio-codec / talk-session pipeline behaviour\n * without standing up a real RTCPeerConnection.\n * - Future variants (e.g. an audio-only sub-cap on the\n * `addon-stream-broker`) can plug in different peer impls\n * without touching the orchestrator.\n *\n * This module deliberately knows NOTHING about werift — the\n * concrete WebRTC peer lives in `intercom-webrtc-peer.ts` and is\n * imported only by the cap registration site in `reolink-camera.ts`.\n */\n\nimport type { IScopedLogger } from '@camstack/types'\nimport { ReolinkIntercomSession } from './intercom-session.js'\nimport type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\n\n/**\n * Audio-codec cap façade — narrow surface the orchestrator needs.\n * Mirrors the methods on `audioCodecCapability` exactly; passing the\n * full `ctx.api.audioCodec` router from the call site is fine.\n */\nexport interface IntercomAudioCodecApi {\n createDecodeSession(input: {\n codec: string\n sourceSampleRate: number\n sourceChannels: number\n targetSampleRate: number\n targetChannels: number\n /** Output PCM format. Defaults to 'f32le' on the cap side; the\n * intercom orchestrator pins it to 's16le' so the talk session\n * can read the buffer as Int16Array. */\n targetFormat?: 'f32le' | 's16le'\n tag?: string\n }): Promise<{ sessionId: string; nodeId: string }>\n pushEncodedFrame(input: {\n sessionId: string\n nodeId?: string\n data: Uint8Array\n pts?: number\n }): Promise<void>\n pullPcm(input: {\n sessionId: string\n nodeId?: string\n maxCount: number\n }): Promise<readonly { data: Uint8Array; sampleRate: number; channels: number; pts: number }[]>\n closeSession(input: { sessionId: string; nodeId?: string }): Promise<void>\n}\n\n/**\n * Minimal WebRTC peer contract the orchestrator drives. Implementations\n * own the werift `RTCPeerConnection` lifecycle; the orchestrator only\n * sees the SDP exchange + the Opus frame stream. The `onOpusFrame`\n * callback is registered before `createOffer()` returns so the impl\n * can fire it as soon as RTP starts flowing post-handshake.\n */\nexport interface IntercomWebrtcPeer {\n /** Build the SDP offer (sendrecv audio) and return its serialized\n * form. The peer creates the underlying RTCPeerConnection and\n * audio-recvonly transceiver inside this call. */\n createOffer(): Promise<{ sdp: string }>\n /** Apply the browser's SDP answer. After this resolves and ICE\n * completes, `onOpusFrame` callbacks start firing. */\n setAnswer(sdp: string): Promise<void>\n /** Subscribe to incoming Opus frames (one call per RTP packet\n * payload). May be invoked before or after `createOffer` — the\n * impl buffers callbacks until the receiver track is wired. */\n onOpusFrame(cb: (frame: Buffer, pts: number) => void): void\n /** Best-effort teardown. Idempotent — safe to call multiple times. */\n close(): Promise<void>\n}\n\n/** Factory passed into the orchestrator so the DI seam stays small.\n * The factory is invoked once per session — implementations can hold\n * per-session state (peer connection, ICE candidates) without\n * global singletons. */\nexport type IntercomWebrtcPeerFactory = (deps: { logger: IScopedLogger }) => IntercomWebrtcPeer\n\n/**\n * Configuration the orchestrator needs at construction time. Heavy\n * dependencies (audio-codec router, Reolink API) are passed in by the\n * cap registration site — the orchestrator itself stays vendor-\n * neutral above the talk-session boundary.\n */\nexport interface IntercomOrchestratorOptions {\n readonly deviceTag: string\n readonly channel: number\n readonly api: ReolinkBaichuanApi\n readonly logger: IScopedLogger\n readonly audioCodec: IntercomAudioCodecApi\n readonly peerFactory: IntercomWebrtcPeerFactory\n /**\n * Sample rate the camera negotiated for IMA ADPCM (Reolink typical:\n * 16000). Resolved at the camera-side from the live talk session\n * post-`start()`; we accept it as an option here so the\n * orchestrator can be unit tested without booting the real lib.\n * Defaults to `16000` when unset.\n */\n readonly cameraSampleRate?: number\n /**\n * Opus codec parameters the browser ships. Default: 48 kHz / 1\n * channel — the audio-codec cap handles resampling down to the\n * camera's rate. Override for stereo intercom devices (none of\n * the supported Reolinks ship stereo at the moment).\n */\n readonly opusSampleRate?: number\n readonly opusChannels?: number\n /**\n * Battery-cam pre-wake hook (Scrypted parity, `intercom.ts:90-106`).\n * When provided, called BEFORE `talkSession.start()` so the cam is\n * awake at handshake time. The hook runs ONLY when the implementer\n * wants to wake — typically gated on `isBatteryCam && isSleeping`\n * at the call site so wired cams skip the cost. Best-effort: a\n * thrown error from the hook bubbles up and aborts session start.\n */\n readonly wakeBeforeStart?: () => Promise<void>\n /**\n * Per-camera intercom tuning settings — mirror the Reolink schema\n * fields with the same names. All optional: each falls back to the\n * `ReolinkIntercomSession` defaults. See `schema.ts` for the\n * operator-facing range descriptions.\n */\n readonly blocksPerPayload?: number\n readonly maxBacklogMs?: number\n readonly outputGain?: number\n}\n\ninterface ActiveSession {\n readonly sessionId: string\n readonly peer: IntercomWebrtcPeer\n readonly talkSession: ReolinkIntercomSession\n readonly codec: { sessionId: string; nodeId: string }\n closed: boolean\n /** Anchored zero-PTS reference for monotonic frame stamping. */\n readonly startedAtMs: number\n framesPushed: number\n pcmBytesPushed: number\n}\n\n/** Default Opus parameters used when the orchestrator caller doesn't\n * override them. Browser Opus is canonically 48 kHz; mono is the only\n * practical choice for an intercom (the camera's talk channel is\n * mono — sending stereo would just cost bandwidth). */\nconst DEFAULT_OPUS_SAMPLE_RATE = 48_000\nconst DEFAULT_OPUS_CHANNELS = 1\nconst DEFAULT_CAMERA_SAMPLE_RATE = 16_000\n\nexport class IntercomOrchestrator {\n private session: ActiveSession | null = null\n\n constructor(private readonly opts: IntercomOrchestratorOptions) {}\n\n /** True while a session is open (between `start()` resolve and\n * `stop()` resolve). */\n get isOpen(): boolean { return this.session !== null && !this.session.closed }\n\n /**\n * Open a fresh WebRTC peer + audio-codec decode session + Reolink\n * talk session, wire them, return the SDP offer. Throws (and tears\n * down everything it had spun up) on any failure — the cap router\n * surfaces the error to the client unchanged.\n *\n * Single-active-session semantics: a second `start()` while the\n * first is still open closes the old session before opening the\n * new one. The cap router enforces this on the caller side via\n * `intercomSessions.size === 1` invariants.\n */\n async start(): Promise<{ sessionId: string; sdpOffer: string }> {\n if (this.session && !this.session.closed) {\n // Reuse callers may double-call `start` on flaky clients; close\n // the stale session before standing up a new one. Idempotent\n // teardown means the previous peer + codec + talk session all\n // unwind cleanly.\n await this.stop(this.session.sessionId).catch(() => { /* swallow */ })\n }\n\n const sessionId = generateSessionId()\n const cameraRate = this.opts.cameraSampleRate ?? DEFAULT_CAMERA_SAMPLE_RATE\n const opusRate = this.opts.opusSampleRate ?? DEFAULT_OPUS_SAMPLE_RATE\n const opusChannels = this.opts.opusChannels ?? DEFAULT_OPUS_CHANNELS\n\n // 0. Battery-cam pre-wake (Scrypted parity, `intercom.ts:90-106`).\n // The talk session handshake against a sleeping UDP/battery cam\n // times out before we even get a chance to send PCM; explicit\n // wake first is the only reliable path. Wired cams pass a no-op\n // or no hook at all so this branch is free for them.\n if (this.opts.wakeBeforeStart) {\n try {\n await this.opts.wakeBeforeStart()\n } catch (err) {\n this.opts.logger.warn('intercom: pre-wake failed', {\n meta: { error: errMsg(err) },\n })\n throw err\n }\n }\n\n // 1. Reolink talk session (the camera-facing leg). Open it FIRST\n // so that if the camera refuses (sleeping battery cam, no talk\n // permission, …) we fail fast before allocating WebRTC + codec\n // resources.\n const talkSession = new ReolinkIntercomSession({\n channel: this.opts.channel,\n api: this.opts.api,\n logger: this.opts.logger.withTags?.({ sessionId }) ?? this.opts.logger,\n deviceTag: this.opts.deviceTag,\n ...(this.opts.blocksPerPayload !== undefined ? { blocksPerPayload: this.opts.blocksPerPayload } : {}),\n ...(this.opts.maxBacklogMs !== undefined ? { maxBacklogMs: this.opts.maxBacklogMs } : {}),\n ...(this.opts.outputGain !== undefined ? { outputGain: this.opts.outputGain } : {}),\n })\n try {\n await talkSession.start()\n } catch (err) {\n this.opts.logger.warn('intercom: talk session open failed', {\n meta: { error: errMsg(err) },\n })\n throw err\n }\n // The lib reports the negotiated rate post-start — use it\n // (overrides the configured default) so the audio-codec resamples\n // to the right target.\n const realCameraRate = talkSession.sampleRate\n const targetRate = Number.isFinite(realCameraRate) && realCameraRate > 0 ? realCameraRate : cameraRate\n\n // 2. Audio-codec decode session — Opus → PCM s16le @ camera rate.\n let codec: { sessionId: string; nodeId: string }\n try {\n codec = await this.opts.audioCodec.createDecodeSession({\n codec: 'opus',\n sourceSampleRate: opusRate,\n sourceChannels: opusChannels,\n targetSampleRate: targetRate,\n targetChannels: 1,\n // CRITICAL: explicitly request s16le PCM. The cap defaults to\n // 'f32le' (32-bit float) and the talk session reads the buffer\n // as Int16Array — mismatch produces pure noise on the camera\n // speaker (each pair of float bytes interpreted as a random\n // int16 sample).\n targetFormat: 's16le',\n tag: `reolink-intercom:${this.opts.deviceTag}:${sessionId}`,\n })\n } catch (err) {\n await talkSession.stop().catch(() => { /* swallow */ })\n this.opts.logger.warn('intercom: audio-codec createDecodeSession failed', {\n meta: { error: errMsg(err), opusRate, opusChannels, targetRate },\n })\n throw err\n }\n\n // 3. WebRTC peer — last resource opened so cleanup ordering\n // matches the open ordering in reverse.\n const peer = this.opts.peerFactory({ logger: this.opts.logger })\n const active: ActiveSession = {\n sessionId,\n peer,\n talkSession,\n codec,\n closed: false,\n startedAtMs: Date.now(),\n framesPushed: 0,\n pcmBytesPushed: 0,\n }\n this.session = active\n\n // Wire the pump BEFORE createOffer — werift may emit RTP as soon\n // as ICE completes; subscribing late risks losing the first\n // packet.\n peer.onOpusFrame((frame, pts) => {\n // Best-effort fire-and-forget: a slow audio-codec decode must\n // never block the WebRTC RTP receive loop. Errors are logged\n // and the frame is dropped — Opus FEC + the talk session's\n // own backlog clamping cover transient losses.\n void this.handleOpusFrame(active, frame, pts).catch((err: unknown) => {\n this.opts.logger.debug('intercom: opus frame pump error (dropped)', {\n meta: { error: errMsg(err) },\n })\n })\n })\n\n let offer: { sdp: string }\n try {\n offer = await peer.createOffer()\n } catch (err) {\n await this.stop(sessionId).catch(() => { /* swallow */ })\n this.opts.logger.warn('intercom: webrtc createOffer failed', {\n meta: { error: errMsg(err) },\n })\n throw err\n }\n\n this.opts.logger.info('intercom session opened', {\n meta: {\n sessionId,\n channel: this.opts.channel,\n targetRate,\n codecSessionId: codec.sessionId,\n codecNodeId: codec.nodeId,\n },\n })\n return { sessionId, sdpOffer: offer.sdp }\n }\n\n /**\n * Apply the browser's SDP answer. The session is identified by id\n * (callers may have multiple cameras with overlapping intercom\n * sessions in flight — though this orchestrator only tracks one).\n * Mismatched session id throws.\n */\n async handleAnswer(sessionId: string, sdpAnswer: string): Promise<void> {\n const active = this.requireActive(sessionId)\n await active.peer.setAnswer(sdpAnswer)\n this.opts.logger.debug('intercom: SDP answer accepted', {\n meta: { sessionId },\n })\n }\n\n /**\n * Tear down the session in reverse-open order. Idempotent: a stop\n * for an unknown / already-closed session resolves silently. Best-\n * effort: a partial teardown (e.g. peer.close fails) does NOT\n * abort the rest — the next steps still try to release their own\n * resources.\n */\n async stop(sessionId: string): Promise<void> {\n const active = this.session\n if (!active || active.sessionId !== sessionId || active.closed) return\n active.closed = true\n this.session = null\n\n // Reverse-order teardown: WebRTC peer first (stop receiving more\n // Opus frames), then audio-codec, then talk session.\n try {\n await active.peer.close()\n } catch (err) {\n this.opts.logger.debug('intercom: peer.close error (continuing)', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n try {\n await this.opts.audioCodec.closeSession({\n sessionId: active.codec.sessionId,\n nodeId: active.codec.nodeId,\n })\n } catch (err) {\n this.opts.logger.debug('intercom: audio-codec.closeSession error (continuing)', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n try {\n await active.talkSession.stop()\n } catch (err) {\n this.opts.logger.debug('intercom: talk-session.stop error (continuing)', {\n meta: { sessionId, error: errMsg(err) },\n })\n }\n this.opts.logger.info('intercom session closed', {\n meta: {\n sessionId,\n framesPushed: active.framesPushed,\n pcmBytesPushed: active.pcmBytesPushed,\n durationMs: Date.now() - active.startedAtMs,\n },\n })\n }\n\n /**\n * Push one Opus frame into the audio-codec, immediately drain any\n * decoded PCM, and feed it to the talk session. Push-then-pull\n * keeps latency tight: each Opus frame produces ~20ms of PCM and\n * we surface it on the same async tick.\n *\n * Errors here are isolated to a single frame — the caller wraps in\n * a void-fire-and-forget so an audio-codec hiccup doesn't break\n * the RTP receive loop.\n */\n private async handleOpusFrame(active: ActiveSession, frame: Buffer, pts: number): Promise<void> {\n if (active.closed) return\n active.framesPushed += 1\n await this.opts.audioCodec.pushEncodedFrame({\n sessionId: active.codec.sessionId,\n nodeId: active.codec.nodeId,\n data: frame,\n pts,\n })\n if (active.closed) return\n const chunks = await this.opts.audioCodec.pullPcm({\n sessionId: active.codec.sessionId,\n nodeId: active.codec.nodeId,\n maxCount: 8,\n })\n if (active.closed) return\n for (const chunk of chunks) {\n // The audio-codec emits Uint8Array; talk session expects Buffer.\n // `Buffer.from(chunk.data.buffer, byteOffset, byteLength)` keeps\n // the original storage (no copy).\n const buf = Buffer.from(chunk.data.buffer, chunk.data.byteOffset, chunk.data.byteLength)\n active.pcmBytesPushed += buf.length\n active.talkSession.feedPcm(buf)\n }\n }\n\n private requireActive(sessionId: string): ActiveSession {\n const active = this.session\n if (!active || active.closed || active.sessionId !== sessionId) {\n throw new Error(`Reolink intercom: unknown or closed sessionId ${sessionId}`)\n }\n return active\n }\n}\n\n/** Random-enough session id — no crypto requirement, just needs to be\n * unique-per-camera across reasonable timescales. */\nfunction generateSessionId(): string {\n return `intercom-${Date.now().toString(36)}-${Math.floor(Math.random() * 0xffffff).toString(36)}`\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","/**\n * Concrete `IntercomWebrtcPeer` impl backed by werift.\n *\n * Audio-only `RTCPeerConnection`:\n * - One audio transceiver, direction `sendrecv` so the browser can\n * push mic-Opus to us. The server doesn't actually send audio\n * down (no `replaceTrack` / no outgoing audio frames) — Reolink's\n * intercom is camera-driven on the receive side, our talk-back\n * leg is the upstream Baichuan ADPCM channel, not WebRTC.\n * - No video track / no video transceiver. Saves a few hundred ms of\n * handshake time + avoids edge cases where Chrome rejects an\n * audio-only m-line offer that's missing video lines (it doesn't\n * — pure audio is well-supported, but worth flagging if we ever\n * see SDP weirdness).\n *\n * Lifecycle:\n * 1. `createOffer()` — lazy-loads werift, builds the peer + audio\n * transceiver, subscribes to `onTrack.onReceiveRtp` so Opus\n * payloads route straight to the registered callback. Returns\n * the local SDP for the cap router to surface to the client.\n * 2. `setAnswer(sdp)` — applies remote description; ICE proceeds.\n * 3. `close()` — best-effort `pc.close()` + clears state.\n *\n * No PLI / NACK / FIR handling: there's no video involved and Opus\n * has its own FEC + PLC, so RTCP feedback isn't useful here. The\n * impl is therefore much smaller than the broker's `AdaptiveSession`\n * (~150 LOC vs ~1.5k LOC).\n *\n * werift is loaded via `Function('m', 'return import(m)')(...)` to\n * avoid bundler static-analysis pulling it into builds where it's\n * not installed (the broker uses the same dance — see\n * `addon-stream-broker/src/webrtc/session.ts:loadWerift`).\n */\n\nimport type { IScopedLogger } from '@camstack/types'\nimport type { IntercomWebrtcPeer } from './intercom-orchestrator.js'\n\n/** Minimal werift surface this file relies on. We type-shadow the\n * module rather than `import type` from werift directly because\n * werift ships with no published `.d.ts` and the broker's own\n * `werift-types.ts` lives in another package. Duplicating the few\n * shapes we need keeps this file self-contained. */\ninterface WeriftRtpPacket {\n payload: Buffer\n header: { timestamp: number; sequenceNumber: number }\n}\ninterface WeriftMediaStreamTrack {\n kind: 'audio' | 'video'\n onReceiveRtp: { subscribe(cb: (pkt: WeriftRtpPacket) => void): { unsubscribe?: () => void } | void }\n}\ninterface WeriftTransceiver {\n onTrack: { subscribe(cb: (track: WeriftMediaStreamTrack) => void): { unsubscribe?: () => void } | void }\n}\ninterface WeriftSessionDescription {\n sdp: string\n type: 'offer' | 'answer' | 'pranswer' | 'rollback'\n}\ninterface WeriftPeerConnection {\n createOffer(): Promise<WeriftSessionDescription>\n setLocalDescription(desc: WeriftSessionDescription): Promise<void>\n setRemoteDescription(desc: WeriftSessionDescription): Promise<void>\n addTransceiver(\n track: WeriftMediaStreamTrack | string,\n options: { direction: 'sendrecv' | 'recvonly' | 'sendonly' | 'inactive' },\n ): WeriftTransceiver\n iceConnectionStateChange: { subscribe(cb: (state: string) => void): { unsubscribe?: () => void } | void }\n iceGatheringStateChange: { subscribe(cb: (state: string) => void): { unsubscribe?: () => void } | void }\n localDescription: WeriftSessionDescription | null\n close(): Promise<void> | void\n}\ninterface WeriftModule {\n RTCPeerConnection: new (options?: WeriftPcOptions) => WeriftPeerConnection\n MediaStreamTrack: new (init: { kind: 'audio' | 'video' }) => WeriftMediaStreamTrack\n RTCIceCandidate?: unknown\n}\ninterface WeriftIceServer {\n urls: string\n username?: string\n credential?: string\n}\ninterface WeriftPcOptions {\n iceServers?: WeriftIceServer[]\n iceTransportPolicy?: 'all' | 'relay'\n}\n\nlet _werift: WeriftModule | undefined\n\n/**\n * Lazy import — werift is an optional peer dep of this package\n * (declared via peerDependenciesMeta so npm install doesn't fail on\n * agents/clusters where the intercom is never used).\n */\nasync function loadWerift(): Promise<WeriftModule> {\n if (_werift) return _werift\n try {\n const moduleName = 'werift'\n // Dynamic import via Function to avoid bundler static analysis —\n // mirrors the broker's `loadWerift`. tsup's tree-shaker won't\n // resolve `Function('m', 'return import(m)')(...)` so the dep\n // stays optional at build time.\n // eslint-disable-next-line @typescript-eslint/no-unsafe-call -- intentional dynamic import\n _werift = await (Function('m', 'return import(m)')(moduleName) as Promise<WeriftModule>)\n return _werift!\n } catch {\n throw new Error(\n \"The 'werift' package is required for Reolink intercom support but is not installed. \" +\n 'Install it with: npm install werift',\n )\n }\n}\n\nexport interface IntercomWebrtcPeerOptions {\n readonly logger: IScopedLogger\n /** STUN / TURN servers to advertise in the SDP. Empty array =\n * host-only (LAN-only intercom). */\n readonly iceServers?: readonly WeriftIceServer[]\n}\n\nexport class WeriftIntercomPeer implements IntercomWebrtcPeer {\n private pc: WeriftPeerConnection | null = null\n private opusCallbacks: Array<(frame: Buffer, pts: number) => void> = []\n private rtpUnsubscribe: (() => void) | null = null\n private trackUnsubscribe: (() => void) | null = null\n private closed = false\n /** Anchor PTS to the first observed RTP timestamp so the audio-\n * codec receives monotonically-increasing PTS values from zero\n * rather than the camera's free-running 32-bit RTP clock. */\n private firstRtpTimestamp: number | null = null\n\n constructor(private readonly opts: IntercomWebrtcPeerOptions) {}\n\n onOpusFrame(cb: (frame: Buffer, pts: number) => void): void {\n if (this.closed) return\n this.opusCallbacks.push(cb)\n }\n\n async createOffer(): Promise<{ sdp: string }> {\n if (this.pc) throw new Error('WeriftIntercomPeer: createOffer called twice')\n const werift = await loadWerift()\n const pcOptions: WeriftPcOptions = {}\n if (this.opts.iceServers && this.opts.iceServers.length > 0) {\n pcOptions.iceServers = [...this.opts.iceServers]\n }\n const pc = new werift.RTCPeerConnection(pcOptions)\n this.pc = pc\n\n // Audio-only sendrecv transceiver. The local track placeholder is\n // a dummy `MediaStreamTrack({kind:'audio'})` — werift requires\n // it to materialise the m-line in the offer SDP. We never emit\n // RTP on the local sender; the camera-side talk back goes\n // through the Baichuan ADPCM channel (`ReolinkIntercomSession`),\n // not this WebRTC peer.\n const localTrack = new werift.MediaStreamTrack({ kind: 'audio' })\n const transceiver = pc.addTransceiver(localTrack, { direction: 'sendrecv' })\n\n // Wire the remote-track receiver. The Opus payload is everything\n // in the RTP `payload` field — werift exposes the wire bytes\n // without re-packetizing, which is exactly what the audio-codec\n // cap accepts (one frame per RTP packet).\n const trackSub = transceiver.onTrack.subscribe((track) => {\n if (track.kind !== 'audio') return\n const rtpSub = track.onReceiveRtp.subscribe((pkt) => {\n if (this.closed) return\n const payload = pkt.payload\n if (!payload || payload.length === 0) return\n // Anchor PTS at the first RTP packet — relative timestamps\n // are easier for the audio-codec session to interpret +\n // avoid wraparound concerns at the 32-bit boundary.\n const ts = pkt.header.timestamp\n if (this.firstRtpTimestamp === null) this.firstRtpTimestamp = ts\n const relTs = (ts - this.firstRtpTimestamp) >>> 0\n // RTP timestamp clock for Opus is 48 kHz — convert to ms.\n const ptsMs = Math.round(relTs / 48)\n for (const cb of this.opusCallbacks) {\n try { cb(payload, ptsMs) } catch (err) {\n this.opts.logger.debug('intercom-peer: opus callback threw', {\n meta: { error: errMsg(err) },\n })\n }\n }\n })\n if (rtpSub && typeof rtpSub.unsubscribe === 'function') {\n this.rtpUnsubscribe = () => rtpSub.unsubscribe?.()\n }\n })\n if (trackSub && typeof trackSub.unsubscribe === 'function') {\n this.trackUnsubscribe = () => trackSub.unsubscribe?.()\n }\n\n // ICE state logging — info-level so a stuck `checking` is\n // visible without flipping a debug flag (mirrors the broker's\n // session for symmetry).\n pc.iceConnectionStateChange.subscribe((state) => {\n this.opts.logger.info('intercom-peer: ICE state', { meta: { state } })\n })\n pc.iceGatheringStateChange.subscribe((state) => {\n this.opts.logger.debug('intercom-peer: ICE gathering', { meta: { state } })\n })\n\n const offer = await pc.createOffer()\n await pc.setLocalDescription(offer)\n // Use the local description after `setLocalDescription` — werift\n // populates fingerprints + ICE-ufrag/pwd at that point. Returning\n // the raw offer (pre-set) would emit a placeholder fingerprint\n // and the browser would reject the answer.\n const localSdp = pc.localDescription?.sdp ?? offer.sdp\n return { sdp: localSdp }\n }\n\n async setAnswer(sdp: string): Promise<void> {\n if (!this.pc) throw new Error('WeriftIntercomPeer: setAnswer called before createOffer')\n if (this.closed) throw new Error('WeriftIntercomPeer: setAnswer called on closed peer')\n await this.pc.setRemoteDescription({ sdp, type: 'answer' })\n }\n\n async close(): Promise<void> {\n if (this.closed) return\n this.closed = true\n this.opusCallbacks = []\n if (this.rtpUnsubscribe) {\n try { this.rtpUnsubscribe() } catch { /* swallow */ }\n this.rtpUnsubscribe = null\n }\n if (this.trackUnsubscribe) {\n try { this.trackUnsubscribe() } catch { /* swallow */ }\n this.trackUnsubscribe = null\n }\n if (this.pc) {\n try { await Promise.resolve(this.pc.close()) } catch { /* swallow */ }\n this.pc = null\n }\n }\n}\n\nfunction errMsg(err: unknown): string {\n return err instanceof Error ? err.message : String(err)\n}\n","import type { ReolinkBaichuanApi } from '@apocaliss92/nodelink-js'\nimport type { ConfigUISchema, IScopedLogger } from '@camstack/types'\n\n/**\n * Shared \"Sessions\" diagnostic helper used by both `ReolinkCamera` and\n * `ReolinkHub`. The snapshot shape + the form-builder block are\n * identical — only the api source differs (camera owns its own api;\n * hub-children borrow the parent's api). Centralising here keeps\n * future tweaks (extra columns, new lib-exposed fields) consistent\n * across all device types without duplication.\n */\n\nexport interface SessionsSnapshotUser {\n /** Firmware-reported username. */\n readonly userName: string\n /** Source IP. */\n readonly ip: string\n /** Firmware sessionId (number from cmd_id 145). `null` on legacy\n * payloads that don't expose it. */\n readonly sessionId: number | null\n /** Permission level — 'admin' / 'user' / numeric fallback. */\n readonly level: string\n /** Online flag from the firmware payload. Defaults to true when\n * the firmware doesn't include the field. */\n readonly online: boolean\n}\n\nexport interface SessionsSnapshotPoolEntry {\n /** Pool tag — `general`, `streaming:ch3`, `talk:ch0`, etc. */\n readonly tag: string\n /** Connection state — only `open` is currently exposed by the lib;\n * reserved for future extension. */\n readonly state: string\n}\n\nexport interface SessionsSnapshotDedicated {\n /** Lib-side identifier for the dedicated session. */\n readonly key: string\n}\n\nexport interface SessionsSnapshot {\n /** Wall-clock ms when this snapshot was captured. */\n readonly ts: number\n /**\n * Active firmware-reported user sessions. `null` when the lib call\n * threw — keeps the rest of the snapshot usable so the cooldown /\n * socket-pool blocks render even if the camera rejected the\n * user-list query.\n */\n readonly onlineUsers: readonly SessionsSnapshotUser[] | null\n /** Snapshot of OUR Baichuan socket pool (one row per tag). */\n readonly socketPool: readonly SessionsSnapshotPoolEntry[]\n /** Lib-internal dedicated streaming/talk sessions. */\n readonly dedicatedSessions: readonly SessionsSnapshotDedicated[]\n /**\n * Socket-pool cooldown diagnostic. `null` when lib hasn't tracked\n * any cooldowns for this host.\n */\n readonly cooldown: {\n readonly host: string\n readonly inCooldown: boolean\n readonly failureCount: number\n readonly cooldownRemainingMs: number\n readonly cooldownUntilIso: string | null\n } | null\n /** Top-level error message when refresh failed before any field could be read. */\n readonly error?: string\n}\n\nexport const SESSIONS_SNAPSHOT_MAX_AGE_MS = 60_000\n\nfunction formatUserLevel(level: unknown): string {\n if (level === undefined || level === null) return 'user'\n if (typeof level === 'string' && level.length > 0) return level\n if (typeof level === 'number') {\n if (level === 0) return 'admin'\n if (level === 1) return 'user'\n return String(level)\n }\n return 'user'\n}\n\n/**\n * Pull a fresh sessions snapshot from a live Baichuan api. Best-effort\n * across the board: any individual lib call that throws leaves the\n * corresponding snapshot field at its empty default — the rest of the\n * snapshot is still populated so the UI never goes blank on a single\n * firmware quirk.\n */\nexport async function captureSessionsSnapshot(\n api: ReolinkBaichuanApi,\n opts: { readonly logger: IScopedLogger; readonly deviceId: number },\n): Promise<SessionsSnapshot> {\n const ts = Date.now()\n\n // Firmware sessions (cmd_id 145). Pull the structured shape, not the\n // formatted-string variant — we render rows with dedicated columns.\n // Both legacy `item[]` and current `OnlineUser[]` payload shapes are\n // mapped to the same row schema.\n let onlineUsers: SessionsSnapshotUser[] | null\n try {\n const raw = await api.getOnlineUserList({ timeoutMs: 5_000 })\n const list = raw?.body?.OnlineUserList\n const rows: SessionsSnapshotUser[] = []\n for (const u of list?.OnlineUser ?? []) {\n rows.push({\n userName: typeof u.userName === 'string' ? u.userName : 'unknown',\n ip: typeof u.ipAddress === 'string' ? u.ipAddress : '',\n sessionId: typeof u.sessionId === 'number' ? u.sessionId : null,\n level: formatUserLevel(u.userLevel),\n online: u.isOnline === undefined ? true : u.isOnline === 1,\n })\n }\n for (const u of list?.item ?? []) {\n rows.push({\n userName: typeof u.userName === 'string' ? u.userName : 'unknown',\n ip: typeof u.ip === 'string' ? u.ip : '',\n sessionId: null,\n level: formatUserLevel(u.level),\n online: true,\n })\n }\n onlineUsers = rows\n } catch (err) {\n opts.logger.debug('captureSessionsSnapshot: getOnlineUserList failed', {\n tags: { deviceId: opts.deviceId },\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n onlineUsers = null\n }\n\n const poolRaw = api.getSocketPoolSummary()\n const socketPool: SessionsSnapshotPoolEntry[] = poolRaw.tags.map((tag) => ({\n tag,\n state: 'open',\n }))\n\n let dedicatedSessions: SessionsSnapshotDedicated[] = []\n try {\n const dedRaw = (api as unknown as { getDedicatedSessionsSummary?: () => { keys: string[] } }).getDedicatedSessionsSummary?.()\n if (dedRaw && Array.isArray(dedRaw.keys)) {\n dedicatedSessions = dedRaw.keys.map((key) => ({ key }))\n }\n } catch {\n /* lib version doesn't expose it — leave empty */\n }\n\n const cooldownRaw = api.getSocketPoolCooldownStatus()\n const cooldown = cooldownRaw ? {\n host: cooldownRaw.host,\n inCooldown: cooldownRaw.inCooldown,\n failureCount: cooldownRaw.failureCount,\n cooldownRemainingMs: cooldownRaw.cooldownRemainingMs,\n cooldownUntilIso: cooldownRaw.cooldownUntil ? cooldownRaw.cooldownUntil.toISOString() : null,\n } : null\n\n return {\n ts,\n onlineUsers,\n socketPool,\n dedicatedSessions,\n cooldown,\n }\n}\n\n/**\n * Build the form-builder sections for a Sessions tab from a snapshot.\n * `tabId` is the layout tab the section attaches to (drivers can put\n * Sessions on any tab they want — most use the literal `'sessions'`\n * tab id but the helper stays decoupled).\n */\nexport function buildSessionsTabSections(\n snap: SessionsSnapshot | null,\n opts: { readonly tabId: string; readonly title?: string },\n): readonly ConfigUISchema['sections'][number][] {\n const now = Date.now()\n const ageInfo = snap === null\n ? 'Loading…'\n : `${Math.round((now - snap.ts) / 1000)}s ago (${new Date(snap.ts).toLocaleTimeString()})`\n\n const cooldownText = snap === null\n ? 'Loading…'\n : snap.cooldown === null\n ? 'idle (no recent failures)'\n : snap.cooldown.inCooldown\n ? `cooling down: ${snap.cooldown.failureCount} failures, ${Math.ceil(snap.cooldown.cooldownRemainingMs / 1000)}s remaining (until ${snap.cooldown.cooldownUntilIso ?? '?'})`\n : `tracked: ${snap.cooldown.failureCount} failures, last reset clean`\n\n const errorBanner: ConfigUISchema['sections'][number]['fields'][number][] = snap?.error\n ? [{ type: 'info', key: 'sessionsError', label: 'Refresh error', content: snap.error, variant: 'danger' }]\n : []\n\n const onlineRows = snap?.onlineUsers\n ? snap.onlineUsers.map((u) => ({\n userName: u.userName,\n ip: u.ip,\n sessionId: u.sessionId === null ? '' : String(u.sessionId),\n level: u.level,\n status: u.online ? 'ok' : 'idle',\n }))\n : []\n const onlineEmptyMsg = snap === null\n ? 'Loading…'\n : snap.onlineUsers === null\n ? 'unavailable (firmware rejected the user-list query)'\n : 'No active sessions'\n\n const poolRows = snap?.socketPool.map((p) => ({ tag: p.tag, state: p.state })) ?? []\n const poolEmptyMsg = snap === null ? 'Loading…' : 'No open sockets'\n const dedicatedRows = snap?.dedicatedSessions.map((d) => ({ key: d.key })) ?? []\n\n return [\n {\n id: 'sessions-status',\n title: opts.title ?? 'Sessions',\n description: 'Diagnostics for the Baichuan control channel. Auto-refreshes when older than 1 minute; click Refresh to force.',\n tab: opts.tabId,\n order: 0,\n columns: 1,\n fields: [\n ...errorBanner,\n { type: 'info', key: 'sessionsLastRefreshed', label: 'Last refreshed', content: ageInfo },\n {\n type: 'object-array',\n key: 'sessionsOnlineUsers',\n label: 'Online users (firmware)',\n description: 'Active user sessions reported by the device (cmd_id 145). Includes other admins, mobile app, NVR linkups.',\n columns: [\n { key: 'userName', label: 'User' },\n { key: 'ip', label: 'IP', kind: 'monospace', width: '160px' },\n { key: 'sessionId', label: 'Session', kind: 'monospace', width: '90px' },\n { key: 'level', label: 'Level', width: '80px' },\n { key: 'status', label: 'State', kind: 'status', width: '100px' },\n ],\n value: onlineRows,\n emptyMessage: onlineEmptyMsg,\n },\n {\n type: 'object-array',\n key: 'sessionsSocketPool',\n label: 'Our socket pool',\n description: 'Open Baichuan TCP/UDP sockets this process holds for the device. One row per pool tag.',\n columns: [\n { key: 'tag', label: 'Tag', kind: 'monospace' },\n { key: 'state', label: 'State', kind: 'status', width: '100px' },\n ],\n value: poolRows,\n emptyMessage: poolEmptyMsg,\n },\n {\n type: 'object-array',\n key: 'sessionsDedicated',\n label: 'Dedicated lib sessions',\n description: 'Internal lib-side dedicated sessions (streaming/talk channels). Older lib versions leave this list empty.',\n columns: [\n { key: 'key', label: 'Session key', kind: 'monospace' },\n ],\n value: dedicatedRows,\n emptyMessage: 'No dedicated sessions',\n },\n { type: 'info', key: 'sessionsCooldown', label: 'Reconnect cooldown', content: cooldownText },\n {\n type: 'button',\n key: '_refreshSessions',\n label: 'Refresh sessions',\n buttonLabel: 'Refresh now',\n action: 'refresh-driver-data',\n variant: 'default',\n },\n ],\n },\n ]\n}\n","/**\n * Build a synthetic SDP for `pull-rfc4571` entries from device-side\n * metadata (Reolink `getStreamMetadata` cmd_id 56) WITHOUT opening an\n * upstream streaming session.\n *\n * The standard Reolink rfc4571 path (in `nodelink-js`) builds SDP from\n * the actual H.264/H.265 SPS/PPS bytes extracted from the first IDR\n * frame of the live stream — which requires the upstream Baichuan\n * streaming session to be open. That eager open is precisely what we\n * want to avoid for hub-children: the lib's session table on the NVR\n * fills with stale sessions every time we adopt + release.\n *\n * This builder produces SDP with:\n * - codec rtpmap (h264 → 96 / h265 → 96)\n * - audio rtpmap (AAC → 97 with Reolink-standard 16kHz mono)\n * - fmtp WITHOUT `sprop-parameter-sets` / `sprop-vps/sps/pps`\n *\n * The lib's `Rfc4571Muxer.sendCachedVideoConfigToClient` learns SPS/PPS\n * (and VPS for H.265) from the first IDR access unit it forwards and\n * primes new clients with cached config bytes via in-band STAP-A or\n * single NAL packets — so downstream consumers (WebRTC peers, RTSP\n * restreamer) see decoder-ready streams within one keyframe of\n * subscribing, even though the SDP they negotiated lacks `sprop-*`.\n *\n * Defaults match the Reolink standard codec config: AAC-LC, 16000Hz\n * mono. Cameras that stream a different audio config still work — the\n * `Rfc4571Muxer.sendAudioAdtsFrame` parses the ADTS header on every\n * frame and re-derives the config in the data path.\n */\n\nconst VIDEO_PT = 96\nconst AUDIO_PT = 97\n\n/**\n * Build SDP from a Reolink `StreamMetadata.videoEncType` + audio flags.\n * Both H.264 and H.265 are supported; unknown codecs default to H.264.\n *\n * `audioEnabled` toggles the audio media section. When false, the SDP\n * declares video only — matches what the rfc4571 server emits when the\n * camera reports `audio: 0`.\n */\nexport function buildSyntheticRfc4571Sdp(input: {\n readonly videoEncType: string // 'H.264' | 'H.265' | 'h264' | 'h265' | etc.\n readonly audioEnabled: boolean\n /** AAC sample rate — Reolink standard: 16000 Hz. Override only when\n * the camera reports something different in `getEnc` audio block. */\n readonly audioSampleRate?: number\n /** AAC channel count — Reolink standard: 1 (mono). */\n readonly audioChannels?: number\n}): string {\n const enc = String(input.videoEncType ?? '').toLowerCase()\n const isH265 = enc.includes('265') || enc.includes('hevc')\n const videoCodec = isH265 ? 'H265' : 'H264'\n\n const sampleRate = input.audioSampleRate ?? 16000\n const channels = input.audioChannels ?? 1\n\n let out = 'v=0\\r\\n'\n out += 'o=- 0 0 IN IP4 0.0.0.0\\r\\n'\n out += 's=No Name\\r\\n'\n out += 't=0 0\\r\\n'\n\n out += `m=video 0 RTP/AVP ${VIDEO_PT}\\r\\n`\n out += 'c=IN IP4 0.0.0.0\\r\\n'\n out += `a=rtpmap:${VIDEO_PT} ${videoCodec}/90000\\r\\n`\n if (!isH265) {\n // H.264 packetization-mode=1 = single NAL + STAP-A + FU-A, the\n // most common mode the rfc4571 server emits. No sprop-parameter-sets\n // — the muxer primes clients in-band on first IDR.\n out += `a=fmtp:${VIDEO_PT} packetization-mode=1\\r\\n`\n }\n // H.265: no fmtp without sprop-vps/sps/pps. Decoders accept this and\n // pick up VPS/SPS/PPS from in-band IRAP NALs.\n\n if (input.audioEnabled) {\n out += `m=audio 0 RTP/AVP ${AUDIO_PT}\\r\\n`\n out += 'c=IN IP4 0.0.0.0\\r\\n'\n out += 'b=AS:128\\r\\n'\n out += `a=rtpmap:${AUDIO_PT} MPEG4-GENERIC/${sampleRate}/${channels}\\r\\n`\n // AAC-hbr generic config without explicit `config=` hex — the muxer\n // adapts to the camera's actual ADTS-derived config on every frame.\n out += `a=fmtp:${AUDIO_PT} profile-level-id=1; mode=AAC-hbr; sizelength=13; indexlength=3; indexdeltalength=3\\r\\n`\n }\n\n return out\n}\n\n/**\n * Sentinel URL used as a placeholder before the rfc4571 server is\n * materialized. The broker detects this prefix in `startRfc4571Reader`\n * and triggers `notifySourceRefresh` instead of attempting to connect\n * — the publisher's listener responds by calling\n * `materializeStreamSocket(camStreamId)` which opens the real socket\n * and re-publishes the entry with the actual `tcp://...` URL.\n *\n * Format: `lazy:rfc4571:<camStreamId>` — opaque to anything that\n * doesn't know to look for it. `new URL(...)` parses it cleanly so the\n * reader's constructor doesn't synchronously throw.\n */\nexport function buildLazyRfc4571Url(camStreamId: string): string {\n // Normalise camStreamId so it's a safe URL component (the broker\n // matches on the prefix, not the suffix, but keeping it readable\n // helps log diagnostics).\n return `lazy:rfc4571:${encodeURIComponent(camStreamId)}`\n}\n\nexport const LAZY_RFC4571_URL_PREFIX = 'lazy:rfc4571:'\n","import { ReolinkBaichuanApi, type ReolinkSimpleEvent } from '@apocaliss92/nodelink-js'\nimport {\n BaseDevice,\n DeviceFeature,\n DeviceType,\n EventCategory,\n hydrateSchema,\n type ConfigUISchema,\n type ConfigUISchemaWithValues,\n type DeviceContext,\n type InferNativeProvider,\n type IDevice,\n deviceDiscoveryCapability,\n rebootCapability,\n type DeviceDiscoveryStatus,\n type DiscoveredChildDevice,\n type DiscoveredChildStatus,\n} from '@camstack/types'\n\nimport { reolinkHubSchema, ReolinkSocketDebugFlagSchema } from './schema.js'\nimport type { ReolinkSocketDebugFlag } from './schema.js'\nimport { ReolinkCamera, type ReolinkHubLike } from './reolink-camera.js'\nimport { populateReolinkMetadata } from './metadata-populator.js'\nimport {\n captureSessionsSnapshot,\n buildSessionsTabSections,\n SESSIONS_SNAPSHOT_MAX_AGE_MS,\n type SessionsSnapshot,\n} from './sessions-snapshot.js'\n\nconst HUB_DISCOVERY_REFRESH_TIMEOUT_MS = 8000\nconst HUB_RECONNECT_BASE_MS = 2000\nconst HUB_RECONNECT_MAX_MS = 30_000\n\n/**\n * Reolink Hub / NVR — single Baichuan socket fronting N channels.\n *\n * Mirrors the Scrypted `ReolinkNativeNvrDevice` pattern:\n * - One persistent control + event socket on the parent (no\n * per-channel login).\n * - `device-discovery` cap exposes the channel list with status,\n * plus adopt / release for promoting a channel to a real child\n * `ReolinkCamera`.\n * - Every `simpleEvent` from any channel is dispatched into the\n * matching child's handler via the channel→deviceId map.\n *\n * Streaming + per-channel settings are NOT owned by this class — each\n * adopted child is a full `ReolinkCamera` with its own runtime-state\n * slices. The hub deliberately does not register `snapshot`, `motion`,\n * `intercom`, `ptz` etc. on itself: a Hub is not a camera, and the UI\n * shouldn't surface streaming affordances on the parent.\n *\n * Battery channels under a Hub: the child `ReolinkCamera` may proxy\n * status reads through the Hub's already-warm socket so a \"is the cam\n * online\" UI poll doesn't need to wake the cam. The pattern lives in\n * `ReolinkCamera`; the Hub just provides `getApi()` to children that\n * ask for it via `parentHub`.\n */\nexport class ReolinkHub extends BaseDevice<typeof reolinkHubSchema> implements ReolinkHubLike {\n readonly features: readonly DeviceFeature[] = [DeviceFeature.Rebootable]\n\n /** Long-lived control + event socket. Lazily built; one per Hub. */\n private api: ReolinkBaichuanApi | null = null\n private connectInFlight: Promise<ReolinkBaichuanApi> | null = null\n private reconnectAttempts = 0\n private reconnectTimer: ReturnType<typeof setTimeout> | null = null\n private shutdownRequested = false\n\n /** Diagnostic Sessions snapshot — same shape as the camera's. */\n private sessionsSnapshot: SessionsSnapshot | null = null\n private sessionsRefreshInFlight: Promise<void> | null = null\n\n /**\n * Map from camera-side channel index → kernel device id of the\n * adopted child. Drives `onSimpleEvent` dispatch (fast lookup on\n * the hot event path). Rebuilt on every adopt / release / discovery\n * refresh; the kernel registry remains the canonical truth (the\n * `parentDeviceId` filter on `DeviceRegistered`/`DeviceUnregistered`\n * keeps the slice + map in sync after every change).\n */\n private channelToDeviceId = new Map<number, number>()\n\n /** Wall-clock ms of the last discovery refresh that included this\n * child's channel. Drives the offline marker — a child that hasn't\n * appeared in the firmware's channel list for `MISSING_OFFLINE_MS`\n * gets `markOnline(false)`. Re-appearance (next refresh seeing the\n * channel) restores `markOnline(true)`. */\n private childLastSeenAt = new Map<number, number>()\n\n /** Threshold for marking an adopted child offline when missing from\n * consecutive discovery refreshes. 10 minutes covers ~3 default\n * refresh cycles + transient firmware latency without false-flagging\n * battery cams that take a few cycles to wake into discovery. */\n private static readonly MISSING_OFFLINE_MS = 10 * 60_000\n\n /** Bound listener so off-then-on is idempotent across reconnects. */\n private readonly handleSimpleEventBound = (ev: ReolinkSimpleEvent): void => {\n this.routeSimpleEvent(ev)\n }\n\n constructor(ctx: DeviceContext) {\n super(ctx, reolinkHubSchema, { type: DeviceType.Hub })\n this.registerDeviceDiscovery()\n this.registerReboot()\n }\n\n /**\n * Hub-level reboot via Baichuan cmd_id 23. Identical wire shape to\n * the per-camera path on `ReolinkCamera`, but the Hub passes\n * `channel = undefined` so the firmware reboots the host (not a\n * sub-channel). The lib's `api.reboot()` accepts the unset arg.\n *\n * The cap is mounted unconditionally because every Reolink Hub /\n * NVR firmware we support exposes cmd 23. `features` already\n * advertises `Rebootable` so the UI shows the button regardless.\n * Without this registration the cap router's `requireDeviceScoped`\n * lookup throws \"Capability 'reboot' not registered for device N\"\n * and the operator click silently fails.\n */\n private registerReboot(): void {\n const provider: InferNativeProvider<typeof rebootCapability> = {\n reboot: async ({ deviceId }) => {\n if (deviceId !== this.id) {\n throw new Error(`ReolinkHub: deviceId mismatch, expected ${this.id}, got ${deviceId}`)\n }\n const api = await this.ensureApi()\n await api.reboot()\n return { success: true as const }\n },\n }\n this.ctx.registerNativeCap(rebootCapability, provider)\n }\n\n override async onActivate(): Promise<void> {\n // Best-effort first dial. Any failure is logged and retried via\n // the reconnect loop — discovery callers will block on\n // `ensureApi()` until the first connect completes.\n void this.ensureApi().catch((err) => {\n this.ctx.logger.warn('Reolink Hub initial connect failed — will retry', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n\n // Listen for EXTERNAL child unregistration so a manual delete\n // (operator removed a child via the device-manager UI, restore\n // pruned a stale row, …) flips the panel's `alreadyAdopted` flag\n // without waiting for the 10s poll.\n //\n // We deliberately do NOT listen on `DeviceRegistered`:\n // - `adoptDiscoveredChild` fires its own `await refreshDiscoveryFromCamera()`\n // after `kernel.devices.create` returns, so the mutation's\n // post-success state is already fresh by the time the UI\n // invalidates.\n // - listening to `DeviceRegistered` would double-fire the\n // refresh: once on the first `registerDevice` from the kernel's\n // Phase 2, then again from the explicit refresh in adopt.\n // - external adopts (someone calling `kernel.devices.create`\n // without going through `hub.adoptDevice`) are extremely rare\n // and the next 10s poll will catch them.\n //\n // Filter by `parentDeviceId` from the event payload — only events\n // about OUR children trigger a refresh.\n this.eventUnsubscribers.push(\n this.ctx.eventBus.subscribe(\n { category: EventCategory.DeviceUnregistered },\n (event) => {\n const data = event.data as {\n deviceId?: number\n parentDeviceId?: number | null\n }\n if (data.parentDeviceId !== this.id) return\n const cid = typeof data.deviceId === 'number' ? data.deviceId : null\n this.ctx.logger.info('Reolink Hub: child unregistered externally — refreshing discovery', {\n ...(cid !== null ? { tags: { deviceId: cid } } : {}),\n })\n void this.refreshDiscoveryFromCamera().catch(() => { /* best-effort */ })\n },\n ),\n )\n }\n\n private eventUnsubscribers: Array<() => void> = []\n\n override async removeDevice(): Promise<void> {\n this.shutdownRequested = true\n if (this.reconnectTimer) {\n clearTimeout(this.reconnectTimer)\n this.reconnectTimer = null\n }\n for (const unsub of this.eventUnsubscribers) {\n try { unsub() } catch { /* best-effort */ }\n }\n this.eventUnsubscribers = []\n if (this.api) {\n try { await this.api.close({ reason: 'hub-shutdown' }) } catch { /* best-effort */ }\n this.api = null\n }\n }\n\n // ── Settings UI ──────────────────────────────────────────────────────\n\n /**\n * Hub-specific settings form. Mirrors the Scrypted\n * `ReolinkNativeNvrDevice.storageSettings` block (`nvr.ts:49-178`):\n * connection (host / port / credentials) on the General tab,\n * Baichuan debug flags on Advanced. Per-channel settings (image,\n * motion, AI sensitivity, …) live on the adopted child cameras —\n * the Hub is purely a connection + discovery surface.\n */\n override getSettingsUISchema(): ConfigUISchemaWithValues {\n const cache = this.config.get('deviceCache')\n // Kick off the Sessions snapshot refresh if stale — non-blocking,\n // results land on the next aggregate poll.\n const snap = this.sessionsSnapshot\n const isStale = snap === null || (Date.now() - snap.ts) > SESSIONS_SNAPSHOT_MAX_AGE_MS\n if (isStale) {\n void this.refreshSessionsSnapshot().catch((err) => {\n this.ctx.logger.debug('Hub sessions refresh threw', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n const schema: ConfigUISchema = {\n tabs: [\n { id: 'general', label: 'General', icon: 'settings', order: 0 },\n { id: 'sessions', label: 'Sessions', icon: 'users', order: 80 },\n ],\n sections: [\n ...buildSessionsTabSections(snap, { tabId: 'sessions', title: 'Hub Sessions' }),\n {\n id: 'connection',\n tab: 'general',\n title: 'Connection',\n description: 'Baichuan protocol connection settings for the hub. The same socket fronts every adopted channel — child cameras inherit these credentials, no per-channel login needed.',\n columns: 2,\n fields: [\n { type: 'text', key: 'host', label: 'Hub IP', required: true, placeholder: '192.168.1.100' },\n { type: 'number', key: 'port', label: 'Port', default: 9000, min: 1, max: 65535, step: 1 },\n { type: 'text', key: 'username', label: 'Username', default: 'admin' },\n { type: 'password', key: 'password', label: 'Password', required: true, showToggle: true },\n ],\n },\n {\n id: 'identity',\n tab: 'general',\n title: 'Hub identity',\n description: 'Read-only summary from the autodetect probe. Channel count and model are persisted at first contact so the panel renders without re-probing.',\n columns: 2,\n fields: [\n { type: 'text', key: '_model', label: 'Model', default: cache?.model ?? 'unknown', disabled: true },\n { type: 'text', key: '_channelCount', label: 'Total channels', default: String(cache?.channelCount ?? 0), disabled: true },\n ],\n },\n {\n id: 'debug',\n tab: 'advanced',\n title: 'Debug',\n description: 'Lib-side Baichuan tracing. Mirrors `scrypted-reolink-native`. Changing these resets the control-channel client (active streams keep running) so new debug options take effect on the next dial.',\n columns: 1,\n fields: [\n {\n type: 'boolean',\n key: 'debugGeneral',\n label: 'General debug logs',\n description: 'Forwards `DebugOptions.general=true` — verbose lib protocol logs.',\n default: false,\n style: 'switch',\n },\n {\n type: 'multiselect',\n key: 'debugSocketLogs',\n label: 'Socket trace categories',\n description: 'Per-area lib tracing. Pick only what you need; each adds log volume.',\n default: [],\n options: [\n { value: 'debugRtsp', label: 'RTSP (proxy/server traffic)' },\n { value: 'traceNativeStream', label: 'Native stream (cmd 3/4 + H.264/H.265 + param sets)' },\n { value: 'traceRecordings', label: 'Recordings (FileInfoList, findAlarmVideo, download)' },\n { value: 'traceTalk', label: 'Talkback (cmd 10/11/201/202)' },\n { value: 'traceEvents', label: 'Events (cmd 33 — AlarmEventList push)' },\n ],\n },\n ],\n },\n ],\n }\n const values: Record<string, unknown> = {\n host: this.config.get('host'),\n port: this.config.get('port'),\n username: this.config.get('username'),\n password: this.config.get('password'),\n debugGeneral: this.config.get('debugGeneral') ?? false,\n debugSocketLogs: this.config.get('debugSocketLogs') ?? [],\n _model: cache?.model ?? 'unknown',\n _channelCount: String(cache?.channelCount ?? 0),\n }\n return hydrateSchema(schema, values)\n }\n\n /**\n * Pull a fresh Sessions snapshot from the live api. Mirrors\n * `ReolinkCamera.refreshSessionsSnapshot`. Single-flight: concurrent\n * callers fold into the same in-flight promise.\n */\n private async refreshSessionsSnapshot(): Promise<void> {\n if (this.sessionsRefreshInFlight) return this.sessionsRefreshInFlight\n const promise = (async (): Promise<void> => {\n let api: ReolinkBaichuanApi\n try {\n api = await this.ensureApi()\n } catch (err) {\n this.sessionsSnapshot = {\n ts: Date.now(),\n onlineUsers: null,\n socketPool: [],\n dedicatedSessions: [],\n cooldown: null,\n error: err instanceof Error ? err.message : String(err),\n }\n return\n }\n this.sessionsSnapshot = await captureSessionsSnapshot(api, {\n logger: this.ctx.logger,\n deviceId: this.id,\n })\n })().finally(() => {\n this.sessionsRefreshInFlight = null\n })\n this.sessionsRefreshInFlight = promise\n return promise\n }\n\n /**\n * Apply operator edits to the persisted blob and re-init the\n * Baichuan socket when credentials / host changed. Read-only\n * identity fields (prefixed `_`) are stripped before persist so\n * they don't trip the schema's strict-fields parse.\n */\n override async applySettingsPatch(patch: Record<string, unknown>): Promise<void> {\n // Action sentinels (`_refreshSessions`, …) trigger a side-effect\n // and are stripped before storage. Mirrors `ReolinkCamera`.\n if ('_refreshSessions' in patch) {\n await this.refreshSessionsSnapshot().catch((err) => {\n this.ctx.logger.warn('forced sessions refresh failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n const writable: Record<string, unknown> = {}\n for (const [k, v] of Object.entries(patch)) {\n if (k.startsWith('_')) continue\n writable[k] = v\n }\n if ('debugSocketLogs' in writable && Array.isArray(writable['debugSocketLogs'])) {\n // Coerce to the schema's flag enum (drops unknown values silently).\n writable['debugSocketLogs'] = (writable['debugSocketLogs'] as readonly unknown[])\n .filter((v): v is ReolinkSocketDebugFlag =>\n typeof v === 'string' &&\n (ReolinkSocketDebugFlagSchema.safeParse(v)).success,\n )\n }\n if (Object.keys(writable).length === 0) return\n await this.config.setAll(writable)\n\n // Connection-affecting changes → tear down + let the reconnect\n // loop re-dial with the new credentials. Pure debug toggles also\n // need a fresh client (DebugOptions are wired at construction).\n const credChanged = ['host', 'port', 'username', 'password', 'transport'].some((k) => k in writable)\n const debugChanged = 'debugGeneral' in writable || 'debugSocketLogs' in writable\n if (credChanged || debugChanged) {\n if (this.api) {\n try { await this.api.close({ reason: 'settings-changed' }) } catch { /* best-effort */ }\n this.api = null\n }\n this.markOnline(false)\n // Re-dial via the reconnect path — on credential change the\n // first dial may fail (operator typo), the loop surfaces it.\n void this.ensureApi().catch((err) => {\n this.ctx.logger.warn('Reolink Hub re-dial after settings change failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n }\n\n /**\n * Public accessor for child cameras under this hub. Returns the\n * already-logged-in api (single round-trip on first call). When\n * the api is mid-disconnect, callers wait for the next reconnect.\n */\n async getApi(): Promise<ReolinkBaichuanApi> {\n return this.ensureApi()\n }\n\n // ── Capability registration ──────────────────────────────────────────\n\n private registerDeviceDiscovery(): void {\n const provider: InferNativeProvider<typeof deviceDiscoveryCapability> = {\n getStatus: async ({ deviceId }): Promise<DeviceDiscoveryStatus | null> => {\n if (deviceId !== this.id) return null\n return this.runtimeState.getCapState<DeviceDiscoveryStatus>('device-discovery') ?? null\n },\n listDiscovered: async ({ deviceId }): Promise<readonly DiscoveredChildDevice[]> => {\n if (deviceId !== this.id) return []\n const slice = this.runtimeState.getCapState<DeviceDiscoveryStatus>('device-discovery')\n return slice?.discovered ?? []\n },\n refreshDiscovery: async ({ deviceId }): Promise<readonly DiscoveredChildDevice[]> => {\n if (deviceId !== this.id) return []\n await this.refreshDiscoveryFromCamera()\n const slice = this.runtimeState.getCapState<DeviceDiscoveryStatus>('device-discovery')\n return slice?.discovered ?? []\n },\n adoptDevice: async ({ deviceId, childNativeId, name }): Promise<{ deviceId: number; stableId: string }> => {\n if (deviceId !== this.id) {\n throw new Error(`adoptDevice: deviceId mismatch — got ${deviceId}, expected ${this.id}`)\n }\n return this.adoptDiscoveredChild(childNativeId, name)\n },\n releaseDevice: async ({ deviceId, childDeviceId }): Promise<void> => {\n if (deviceId !== this.id) return\n await this.releaseDiscoveredChild(childDeviceId)\n },\n }\n this.ctx.registerNativeCap(deviceDiscoveryCapability, provider)\n // Seed the slice ONLY after registerNativeCap installs the\n // runtime-state schema. Pre-register seed throws because the\n // schema validator isn't bound yet; status null means \"never\n // discovered yet\" so the panel renders an empty state safely.\n this.setCapSlice(deviceDiscoveryCapability, {\n discovered: [],\n lastDiscoveryAt: null,\n lastError: null,\n lastFetchedAt: 0,\n })\n }\n\n // ── Discovery ────────────────────────────────────────────────────────\n\n /**\n * Pull the channel list from the Hub via the lib's\n * `getNvrChannelsSummary({ source: 'cgi' })`. CGI source mirrors\n * Scrypted's choice: `GetChannelstatus` returns the channel list\n * synchronously (no race against the cmd_id 145 push cache that\n * can be empty right after login).\n *\n * Persists the result into the device-discovery runtime-state slice\n * so the panel renders the same view across reloads + cross-process.\n */\n private async refreshDiscoveryFromCamera(): Promise<void> {\n let lastError: string | null = null\n let discovered: DiscoveredChildDevice[] = []\n const startedAt = Date.now()\n\n this.ctx.logger.info('Reolink Hub: discovery refresh started', {\n meta: { source: 'cgi', timeoutMs: HUB_DISCOVERY_REFRESH_TIMEOUT_MS },\n })\n\n try {\n const api = await this.ensureApi()\n const summary = await api.getNvrChannelsSummary({\n source: 'cgi',\n timeoutMs: HUB_DISCOVERY_REFRESH_TIMEOUT_MS,\n })\n // Look up which of these channels we've already adopted as\n // kernel children — drives the `alreadyAdopted` flag for the UI.\n const adoptedByChannel = await this.loadAdoptedChildrenByChannel()\n\n discovered = summary.devices.map((d): DiscoveredChildDevice => {\n const childNativeId = computeChildNativeId(this.stableId, d.channel, d.uid)\n const adoptedDeviceId = adoptedByChannel.get(d.channel) ?? null\n return {\n childNativeId,\n name: d.name ?? `Channel ${d.channel}`,\n // Doorbell vs camera distinction lives in metadata; the\n // adopted child is always a Camera in our type system.\n type: DeviceType.Camera,\n status: discoveredStatusFor(d),\n metadata: {\n ...(d.model ? { model: d.model } : {}),\n ...(d.serialNumber ? { serialNumber: d.serialNumber } : {}),\n ...(d.uid ? { uid: d.uid } : {}),\n rtspChannel: d.channel,\n isBattery: d.isBattery === true,\n isDoorbell: d.isDoorbell === true,\n isMultifocal: d.isMultifocal === true,\n },\n alreadyAdopted: adoptedDeviceId !== null,\n adoptedDeviceId,\n }\n })\n } catch (err) {\n lastError = err instanceof Error ? err.message : String(err)\n this.ctx.logger.warn('Reolink Hub discovery refresh failed', {\n meta: { error: lastError },\n })\n }\n\n const slice: DeviceDiscoveryStatus & { lastFetchedAt: number } = {\n discovered,\n lastDiscoveryAt: Date.now(),\n lastError,\n lastFetchedAt: Date.now(),\n }\n this.runtimeState.setCapState(deviceDiscoveryCapability.name, slice)\n\n if (lastError === null) {\n const adoptedCount = discovered.filter((d) => d.alreadyAdopted).length\n const availableCount = discovered.length - adoptedCount\n const onlineCount = discovered.filter((d) => d.status === 'online').length\n const sleepingCount = discovered.filter((d) => d.status === 'sleeping').length\n this.ctx.logger.info('Reolink Hub: discovery refresh complete', {\n meta: {\n durationMs: Date.now() - startedAt,\n total: discovered.length,\n adopted: adoptedCount,\n available: availableCount,\n online: onlineCount,\n sleeping: sleepingCount,\n channels: discovered\n .map((d) => ({\n ch: d.metadata.rtspChannel,\n name: d.name,\n model: d.metadata.model ?? null,\n status: d.status,\n adopted: d.alreadyAdopted,\n })),\n },\n })\n }\n\n // Sync the channel→deviceId map so simpleEvent routing always\n // uses the freshest set of adopted children.\n this.channelToDeviceId.clear()\n for (const entry of discovered) {\n const ch = entry.metadata.rtspChannel\n if (typeof ch === 'number' && entry.adoptedDeviceId !== null) {\n this.channelToDeviceId.set(ch, entry.adoptedDeviceId)\n }\n }\n\n // Adopted-child offline reconciliation. Skip when the discovery call\n // itself failed — `discovered === []` then would falsely flag every\n // child offline (firmware blip, transient socket drop). Only act on\n // a successful refresh.\n if (lastError === null) {\n await this.reconcileAdoptedChildOnline(discovered)\n }\n }\n\n /**\n * After a successful discovery refresh, mark every adopted child\n * still seen as `online`, and any child not seen for more than\n * `MISSING_OFFLINE_MS` as `offline`. Mirrors the operator\n * expectation that \"if the hub firmware doesn't list it, the\n * device isn't reachable through the hub\".\n *\n * Battery cams that briefly disappear from `getNvrChannelsSummary`\n * during deep sleep aren't false-flagged — the threshold gives\n * 3+ refresh cycles of grace.\n */\n private async reconcileAdoptedChildOnline(discovered: readonly DiscoveredChildDevice[]): Promise<void> {\n const now = Date.now()\n // Mark every channel still seen NOW.\n const seenChannels = new Set<number>()\n for (const entry of discovered) {\n const ch = entry.metadata.rtspChannel\n if (typeof ch !== 'number' || entry.adoptedDeviceId === null) continue\n seenChannels.add(ch)\n this.childLastSeenAt.set(entry.adoptedDeviceId, now)\n }\n\n // Walk every adopted child + decide online/offline.\n let children: readonly IDevice[]\n try {\n children = await this.ctx.devices.getChildren(this.id)\n } catch (err) {\n this.ctx.logger.debug('reconcileAdoptedChildOnline: getChildren failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n return\n }\n for (const child of children) {\n const cfg = (child.config as { values: unknown }).values as { channel?: number } | undefined\n const ch = cfg?.channel\n if (typeof ch !== 'number') continue\n // Accessory grand-children (siren / floodlight / pir attached to\n // a hub-adopted camera) carry their own parent linkage and are\n // out of scope here — we only reconcile direct camera children.\n if (child.type !== DeviceType.Camera) continue\n\n const wasSeenNow = seenChannels.has(ch)\n const lastSeen = this.childLastSeenAt.get(child.id) ?? 0\n const missingMs = wasSeenNow ? 0 : (lastSeen > 0 ? now - lastSeen : Number.POSITIVE_INFINITY)\n\n // Cast to the live ReolinkCamera shape — `markOnline` is part of\n // the IDevice surface but not narrowed by the type system here.\n const live = child as unknown as { online: boolean; markOnline: (online: boolean) => void }\n\n if (wasSeenNow) {\n if (live.online !== true) {\n live.markOnline(true)\n this.ctx.logger.info('Reolink Hub: adopted child back online', {\n tags: { deviceId: child.id, deviceName: child.name },\n meta: { channel: ch },\n })\n }\n continue\n }\n if (missingMs >= ReolinkHub.MISSING_OFFLINE_MS) {\n if (live.online !== false) {\n live.markOnline(false)\n this.ctx.logger.warn('Reolink Hub: adopted child marked offline (missing from discovery)', {\n tags: { deviceId: child.id, deviceName: child.name },\n meta: { channel: ch, missingMs },\n })\n }\n }\n }\n }\n\n /**\n * Walks the kernel device registry for children with `parentDeviceId\n * === this.id` and reads each child's persisted config to extract\n * the channel index. Returns a `channel → kernel deviceId` map.\n * Used to gate the `alreadyAdopted` flag in the discovery list.\n */\n private async loadAdoptedChildrenByChannel(): Promise<Map<number, number>> {\n const result = new Map<number, number>()\n const children = await this.ctx.devices.getChildren(this.id)\n for (const child of children) {\n // Each adopted ReolinkCamera carries its channel in the persisted\n // config blob. `BaseDevice.config.values` returns the parsed shape.\n const cfg = (child.config as { values: unknown }).values as { channel?: number } | undefined\n const ch = cfg?.channel\n if (typeof ch === 'number') result.set(ch, child.id)\n }\n return result\n }\n\n /**\n * Promote a discovered channel to a real `ReolinkCamera` child. The\n * child inherits credentials + transport from the Hub and stores\n * its `channel` so the lib's per-channel routing (RTSP URL,\n * settings calls, snapshot fetch) works without reaching back into\n * the Hub for every read.\n */\n private async adoptDiscoveredChild(childNativeId: string, nameOverride?: string): Promise<{ deviceId: number; stableId: string }> {\n const slice = this.runtimeState.getCapState<DeviceDiscoveryStatus>('device-discovery')\n const entry = slice?.discovered.find((d) => d.childNativeId === childNativeId)\n if (!entry) {\n throw new Error(`adoptDevice: ${childNativeId} not in discovered list — refresh first`)\n }\n if (entry.alreadyAdopted && entry.adoptedDeviceId !== null) {\n // Idempotent — return the existing child id rather than erroring.\n const all = await this.ctx.devices.getAll()\n const existing = all.find((d: IDevice) => d.id === entry.adoptedDeviceId)\n if (existing) return { deviceId: existing.id, stableId: existing.stableId }\n }\n const channel = entry.metadata.rtspChannel\n if (typeof channel !== 'number') {\n throw new Error(`adoptDevice: ${childNativeId} has no rtspChannel — corrupt discovery payload`)\n }\n\n const hubCfg = this.config.values\n const childConfig = {\n // Children inherit Hub credentials (reuse parent socket).\n host: hubCfg.host,\n port: hubCfg.port,\n username: hubCfg.username,\n password: hubCfg.password,\n transport: hubCfg.transport,\n ...(entry.metadata.uid ? { uid: entry.metadata.uid } : {}),\n // Channel is the discriminator that turns a \"regular camera\"\n // config into \"this slot under this hub\". `ReolinkCamera`\n // detects parentDeviceId !== null and uses parent.api instead\n // of dialling its own login.\n channel,\n deviceCache: {\n deviceType: 'camera' as const,\n channelCount: 1,\n ...(entry.metadata.model ? { model: entry.metadata.model } : {}),\n ...(entry.metadata.isBattery !== undefined ? { hasBattery: entry.metadata.isBattery } : {}),\n ...(entry.metadata.isDoorbell !== undefined ? { hasDoorbell: entry.metadata.isDoorbell } : {}),\n // NOTE: do NOT set `probedAt` here. The metadata above\n // is the discovery-CGI snapshot — it doesn't carry\n // hasPtz/hasIntercom/hasFloodlight/hasSiren/hasAutotrack.\n // `ensureFeaturesProbed()` (called below, line ~528)\n // gates on `probedAt` to decide whether to run the live\n // capabilities probe; setting it here would early-return\n // and the freshly-adopted child would be stuck with the\n // discovery-only flag set forever (until manual refresh).\n },\n }\n\n const stableId = `${this.stableId}-${childNativeId}`\n this.ctx.logger.info('Reolink Hub: adopting child', {\n meta: {\n childNativeId,\n channel,\n name: nameOverride ?? entry.name,\n model: entry.metadata.model ?? null,\n isBattery: entry.metadata.isBattery === true,\n isDoorbell: entry.metadata.isDoorbell === true,\n stableId,\n },\n })\n const created = await this.ctx.devices.create(\n stableId,\n ReolinkCamera,\n childConfig,\n this.id,\n { type: DeviceType.Camera, name: nameOverride ?? entry.name },\n )\n // The kernel's `register()` already runs the lifecycle phases\n // (onProbe → reconcile accessories → onActivate) inline before\n // `kernel.devices.create()` returns. By the time we're here, the\n // freshly-adopted child has its `features` array populated AND its\n // accessory children spawned. No additional probe call needed.\n this.ctx.logger.info('Reolink Hub: child adopted', {\n tags: { deviceId: created.id },\n meta: { childNativeId, channel, stableId, features: [...created.features] },\n })\n\n // Mirror the new mapping immediately so simpleEvents that arrive\n // before the next discovery refresh hit the right child.\n this.channelToDeviceId.set(channel, created.id)\n // AWAIT the refresh before returning so the runtime-state slice\n // is up-to-date by the time the UI's `onSuccess` invalidator\n // fires `listDiscovered.invalidate({deviceId})`. Fire-and-forget\n // here used to leave the panel staring at stale data until the\n // next 10s poll tick — operator had to refresh the page.\n await this.refreshDiscoveryFromCamera().catch(() => { /* best-effort */ })\n\n return { deviceId: created.id, stableId }\n }\n\n /**\n * Inverse of `adoptDevice`: removes the child from the kernel\n * registry. The discovered entry remains in the discovery list so\n * the operator can re-adopt without a fresh refresh.\n */\n private async releaseDiscoveredChild(childDeviceId: number): Promise<void> {\n const all = await this.ctx.devices.getAll()\n const child = all.find((d: IDevice) => d.id === childDeviceId)\n if (!child || child.parentDeviceId !== this.id) {\n throw new Error(`releaseDevice: ${childDeviceId} is not a child of hub ${this.id}`)\n }\n this.ctx.logger.info('Reolink Hub: releasing child', {\n tags: { deviceId: childDeviceId },\n meta: { stableId: child.stableId, name: child.name },\n })\n await this.ctx.devices.remove(childDeviceId)\n\n // Drop the channel mapping eagerly so post-release events don't\n // route into a freed device. Refresh syncs the panel state.\n for (const [ch, did] of this.channelToDeviceId.entries()) {\n if (did === childDeviceId) this.channelToDeviceId.delete(ch)\n }\n this.ctx.logger.info('Reolink Hub: child released', {\n tags: { deviceId: childDeviceId },\n })\n // Same await pattern as adoptDiscoveredChild — make the slice fresh\n // before the mutation returns so the panel's invalidate sees the\n // post-release state on first re-fetch.\n await this.refreshDiscoveryFromCamera().catch(() => { /* best-effort */ })\n }\n\n // ── Event routing ────────────────────────────────────────────────────\n\n /**\n * Dispatches a Hub-received simpleEvent to the matching child's\n * `handleSimpleEvent`. Mirrors Scrypted's\n * `ReolinkNativeNvrDevice.onSimpleEvent` (`nvr.ts:288-319`).\n */\n private routeSimpleEvent(ev: ReolinkSimpleEvent): void {\n const channel = ev?.channel\n if (typeof channel !== 'number') return\n const deviceId = this.channelToDeviceId.get(channel)\n if (deviceId === undefined) return\n void this.ctx.devices.getAll()\n .then((all) => all.find((d) => d.id === deviceId))\n .then((child) => {\n if (!(child instanceof ReolinkCamera)) return\n child.handleSimpleEvent(ev)\n })\n .catch((err) => {\n this.ctx.logger.debug('Hub simpleEvent dispatch failed', {\n meta: {\n channel,\n deviceId,\n error: err instanceof Error ? err.message : String(err),\n },\n })\n })\n }\n\n // ── Connect / reconnect ──────────────────────────────────────────────\n\n private async ensureApi(): Promise<ReolinkBaichuanApi> {\n if (this.api && this.api.client.isSocketConnected() && this.api.client.loggedIn) {\n return this.api\n }\n if (this.connectInFlight) return this.connectInFlight\n this.connectInFlight = this.connect().finally(() => {\n this.connectInFlight = null\n })\n return this.connectInFlight\n }\n\n private async connect(): Promise<ReolinkBaichuanApi> {\n if (this.api) {\n try { await this.api.close({ reason: 'hub-reconnect' }) } catch { /* best-effort */ }\n this.api = null\n }\n const cfg = this.config.values\n if (!cfg.host) throw new Error('Reolink Hub: host not configured')\n if (!cfg.password) throw new Error('Reolink Hub: password not configured')\n\n const api = new ReolinkBaichuanApi({\n host: cfg.host,\n port: cfg.port,\n username: cfg.username,\n password: cfg.password,\n transport: cfg.transport,\n ...(cfg.uid ? { uid: cfg.uid } : {}),\n })\n this.ctx.logger.info('Reolink Hub connecting', {\n meta: { host: cfg.host, port: cfg.port, transport: cfg.transport },\n })\n await api.login()\n\n // Subscribe simpleEvent BEFORE the first refresh so events that\n // fire during enumeration (online/sleeping bursts on Hub login)\n // hit the routing path even if no child is adopted yet.\n try {\n await api.offSimpleEvent(this.handleSimpleEventBound)\n } catch { /* first subscribe — handler not registered */ }\n await api.onSimpleEvent(this.handleSimpleEventBound)\n\n api.client.on('close', () => this.scheduleReconnect('socket-close'))\n api.client.on('error', (err) => {\n this.ctx.logger.warn('Reolink Hub socket error — scheduling reconnect', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n this.scheduleReconnect('socket-error')\n })\n\n this.api = api\n this.reconnectAttempts = 0\n this.markOnline(true)\n\n // Initial discovery — non-blocking; the panel hydrates from the\n // slice and shows \"loading\" until the first refresh lands.\n void this.refreshDiscoveryFromCamera().catch(() => { /* best-effort */ })\n\n // Hardware metadata blob — populates the device-meta `metadata`\n // field consumed by the device-detail Hardware sub-tab. Mirrors\n // `ReolinkCamera.populateMetadataFromFirmware`. Best-effort;\n // failures leave the existing blob untouched.\n void this.populateMetadataFromFirmware(api).catch(() => { /* best-effort */ })\n\n return api\n }\n\n /** Pull `getInfo` + `getNetworkInfo` and patch the device-meta\n * metadata blob + the deviceCache durable-identifier fields.\n * Shared implementation with `ReolinkCamera` — see\n * `metadata-populator.ts`. */\n private async populateMetadataFromFirmware(api: ReolinkBaichuanApi): Promise<void> {\n const cfgUid = this.config.values['uid']\n // Hub IS the host — pass `undefined` channel so the populator\n // hits cmd 80 (host getInfo) + cmd 76 host (getNetworkInfo).\n // Passing `0` would route to cmd 318 ch=0 / cmd 76 ch=0 — which\n // returns one of the attached cameras' data, polluting the hub's\n // metadata blob with a sibling's identity.\n await populateReolinkMetadata(api, undefined, {\n id: this.id,\n ctx: this.ctx,\n uid: typeof cfgUid === 'string' && cfgUid.length > 0 ? cfgUid : undefined,\n setDeviceCachePatch: async (cachePatch) => {\n const previous = this.config.get('deviceCache') ?? {}\n await this.config.setAll({ deviceCache: { ...previous, ...cachePatch } })\n },\n })\n }\n\n private scheduleReconnect(reason: string): void {\n if (this.shutdownRequested) return\n this.api = null\n this.markOnline(false)\n if (this.reconnectTimer) return\n const backoff = Math.min(\n HUB_RECONNECT_BASE_MS * 2 ** this.reconnectAttempts,\n HUB_RECONNECT_MAX_MS,\n )\n this.reconnectAttempts += 1\n this.ctx.logger.info('Reolink Hub reconnect scheduled', {\n meta: { reason, attempt: this.reconnectAttempts, backoffMs: backoff },\n })\n this.reconnectTimer = setTimeout(() => {\n this.reconnectTimer = null\n void this.ensureApi().catch(() => { /* logged in connect() */ })\n }, backoff)\n }\n\n /**\n * Adopt a pre-built api (used by `onCreateDevice` to reuse the\n * autodetect socket — Slice 15 socket reuse). Mirrors\n * `ReolinkCamera.adoptApi`.\n */\n adoptApi(api: ReolinkBaichuanApi): void {\n this.api = api\n this.reconnectAttempts = 0\n this.markOnline(true)\n api.client.on('close', () => this.scheduleReconnect('socket-close'))\n api.client.on('error', (err) => {\n this.ctx.logger.warn('Reolink Hub adopted socket error', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n this.scheduleReconnect('socket-error')\n })\n void api.offSimpleEvent(this.handleSimpleEventBound)\n .catch(() => { /* first subscribe */ })\n .then(() => api.onSimpleEvent(this.handleSimpleEventBound))\n .then(() => this.refreshDiscoveryFromCamera())\n .catch((err) => {\n this.ctx.logger.warn('Reolink Hub post-adopt discovery failed', {\n meta: { error: err instanceof Error ? err.message : String(err) },\n })\n })\n }\n}\n\n// ── helpers ────────────────────────────────────────────────────────────\n\n/**\n * Build a stable per-child identifier. UID is preferred when available\n * (uniquely identifies a paired camera across re-pairings); otherwise\n * we fall back to `channel-N` which is stable for a fixed channel\n * assignment.\n */\nfunction computeChildNativeId(parentStableId: string, channel: number, uid?: string): string {\n void parentStableId\n if (uid && uid.length > 0) return `cam-${uid}`\n return `channel-${channel}`\n}\n\n/**\n * Map the lib's per-channel summary booleans to our `DiscoveredChildStatus`.\n * Order of precedence mirrors the Scrypted handling:\n * 1. `online === false` → offline\n * 2. `sleeping === true` → sleeping\n * 3. anything else with non-empty state → online\n * 4. unknown otherwise (cmd_id 145 push hasn't landed yet)\n */\nfunction discoveredStatusFor(d: { online?: boolean; sleeping?: boolean; state?: string }): DiscoveredChildStatus {\n if (d.online === false) return 'offline'\n if (d.sleeping === true) return 'sleeping'\n if (d.online === true) return 'online'\n if (typeof d.state === 'string' && d.state.length > 0) return 'online'\n return 'unknown'\n}\n\n","import type { ConfigUISchema } from '@camstack/types'\n\n/**\n * Creation form for Reolink devices.\n *\n * Designed to be **type-agnostic** at input time — the operator never\n * picks NVR / battery / regular themselves. Instead they provide\n * connection parameters (transport, host, port, credentials, optional\n * UID) and click `Test`. The provider runs `autoDetectDeviceType` from\n * the nodelink library, returns the detected device class as labels,\n * and on save the `onCreateDevice` handler routes to the right device\n * factory (single TCP camera, UDP/battery camera with UID, NVR with N\n * channels, …).\n *\n * No `channel` field is exposed — channel selection is internal:\n * - regular cameras use channel 0\n * - NVR devices expose every channel via `ch{N}-main` / `ch{N}-sub`\n * stream IDs (Slice 8); no operator choice is required\n */\nexport function buildCreationFormSchema(): ConfigUISchema {\n return {\n sections: [\n {\n id: 'identity',\n title: 'Camera',\n columns: 1,\n fields: [\n {\n type: 'text',\n key: 'name',\n label: 'Name',\n required: true,\n placeholder: 'Front Door',\n },\n ],\n },\n {\n id: 'credentials',\n title: 'Credentials',\n description: 'Required for the autodetect probe — entered before the host so Test can run immediately.',\n columns: 2,\n fields: [\n {\n type: 'text',\n key: 'username',\n label: 'Username',\n default: 'admin',\n required: true,\n },\n {\n type: 'password',\n key: 'password',\n label: 'Password',\n required: true,\n showToggle: true,\n },\n ],\n },\n {\n id: 'connection',\n title: 'Connection',\n description:\n 'Click Test to auto-detect the device type (regular camera / NVR / battery / UDP-only). The detected type is then used at save time.',\n columns: 2,\n fields: [\n {\n type: 'select',\n key: 'transport',\n label: 'Connection mode',\n default: 'auto',\n description:\n 'Auto: try TCP first, fall back to UDP — best for unknown devices. UDP is required for battery cameras behind NAT.',\n options: [\n { value: 'auto', label: 'Auto (recommended)' },\n { value: 'tcp', label: 'TCP only' },\n { value: 'udp', label: 'UDP only (battery / cloud)' },\n ],\n },\n {\n type: 'probe',\n key: 'host',\n label: 'Host (IP / hostname / UID)',\n required: true,\n placeholder: '192.168.1.100',\n description:\n 'For UDP-only mode you can leave the host blank and supply the camera UID below — the library will discover it on the network.',\n inputType: 'text',\n },\n {\n type: 'number',\n key: 'port',\n label: 'Port',\n default: 9000,\n min: 1,\n max: 65535,\n step: 1,\n description: 'Baichuan port. Default 9000 — applies to TCP. Ignored for UDP transport.',\n showWhen: { field: 'transport', notEquals: 'udp' },\n },\n {\n type: 'text',\n key: 'uid',\n label: 'Device UID',\n placeholder: '9527000XXXXXXXX',\n description:\n 'Required for battery / UDP cameras when not on the same LAN. Optional for local-direct UDP discovery and ignored for pure TCP.',\n showWhen: { field: 'transport', notEquals: 'tcp' },\n },\n ],\n },\n ],\n }\n}\n","import { createHash } from 'node:crypto'\nimport type { AutoDetectResult } from '@apocaliss92/nodelink-js'\n\n/**\n * Short-lived cache that lets `testCreationField` and `onCreateDevice`\n * share a single `autoDetectDeviceType()` call (and the live API\n * instance it returns) — so the operator's `Test` click and the\n * subsequent `Save` don't each pay a full Baichuan login.\n *\n * Key derivation hashes the password (sha256, hex) so the cache key\n * never carries cleartext credentials. UID is part of the key when\n * present; transport mode is too, because changing the mode invalidates\n * the cached api (a TCP-only api can't service a UDP-flagged save).\n *\n * Entries expire after `DEFAULT_TTL_MS` (60s) — long enough for a human\n * to click Test → Save, short enough that stale device-type information\n * never leaks across firmware upgrades or network changes.\n */\n\nexport interface AutodetectCacheEntry {\n readonly result: AutoDetectResult\n readonly expiresAt: number\n}\n\ninterface AutodetectCacheOptions {\n /** Time-to-live in ms. Default 60_000. */\n readonly ttlMs?: number\n /** Override the wall clock (testing). */\n readonly now?: () => number\n}\n\nconst DEFAULT_TTL_MS = 60_000\n\nexport class AutodetectCache {\n private readonly ttlMs: number\n private readonly now: () => number\n private readonly entries: Map<string, AutodetectCacheEntry> = new Map()\n\n constructor(options: AutodetectCacheOptions = {}) {\n this.ttlMs = options.ttlMs ?? DEFAULT_TTL_MS\n this.now = options.now ?? Date.now\n }\n\n /**\n * Build a cache key from the connection parameters. The password is\n * sha256-hashed to keep the key index-safe and to ensure the\n * cleartext value never lives in the Map. UID and transport are\n * included so two creation attempts with different modes don't share\n * the same api instance.\n */\n static keyFor(input: {\n readonly host: string\n readonly username: string\n readonly password: string\n readonly uid?: string\n readonly transport: 'auto' | 'tcp' | 'udp'\n }): string {\n const passHash = createHash('sha256').update(input.password).digest('hex').slice(0, 16)\n return [\n input.host.trim().toLowerCase(),\n input.username,\n passHash,\n input.uid?.trim() ?? '',\n input.transport,\n ].join(':')\n }\n\n /**\n * Return the cached entry if it has not expired yet. Expired entries\n * are evicted on lookup so a stale `api` reference doesn't survive\n * past the TTL.\n */\n get(key: string): AutodetectCacheEntry | null {\n const entry = this.entries.get(key)\n if (!entry) return null\n if (entry.expiresAt <= this.now()) {\n this.entries.delete(key)\n void this.disposeApi(entry.result)\n return null\n }\n return entry\n }\n\n /**\n * Store a new entry. Replaces (and disposes) any prior entry for the\n * same key so we never leak Baichuan sockets when the user clicks\n * Test twice in a row with slightly different inputs.\n */\n set(key: string, result: AutoDetectResult): void {\n const prior = this.entries.get(key)\n if (prior) {\n void this.disposeApi(prior.result)\n }\n this.entries.set(key, {\n result,\n expiresAt: this.now() + this.ttlMs,\n })\n }\n\n /**\n * Take the cached entry — removes it from the cache without\n * disposing. Used by `onCreateDevice` when it wants to keep the api\n * alive for the new device.\n */\n take(key: string): AutoDetectResult | null {\n const entry = this.entries.get(key)\n if (!entry) return null\n this.entries.delete(key)\n if (entry.expiresAt <= this.now()) {\n void this.disposeApi(entry.result)\n return null\n }\n return entry.result\n }\n\n /** Drop everything and dispose every cached api. */\n async dispose(): Promise<void> {\n const all = [...this.entries.values()]\n this.entries.clear()\n await Promise.all(all.map((e) => this.disposeApi(e.result)))\n }\n\n /** Number of live entries (testing). */\n get size(): number {\n return this.entries.size\n }\n\n private async disposeApi(result: AutoDetectResult): Promise<void> {\n try {\n await result.api.close({ reason: 'autodetect-cache-evict' })\n } catch {\n /* ignore — disposal is best-effort */\n }\n }\n}\n"],"mappings":";AAUA,SAAS,oBAAoB,cAAAA,aAAY,iBAAAC,sBAAqB;AAC9D,SAAS,4BAAmD;;;ACX5D;AAAA,EACE,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA,iBAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EAIA,4BAAAC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAGK;;;ACXP,SAAS,iBAAAC,sBAAqB;;;ACK9B,SAAS,KAAAC,UAAS;AAClB;AAAA,EACE;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,OAIK;;;ACNP,SAAS,SAAS;AAClB;AAAA,EACE;AAAA,OAEK;AAWA,IAAM,6BAA6B,EAAE,OAAO;AAAA,EACjD,SAAS,EAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,QAAQ,CAAC;AAAA,EACjD,MAAM,EAAE,OAAO;AACjB,CAAC;;;ADCM,IAAM,4BAA4B;AAAA,EACvC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,uBAAuB,2BAA2B,OAAO;AAAA,EACpE,MAAMC,GAAE,QAAQ,cAAc,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOnC,gBAAgBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS1D,uBAAuBA,GAAE,KAAK,yBAAyB,EAAE,QAAQ,2BAA2B;AAC9F,CAAC;AAID,IAAM,WAAW;AAIjB,IAAM,cAAc;AAEb,IAAM,iBAAN,cAA6B,WAAwC;AAAA,EAW1E,YAAY,KAAqC,QAA8B;AAC7E,UAAM,KAAK,sBAAsB;AAAA,MAC/B,MAAM,WAAW;AAAA,MACjB,MAAM,cAAc;AAAA,IACtB,CAAC;AAJ8C;AAK/C,SAAK,kBAAkB;AACvB,SAAK,yBAAyB;AAO9B,SAAK,OAAO,yBAAyB;AAAA,MACnC,MAAM,cAAc;AAAA,MACpB,IAAI,KAAK;AAAA,MACT,kBAAkB,MAAM,KAAK,iCAAiC;AAAA,MAC9D,cAAc,MAAM,KAAK,cAAc,KAAK,KAAK,IAAI,IAAI,KAAK,cAAc;AAAA,IAC9E,CAAC;AAAA,EACH;AAAA,EAnBiD;AAAA,EAVxC,WAAW,CAAC,cAAc,aAAa;AAAA,EAExC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,yBAAyB;AAAA,EAuBjC,IAAY,UAAkB;AAAE,WAAO,KAAK,OAAO,IAAI,SAAS,KAAK;AAAA,EAAE;AAAA,EACvE,IAAY,MAAmC;AAAE,WAAO,KAAK,OAAO,UAAU;AAAA,EAAiC;AAAA,EAEvG,aAA2B;AACjC,WAAO,KAAK,YAAY,gBAAgB,KAAK,EAAE,IAAI,OAAO,eAAe,EAAE;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA2BA,MAAe,UAAyB;AACtC,UAAM,oBAAoB,KAAK,OAAO,eAAe,MAAM,QAAQ,KAAK,OAAO,aAAa,MAAM;AAClG,QAAI,mBAAmB;AACrB,UAAI,KAAK,wBAAwB;AAC/B,aAAK,IAAI,OAAO,MAAM,mFAA8E;AAAA,UAClG,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC5B,CAAC;AACD;AAAA,MACF;AAKA,WAAK,IAAI,OAAO,KAAK,wEAAmE;AAAA,QACtF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD,UAAI;AACF,cAAM,MAAM,MAAM,KAAK;AACvB,cAAM,IAAI,OAAO,KAAK,SAAS,EAAE,iBAAiB,IAAK,CAAC;AACxD,cAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAAA,MAChE,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,mEAA8D;AAAA,UACjF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK,iCAAiC;AAAA,EAC9C;AAAA,EAEA,MAAc,mCAAkD;AAC9D,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,SAAS,MAAM,IAAI,iBAAiB,KAAK,OAAO;AAMtD,YAAM,YAAa,QAAoJ,MAAM;AAC7K,YAAM,SAAS,WAAW,UACpB,QAA6E,UAC7E,QAA4D,WAAW;AAC7E,YAAM,UAAU,WAAW;AAC3B,YAAM,WAAW,KAAK,YAAY,uBAAuB;AACzD,YAAM,gBAAgB,YAAY,SAAS,YAAY,UACnD,SAAS,gBACT,KAAK,IAAI;AACb,WAAK,YAAY,yBAAyB;AAAA,QACxC;AAAA,QACA;AAAA,QACA,eAAe,KAAK,IAAI;AAAA,MAC1B,CAAC;AACD,WAAK,yBAAyB;AAS9B,YAAM,QAAQ,WAAW,kBAAkB,QAAQ,CAAC;AACpD,UAAI,MAAM,SAAS,GAAG;AACpB,cAAM,YAAY,gCAAgC,KAAK;AACvD,YAAI,cAAc,UAAc,0BAAgD,SAAS,SAAS,GAAG;AACnG,gBAAM,KAAK,OAAO,OAAO,EAAE,uBAAuB,UAAU,CAAC,EAAE,MAAM,CAAC,QAAiB;AACrF,iBAAK,IAAI,OAAO,MAAM,kCAAkC;AAAA,cACtD,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,cACjD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,YAClE,CAAC;AAAA,UACH,CAAC;AAAA,QACH,WAAW,cAAc,QAAW;AAClC,eAAK,IAAI,OAAO,MAAM,+DAA0D;AAAA,YAC9E,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,UAAU;AAAA,UACpB,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,qCAAqC;AAAA,QACxD,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,QACjD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAGQ,oBAA0B;AAChC,UAAM,WAAyD;AAAA;AAAA;AAAA,MAG7D,WAAW,YAAmC,KAAK,WAAW;AAAA,MAC9D,UAAU,OAAO,EAAE,UAAU,GAAG,MAAM;AACpC,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,MAAM,MAAM,KAAK;AAGvB,cAAM,WAAW,KAAM,KAAK,OAAO,IAAI,gBAAgB,KAAK,IAAK;AACjE,cAAM,IAAI,SAAS,IAAI,UAAU,KAAK,OAAO;AAC7C,aAAK,YAAY,kBAAkB,EAAE,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;AAAA,MAItE;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,kBAAkB,QAAQ;AACrD,SAAK,YAAY,kBAAkB,EAAE,IAAI,OAAO,eAAe,EAAE,CAAC;AAAA,EACpE;AAAA,EAEQ,2BAAiC;AACvC,UAAM,SAAS,yBAAyB;AAAA,MACtC,cAAc,KAAK;AAAA,MACnB,KAAK;AAAA,MACL,aAAa,KAAK;AAAA,MAClB,SAAS,MAAM,KAAK,iCAAiC;AAAA,MACrD,SAAS;AAAA,MACT,OAAO,OAA4B,EAAE,SAAS,OAAO,eAAe,EAAE;AAAA,IACxE,CAAC;AAED,UAAM,WAAgE;AAAA,MACpE,WAAW,OAAO;AAAA,MAClB,kBAAkB,OAAO,EAAE,UAAU,QAAQ,MAAM;AACjD,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,aAAa,KAAK,OAAO,IAAI,uBAAuB,KAAK;AAC/D,aAAK,IAAI,OAAO,KAAK,yCAAyC;AAAA,UAC5D,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,UACjD,MAAM,EAAE,SAAS,WAAW;AAAA,QAC9B,CAAC;AACD,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK;AACvB,cAAI,SAAS;AACX,kBAAM,mBAAmB,2BAA2B,UAAU;AAC9D,kBAAM,IAAI,iBAAiB,EAAE,QAAQ,GAAG,iBAAiB,GAAG,KAAK,OAAO;AAAA,UAC1E,OAAO;AACL,kBAAM,IAAI,iBAAiB,EAAE,QAAQ,EAAE,GAAG,KAAK,OAAO;AAAA,UACxD;AACA,eAAK,YAAY,yBAAyB;AAAA,YACxC;AAAA,YACA,eAAe,KAAK,IAAI;AAAA,YACxB,eAAe,KAAK,IAAI;AAAA,UAC1B,CAAC;AACD,eAAK,cAAc,KAAK,IAAI;AAAA,QAC9B,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,KAAK,mCAAmC;AAAA,YACtD,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,SAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAC3E,CAAC;AACD,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,yBAAyB,QAAQ;AAC5D,UAAM,OAAkC,EAAE,SAAS,OAAO,eAAe,GAAG,eAAe,EAAE;AAC7F,SAAK,YAAY,yBAAyB,IAAI;AAAA,EAChD;AAAA,EAES,sBAAgD;AACvD,UAAM,SAAyB;AAAA,MAC7B,UAAU;AAAA,QACR;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ,CAAC;AAAA,YACP,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,SAAS;AAAA,YACT,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,UACR,CAAC;AAAA,QACH;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,OAAO;AAAA,UACP,aACE;AAAA,UAGF,SAAS;AAAA,UACT,QAAQ,CAAC;AAAA,YACP,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,SAAS,0BAA0B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,EAAE;AAAA,YAC9G,SAAS;AAAA,UACX,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AACA,WAAO,cAAc,QAAQ;AAAA,MAC3B,gBAAgB,KAAK,OAAO,IAAI,gBAAgB,KAAK;AAAA,MACrD,uBAAuB,KAAK,OAAO,IAAI,uBAAuB,KAAK;AAAA,IACrE,CAAC;AAAA,EACH;AAAA,EAEA,MAAe,mBAAmB,OAA+C;AAC/E,UAAM,KAAK,OAAO,OAAO,KAAK;AAG9B,QAAI,2BAA2B,OAAO;AACpC,YAAM,QAAQ,KAAK,aAAa,YAAuC,gBAAgB;AACvF,UAAI,OAAO,YAAY,MAAM;AAC3B,cAAM,aAAc,MAAM,yBAAgD,KAAK,OAAO,IAAI,uBAAuB,KAAK;AACtH,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK;AACvB,gBAAM,mBAAmB,2BAA2B,UAAU;AAC9D,gBAAM,IAAI,iBAAiB,EAAE,QAAQ,GAAG,iBAAiB,GAAG,KAAK,OAAO;AAAA,QAC1E,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,KAAK,6DAA6D;AAAA,YAChF,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,YAAY,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAC9E,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;AAKA,SAAS,2BAA2B,YAAiE;AACnG,QAAM,QAAQ,WAAW,MAAM,GAAG,EAAE,IAAI,CAAC,MAAM,EAAE,KAAK,CAAC;AACvD,QAAM,QAAQ,IAAI,OAAO,GAAG;AAC5B,QAAM,SAAS,IAAI,OAAO,GAAG;AAC7B,SAAO;AAAA,IACL,EAAE,MAAM,MAAW,YAAY,MAAM,SAAS,IAAI,IAAS,QAAQ,OAAO;AAAA,IAC1E,EAAE,MAAM,UAAW,YAAY,MAAM,SAAS,QAAQ,IAAK,QAAQ,OAAO;AAAA,IAC1E,EAAE,MAAM,WAAW,YAAY,MAAM,SAAS,SAAS,IAAI,QAAQ,OAAO;AAAA,IAC1E,EAAE,MAAM,WAAW,YAAY,MAAM,SAAS,SAAS,IAAI,QAAQ,OAAO;AAAA,EAC5E;AACF;AAUA,SAAS,gCACP,OACoB;AACpB,QAAM,SAAS,oBAAI,IAAY;AAC/B,aAAW,QAAQ,OAAO;AACxB,QAAI,CAAC,KAAK,QAAQ,OAAO,KAAK,eAAe,SAAU;AACvD,QAAI,KAAK,WAAW,SAAS,GAAG,EAAG,QAAO,IAAI,KAAK,IAAI;AAAA,EACzD;AACA,QAAM,iBAAiB,CAAC,MAAM,UAAU,WAAW,SAAS;AAC5D,QAAM,UAAU,eAAe,OAAO,CAAC,MAAM,OAAO,IAAI,CAAC,CAAC;AAC1D,SAAO,QAAQ,SAAS,IAAI,QAAQ,KAAK,GAAG,IAAI;AAClD;;;AEvYA,SAAS,KAAAC,UAAS;AAClB;AAAA,EACE,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,oBAAAC;AAAA,EACA;AAAA,EACA,2BAAAC;AAAA,EACA,4BAAAC;AAAA,OAKK;AAkBP,IAAM,iCAAiC;AAAA,EACrC;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,IAAM,4BAA4B,2BAA2B,OAAO;AAAA,EACzE,MAAMC,GAAE,QAAQ,cAAc,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOxC,sBAAsBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,QAAQ,GAAG,EAC/D,SAAS,6DAA6D;AAAA;AAAA;AAAA;AAAA,EAIzE,uBAAuBA,GAAE,KAAK,8BAA8B,EAAE,QAAQ,wBAAwB,EAC3F,SAAS,+CAA+C;AAC7D,CAAC;AAQD,IAAMC,YAAW;AAMjB,IAAMC,eAAc;AAEb,IAAM,sBAAN,cAAkCC,YAA6C;AAAA,EAepF,YAAY,KAAqC,QAA8B;AAC7E,UAAM,KAAK,2BAA2B;AAAA,MACpC,MAAMC,YAAW;AAAA,MACjB,MAAM,cAAc;AAAA,IACtB,CAAC;AAJ8C;AAK/C,SAAK,kBAAkB;AACvB,SAAK,sBAAsB;AAC3B,SAAK,yBAAyB;AAI9B,SAAK,OAAO,yBAAyB;AAAA,MACnC,MAAM,cAAc;AAAA,MACpB,IAAI,KAAK;AAAA,MACT,kBAAkB,YAAY;AAC5B,cAAM,QAAQ,WAAW;AAAA,UACvB,KAAK,yBAAyB;AAAA,UAC9B,KAAK,iCAAiC;AAAA,QACxC,CAAC;AAAA,MACH;AAAA,MACA,cAAc,MAAM,KAAK,cAAc,KAAK,KAAK,IAAI,IAAI,KAAK,cAAcF;AAAA,IAC9E,CAAC;AAAA,EACH;AAAA,EAtBiD;AAAA,EAdxC,WAAW,CAACG,eAAc,aAAa;AAAA;AAAA;AAAA;AAAA,EAKxC,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOd,yBAAyB;AAAA,EA0BjC,IAAY,UAAkB;AAAE,WAAO,KAAK,OAAO,IAAI,SAAS,KAAK;AAAA,EAAE;AAAA,EACvE,IAAY,MAAmC;AAAE,WAAO,KAAK,OAAO,UAAU;AAAA,EAAiC;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/G,MAAc,2BAA0C;AACtD,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,QAAQ,MAAM,IAAI,iBAAiB,KAAK,OAAO;AACrD,YAAM,KAAK,QAAQ,OAAO,OAAO;AACjC,YAAM,aAAa,OAAO,OAAO,eAAe,WAAW,MAAM,aAAa;AAC9E,YAAM,KAAK,KAAK,IAAI;AACpB,WAAK,YAAYC,mBAAkB,EAAE,IAAI,eAAe,GAAG,CAAC;AAC5D,UAAI,eAAe,QAAW;AAC5B,aAAK,YAAY,sBAAsB,EAAE,YAAY,YAAY,eAAe,GAAG,CAAC;AAAA,MACtF;AACA,WAAK,yBAAyB;AAAA,IAChC,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,mCAAmC;AAAA,QACvD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAe,UAAyB;AACtC,UAAM,oBAAoB,KAAK,OAAO,eAAe,MAAM,QAAQ,KAAK,OAAO,aAAa,MAAM;AAClG,QAAI,mBAAmB;AACrB,UAAI,KAAK,wBAAwB;AAC/B,aAAK,IAAI,OAAO,MAAM,wFAAmF;AAAA,UACvG,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC5B,CAAC;AACD;AAAA,MACF;AAKA,WAAK,IAAI,OAAO,KAAK,6EAAwE;AAAA,QAC3F,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD,UAAI;AACF,cAAM,MAAM,MAAM,KAAK;AACvB,cAAM,IAAI,OAAO,KAAK,SAAS,EAAE,iBAAiB,IAAK,CAAC;AACxD,cAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAAA,MAChE,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,wEAAmE;AAAA,UACtF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,QAAQ,IAAI;AAAA,MAChB,KAAK,yBAAyB;AAAA,MAC9B,KAAK,iCAAiC;AAAA,IACxC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,mCAAkD;AAC9D,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,SAAS,MAAM,IAAI,sBAAsB,KAAK,OAAO;AAC3D,YAAM,UAAU,QAAQ,QAAQ,kBAAkB;AAClD,YAAM,WAAW,KAAK,YAAYC,wBAAuB;AACzD,YAAM,gBAAgB,YAAY,SAAS,YAAY,UACnD,SAAS,gBACT,KAAK,IAAI;AACb,WAAK,YAAYA,0BAAyB;AAAA,QACxC;AAAA,QACA;AAAA,QACA,eAAe,KAAK,IAAI;AAAA,MAC1B,CAAC;AACD,WAAK,yBAAyB;AAO9B,YAAM,QAAiC,CAAC;AACxC,UAAI,OAAO,QAAQ,aAAa,YAAY,OAAO,WAAW,GAAG;AAC/D,cAAM,sBAAsB,IAAI,OAAO;AAAA,MACzC;AACA,UAAI,OAAO,QAAQ,eAAe,UAAU;AAC1C,cAAM,aAAc,+BAAqD,SAAS,OAAO,UAAU,IAC/F,OAAO,aACP;AACJ,YAAI,eAAe,QAAW;AAC5B,gBAAM,uBAAuB,IAAI;AAAA,QACnC,OAAO;AACL,eAAK,IAAI,OAAO,MAAM,oEAA+D;AAAA,YACnF,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,YAAY,OAAO,WAAW;AAAA,UACxC,CAAC;AAAA,QACH;AAAA,MACF;AACA,UAAI,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AACjC,cAAM,KAAK,OAAO,OAAO,KAAK,EAAE,MAAM,CAAC,QAAiB;AACtD,eAAK,IAAI,OAAO,MAAM,mCAAmC;AAAA,YACvD,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,0CAA0C;AAAA,QAC7D,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,QACjD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA,EAQQ,aAA2B;AACjC,WAAO,KAAK,YAAYD,iBAAgB,KAAK,EAAE,IAAI,OAAO,eAAe,EAAE;AAAA,EAC7E;AAAA,EACQ,iBAAmC;AACzC,WAAO,KAAK,YAAY,oBAAoB,KAAK,EAAE,YAAY,KAAK,eAAe,EAAE;AAAA,EACvF;AAAA,EAEQ,oBAA0B;AAChC,UAAM,WAAyD;AAAA;AAAA;AAAA;AAAA,MAI7D,WAAW,YAAmC,KAAK,WAAW;AAAA,MAC9D,UAAU,OAAO,EAAE,UAAU,GAAG,MAAM;AACpC,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,MAAM,MAAM,KAAK;AACvB,cAAM,aAAa,KAAK,eAAe,EAAE;AACzC,cAAM,IAAI,iBAAiB,IAAI,YAAY,KAAK,OAAO;AACvD,aAAK,YAAYA,mBAAkB,EAAE,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;AACpE,aAAK,cAAc,KAAK,IAAI;AAAA,MAC9B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkBA,mBAAkB,QAAQ;AAGrD,SAAK,YAAYA,mBAAkB,EAAE,IAAI,OAAO,eAAe,EAAE,CAAC;AAAA,EACpE;AAAA,EAEQ,wBAA8B;AACpC,UAAM,WAA6D;AAAA,MACjE,WAAW,YAAuC,KAAK,eAAe;AAAA,MACtE,eAAe,OAAO,EAAE,UAAU,WAAW,MAAM;AACjD,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,QAAQ,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,UAAU,CAAC;AAInD,YAAI,KAAK,WAAW,EAAE,IAAI;AACxB,gBAAM,MAAM,MAAM,KAAK;AACvB,gBAAM,IAAI,iBAAiB,MAAM,OAAO,KAAK,OAAO;AAAA,QACtD;AACA,aAAK,YAAY,sBAAsB,EAAE,YAAY,OAAO,eAAe,KAAK,IAAI,EAAE,CAAC;AACvF,aAAK,cAAc,KAAK,IAAI;AAAA,MAC9B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,sBAAsB,QAAQ;AACzD,SAAK,YAAY,sBAAsB,EAAE,YAAY,KAAK,eAAe,EAAE,CAAC;AAAA,EAC9E;AAAA,EAEQ,2BAAiC;AACvC,UAAM,SAASE,0BAAyB;AAAA,MACtC,cAAc,KAAK;AAAA,MACnB,KAAKD;AAAA,MACL,aAAa,KAAK;AAAA,MAClB,SAAS,MAAM,KAAK,iCAAiC;AAAA,MACrD,SAASN;AAAA,MACT,OAAO,OAA4B,EAAE,SAAS,OAAO,eAAe,EAAE;AAAA,IACxE,CAAC;AAED,UAAM,WAAgE;AAAA,MACpE,WAAW,OAAO;AAAA,MAClB,kBAAkB,OAAO,EAAE,UAAU,QAAQ,MAAM;AACjD,YAAI,aAAa,KAAK,GAAI;AAC1B,aAAK,IAAI,OAAO,KAAK,8CAA8C;AAAA,UACjE,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,UACjD,MAAM,EAAE,QAAQ;AAAA,QAClB,CAAC;AACD,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK;AACvB,gBAAM,IAAI,sBAAsB,SAAS,KAAK,OAAO;AAKrD,cAAI,QAAS,OAAM,KAAK,uBAAuB;AAG/C,eAAK,YAAYM,0BAAyB;AAAA,YACxC;AAAA,YACA,eAAe,KAAK,IAAI;AAAA,YACxB,eAAe,KAAK,IAAI;AAAA,UAC1B,CAAC;AACD,eAAK,cAAc,KAAK,IAAI;AAAA,QAC9B,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,KAAK,wCAAwC;AAAA,YAC3D,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,YACjD,MAAM,EAAE,SAAS,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAC3E,CAAC;AACD,gBAAM;AAAA,QACR;AAAA,MACF;AAAA,IACF;AACA,SAAK,IAAI,kBAAkBA,0BAAyB,QAAQ;AAG5D,UAAM,OAAkC,EAAE,SAAS,OAAO,eAAe,GAAG,eAAe,EAAE;AAC7F,SAAK,YAAYA,0BAAyB,IAAI;AAAA,EAChD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,yBAAwC;AACpD,UAAM,WAAW,KAAK,OAAO,IAAI,sBAAsB,KAAK;AAC5D,UAAM,aAAa,KAAK,OAAO,IAAI,uBAAuB,KAAK;AAC/D,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,IAAI,sBAAsB,KAAK,SAAS,EAAE,UAAU,WAAW,CAAC;AAAA,IACxE,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,0CAA0C;AAAA,QAC7D,MAAM,EAAE,UAAU,KAAK,IAAI,SAAS,KAAK,QAAQ;AAAA,QACjD,MAAM,EAAE,UAAU,YAAY,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MACxF,CAAC;AAAA,IACH;AAAA,EACF;AAAA,EAES,sBAAgD;AACvD,UAAM,SAAyB;AAAA,MAC7B,UAAU,CAAC;AAAA,QACT,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QAEF,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,YACN,SAAS;AAAA,UACX;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,SAAS,+BAA+B,IAAI,CAAC,OAAO,EAAE,OAAO,GAAG,OAAO,EAAE,QAAQ,MAAM,KAAK,EAAE,QAAQ,MAAM,GAAG,EAAE,EAAE;AAAA,YACnH,SAAS;AAAA,UACX;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAOE,eAAc,QAAQ;AAAA,MAC3B,sBAAsB,KAAK,OAAO,IAAI,sBAAsB;AAAA,MAC5D,uBAAuB,KAAK,OAAO,IAAI,uBAAuB;AAAA,IAChE,CAAC;AAAA,EACH;AAAA,EAEA,MAAe,mBAAmB,OAA+C;AAC/E,UAAM,KAAK,OAAO,OAAO,KAAK;AAC9B,QAAI,0BAA0B,SAAS,2BAA2B,OAAO;AACvE,YAAM,KAAK,uBAAuB;AAAA,IACpC;AAAA,EACF;AACF;;;AC1aA,SAAS,KAAAC,UAAS;AAClB;AAAA,EACE,cAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,oBAAAC;AAAA,OAEK;AAUA,IAAM,qBAAqB,2BAA2B,OAAO;AAAA,EAClE,MAAMC,GAAE,QAAQ,cAAc,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASvC,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACxD,gBAAgBA,GAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASrC,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMxD,gBAAgBA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AACvD,CAAC;AAMD,IAAMC,eAAc;AAEb,IAAM,eAAN,cAA2BC,YAAsC;AAAA,EAWtE,YAAY,KAAqC,QAA8B;AAC7E,UAAM,KAAK,oBAAoB;AAAA,MAC7B,MAAMC,YAAW;AAAA,MACjB,MAAM,cAAc;AAAA,IACtB,CAAC;AAJ8C;AAK/C,SAAK,kBAAkB;AAGvB,SAAK,OAAO,yBAAyB;AAAA,MACnC,MAAM,cAAc;AAAA,MACpB,IAAI,KAAK;AAAA,MACT,kBAAkB,MAAM,KAAK,oBAAoB;AAAA,MACjD,cAAc,MAAM,KAAK,cAAc,KAAK,KAAK,IAAI,IAAI,KAAK,cAAcF;AAAA,IAC9E,CAAC;AAAA,EACH;AAAA,EAdiD;AAAA,EAVxC,WAAW,CAAC;AAAA,EAEb,cAAc;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMd,yBAAyB;AAAA,EAkBjC,IAAY,UAAkB;AAAE,WAAO,KAAK,OAAO,IAAI,SAAS,KAAK;AAAA,EAAE;AAAA,EACvE,IAAY,MAAmC;AAAE,WAAO,KAAK,OAAO,UAAU;AAAA,EAAiC;AAAA,EAEvG,aAA2B;AACjC,WAAO,KAAK,YAAYG,iBAAgB,KAAK,EAAE,IAAI,OAAO,eAAe,EAAE;AAAA,EAC7E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,0BAA0B,OAAmI;AAC3J,UAAM,KAAK,QAAQ,OAAO,OAAO;AACjC,UAAM,WAAW,KAAK,WAAW;AACjC,UAAM,gBAAgB,SAAS,OAAO,KAAK,SAAS,gBAAgB,KAAK,IAAI;AAC7E,SAAK,YAAYA,mBAAkB,EAAE,IAAI,cAAc,CAAC;AAiBxD,UAAM,QAAQ,OAAO;AACrB,QAAI,OAAO;AACT,YAAM,QAAiC,CAAC;AACxC,UAAI,OAAO,MAAM,cAAc,SAAU,OAAM,cAAc,IAAI,MAAM,MAAM;AAC7E,UAAI,OAAO,MAAM,gBAAgB,SAAU,OAAM,gBAAgB,IAAI,MAAM,gBAAgB;AAC3F,UAAI,OAAO,MAAM,aAAa,SAAU,OAAM,cAAc,IAAI,MAAM;AACtE,UAAI,OAAO,MAAM,gBAAgB,SAAU,OAAM,gBAAgB,IAAI,MAAM;AAC3E,UAAI,OAAO,KAAK,KAAK,EAAE,SAAS,GAAG;AACjC,aAAK,KAAK,OAAO,OAAO,KAAK,EAAE,MAAM,CAAC,QAAiB;AACrD,eAAK,IAAI,OAAO,MAAM,4BAA4B;AAAA,YAChD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,YAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,sBAAqC;AACjD,QAAI;AACF,YAAM,MAAM,MAAM,KAAK;AACvB,YAAM,QAAQ,MAAM,IAAI,WAAW,KAAK,OAAO;AAC/C,WAAK,0BAA0B,KAAK;AACpC,WAAK,yBAAyB;AAAA,IAChC,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,sBAAsB;AAAA,QAC1C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA0BA,MAAe,UAAyB;AACtC,UAAM,oBAAoB,KAAK,OAAO,eAAe,MAAM,QAAQ,KAAK,OAAO,aAAa,MAAM;AAClG,QAAI,mBAAmB;AACrB,UAAI,KAAK,wBAAwB;AAC/B,aAAK,IAAI,OAAO,MAAM,iFAA4E;AAAA,UAChG,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC5B,CAAC;AACD;AAAA,MACF;AAMA,WAAK,IAAI,OAAO,KAAK,sEAAiE;AAAA,QACpF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD,UAAI;AACF,cAAM,MAAM,MAAM,KAAK;AACvB,cAAM,IAAI,OAAO,KAAK,SAAS,EAAE,iBAAiB,IAAK,CAAC;AACxD,cAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAAA,MAChE,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,iEAA4D;AAAA,UAC/E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AACA,UAAM,KAAK,oBAAoB;AAAA,EACjC;AAAA,EAGQ,oBAA0B;AAChC,UAAM,WAAyD;AAAA;AAAA,MAE7D,WAAW,YAAmC,KAAK,WAAW;AAAA,MAC9D,UAAU,OAAO,EAAE,UAAU,GAAG,MAAM;AACpC,YAAI,aAAa,KAAK,GAAI;AAI1B,cAAM,KAAK,qBAAqB,EAAE;AAClC,aAAK,YAAYA,mBAAkB,EAAE,IAAI,eAAe,KAAK,IAAI,EAAE,CAAC;AACpE,aAAK,cAAc,KAAK,IAAI;AAAA,MAC9B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkBA,mBAAkB,QAAQ;AACrD,SAAK,YAAYA,mBAAkB,EAAE,IAAI,OAAO,eAAe,EAAE,CAAC;AAAA,EACpE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,qBAAqB,QAAgC;AACjE,UAAM,MAAM,MAAM,KAAK;AACvB,UAAM,YAAY,KAAK,OAAO,IAAI,cAAc;AAChD,UAAM,cAAc,KAAK,OAAO,IAAI,gBAAgB;AACpD,UAAM,WAAW,KAAK,OAAO,IAAI,cAAc;AAC/C,UAAM,IAAI,WAAW;AAAA,MACnB,QAAQ,SAAS,IAAI;AAAA,MACrB,GAAI,OAAO,cAAc,WAAW,EAAE,WAAW,MAAM,UAAU,IAAI,CAAC;AAAA,MACtE,GAAI,OAAO,gBAAgB,YAAY,EAAE,aAAa,cAAc,IAAI,EAAE,IAAI,CAAC;AAAA,MAC/E,GAAI,OAAO,aAAa,WAAW,EAAE,SAAS,IAAI,CAAC;AAAA,IACrD,GAAG,KAAK,OAAO;AAAA,EACjB;AAAA,EAES,sBAAgD;AAOvD,UAAM,YAAY,KAAK,OAAO,IAAI,cAAc;AAChD,UAAM,WAAW,KAAK,OAAO,IAAI,cAAc;AAC/C,UAAM,cAAc,KAAK,OAAO,IAAI,gBAAgB;AASpD,UAAM,cAAc,KAAK,IAAI,KAAK,OAAO,IAAI,gBAAgB,KAAK,KAAK,GAAG;AAC1E,UAAM,SAAyB;AAAA,MAC7B,UAAU,CAAC;AAAA,QACT,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,YACN,GAAI,OAAO,cAAc,WAAW,EAAE,SAAS,UAAU,IAAI,CAAC;AAAA,YAC9D,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa,wEAAwE,WAAW;AAAA,YAChG,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,YACN,GAAI,OAAO,aAAa,WAAW,EAAE,SAAS,SAAS,IAAI,CAAC;AAAA,YAC5D,WAAW;AAAA,YACX,aAAa;AAAA,UACf;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,GAAI,OAAO,gBAAgB,YAAY,EAAE,SAAS,YAAY,IAAI,CAAC;AAAA,YACnE,OAAO;AAAA,YACP,MAAM;AAAA,UACR;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH;AACA,WAAOC,eAAc,QAAQ;AAAA,MAC3B,GAAI,OAAO,cAAc,WAAW,EAAE,cAAc,UAAU,IAAI,CAAC;AAAA,MACnE,GAAI,OAAO,aAAa,WAAW,EAAE,cAAc,SAAS,IAAI,CAAC;AAAA,MACjE,GAAI,OAAO,gBAAgB,YAAY,EAAE,gBAAgB,YAAY,IAAI,CAAC;AAAA,IAC5E,CAAC;AAAA,EACH;AAAA,EAEA,MAAe,mBAAmB,OAA+C;AAC/E,UAAM,KAAK,OAAO,OAAO,KAAK;AAC9B,QAAI,kBAAkB,SAAS,oBAAoB,SAAS,kBAAkB,OAAO;AACnF,UAAI;AACF,cAAM,KAAK,qBAAqB,KAAK,WAAW,EAAE,EAAE;AAAA,MACtD,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,4BAA4B;AAAA,UAC/C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AACD,cAAM;AAAA,MACR;AAAA,IACF;AAAA,EACF;AACF;;;AJjSO,SAAS,sBACd,MACA,KACA,QACS;AACT,UAAQ,MAAM;AAAA,IACZ,KAAKC,eAAc;AACjB,aAAO,IAAI,eAAe,KAAK,MAAM;AAAA,IACvC,KAAKA,eAAc;AACjB,aAAO,IAAI,oBAAoB,KAAK,MAAM;AAAA,IAC5C,KAAKA,eAAc;AACjB,aAAO,IAAI,aAAa,KAAK,MAAM;AAAA,IACrC;AACE,YAAM,IAAI,MAAM,uDAAuD,OAAO,IAAI,CAAC,GAAG;AAAA,EAC1F;AACF;;;AKIA,IAAM,qBAAwC;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEA,eAAsB,wBACpB,KACA,SACA,QACe;AACf,QAAM,OAAO;AAEb,MAAI;AACJ,MAAI;AACF,WAAO,MAAM,KAAK,QAAQ,SAAS,EAAE,WAAW,KAAM,MAAM,mBAAmB,CAAC;AAAA,EAClF,SAAS,KAAK;AACZ,WAAO,IAAI,OAAO,KAAK,oEAA+D;AAAA,MACpF,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,MAC5B,MAAM,EAAE,SAAS,WAAW,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC9F,CAAC;AACD;AAAA,EACF;AACA,MAAI,CAAC,MAAM;AACT,WAAO,IAAI,OAAO,KAAK,4EAAuE;AAAA,MAC5F,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,MAC5B,MAAM,EAAE,SAAS,WAAW,OAAO;AAAA,IACrC,CAAC;AACD;AAAA,EACF;AAEA,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,KAAK,eAAe,SAAS,EAAE,WAAW,IAAK,CAAC;AAAA,EAC9D,SAAS,KAAK;AACZ,WAAO,IAAI,OAAO,MAAM,8DAA8D;AAAA,MACpF,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,MAC5B,MAAM,EAAE,SAAS,WAAW,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAC9F,CAAC;AAAA,EACH;AAIA,MAAI;AACJ,MAAI,YAAY,UAAa,OAAO,KAAK,oBAAoB,YAAY;AACvE,QAAI;AACF,YAAM,IAAI,MAAM,KAAK,gBAAgB;AACrC,UAAI,OAAO,MAAM,YAAY,IAAI,EAAG,gBAAe;AAAA,IACrD,QAAQ;AAAA,IAER;AAAA,EACF;AAEA,QAAM,MAAM,KAAK;AACjB,QAAM,KAAK,KAAK;AAChB,QAAM,aAAa,KAAK;AAGxB,QAAM,QAAiC;AAAA,IACrC,cAAc;AAAA,IACd,GAAI,KAAK,OAAO,EAAE,OAAO,KAAK,KAAK,IAAI,CAAC;AAAA,IACxC,GAAI,KAAK,kBAAkB,EAAE,UAAU,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACjE,GAAI,KAAK,kBAAkB,EAAE,UAAU,KAAK,gBAAgB,IAAI,CAAC;AAAA,IACjE,GAAI,KAAK,eAAe,EAAE,cAAc,KAAK,aAAa,IAAI,CAAC;AAAA,IAC/D,GAAI,KAAK,SAAS,EAAE,QAAQ,KAAK,OAAO,IAAI,CAAC;AAAA,IAC7C,GAAI,KAAK,SAAS,EAAE,gBAAgB,KAAK,OAAO,IAAI,CAAC;AAAA,IACrD,GAAI,KAAK,OAAO,EAAE,cAAc,KAAK,KAAK,IAAI,CAAC;AAAA,IAC/C,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,IACrB,GAAI,KAAK,EAAE,GAAG,IAAI,CAAC;AAAA,IACnB,GAAI,aAAa,EAAE,aAAa,WAAW,IAAI,CAAC;AAAA,IAChD,GAAI,OAAO,MAAM,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,IACxC,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,EACvD;AACA,MAAI;AACF,UAAO,OAAO,IAAI,KACd,eAAe,aAAa,SAAS,EAAE,UAAU,OAAO,IAAI,MAAM,CAAC;AACvE,WAAO,IAAI,OAAO,KAAK,6CAA6C;AAAA,MAClE,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,MAC5B,MAAM;AAAA,QACJ,SAAS,WAAW;AAAA,QACpB,MAAM,OAAO,KAAK,KAAK;AAAA,MACzB;AAAA,IACF,CAAC;AAAA,EACH,SAAS,KAAK;AACZ,WAAO,IAAI,OAAO,KAAK,+CAA+C;AAAA,MACpE,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,MAC5B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAClE,CAAC;AAAA,EACH;AAGA,QAAM,aAAsC,CAAC;AAC7C,MAAI,KAAK,aAAc,YAAW,cAAc,IAAI,KAAK;AACzD,MAAI,IAAK,YAAW,KAAK,IAAI;AAC7B,MAAI,KAAK,gBAAiB,YAAW,iBAAiB,IAAI,KAAK;AAC/D,MAAI,KAAK,gBAAiB,YAAW,iBAAiB,IAAI,KAAK;AAC/D,MAAI,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACtC,QAAI;AACF,YAAM,OAAO,oBAAoB,UAAU;AAAA,IAC7C,SAAS,KAAK;AACZ,aAAO,IAAI,OAAO,MAAM,mDAAmD;AAAA,QACzE,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,QAC5B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AACF;;;ANnJA,SAAS,mBAAmB;AAC5B;AAAA,EACE;AAAA,EACA;AAAA,OAGK;;;AO1CP,SAAS,KAAAC,UAAS;AAQX,IAAM,mBAAmB;AASzB,IAAM,kBAAkB;AAOxB,IAAM,eAAiD;AAAA,EAC5D,QAAQ;AAAA,EACR,SAAS;AAAA,EACT,QAAQ;AAAA,EACR,MAAM;AAAA,EACN,SAAS;AACX;AAQO,IAAM,0BAA0BA,GAAE,KAAK;AAAA,EAC5C;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAEM,IAAM,kCAAkCA,GAAE,KAAK;AAAA,EACpD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAQM,IAAM,+BAA+BA,GAAE,KAAK;AAAA,EACjD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF,CAAC;AAIM,IAAM,sBAAsBA,GAAE,OAAO;AAAA;AAAA;AAAA;AAAA;AAAA,EAK1C,MAAMA,GAAE,OAAO,EAAE,SAAS,uBAAuB;AAAA,EACjD,MAAMA,GAAE,OAAO,EAAE,QAAQ,GAAI,EAAE,SAAS,8BAA8B;AAAA,EACtE,UAAUA,GAAE,OAAO,EAAE,QAAQ,OAAO,EAAE,SAAS,UAAU;AAAA,EACzD,UAAUA,GAAE,OAAO,EAAE,SAAS,UAAU;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaxC,WAAWA,GAAE,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAM/C,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAKzB,oBAAoB,gCAAgC,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgB7D,SAASA,GAAE,OAAO,EAAE,IAAI,EAAE,YAAY,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAajD,aAAaA,GACV,OAAO;AAAA,IACN,YAAY,wBAAwB,SAAS;AAAA,IAC7C,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,OAAOA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA,IAG3B,cAAcA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAKlC,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA,IAEzB,iBAAiBA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA,IAErC,iBAAiBA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAarC,gBAAgBA,GAAE,OAAO;AAAA,MACvB,SAASA,GAAE,QAAQ,EAAE,SAAS;AAAA,MAC9B,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IAC9C,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IASZ,uBAAuBA,GAAE;AAAA,MACvBA,GAAE,OAAO;AAAA,MACTA,GAAE,OAAO;AAAA,QACP,aAAaA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QAC5C,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,CAAC;AAAA,IACH,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOX,eAAeA,GAAE,MAAMA,GAAE,OAAO,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAK5C,eAAeA,GAAE,OAAO;AAAA,MACtB,QAAQA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACvC,UAAUA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,YAAYA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC3C,KAAKA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACpC,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MAC1C,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,IAChC,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMZ,aAAaA,GAAE,OAAO;AAAA,MACpB,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACtC,YAAYA,GAAE,OAAO;AAAA,QACnB,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QACxC,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QAC1C,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QAC7C,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QACtC,QAAQA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,CAAC,EAAE,SAAS;AAAA,MACZ,WAAWA,GAAE,OAAO;AAAA,QAClB,SAASA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QACxC,WAAWA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QAC1C,cAAcA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QAC7C,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,QACtC,QAAQA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,CAAC,EAAE,SAAS;AAAA,IACd,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAMZ,cAAcA,GAAE,OAAO;AAAA,MACrB,SAASA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,IAC3C,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAKZ,oBAAoBA,GAAE,OAAO;AAAA,MAC3B,SAASA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,OAAOA,GAAE,OAAO,EAAE,SAAS,EAAE,SAAS;AAAA,IACxC,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,IAOZ,mBAAmBA,GAAE,OAAO;AAAA,MAC1B,SAASA,GAAE,QAAQ,EAAE,SAAS,EAAE,SAAS;AAAA,MACzC,WAAWA,GAAE,QAAQ,EAAE,SAAS;AAAA,IAClC,CAAC,EAAE,SAAS;AAAA,EACd,CAAC,EACA,MAAM,EACN,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMZ,cAAcA,GAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMvC,iBAAiBA,GAAE,MAAM,4BAA4B,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAajE,eAAeA,GAAE,QAAQ,EAAE,SAAS;AAAA,EACpC,mBAAmBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW5D,qBAAqBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC/D,sBAAsBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAChE,qBAAqBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC/D,mBAAmBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAC7D,sBAAsBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOhE,WAAWA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACrD,aAAaA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvD,eAAeA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACzD,QAAQA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EAClD,YAAYA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOtD,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,aAAaA,GAAE,OAAO,EAAE,SAAS;AAAA,EACjC,QAAQA,GAAE,MAAM,CAACA,GAAE,QAAQ,CAAC,GAAGA,GAAE,QAAQ,CAAC,CAAC,CAAC,EAAE,SAAS;AAAA,EACvD,gBAAgBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA,EACjD,sBAAsBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMhE,eAAeA,GAAE,KAAK,CAAC,MAAM,OAAO,MAAM,CAAC,EAAE,SAAS;AAAA,EACtD,oBAAoBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,EAK9D,aAAaA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACvD,yBAAyBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA,EACnE,oBAAoBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,GAAG,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY9D,mBAAmBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,KAAK,EAAE,SAAS;AAAA,EAChE,qBAAqBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC9D,kBAAkBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,IAAI,EAAE,SAAS;AAAA,EAC9D,oBAAoBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,EAAE,EAAE,SAAS;AAAA,EAC7D,oBAAoBA,GAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMzC,oBAAoBA,GAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOzC,iBAAiBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQzD,kBAAkBA,GAAE,QAAQ,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcvC,0BAA0BA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,CAAC,EAAE,IAAI,CAAC,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQlE,sBAAsBA,GAAE,OAAO,EAAE,IAAI,EAAE,IAAI,EAAE,EAAE,IAAI,GAAI,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlE,cAAcA,GAAE,OAAO,EAAE,IAAI,GAAG,EAAE,IAAI,EAAE,EAAE,SAAS;AACrD,CAAC;AAgBM,IAAM,mBAAmBA,GAAE,OAAO;AAAA,EACvC,MAAMA,GAAE,OAAO,EAAE,SAAS,oBAAoB;AAAA,EAC9C,MAAMA,GAAE,OAAO,EAAE,QAAQ,GAAI,EAAE,SAAS,8BAA8B;AAAA,EACtE,UAAUA,GAAE,OAAO,EAAE,QAAQ,OAAO;AAAA,EACpC,UAAUA,GAAE,OAAO;AAAA;AAAA,EAEnB,WAAWA,GAAE,KAAK,CAAC,OAAO,KAAK,CAAC,EAAE,QAAQ,KAAK;AAAA;AAAA,EAE/C,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA,EAEzB,cAAcA,GAAE,QAAQ,EAAE,QAAQ,KAAK;AAAA,EACvC,iBAAiBA,GAAE,MAAM,4BAA4B,EAAE,QAAQ,CAAC,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAMjE,aAAaA,GAAE,OAAO;AAAA,IACpB,YAAYA,GAAE,QAAQ,KAAK,EAAE,SAAS;AAAA,IACtC,cAAcA,GAAE,OAAO,EAAE,IAAI,EAAE,SAAS,EAAE,SAAS;AAAA,IACnD,OAAOA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA;AAAA;AAAA,IAK3B,cAAcA,GAAE,OAAO,EAAE,SAAS;AAAA;AAAA;AAAA,IAGlC,KAAKA,GAAE,OAAO,EAAE,SAAS;AAAA,IACzB,iBAAiBA,GAAE,OAAO,EAAE,SAAS;AAAA,IACrC,iBAAiBA,GAAE,OAAO,EAAE,SAAS;AAAA,IACrC,UAAUA,GAAE,OAAO,EAAE,SAAS;AAAA,EAChC,CAAC,EAAE,MAAM,EAAE,SAAS;AACtB,CAAC;;;AC7ZD,IAAM,UAAU;AAChB,IAAM,gBAAgB;AACtB,IAAM,kBAAkB;AAOjB,SAAS,iBACd,MACA,SACA,SACA,cACQ;AACR,MAAI,eAAe,EAAG,QAAO,GAAG,IAAI,MAAM,OAAO,IAAI,OAAO;AAC5D,SAAO,GAAG,IAAI,IAAI,OAAO;AAC3B;AAOO,SAAS,iBAAiB,aAAqB,gBAA8C;AAClG,QAAM,IAAI,QAAQ,KAAK,WAAW;AAClC,MAAI,CAAC,EAAG,QAAO;AACf,QAAM,OAAO,EAAE,CAAC;AAChB,QAAM,OAAO,EAAE,CAAC;AAChB,QAAM,KAAK,cAAc,KAAK,IAAI;AAClC,MAAI,IAAI;AACN,WAAO;AAAA,MACL;AAAA,MACA,SAAS,SAAS,GAAG,CAAC,GAAI,EAAE;AAAA,MAC5B,SAAS,GAAG,CAAC;AAAA,IACf;AAAA,EACF;AACA,QAAM,IAAI,gBAAgB,KAAK,IAAI;AACnC,MAAI,GAAG;AACL,WAAO;AAAA,MACL;AAAA,MACA,SAAS;AAAA,MACT,SAAS,EAAE,CAAC;AAAA,IACd;AAAA,EACF;AACA,SAAO;AACT;AAkBO,SAAS,YAAY,GAAqB,MAAkC;AACjF,QAAM,QAAkB,CAAC;AACzB,MAAI,KAAM,OAAM,KAAK,UAAU,IAAI,CAAC;AACpC,MAAI,EAAE,YAAY,OAAW,OAAM,KAAK,KAAK,EAAE,OAAO,EAAE;AACxD,QAAM,KAAK,EAAE,QAAQ,OAAO,CAAC,EAAE,YAAY,IAAI,EAAE,QAAQ,MAAM,CAAC,CAAC;AACjE,MAAI,EAAE,QAAQ,EAAE,SAAS,OAAQ,OAAM,KAAK,IAAI,EAAE,IAAI,GAAG;AACzD,SAAO,MAAM,KAAK,GAAG;AACvB;AAEA,SAAS,UAAU,MAAiC;AAClD,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAU,aAAO;AAAA,EACxB;AACF;AAQO,SAAS,eAAe,cAAoE;AACjG,MAAI,gBAAgB,GAAG;AACrB,WAAO;AAAA,MACL,EAAE,IAAI,eAAe,OAAO,cAAc;AAAA,MAC1C,EAAE,IAAI,cAAc,OAAO,aAAa;AAAA,IAC1C;AAAA,EACF;AACA,QAAM,MAA4C,CAAC;AACnD,WAAS,KAAK,GAAG,KAAK,cAAc,MAAM;AACxC,QAAI,KAAK,EAAE,IAAI,YAAY,EAAE,SAAS,OAAO,YAAY,EAAE,QAAQ,CAAC;AACpE,QAAI,KAAK,EAAE,IAAI,YAAY,EAAE,QAAQ,OAAO,YAAY,EAAE,OAAO,CAAC;AAAA,EACpE;AACA,SAAO;AACT;;;AC7GA,IAAM,wBAA+C;AAAA,EACnD;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AAAA,EACA;AACF;AAEO,SAAS,2BAA2B,KAAuB;AAChE,QAAM,UAAU,eAAe,QAC3B,IAAI,UACJ,OAAO,QAAQ,WACb,MACC,KAAqC,WAAW,KAAK;AAC5D,SAAO,sBAAsB,KAAK,CAAC,aAAa,QAAQ,SAAS,QAAQ,CAAC;AAC5E;;;ACXA,IAAM,kBAAkB,UAAU,KAAK;AAAA,EACrC;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAG;AAAA,EAAG;AAAA,EAAG;AAAA,EACzB;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAG;AAAA,EAAG;AAAA,EAAG;AAC3B,CAAC;AAED,IAAM,iBAAiB,WAAW,KAAK;AAAA,EACrC;AAAA,EAAG;AAAA,EAAG;AAAA,EAAG;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EACzD;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAI;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC/D;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAC3E;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAK;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EACtF;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAM;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAC7F;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAAA,EAAO;AAC1D,CAAC;AAED,SAAS,QAAQ,GAAmB;AAClC,MAAI,IAAI,MAAO,QAAO;AACtB,MAAI,IAAI,OAAQ,QAAO;AACvB,SAAO,IAAI;AACb;AAkBO,SAAS,eAAe,KAAiB,gBAAgC;AAC9E,QAAM,kBAAkB,iBAAiB,IAAI;AAC7C,QAAM,cAAc,KAAK,KAAK,IAAI,SAAS,eAAe;AAC1D,QAAM,YAAsB,CAAC;AAE7B,MAAI,cAAc;AAClB,WAAS,IAAI,GAAG,IAAI,aAAa,KAAK;AACpC,UAAM,QAAQ,OAAO,MAAM,IAAI,cAAc;AAC7C,UAAM,QAAQ,IAAI,WAAW,KAAK;AAClC,QAAI,YAAY;AAChB,QAAI,QAAQ;AACZ,UAAM,aAAa,WAAW,CAAC;AAC/B,UAAM,WAAW,OAAO,CAAC;AACzB,UAAM,WAAW,GAAG,CAAC;AACrB;AAEA,UAAM,QAAQ,IAAI,WAAW,iBAAiB,CAAC;AAC/C,aAAS,IAAI,GAAG,IAAI,MAAM,QAAQ,KAAK;AACrC,YAAM,SAAS,IAAI,WAAW,KAAK;AACnC;AACA,UAAI,OAAO,SAAS;AACpB,UAAI,OAAO;AACX,UAAI,OAAO,GAAG;AAAE,eAAO;AAAG,eAAO,CAAC;AAAA,MAAK;AACvC,UAAI,OAAO,eAAe,KAAK,KAAK;AACpC,UAAI,QAAQ;AACZ,UAAI,SAAS,QAAQ;AACrB,UAAI,QAAQ,MAAM;AAAE,iBAAS;AAAG,gBAAQ;AAAM,kBAAU;AAAA,MAAK;AAC7D,eAAS;AACT,UAAI,QAAQ,MAAM;AAAE,iBAAS;AAAG,gBAAQ;AAAM,kBAAU;AAAA,MAAK;AAC7D,eAAS;AACT,UAAI,QAAQ,MAAM;AAAE,iBAAS;AAAG,kBAAU;AAAA,MAAK;AAC/C,kBAAY,OAAO,QAAQ,YAAY,MAAM,IAAI,QAAQ,YAAY,MAAM;AAC3E,eAAS,gBAAgB,KAAK,KAAK;AACnC,UAAI,QAAQ,EAAG,SAAQ;AACvB,UAAI,QAAQ,GAAI,SAAQ;AACxB,YAAM,CAAC,KAAK,QAAQ,QAAQ;AAAA,IAC9B;AAEA,aAAS,IAAI,GAAG,IAAI,gBAAgB,KAAK;AACvC,YAAM,KAAK,MAAM,IAAI,CAAC,KAAK;AAC3B,YAAM,KAAK,MAAM,IAAI,IAAI,CAAC,KAAK;AAC/B,YAAM,IAAI,CAAC,IAAK,KAAK,MAAU,KAAK,OAAS;AAAA,IAC/C;AACA,cAAU,KAAK,KAAK;AAAA,EACtB;AACA,SAAO,OAAO,OAAO,SAAS;AAChC;;;ACrDA,IAAM,qBAAqB;AAC3B,IAAM,iBAAiB;AACvB,IAAM,iBAAiB;AAEvB,IAAM,6BAA6B;AAEnC,IAAM,eAAe;AACrB,IAAM,WAAW;AACjB,IAAM,WAAW;AAqBV,IAAM,yBAAN,MAA6B;AAAA,EAWlC,YAA6B,MAAqC;AAArC;AAAA,EAAsC;AAAA,EAAtC;AAAA,EAVrB,UAA8B;AAAA,EAC9B,YAAoB,OAAO,MAAM,CAAC;AAAA,EAClC,UAAU;AAAA,EACV,cAAoC;AAAA,EACpC,kBAAkB;AAAA,EAClB,gBAAgB;AAAA,EAChB,YAAY;AAAA,EACZ,0BAA0B;AAAA,EAC1B,aAAa;AAAA;AAAA,EAKrB,IAAI,SAAkB;AAAE,WAAO,KAAK,YAAY;AAAA,EAAK;AAAA;AAAA,EAGrD,IAAI,aAAqB;AACvB,QAAI,CAAC,KAAK,QAAS,OAAM,IAAI,MAAM,uDAAuD;AAC1F,WAAO,KAAK,QAAQ,KAAK,YAAY;AAAA,EACvC;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,QAAS;AAClB,SAAK,aAAa,UAAU,KAAK,KAAK,UAAU;AAChD,UAAM,UAAU,KAAK,KAAK;AAC1B,UAAM,UAAU,MAAM,QAAQ,2BAA2B,KAAK,KAAK,SAAS;AAAA,MAC1E,kBAAkB,YAAY,KAAK,KAAK,gBAAgB;AAAA,MACxD,eAAe,KAAK,KAAK,iBAAiB;AAAA,MAC1C,UAAU,KAAK,KAAK;AAAA,MACpB,QAAQ,EAAE,KAAK,CAAC,QAAQ,SAAS,KAAK,KAAK,OAAO,MAAM,SAAS,GAAG,IAAI,EAAE,MAAM,EAAE,KAAK,EAAE,CAAC,EAAE;AAAA,IAC9F,CAAC;AACD,UAAM,EAAE,WAAW,cAAc,IAAI,QAAQ;AAC7C,QAAI,CAAC,OAAO,SAAS,SAAS,KAAK,aAAa,KAAK,kBAAkB,YAAY,GAAG;AACpF,UAAI;AAAE,cAAM,QAAQ,KAAK;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACnD,YAAM,IAAI,MAAM,gEAAgE,SAAS,kBAAkB,aAAa,EAAE;AAAA,IAC5H;AACA,UAAM,kBAAkB,YAAY,IAAI;AACxC,SAAK,gBAAgB,kBAAkB;AACvC,SAAK,YAAY;AACjB,UAAM,aAAa,QAAQ,KAAK,YAAY;AAC5C,QAAI,CAAC,OAAO,SAAS,UAAU,KAAK,cAAc,GAAG;AACnD,UAAI;AAAE,cAAM,QAAQ,KAAK;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACnD,YAAM,IAAI,MAAM,qDAAqD,UAAU,EAAE;AAAA,IACnF;AACA,UAAM,kBAAkB,KAAK,IAAI,gBAAgB,KAAK,IAAI,gBAAgB,KAAK,KAAK,gBAAgB,kBAAkB,CAAC;AAEvH,SAAK,kBAAkB,KAAK,IAAI,KAAK,eAAe,KAAK,MAAM,kBAAkB,MAAO,aAAa,CAAC,CAAC;AACvG,SAAK,UAAU;AACf,SAAK,YAAY,OAAO,MAAM,CAAC;AAC/B,SAAK,KAAK,OAAO,KAAK,gCAAgC;AAAA,MACpD,MAAM;AAAA,QACJ,SAAS,KAAK,KAAK;AAAA,QACnB;AAAA,QACA;AAAA,QACA,eAAe,KAAK;AAAA,QACpB,WAAW;AAAA,QACX,iBAAiB,KAAK;AAAA,QACtB,kBAAkB,YAAY,KAAK,KAAK,gBAAgB;AAAA,QACxD,YAAY,KAAK;AAAA,MACnB;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,QAAQ,KAAmB;AACzB,QAAI,CAAC,KAAK,QAAS;AACnB,QAAI,IAAI,WAAW,EAAG;AACtB,SAAK,YAAY,KAAK,UAAU,SAAS,OAAO,OAAO,CAAC,KAAK,WAAW,GAAG,CAAC,IAAI;AAKhF,QAAI,KAAK,UAAU,SAAS,KAAK,iBAAiB;AAChD,YAAM,OAAO,KAAK,kBAAmB,KAAK,kBAAkB;AAC5D,YAAM,UAAU,KAAK,UAAU,SAAS;AACxC,WAAK,YAAY,KAAK,UAAU,SAAS,KAAK,UAAU,SAAS,IAAI;AACrE,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,MAAM,KAAK,0BAA0B,KAAO;AAC9C,aAAK,0BAA0B;AAC/B,aAAK,KAAK,OAAO,KAAK,2CAA2C;AAAA,UAC/D,MAAM,EAAE,cAAc,SAAS,WAAW,MAAM,UAAU,KAAK,gBAAgB;AAAA,QACjF,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,CAAC,KAAK,QAAS,MAAK,UAAU;AAAA,EACpC;AAAA,EAEQ,YAAkB;AACxB,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,QAAS;AACd,SAAK,UAAU;AACf,SAAK,eAAe,YAAY;AAC9B,UAAI;AAEF,eAAO,MAAM;AACX,cAAI,KAAK,YAAY,QAAS;AAC9B,cAAI,KAAK,UAAU,SAAS,KAAK,cAAe;AAChD,gBAAM,QAAQ,KAAK,UAAU,SAAS,GAAG,KAAK,aAAa;AAC3D,eAAK,YAAY,KAAK,UAAU,SAAS,KAAK,aAAa;AAE3D,gBAAM,UAAU,IAAI,WAAW,MAAM,QAAQ,MAAM,YAAY,MAAM,SAAS,CAAC;AAS/E,gBAAM,eAAe,KAAK,eAAe,IAAI,UAAU,eAAe,SAAS,KAAK,UAAU;AAC9F,gBAAM,QAAQ,eAAe,cAAc,KAAK,SAAS;AACzD,gBAAM,QAAQ,UAAU,KAAK;AAAA,QAC/B;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,KAAK,OAAO,KAAK,uCAAkC;AAAA,UACtD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,UAAE;AACA,aAAK,UAAU;AAAA,MACjB;AAAA,IACF,GAAG;AAAA,EACL;AAAA,EAEA,MAAM,OAAsB;AAC1B,UAAM,UAAU,KAAK;AACrB,QAAI,CAAC,QAAS;AACd,SAAK,UAAU;AACf,SAAK,YAAY,OAAO,MAAM,CAAC;AAC/B,QAAI,KAAK,aAAa;AACpB,UAAI;AAAE,cAAM,QAAQ,KAAK,CAAC,KAAK,aAAa,IAAI,QAAc,CAAC,MAAM,WAAW,GAAG,GAAG,CAAC,CAAC,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACnH,WAAK,cAAc;AAAA,IACrB;AACA,QAAI;AACF,YAAM,QAAQ,KAAK;AAAA,QACjB,QAAQ,KAAK;AAAA,QACb,IAAI,QAAc,CAAC,GAAG,QAAQ,WAAW,MAAM,IAAI,IAAI,MAAM,2BAA2B,CAAC,GAAG,GAAK,CAAC;AAAA,MACpG,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,KAAK,OAAO,KAAK,+BAA+B;AAAA,QACnD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAIA,SAAS,YAAY,OAAmC;AACtD,MAAI,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAC3D,SAAO,KAAK,IAAI,GAAG,KAAK,IAAI,GAAG,KAAK,MAAM,KAAK,CAAC,CAAC;AACnD;AAIA,SAAS,UAAU,OAAmC;AACpD,MAAI,UAAU,UAAa,CAAC,OAAO,SAAS,KAAK,EAAG,QAAO;AAC3D,SAAO,KAAK,IAAI,UAAU,KAAK,IAAI,UAAU,KAAK,CAAC;AACrD;AAOA,SAAS,eAAe,SAAqB,MAA0B;AACrE,QAAM,MAAM,IAAI,WAAW,QAAQ,MAAM;AACzC,WAAS,IAAI,GAAG,IAAI,QAAQ,QAAQ,KAAK;AACvC,UAAM,UAAU,QAAQ,CAAC,KAAK,KAAK;AACnC,QAAI,CAAC,IAAI,SAAS,QAAQ,QAAQ,SAAS,SAAS,SAAS;AAAA,EAC/D;AACA,SAAO;AACT;;;ACzFA,IAAM,2BAA2B;AACjC,IAAM,wBAAwB;AAC9B,IAAM,6BAA6B;AAE5B,IAAM,uBAAN,MAA2B;AAAA,EAGhC,YAA6B,MAAmC;AAAnC;AAAA,EAAoC;AAAA,EAApC;AAAA,EAFrB,UAAgC;AAAA;AAAA;AAAA,EAMxC,IAAI,SAAkB;AAAE,WAAO,KAAK,YAAY,QAAQ,CAAC,KAAK,QAAQ;AAAA,EAAO;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAa7E,MAAM,QAA0D;AAC9D,QAAI,KAAK,WAAW,CAAC,KAAK,QAAQ,QAAQ;AAKxC,YAAM,KAAK,KAAK,KAAK,QAAQ,SAAS,EAAE,MAAM,MAAM;AAAA,MAAgB,CAAC;AAAA,IACvE;AAEA,UAAM,YAAY,kBAAkB;AACpC,UAAM,aAAa,KAAK,KAAK,oBAAoB;AACjD,UAAM,WAAW,KAAK,KAAK,kBAAkB;AAC7C,UAAM,eAAe,KAAK,KAAK,gBAAgB;AAO/C,QAAI,KAAK,KAAK,iBAAiB;AAC7B,UAAI;AACF,cAAM,KAAK,KAAK,gBAAgB;AAAA,MAClC,SAAS,KAAK;AACZ,aAAK,KAAK,OAAO,KAAK,6BAA6B;AAAA,UACjD,MAAM,EAAE,OAAO,OAAO,GAAG,EAAE;AAAA,QAC7B,CAAC;AACD,cAAM;AAAA,MACR;AAAA,IACF;AAMA,UAAM,cAAc,IAAI,uBAAuB;AAAA,MAC7C,SAAS,KAAK,KAAK;AAAA,MACnB,KAAK,KAAK,KAAK;AAAA,MACf,QAAQ,KAAK,KAAK,OAAO,WAAW,EAAE,UAAU,CAAC,KAAK,KAAK,KAAK;AAAA,MAChE,WAAW,KAAK,KAAK;AAAA,MACrB,GAAI,KAAK,KAAK,qBAAqB,SAAY,EAAE,kBAAkB,KAAK,KAAK,iBAAiB,IAAI,CAAC;AAAA,MACnG,GAAI,KAAK,KAAK,iBAAiB,SAAY,EAAE,cAAc,KAAK,KAAK,aAAa,IAAI,CAAC;AAAA,MACvF,GAAI,KAAK,KAAK,eAAe,SAAY,EAAE,YAAY,KAAK,KAAK,WAAW,IAAI,CAAC;AAAA,IACnF,CAAC;AACD,QAAI;AACF,YAAM,YAAY,MAAM;AAAA,IAC1B,SAAS,KAAK;AACZ,WAAK,KAAK,OAAO,KAAK,sCAAsC;AAAA,QAC1D,MAAM,EAAE,OAAO,OAAO,GAAG,EAAE;AAAA,MAC7B,CAAC;AACD,YAAM;AAAA,IACR;AAIA,UAAM,iBAAiB,YAAY;AACnC,UAAM,aAAa,OAAO,SAAS,cAAc,KAAK,iBAAiB,IAAI,iBAAiB;AAG5F,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,KAAK,KAAK,WAAW,oBAAoB;AAAA,QACrD,OAAO;AAAA,QACP,kBAAkB;AAAA,QAClB,gBAAgB;AAAA,QAChB,kBAAkB;AAAA,QAClB,gBAAgB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMhB,cAAc;AAAA,QACd,KAAK,oBAAoB,KAAK,KAAK,SAAS,IAAI,SAAS;AAAA,MAC3D,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,YAAM,YAAY,KAAK,EAAE,MAAM,MAAM;AAAA,MAAgB,CAAC;AACtD,WAAK,KAAK,OAAO,KAAK,oDAAoD;AAAA,QACxE,MAAM,EAAE,OAAO,OAAO,GAAG,GAAG,UAAU,cAAc,WAAW;AAAA,MACjE,CAAC;AACD,YAAM;AAAA,IACR;AAIA,UAAM,OAAO,KAAK,KAAK,YAAY,EAAE,QAAQ,KAAK,KAAK,OAAO,CAAC;AAC/D,UAAM,SAAwB;AAAA,MAC5B;AAAA,MACA;AAAA,MACA;AAAA,MACA;AAAA,MACA,QAAQ;AAAA,MACR,aAAa,KAAK,IAAI;AAAA,MACtB,cAAc;AAAA,MACd,gBAAgB;AAAA,IAClB;AACA,SAAK,UAAU;AAKf,SAAK,YAAY,CAAC,OAAO,QAAQ;AAK/B,WAAK,KAAK,gBAAgB,QAAQ,OAAO,GAAG,EAAE,MAAM,CAAC,QAAiB;AACpE,aAAK,KAAK,OAAO,MAAM,6CAA6C;AAAA,UAClE,MAAM,EAAE,OAAO,OAAO,GAAG,EAAE;AAAA,QAC7B,CAAC;AAAA,MACH,CAAC;AAAA,IACH,CAAC;AAED,QAAI;AACJ,QAAI;AACF,cAAQ,MAAM,KAAK,YAAY;AAAA,IACjC,SAAS,KAAK;AACZ,YAAM,KAAK,KAAK,SAAS,EAAE,MAAM,MAAM;AAAA,MAAgB,CAAC;AACxD,WAAK,KAAK,OAAO,KAAK,uCAAuC;AAAA,QAC3D,MAAM,EAAE,OAAO,OAAO,GAAG,EAAE;AAAA,MAC7B,CAAC;AACD,YAAM;AAAA,IACR;AAEA,SAAK,KAAK,OAAO,KAAK,2BAA2B;AAAA,MAC/C,MAAM;AAAA,QACJ;AAAA,QACA,SAAS,KAAK,KAAK;AAAA,QACnB;AAAA,QACA,gBAAgB,MAAM;AAAA,QACtB,aAAa,MAAM;AAAA,MACrB;AAAA,IACF,CAAC;AACD,WAAO,EAAE,WAAW,UAAU,MAAM,IAAI;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAM,aAAa,WAAmB,WAAkC;AACtE,UAAM,SAAS,KAAK,cAAc,SAAS;AAC3C,UAAM,OAAO,KAAK,UAAU,SAAS;AACrC,SAAK,KAAK,OAAO,MAAM,iCAAiC;AAAA,MACtD,MAAM,EAAE,UAAU;AAAA,IACpB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAM,KAAK,WAAkC;AAC3C,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,OAAO,cAAc,aAAa,OAAO,OAAQ;AAChE,WAAO,SAAS;AAChB,SAAK,UAAU;AAIf,QAAI;AACF,YAAM,OAAO,KAAK,MAAM;AAAA,IAC1B,SAAS,KAAK;AACZ,WAAK,KAAK,OAAO,MAAM,2CAA2C;AAAA,QAChE,MAAM,EAAE,WAAW,OAAO,OAAO,GAAG,EAAE;AAAA,MACxC,CAAC;AAAA,IACH;AACA,QAAI;AACF,YAAM,KAAK,KAAK,WAAW,aAAa;AAAA,QACtC,WAAW,OAAO,MAAM;AAAA,QACxB,QAAQ,OAAO,MAAM;AAAA,MACvB,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,KAAK,OAAO,MAAM,yDAAyD;AAAA,QAC9E,MAAM,EAAE,WAAW,OAAO,OAAO,GAAG,EAAE;AAAA,MACxC,CAAC;AAAA,IACH;AACA,QAAI;AACF,YAAM,OAAO,YAAY,KAAK;AAAA,IAChC,SAAS,KAAK;AACZ,WAAK,KAAK,OAAO,MAAM,kDAAkD;AAAA,QACvE,MAAM,EAAE,WAAW,OAAO,OAAO,GAAG,EAAE;AAAA,MACxC,CAAC;AAAA,IACH;AACA,SAAK,KAAK,OAAO,KAAK,2BAA2B;AAAA,MAC/C,MAAM;AAAA,QACJ;AAAA,QACA,cAAc,OAAO;AAAA,QACrB,gBAAgB,OAAO;AAAA,QACvB,YAAY,KAAK,IAAI,IAAI,OAAO;AAAA,MAClC;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,gBAAgB,QAAuB,OAAe,KAA4B;AAC9F,QAAI,OAAO,OAAQ;AACnB,WAAO,gBAAgB;AACvB,UAAM,KAAK,KAAK,WAAW,iBAAiB;AAAA,MAC1C,WAAW,OAAO,MAAM;AAAA,MACxB,QAAQ,OAAO,MAAM;AAAA,MACrB,MAAM;AAAA,MACN;AAAA,IACF,CAAC;AACD,QAAI,OAAO,OAAQ;AACnB,UAAM,SAAS,MAAM,KAAK,KAAK,WAAW,QAAQ;AAAA,MAChD,WAAW,OAAO,MAAM;AAAA,MACxB,QAAQ,OAAO,MAAM;AAAA,MACrB,UAAU;AAAA,IACZ,CAAC;AACD,QAAI,OAAO,OAAQ;AACnB,eAAW,SAAS,QAAQ;AAI1B,YAAM,MAAM,OAAO,KAAK,MAAM,KAAK,QAAQ,MAAM,KAAK,YAAY,MAAM,KAAK,UAAU;AACvF,aAAO,kBAAkB,IAAI;AAC7B,aAAO,YAAY,QAAQ,GAAG;AAAA,IAChC;AAAA,EACF;AAAA,EAEQ,cAAc,WAAkC;AACtD,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,UAAU,OAAO,UAAU,OAAO,cAAc,WAAW;AAC9D,YAAM,IAAI,MAAM,iDAAiD,SAAS,EAAE;AAAA,IAC9E;AACA,WAAO;AAAA,EACT;AACF;AAIA,SAAS,oBAA4B;AACnC,SAAO,YAAY,KAAK,IAAI,EAAE,SAAS,EAAE,CAAC,IAAI,KAAK,MAAM,KAAK,OAAO,IAAI,QAAQ,EAAE,SAAS,EAAE,CAAC;AACjG;AAEA,SAAS,OAAO,KAAsB;AACpC,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;ACzWA,IAAI;AAOJ,eAAe,aAAoC;AACjD,MAAI,QAAS,QAAO;AACpB,MAAI;AACF,UAAM,aAAa;AAMnB,cAAU,MAAO,SAAS,KAAK,kBAAkB,EAAE,UAAU;AAC7D,WAAO;AAAA,EACT,QAAQ;AACN,UAAM,IAAI;AAAA,MACR;AAAA,IAEF;AAAA,EACF;AACF;AASO,IAAM,qBAAN,MAAuD;AAAA,EAW5D,YAA6B,MAAiC;AAAjC;AAAA,EAAkC;AAAA,EAAlC;AAAA,EAVrB,KAAkC;AAAA,EAClC,gBAA6D,CAAC;AAAA,EAC9D,iBAAsC;AAAA,EACtC,mBAAwC;AAAA,EACxC,SAAS;AAAA;AAAA;AAAA;AAAA,EAIT,oBAAmC;AAAA,EAI3C,YAAY,IAAgD;AAC1D,QAAI,KAAK,OAAQ;AACjB,SAAK,cAAc,KAAK,EAAE;AAAA,EAC5B;AAAA,EAEA,MAAM,cAAwC;AAC5C,QAAI,KAAK,GAAI,OAAM,IAAI,MAAM,8CAA8C;AAC3E,UAAM,SAAS,MAAM,WAAW;AAChC,UAAM,YAA6B,CAAC;AACpC,QAAI,KAAK,KAAK,cAAc,KAAK,KAAK,WAAW,SAAS,GAAG;AAC3D,gBAAU,aAAa,CAAC,GAAG,KAAK,KAAK,UAAU;AAAA,IACjD;AACA,UAAM,KAAK,IAAI,OAAO,kBAAkB,SAAS;AACjD,SAAK,KAAK;AAQV,UAAM,aAAa,IAAI,OAAO,iBAAiB,EAAE,MAAM,QAAQ,CAAC;AAChE,UAAM,cAAc,GAAG,eAAe,YAAY,EAAE,WAAW,WAAW,CAAC;AAM3E,UAAM,WAAW,YAAY,QAAQ,UAAU,CAAC,UAAU;AACxD,UAAI,MAAM,SAAS,QAAS;AAC5B,YAAM,SAAS,MAAM,aAAa,UAAU,CAAC,QAAQ;AACnD,YAAI,KAAK,OAAQ;AACjB,cAAM,UAAU,IAAI;AACpB,YAAI,CAAC,WAAW,QAAQ,WAAW,EAAG;AAItC,cAAM,KAAK,IAAI,OAAO;AACtB,YAAI,KAAK,sBAAsB,KAAM,MAAK,oBAAoB;AAC9D,cAAM,QAAS,KAAK,KAAK,sBAAuB;AAEhD,cAAM,QAAQ,KAAK,MAAM,QAAQ,EAAE;AACnC,mBAAW,MAAM,KAAK,eAAe;AACnC,cAAI;AAAE,eAAG,SAAS,KAAK;AAAA,UAAE,SAAS,KAAK;AACrC,iBAAK,KAAK,OAAO,MAAM,sCAAsC;AAAA,cAC3D,MAAM,EAAE,OAAOC,QAAO,GAAG,EAAE;AAAA,YAC7B,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF,CAAC;AACD,UAAI,UAAU,OAAO,OAAO,gBAAgB,YAAY;AACtD,aAAK,iBAAiB,MAAM,OAAO,cAAc;AAAA,MACnD;AAAA,IACF,CAAC;AACD,QAAI,YAAY,OAAO,SAAS,gBAAgB,YAAY;AAC1D,WAAK,mBAAmB,MAAM,SAAS,cAAc;AAAA,IACvD;AAKA,OAAG,yBAAyB,UAAU,CAAC,UAAU;AAC/C,WAAK,KAAK,OAAO,KAAK,4BAA4B,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,IACvE,CAAC;AACD,OAAG,wBAAwB,UAAU,CAAC,UAAU;AAC9C,WAAK,KAAK,OAAO,MAAM,gCAAgC,EAAE,MAAM,EAAE,MAAM,EAAE,CAAC;AAAA,IAC5E,CAAC;AAED,UAAM,QAAQ,MAAM,GAAG,YAAY;AACnC,UAAM,GAAG,oBAAoB,KAAK;AAKlC,UAAM,WAAW,GAAG,kBAAkB,OAAO,MAAM;AACnD,WAAO,EAAE,KAAK,SAAS;AAAA,EACzB;AAAA,EAEA,MAAM,UAAU,KAA4B;AAC1C,QAAI,CAAC,KAAK,GAAI,OAAM,IAAI,MAAM,yDAAyD;AACvF,QAAI,KAAK,OAAQ,OAAM,IAAI,MAAM,qDAAqD;AACtF,UAAM,KAAK,GAAG,qBAAqB,EAAE,KAAK,MAAM,SAAS,CAAC;AAAA,EAC5D;AAAA,EAEA,MAAM,QAAuB;AAC3B,QAAI,KAAK,OAAQ;AACjB,SAAK,SAAS;AACd,SAAK,gBAAgB,CAAC;AACtB,QAAI,KAAK,gBAAgB;AACvB,UAAI;AAAE,aAAK,eAAe;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACpD,WAAK,iBAAiB;AAAA,IACxB;AACA,QAAI,KAAK,kBAAkB;AACzB,UAAI;AAAE,aAAK,iBAAiB;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACtD,WAAK,mBAAmB;AAAA,IAC1B;AACA,QAAI,KAAK,IAAI;AACX,UAAI;AAAE,cAAM,QAAQ,QAAQ,KAAK,GAAG,MAAM,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAgB;AACrE,WAAK,KAAK;AAAA,IACZ;AAAA,EACF;AACF;AAEA,SAASA,QAAO,KAAsB;AACpC,SAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AACxD;;;ACvKO,IAAM,+BAA+B;AAE5C,SAAS,gBAAgB,OAAwB;AAC/C,MAAI,UAAU,UAAa,UAAU,KAAM,QAAO;AAClD,MAAI,OAAO,UAAU,YAAY,MAAM,SAAS,EAAG,QAAO;AAC1D,MAAI,OAAO,UAAU,UAAU;AAC7B,QAAI,UAAU,EAAG,QAAO;AACxB,QAAI,UAAU,EAAG,QAAO;AACxB,WAAO,OAAO,KAAK;AAAA,EACrB;AACA,SAAO;AACT;AASA,eAAsB,wBACpB,KACA,MAC2B;AAC3B,QAAM,KAAK,KAAK,IAAI;AAMpB,MAAI;AACJ,MAAI;AACF,UAAM,MAAM,MAAM,IAAI,kBAAkB,EAAE,WAAW,IAAM,CAAC;AAC5D,UAAM,OAAO,KAAK,MAAM;AACxB,UAAM,OAA+B,CAAC;AACtC,eAAW,KAAK,MAAM,cAAc,CAAC,GAAG;AACtC,WAAK,KAAK;AAAA,QACR,UAAU,OAAO,EAAE,aAAa,WAAW,EAAE,WAAW;AAAA,QACxD,IAAI,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,QACpD,WAAW,OAAO,EAAE,cAAc,WAAW,EAAE,YAAY;AAAA,QAC3D,OAAO,gBAAgB,EAAE,SAAS;AAAA,QAClC,QAAQ,EAAE,aAAa,SAAY,OAAO,EAAE,aAAa;AAAA,MAC3D,CAAC;AAAA,IACH;AACA,eAAW,KAAK,MAAM,QAAQ,CAAC,GAAG;AAChC,WAAK,KAAK;AAAA,QACR,UAAU,OAAO,EAAE,aAAa,WAAW,EAAE,WAAW;AAAA,QACxD,IAAI,OAAO,EAAE,OAAO,WAAW,EAAE,KAAK;AAAA,QACtC,WAAW;AAAA,QACX,OAAO,gBAAgB,EAAE,KAAK;AAAA,QAC9B,QAAQ;AAAA,MACV,CAAC;AAAA,IACH;AACA,kBAAc;AAAA,EAChB,SAAS,KAAK;AACZ,SAAK,OAAO,MAAM,qDAAqD;AAAA,MACrE,MAAM,EAAE,UAAU,KAAK,SAAS;AAAA,MAChC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAClE,CAAC;AACD,kBAAc;AAAA,EAChB;AAEA,QAAM,UAAU,IAAI,qBAAqB;AACzC,QAAM,aAA0C,QAAQ,KAAK,IAAI,CAAC,SAAS;AAAA,IACzE;AAAA,IACA,OAAO;AAAA,EACT,EAAE;AAEF,MAAI,oBAAiD,CAAC;AACtD,MAAI;AACF,UAAM,SAAU,IAA8E,8BAA8B;AAC5H,QAAI,UAAU,MAAM,QAAQ,OAAO,IAAI,GAAG;AACxC,0BAAoB,OAAO,KAAK,IAAI,CAAC,SAAS,EAAE,IAAI,EAAE;AAAA,IACxD;AAAA,EACF,QAAQ;AAAA,EAER;AAEA,QAAM,cAAc,IAAI,4BAA4B;AACpD,QAAM,WAAW,cAAc;AAAA,IAC7B,MAAM,YAAY;AAAA,IAClB,YAAY,YAAY;AAAA,IACxB,cAAc,YAAY;AAAA,IAC1B,qBAAqB,YAAY;AAAA,IACjC,kBAAkB,YAAY,gBAAgB,YAAY,cAAc,YAAY,IAAI;AAAA,EAC1F,IAAI;AAEJ,SAAO;AAAA,IACL;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,IACA;AAAA,EACF;AACF;AAQO,SAAS,yBACd,MACA,MAC+C;AAC/C,QAAM,MAAM,KAAK,IAAI;AACrB,QAAM,UAAU,SAAS,OACrB,kBACA,GAAG,KAAK,OAAO,MAAM,KAAK,MAAM,GAAI,CAAC,UAAU,IAAI,KAAK,KAAK,EAAE,EAAE,mBAAmB,CAAC;AAEzF,QAAM,eAAe,SAAS,OAC1B,kBACA,KAAK,aAAa,OAChB,8BACA,KAAK,SAAS,aACZ,iBAAiB,KAAK,SAAS,YAAY,cAAc,KAAK,KAAK,KAAK,SAAS,sBAAsB,GAAI,CAAC,sBAAsB,KAAK,SAAS,oBAAoB,GAAG,MACvK,YAAY,KAAK,SAAS,YAAY;AAE9C,QAAM,cAAsE,MAAM,QAC9E,CAAC,EAAE,MAAM,QAAQ,KAAK,iBAAiB,OAAO,iBAAiB,SAAS,KAAK,OAAO,SAAS,SAAS,CAAC,IACvG,CAAC;AAEL,QAAM,aAAa,MAAM,cACrB,KAAK,YAAY,IAAI,CAAC,OAAO;AAAA,IAC3B,UAAU,EAAE;AAAA,IACZ,IAAI,EAAE;AAAA,IACN,WAAW,EAAE,cAAc,OAAO,KAAK,OAAO,EAAE,SAAS;AAAA,IACzD,OAAO,EAAE;AAAA,IACT,QAAQ,EAAE,SAAS,OAAO;AAAA,EAC5B,EAAE,IACF,CAAC;AACL,QAAM,iBAAiB,SAAS,OAC5B,kBACA,KAAK,gBAAgB,OACnB,wDACA;AAEN,QAAM,WAAW,MAAM,WAAW,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,KAAK,OAAO,EAAE,MAAM,EAAE,KAAK,CAAC;AACnF,QAAM,eAAe,SAAS,OAAO,kBAAa;AAClD,QAAM,gBAAgB,MAAM,kBAAkB,IAAI,CAAC,OAAO,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,CAAC;AAE/E,SAAO;AAAA,IACL;AAAA,MACE,IAAI;AAAA,MACJ,OAAO,KAAK,SAAS;AAAA,MACrB,aAAa;AAAA,MACb,KAAK,KAAK;AAAA,MACV,OAAO;AAAA,MACP,SAAS;AAAA,MACT,QAAQ;AAAA,QACN,GAAG;AAAA,QACH,EAAE,MAAM,QAAQ,KAAK,yBAAyB,OAAO,kBAAkB,SAAS,QAAQ;AAAA,QACxF;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,YACP,EAAE,KAAK,YAAY,OAAO,OAAO;AAAA,YACjC,EAAE,KAAK,MAAM,OAAO,MAAM,MAAM,aAAa,OAAO,QAAQ;AAAA,YAC5D,EAAE,KAAK,aAAa,OAAO,WAAW,MAAM,aAAa,OAAO,OAAO;AAAA,YACvE,EAAE,KAAK,SAAS,OAAO,SAAS,OAAO,OAAO;AAAA,YAC9C,EAAE,KAAK,UAAU,OAAO,SAAS,MAAM,UAAU,OAAO,QAAQ;AAAA,UAClE;AAAA,UACA,OAAO;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,YACP,EAAE,KAAK,OAAO,OAAO,OAAO,MAAM,YAAY;AAAA,YAC9C,EAAE,KAAK,SAAS,OAAO,SAAS,MAAM,UAAU,OAAO,QAAQ;AAAA,UACjE;AAAA,UACA,OAAO;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,QACA;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,YACP,EAAE,KAAK,OAAO,OAAO,eAAe,MAAM,YAAY;AAAA,UACxD;AAAA,UACA,OAAO;AAAA,UACP,cAAc;AAAA,QAChB;AAAA,QACA,EAAE,MAAM,QAAQ,KAAK,oBAAoB,OAAO,sBAAsB,SAAS,aAAa;AAAA,QAC5F;AAAA,UACE,MAAM;AAAA,UACN,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,QAAQ;AAAA,UACR,SAAS;AAAA,QACX;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AClPA,IAAM,WAAW;AACjB,IAAM,WAAW;AAUV,SAAS,yBAAyB,OAQ9B;AACT,QAAM,MAAM,OAAO,MAAM,gBAAgB,EAAE,EAAE,YAAY;AACzD,QAAM,SAAS,IAAI,SAAS,KAAK,KAAK,IAAI,SAAS,MAAM;AACzD,QAAM,aAAa,SAAS,SAAS;AAErC,QAAM,aAAa,MAAM,mBAAmB;AAC5C,QAAM,WAAW,MAAM,iBAAiB;AAExC,MAAI,MAAM;AACV,SAAO;AACP,SAAO;AACP,SAAO;AAEP,SAAO,qBAAqB,QAAQ;AAAA;AACpC,SAAO;AACP,SAAO,YAAY,QAAQ,IAAI,UAAU;AAAA;AACzC,MAAI,CAAC,QAAQ;AAIX,WAAO,UAAU,QAAQ;AAAA;AAAA,EAC3B;AAIA,MAAI,MAAM,cAAc;AACtB,WAAO,qBAAqB,QAAQ;AAAA;AACpC,WAAO;AACP,WAAO;AACP,WAAO,YAAY,QAAQ,kBAAkB,UAAU,IAAI,QAAQ;AAAA;AAGnE,WAAO,UAAU,QAAQ;AAAA;AAAA,EAC3B;AAEA,SAAO;AACT;AAcO,SAAS,oBAAoB,aAA6B;AAI/D,SAAO,gBAAgB,mBAAmB,WAAW,CAAC;AACxD;;;Af3DA,SAAS,YAAY,OAAuB;AAC1C,SAAO,YAAY,KAAK,EAAE,SAAS,KAAK;AAC1C;AAcA,SAAS,mBAAmB,KAAoD;AAC9E,MAAI,CAAC,IAAK,QAAO;AACjB,QAAM,YAAY,IAAI,YAAY,EAAE,QAAQ,cAAc,EAAE;AAC5D,MAAI,CAAC,UAAW,QAAO;AACvB,MAAI,cAAc,OAAQ,QAAO;AACjC,MAAI,cAAc,MAAO,QAAO;AAChC,SAAO;AACT;AAgIO,IAAM,gBAAN,MAAM,uBACHC,YAEV;AAAA,EACW,OAAOC,YAAW;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY3B,IAAI,WAAqC;AACvC,UAAM,QAAQ,KAAK,cAAiC;AACpD,UAAM,MAAuB,CAACC,eAAc,gBAAgBA,eAAc,UAAU;AACpF,QAAI,MAAM,eAAe,KAAM,KAAI,KAAKA,eAAc,eAAe;AACrE,QAAI,MAAM,WAAW,KAAM,KAAI,KAAKA,eAAc,WAAW;AAC7D,QAAI,MAAM,iBAAiB,KAAM,KAAI,KAAKA,eAAc,YAAY;AACpE,QAAI,MAAM,gBAAgB,KAAM,KAAI,KAAKA,eAAc,WAAW;AAClE,QAAI,MAAM,gBAAgB,KAAM,KAAI,KAAKA,eAAc,cAAc;AACrE,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,MAAiC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBxB,yBAAyB,CAAC,UAAuE;AAChH,SAAK,kBAAkB,KAAK;AAAA,EAC9B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,wBAA+D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAS/D,+BAA+B;AAAA;AAAA;AAAA;AAAA,EAI/B,yBAAyB;AAAA;AAAA,EAEhB,SAAS,oBAAI,IAA0B;AAAA;AAAA,EAEhD,eAAmD;AAAA;AAAA,EAEnD,YAAY;AAAA;AAAA,EAEZ,oBAAoB;AAAA;AAAA,EAEpB,iBAAuD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ/D,IAAY,WAAoB;AAC9B,WAAO,KAAK,MAAM,QAAQ,YAAY;AAAA,EACxC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWQ,sBAAsB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAU9B,OAAwB,sBAAsB;AAAA;AAAA,EAEtC,iBAAwD;AAAA;AAAA;AAAA,EAIxD,gBAAuD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQ9C,mBAAmB,oBAAI,IAAiE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcjG,oBAA2D;AAAA,EAC3D,0BAA0B;AAAA,EAC1B,qBAA4D;AAAA;AAAA,EAE5D,cAAc;AAAA;AAAA,EAEd,oBAAoB;AAAA;AAAA,EAEpB,oBAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOX,qBAAqB,oBAAI,IAAY;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAW9C,mBAA4C;AAAA;AAAA,EAE5C,0BAAgD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaxD,YAAY,KAAoB;AAC9B,UAAM,KAAK,qBAAqB,EAAE,MAAMD,YAAW,OAAO,CAAC;AAG3D,SAAK,2BAA2B;AAAA,EAClC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,aAAqB;AAC3B,UAAM,KAAK,KAAK,OAAO,IAAI,SAAS;AACpC,QAAI,OAAO,OAAO,YAAY,MAAM,EAAG,QAAO;AAC9C,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,aAAsB;AAC5B,WAAO,KAAK,mBAAmB,QAAQ,OAAO,KAAK,OAAO,IAAI,SAAS,MAAM;AAAA,EAC/E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,eAA+C;AAC3D,QAAI,CAAC,KAAK,WAAW,EAAG,QAAO;AAC/B,QAAI,KAAK,oBAAoB,OAAW,QAAO,KAAK;AACpD,UAAM,MAAM,MAAM,KAAK,IAAI,QAAQ,OAAO;AAC1C,UAAM,SAAS,IAAI,KAAK,CAAC,MAAsB,EAAE,OAAO,KAAK,cAAc;AAC3E,SAAK,kBAAkB,UAAU,MAAM,IAAI,SAAS;AACpD,WAAO,KAAK;AAAA,EACd;AAAA,EACQ,kBAAqD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAY7D,MAAe,UAAyB;AACtC,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU;AAAA,IAC7B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,yEAAoE;AAAA,QACxF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,UAAM,KAAK,wBAAwB,GAAG;AAQtC,UAAM,KAAK,6BAA6B,GAAG,EAAE,MAAM,CAAC,QAAiB;AACnE,WAAK,IAAI,OAAO,MAAM,0DAA0D;AAAA,QAC9E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAe,aAA4B;AACzC,QAAI,KAAK,UAAU;AACjB,WAAK,IAAI,OAAO,KAAK,gEAA2D;AAChF;AAAA,IACF;AACA,UAAM,KAAK,gBAAgB,EAAE,MAAM,CAAC,QAAiB;AACnD,WAAK,IAAI,OAAO,KAAK,wEAAmE;AAAA,QACtF,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH,CAAC;AAKD,SAAK,KAAK,8BAA8B;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA,EAMQ,8BAA8B;AAAA,EACtC,OAAwB,iCAAiC;AAAA;AAAA;AAAA;AAAA,EAKjD,6BAAsC;AAC5C,UAAM,QAAQ,KAAK,OAAO,IAAI,aAAa;AAC3C,QAAI,CAAC,MAAO,QAAO;AACnB,WAAO,MAAM,gBAAgB,UACxB,MAAM,iBAAiB,UACvB,MAAM,uBAAuB,UAC7B,MAAM,sBAAsB;AAAA,EACnC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,MAAc,gCAA+C;AAW3D,QAAI,KAAK,aAAa,KAAK,UAAU;AACnC,WAAK,IAAI,OAAO,MAAM,wEAAmE;AAAA,QACvF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU;AAAA,IAC7B,QAAQ;AACN;AAAA,IACF;AACA,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,cAAuC,CAAC;AAC9C,QAAI;AACF,YAAM,QAAQ,MAAM,IAAI,cAAc,OAAO;AAC7C,YAAM,KAAK,OAAO,MAAM;AACxB,UAAI,IAAI;AACN,oBAAY,gBAAgB;AAAA,UAC1B,QAAQ,OAAO,GAAG,WAAW,WAAW,GAAG,SAAS;AAAA,UACpD,UAAU,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW;AAAA,UAC1D,YAAY,OAAO,GAAG,eAAe,WAAW,GAAG,aAAa;AAAA,UAChE,KAAK,OAAO,GAAG,QAAQ,WAAW,GAAG,MAAM;AAAA,UAC3C,WAAW,OAAO,GAAG,cAAc,WAAW,GAAG,YAAY;AAAA,UAC7D,UAAU,OAAO,GAAG,aAAa,WAAW,GAAG,WAAW;AAAA,QAC5D;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,sCAAsC;AAAA,QAC1D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AACA,QAAI;AACF,YAAM,SAAS,MAAM,IAAI,eAAe,OAAO;AAC/C,YAAM,KAAK,QAAQ,MAAM,SAAS;AAClC,UAAI,IAAI;AAON,cAAM,UAAU,OAAO,GAAG,gBAAgB,WAAW,GAAG,cAAc;AACtE,oBAAY,iBAAiB;AAAA,UAC3B,SAAS,GAAG,WAAW;AAAA,UACvB,aAAa,WAAW,OAAO,KAAK,UAAU;AAAA,QAChD;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,uCAAuC;AAAA,QAC3D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAOA,QAAI;AACF,YAAM,cAAc,MAAM,IAAI,iBAAiB,SAAS,EAAE,WAAW,KAAK,CAAC;AAC3E,UAAI,eAAe,YAAY,SAAS,GAAG;AACzC,oBAAY,gBAAgB;AAC5B,cAAM,SAAoF,CAAC;AAC3F,mBAAW,UAAU,aAAa;AAChC,cAAI;AACF,kBAAM,MAAM,MAAM,IAAI,cAAc,SAAS,QAAQ,EAAE,WAAW,KAAK,CAAC;AACxE,kBAAM,MAAO,KAAgG,MAAM;AACnH,mBAAO,MAAM,IAAI;AAAA,cACf,aAAa,OAAO,KAAK,gBAAgB,WAAW,IAAI,cAAc;AAAA,cACtE,UAAU,OAAO,KAAK,aAAa,WAAW,IAAI,WAAW;AAAA,YAC/D;AAAA,UACF,QAAQ;AAAA,UAER;AAAA,QACF;AACA,YAAI,OAAO,KAAK,MAAM,EAAE,SAAS,EAAG,aAAY,wBAAwB;AAAA,MAC1E;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,uCAAuC;AAAA,QAC3D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAaA,QAAI;AACF,YAAM,MAAM,MAAM,IAAI,OAAO,OAAO;AACpC,YAAM,cAAc,KAAK,MAAM;AAC/B,UAAI,aAAa;AACf,cAAM,aAAa,CAAC,MAMH;AACf,cAAI,CAAC,EAAG,QAAO;AAIf,cAAI,QAAuB;AAC3B,cAAI,OAAO,EAAE,iBAAiB,UAAU;AACtC,oBAAQ,EAAE,iBAAiB,IAAI,SAAS;AAAA,UAC1C,WAAW,OAAO,EAAE,iBAAiB,UAAU;AAC7C,oBAAQ,EAAE;AAAA,UACZ;AACA,iBAAO;AAAA,YACL,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,UAAU;AAAA;AAAA,YAErD,WAAW,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;AAAA,YACnD,cAAc;AAAA,YACd,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;AAAA,YAC/C,QAAQ,OAAO,EAAE,WAAW,WAAW,EAAE,SAAS;AAAA,UACpD;AAAA,QACF;AACA,cAAM,aAAa,WAAW,YAAY,UAAU;AACpD,cAAM,YAAY,WAAW,YAAY,SAAS;AAKlD,cAAM,YAAY,OAAO,YAAY,YAAY,UAAU,WACvD,YAAY,WAAW,QACvB;AACJ,oBAAY,cAAc;AAAA,UACxB,OAAO;AAAA,UACP;AAAA,UACA;AAAA,QACF;AAAA,MACF,OAAO;AACL,aAAK,IAAI,OAAO,KAAK,6EAAwE;AAAA,UAC3F,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC5B,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,4DAAuD;AAAA,QAC1E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAGA,QAAI;AACF,YAAM,OAAO,MAAM,IAAI,QAAQ,OAAO;AACtC,YAAM,IAAI,MAAM,MAAM;AACtB,UAAI,GAAG;AACL,oBAAY,eAAe;AAAA,UACzB,SAAS,OAAO,EAAE,WAAW,WAAW,EAAE,WAAW,IAAI;AAAA,QAC3D;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,gCAAgC;AAAA,QACpD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAKA,QAAI;AACF,YAAM,QAAQ,MAAM,IAAI,cAAc,OAAO;AAC7C,YAAM,IAAI,OAAO,MAAM;AACvB,UAAI,GAAG;AACL,oBAAY,qBAAqB;AAAA,UAC/B,SAAS,OAAO,EAAE,WAAW,WAAW,EAAE,WAAW,IAAI;AAAA,UACzD,OAAO,OAAO,EAAE,UAAU,WAAW,EAAE,QAAQ;AAAA,QACjD;AAAA,MACF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,sCAAsC;AAAA,QAC1D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAeA,UAAM,SAAS,KAAK,OAAO,IAAI,aAAa,GAAG;AAC/C,QAAI,QAAQ,cAAc,OAAO;AAC/B,WAAK,IAAI,OAAO,MAAM,2FAAsF;AAAA,QAC1G,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AAAA,IACH,OAAO;AACL,UAAI;AACF,cAAM,KAAK,MAAM,IAAI,aAAa,SAAS,EAAE,WAAW,KAAK,CAAC;AAC9D,cAAM,IAAI,IAAI,MAAM;AACpB,YAAI,GAAG;AACL,sBAAY,oBAAoB;AAAA,YAC9B,SAAS,OAAO,EAAE,YAAY,WAAW,EAAE,YAAY,IAAI;AAAA,YAC3D,WAAW;AAAA,UACb;AAAA,QACF;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,MAAM,qCAAqC;AAAA,UACzD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAID,YAAI,CAAC,QAAQ;AACX,sBAAY,oBAAoB,EAAE,SAAS,MAAM,WAAW,MAAM;AAAA,QACpE;AAAA,MACF;AAAA,IACF;AAEA,QAAI,OAAO,KAAK,WAAW,EAAE,WAAW,EAAG;AAC3C,UAAM,UAAU,KAAK,OAAO,IAAI,aAAa,KAAK,CAAC;AACnD,QAAI;AAQF,YAAM,KAAK,OAAO,OAAO;AAAA,QACvB,aAAa,EAAE,GAAG,SAAS,GAAG,YAAY;AAAA,MAC5C,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,+CAA+C;AAAA,QACnE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBS,uBAAgF;AAIvF,QAAI,CAAC,KAAK,UAAU,EAAG,QAAO,CAAC;AAC/B,UAAM,QAAQ,KAAK,cAAiC;AACpD,UAAM,MAAsD,CAAC;AAC7D,UAAM,OAAO,CAAC,MAA0B,MAAkB,UAAwB;AAChF,UAAI,KAAK;AAAA,QACP,gBAAgB,OAAO,IAAI;AAAA,QAC3B,MAAM;AAAA,UACJ;AAAA,UACA,MAAM,GAAG,KAAK,IAAI,IAAI,KAAK;AAAA,QAC7B;AAAA,QACA,QAAQ,EAAE,MAAM,SAAS,EAAE;AAAA,QAC3B,SAAS,CAAC,QAAQ,sBAAsB,MAAM,KAAK,IAAI;AAAA,MACzD,CAAC;AAAA,IACH;AACA,QAAI,MAAM,aAAa,KAAM,MAAKE,eAAc,OAAOF,YAAW,OAAO,gBAAgBE,eAAc,KAAK,CAAC;AAC7G,QAAI,MAAM,kBAAkB,KAAM,MAAKA,eAAc,YAAYF,YAAW,OAAO,gBAAgBE,eAAc,UAAU,CAAC;AAC5H,QAAI,MAAM,iBAAiB,KAAM,MAAKA,eAAc,WAAWF,YAAW,QAAQ,gBAAgBE,eAAc,SAAS,CAAC;AAG1H,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQQ,6BAAmC;AACzC,UAAM,mBAAmE;AAAA,MACvE,aAAa,OAAO,EAAE,SAAS,MAAM;AACnC,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,8CAA8C,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QAC1F;AACA,eAAO,KAAK,8BAA8B;AAAA,MAC5C;AAAA,MACA,iBAAiB,YAAY;AAAA,MAE7B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,oBAAoB,gBAAgB;AAO/D,UAAM,iBAA+D;AAAA,MACnE,QAAQ,OAAO,EAAE,SAAS,MAAM;AAC9B,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,8CAA8C,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QAC1F;AACA,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,OAAO,KAAK,WAAW,CAAC;AAClC,eAAO,EAAE,SAAS,KAAc;AAAA,MAClC;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,kBAAkB,cAAc;AAU3D,UAAM,iBAA+D;AAAA,MACnE,YAAY,OAAO,EAAE,SAAS,MAAM;AAClC,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,8CAA8C,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QAC1F;AACA,eAAO,KAAK,MAAM,OAAO,YAAY;AAAA,MACvC;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,kBAAkB,cAAc;AAI3D,SAAK,YAAY,kBAAkB;AAAA,MACjC,UAAU;AAAA,MACV,gBAAgB;AAAA,MAChB,kBAAkB;AAAA,IACpB,CAAC;AAKD,SAAK,4BAA4B;AAOjC,SAAK,uBAAuB;AAQ5B,SAAK,gCAAgC;AAWrC,SAAK,4BAA4B;AAqBjC,UAAM,QAAQ,KAAK,OAAO,IAAI,aAAa;AAC3C,QAAI,OAAO,eAAe,iBAAiB,OAAO,eAAe,MAAM;AACrE,WAAK,YAAY;AAOjB,WAAK,2BAA2B;AAAA,IAClC;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcQ,gBAAgB;AAAA;AAAA;AAAA;AAAA,EAIhB,yBAAyB;AAAA;AAAA;AAAA;AAAA,EAIzB,2BAAiD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASjD,mBAAmF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASnF,eAAe,MAAqE;AAC1F,UAAM,aAAa,OAAO,KAAK,mBAAmB,WAC9C,KAAK,IAAI,GAAG,KAAK,IAAI,KAAK,KAAK,MAAM,KAAK,cAAc,CAAC,CAAC,IACzD,KAAK,MAAM,QAAQ,cAAc;AACtC,UAAM,WAAW,KAAK,iBAAiB,IAAI,YAAY;AACvD,UAAM,UAAU,KAAK,gBAAgB,IAAI,YAAY;AACrD,UAAM,aAAa,WAAW,cAAc,WAAW;AACvD,UAAM,WAAsC,QAAQ,SAAS,OAAO,IAChE,UACA,aAAa,OAAO;AACxB,UAAM,WAAW,KAAK,aAAa,QAAQ,KAAK;AAChD,WAAO;AAAA,MACL;AAAA,MACA;AAAA,MACA;AAAA,MACA,aAAa,KAAK,IAAI;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,6BAAmC;AACzC,QAAI,KAAK,kBAAmB;AAC5B,QAAI,CAAC,KAAK,UAAW;AACrB,SAAK,oBAAoB;AAEzB,UAAM,WAA0D;AAAA,MAC9D,WAAW,OAAO,EAAE,SAAS,MAAM;AACjC,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,8CAA8C,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QAC1F;AAKA,eAAO,KAAK,MAAM;AAAA,MACpB;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,mBAAmB,QAAQ;AAMtD,QAAI,KAAK,YAAY,iBAAiB,MAAM,MAAM;AAChD,WAAK,YAAY,mBAAmB;AAAA,QAClC,YAAY;AAAA,QACZ,UAAU;AAAA,QACV,UAAU;AAAA,QACV,aAAa,KAAK,IAAI;AAAA,MACxB,CAAC;AAAA,IACH;AAQA,SAAK,aAAa,aAA4B,WAAW,CAAC,UAAU;AAClE,UAAI,CAAC,MAAO;AACZ,WAAK,IAAI,SAAS,KAAK;AAAA,QACrB,cAAc;AAAA,QACd,KAAK,YAAY;AAAA,QACjB,EAAE,UAAU,KAAK,IAAI,QAAQ,MAAM;AAAA,MACrC,CAAC;AAAA,IACH,CAAC;AAQD,SAAK,KAAK,sBAAsB;AAAA,EAClC;AAAA,EAEA,MAAc,wBAAuC;AAOnD,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU;AAAA,IAC7B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,qCAAqC;AAAA,QACzD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,QAAI;AACF,YAAM,OAAO,MAAM,IAAI,eAAe,KAAK,WAAW,CAAC;AACvD,WAAK,mBAAmB,IAAI;AAAA,IAC9B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,0BAA0B;AAAA,QAC9C,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,yBAAyB,MAAwB;AACvD,QAAI,KAAK,aAAa,KAAM,QAAO;AACnC,UAAM,MAAM,KAAK,IAAI;AACrB,QAAI,KAAK,sBAAsB,KACxB,MAAM,KAAK,sBAAsB,eAAc,qBAAqB;AACzE,WAAK,IAAI,OAAO,MAAM,0DAA0D;AAAA,QAC9E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,MAAM,mBAAmB,MAAM,KAAK,oBAAoB;AAAA,MAClE,CAAC;AACD,aAAO;AAAA,IACT;AACA,SAAK,sBAAsB;AAC3B,WAAO;AAAA,EACT;AAAA,EAEQ,mBAAmB,MAA4D;AAOrF,SAAK,YAAY,mBAAmB,KAAK,eAAe,IAAI,CAAC;AAAA,EAC/D;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA+BA,MAAc,0BAAyC;AACrD,QAAI,KAAK,wBAAyB,QAAO,KAAK;AAC9C,UAAM,WAAW,YAA2B;AAC1C,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,UAAU;AAAA,MAC7B,SAAS,KAAK;AACZ,aAAK,mBAAmB;AAAA,UACtB,IAAI,KAAK,IAAI;AAAA,UACb,aAAa;AAAA,UACb,YAAY,CAAC;AAAA,UACb,mBAAmB,CAAC;AAAA,UACpB,UAAU;AAAA,UACV,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AACA;AAAA,MACF;AACA,WAAK,mBAAmB,MAAM,wBAAwB,KAAK;AAAA,QACzD,QAAQ,KAAK,IAAI;AAAA,QACjB,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH,GAAG,EAAE,QAAQ,MAAM;AACjB,WAAK,0BAA0B;AAAA,IACjC,CAAC;AACD,SAAK,0BAA0B;AAC/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAgBQ,2BAA0E;AAChF,WAAO,yBAA+B,KAAK,kBAAkB,EAAE,OAAO,YAAY,OAAO,kBAAkB,CAAC;AAAA,EAC9G;AAAA,EAEA,MAAc,gCAAyF;AACrG,QAAI,KAAK,iBAAkB,QAAO,KAAK;AACvC,UAAM,WAAW,YAAqE;AACpF,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,UAAU;AAAA,MAC7B,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,8BAA8B;AAAA,UACjD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AACD,eAAO;AAAA,MACT;AAUA,YAAM,sBAAsB,KAAK,cAAc,MAAM;AACnD,YAAI;AACF,gBAAM,SAAS,IAAI,eAAe,EAAE,SAAS,KAAK,WAAW,EAAE,CAAC;AAChE,iBAAO,OAAO,UAAU;AAAA,QAC1B,QAAQ;AACN,iBAAO;AAAA,QACT;AAAA,MACF,GAAG;AACH,UAAI,qBAAqB;AACvB,YAAI;AACF,gBAAM,IAAI,OAAO,KAAK,WAAW,GAAG,EAAE,iBAAiB,MAAM,UAAU,EAAE,CAAC;AAAA,QAC5E,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,MAAM,0DAA0D;AAAA,YAC9E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,YAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF;AAEA,YAAM,UAAU,OAAO,cAA8C;AACnE,cAAM,MAAM,MAAM,IAAI,YAAY,KAAK,WAAW,GAAG,EAAE,YAAY,QAAQ,UAAU,CAAC;AACtF,eAAO,OAAO,IAAI,SAAS,IAAI,MAAM;AAAA,MACvC;AAEA,YAAM,eAAe,CAAC,SAA0D;AAAA,QAC9E,QAAQ,IAAI,SAAS,QAAQ;AAAA,QAC7B,aAAa;AAAA,MACf;AAIA,YAAM,eAAe,KAAK,YAAY,OAAS;AAC/C,UAAI;AACF,cAAM,MAAM,MAAM,QAAQ,YAAY;AACtC,YAAI,IAAK,QAAO,aAAa,GAAG;AAAA,MAClC,SAAS,KAAK;AACZ,cAAM,cAAc,2BAA2B,GAAG;AAClD,cAAM,OAAO;AAAA,UACX,SAAS,KAAK,WAAW;AAAA,UACzB;AAAA,UACA,SAAS;AAAA,UACT,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AACA,YAAI,aAAa;AACf,eAAK,IAAI,OAAO,MAAM,yCAAyC,EAAE,MAAM,EAAE,UAAU,KAAK,GAAG,GAAG,KAAK,CAAC;AAAA,QACtG,OAAO;AACL,eAAK,IAAI,OAAO,KAAK,4CAA4C,EAAE,MAAM,EAAE,UAAU,KAAK,GAAG,GAAG,KAAK,CAAC;AAAA,QACxG;AAMA,YAAI,KAAK,WAAW;AAClB,cAAI;AACF,kBAAM,IAAI,OAAO,KAAK,WAAW,GAAG,EAAE,iBAAiB,KAAM,UAAU,GAAG,WAAW,KAAK,CAAC;AAC3F,kBAAM,MAAM,MAAM,QAAQ,IAAM;AAChC,gBAAI,IAAK,QAAO,aAAa,GAAG;AAAA,UAClC,SAAS,UAAU;AACjB,iBAAK,IAAI,OAAO,KAAK,uDAAuD;AAAA,cAC1E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,cAC1B,MAAM;AAAA,gBACJ,SAAS,KAAK,WAAW;AAAA,gBACzB,OAAO,oBAAoB,QAAQ,SAAS,UAAU,OAAO,QAAQ;AAAA,cACvE;AAAA,YACF,CAAC;AAAA,UACH;AAAA,QACF;AAAA,MACF;AACA,aAAO;AAAA,IACT,GAAG;AACH,SAAK,mBAAmB;AACxB,QAAI;AACF,aAAO,MAAM;AAAA,IACf,UAAE;AACA,WAAK,mBAAmB;AAAA,IAC1B;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,qBAAqB;AAAA;AAAA,EAGrB,+BAA+B;AAAA,EAC/B,wBAAuC;AAAA,EAEvC,8BAAoC;AAC1C,QAAI,KAAK,mBAAoB;AAC7B,UAAM,QAAQ,KAAK,cAAiC;AACpD,QAAI,CAAC,MAAM,YAAa;AACxB,SAAK,qBAAqB;AAC1B,UAAM,mBAAmE;AAAA,MACvE,WAAW,aAAa;AAAA,QACtB,eAAe,KAAK;AAAA,QACpB,sBAAsB,KAAK;AAAA,MAC7B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,oBAAoB,gBAAgB;AAAA,EACjE;AAAA;AAAA,EAGQ,qBAAqB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASrB,uBAAoD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBpD,8BAAoC;AAC1C,QAAI,KAAK,mBAAoB;AAO7B,UAAM,QAAQ,KAAK,cAAiC;AACpD,QAAI,MAAM,gBAAgB,KAAM;AAChC,SAAK,qBAAqB;AAE1B,UAAM,WAA2D;AAAA,MAC/D,cAAc,OAAO,EAAE,SAAS,MAAM;AACpC,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,uDAAuD,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QACnG;AACA,YAAI,KAAK,UAAU;AACjB,gBAAM,IAAI,MAAM,wFAAmF;AAAA,QACrG;AACA,cAAM,aAAa,KAAK,qBAAqB;AAC7C,cAAM,MAAM,MAAM,KAAK,UAAU;AAYjC,YAAI,CAAC,KAAK,sBAAsB;AAC9B,eAAK,uBAAuB,IAAI,qBAAqB;AAAA,YACnD,WAAW,UAAU,KAAK,EAAE;AAAA,YAC5B,SAAS,KAAK,WAAW;AAAA,YACzB;AAAA,YACA,QAAQ,KAAK,IAAI,OAAO,SAAS,EAAE,UAAU,KAAK,IAAI,OAAO,WAAW,CAAC;AAAA,YACzE;AAAA,YACA,aAAa,CAAC,EAAE,OAAO,MAAM,IAAI,mBAAmB,EAAE,OAAO,CAAC;AAAA;AAAA;AAAA;AAAA,YAI9D,iBAAiB,KAAK,YAClB,YAAY;AAAE,oBAAM,KAAK,gBAAgB,GAAG;AAAA,YAAE,IAC9C;AAAA,YACJ,GAAI,KAAK,OAAO,IAAI,0BAA0B,MAAM,SAChD,EAAE,kBAAkB,KAAK,OAAO,IAAI,0BAA0B,EAAE,IAChE,CAAC;AAAA,YACL,GAAI,KAAK,OAAO,IAAI,sBAAsB,MAAM,SAC5C,EAAE,cAAc,KAAK,OAAO,IAAI,sBAAsB,EAAE,IACxD,CAAC;AAAA,YACL,GAAI,KAAK,OAAO,IAAI,cAAc,MAAM,SACpC,EAAE,YAAY,KAAK,OAAO,IAAI,cAAc,EAAE,IAC9C,CAAC;AAAA,UACP,CAAC;AAAA,QACH;AACA,eAAO,KAAK,qBAAqB,MAAM;AAAA,MACzC;AAAA,MACA,cAAc,OAAO,EAAE,UAAU,WAAW,UAAU,MAAM;AAC1D,YAAI,aAAa,KAAK,GAAI;AAC1B,YAAI,CAAC,KAAK,sBAAsB;AAC9B,gBAAM,IAAI,MAAM,yEAAyE,SAAS,GAAG;AAAA,QACvG;AACA,cAAM,KAAK,qBAAqB,aAAa,WAAW,SAAS;AAAA,MACnE;AAAA,MACA,aAAa,OAAO,EAAE,UAAU,UAAU,MAAM;AAC9C,YAAI,aAAa,KAAK,GAAI;AAC1B,YAAI,CAAC,KAAK,qBAAsB;AAChC,cAAM,KAAK,qBAAqB,KAAK,SAAS;AAAA,MAChD;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,oBAAoB,QAAQ;AACvD,SAAK,IAAI,OAAO,KAAK,gEAAgE;AAAA,MACnF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYQ,uBAA8C;AACpD,UAAM,OAAO,KAAK,IAAI;AACtB,UAAM,SAAS,KAAK;AACpB,QAAI,CAAC,QAAQ;AACX,YAAM,IAAI;AAAA,QACR;AAAA,MAIF;AAAA,IACF;AACA,WAAO;AAAA,MACL,qBAAqB,CAAC,UAAU,OAAO,oBAAoB,OAAO,KAAK;AAAA,MACvE,kBAAkB,CAAC,UAAU,OAAO,iBAAiB,OAAO,KAAK;AAAA,MACjE,SAAS,CAAC,UAAU,OAAO,QAAQ,MAAM,KAAK;AAAA,MAC9C,cAAc,CAAC,UAAU,OAAO,aAAa,OAAO,KAAK;AAAA,IAC3D;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,gBAAgB,KAAwC;AACpE,UAAM,UAAU,KAAK,WAAW;AAChC,QAAI;AACJ,QAAI;AACF,oBAAc,IAAI,eAAe,EAAE,QAAQ,CAAC;AAAA,IAC9C,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,2EAAsE;AAAA,QAC1F,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,QAAI,YAAY,UAAU,WAAY;AACtC,SAAK,IAAI,OAAO,KAAK,+DAA0D;AAAA,MAC7E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,IAC5B,CAAC;AACD,QAAI;AACF,YAAM,IAAI,OAAO,SAAS,EAAE,iBAAiB,IAAK,CAAC;AAKnD,YAAM,IAAI,QAAc,CAAC,YAAY,WAAW,SAAS,GAAI,CAAC;AAAA,IAChE,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,oDAA+C;AAAA,QAClE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,oBAAoB,WAAyB;AACnD,SAAK,wBAAwB;AAC7B,SAAK,gCAAgC;AACrC,SAAK,IAAI,SAAS,KAAK;AAAA,MACrB,cAAc;AAAA,MACd,KAAK,YAAY;AAAA,MACjB,EAAE,UAAU,KAAK,IAAI,UAAU;AAAA,IACjC,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,kCAAwC;AAC9C,QAAI,KAAK,uBAAwB;AACjC,UAAM,QAAQ,KAAK,cAAiC;AACpD,QAAI,CAAC,MAAM,aAAc;AACzB,SAAK,yBAAyB;AAE9B,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,WAAW;AAIjB,UAAMC,YAAW;AAOjB,UAAM,yBAAqE;AAAA,MACzE,EAAE,OAAO,UAAU,OAAO,SAAS;AAAA,MACnC,EAAE,OAAO,WAAW,OAAO,UAAU;AAAA,MACrC,EAAE,OAAO,OAAO,OAAO,MAAM;AAAA,MAC7B,EAAE,OAAO,kBAAkB,OAAO,mBAAmB;AAAA,IACvD;AAEA,UAAM,2BAA2B,CAAC,QAA2D;AAC3F,YAAM,MAAM,KAAK,MAAM;AACvB,UAAI,CAAC,IAAK,QAAO;AACjB,aAAO;AAAA,QACL,YAAY,OAAO,IAAI,mBAAmB,WAAW,IAAI,iBAAiB;AAAA,QAC1E,kBAAkB,OAAO,IAAI,6BAA6B,EAAE;AAAA,QAC5D,uBAAuB,OAAO,IAAI,kCAAkC,EAAE;AAAA,MACxE;AAAA,IACF;AAEA,UAAM,YAAY,MAChB,KAAK,aAAa,YAAsC,QAAQ;AAKlE,UAAM,oBAAoB,YAA2B;AACnD,UAAI,KAAK,yBAA0B,QAAO,KAAK;AAC/C,YAAM,WAAW,YAAY;AAC3B,YAAI;AACF,gBAAM,MAAM,MAAM,KAAK,UAAU;AACjC,gBAAM,QAAQ,MAAM,IAAI,gBAAgB,OAAO;AAC/C,gBAAM,UAAU,QAAQ,OAAO,OAAO;AACtC,gBAAM,aAAa,yBAAyB,OAAO,GAAG;AACtD,gBAAM,OAAO,UAAU;AACvB,eAAK,aAAa,YAAY,UAAU;AAAA,YACtC;AAAA,YACA,eAAe,MAAM,YAAY,UAAW,MAAM,iBAAiB,IAAK,KAAK,IAAI;AAAA,YACjF,iBAAiB,cAAc,MAAM,mBAAmB;AAAA,YACxD,sBAAsB;AAAA,YACtB,eAAe,KAAK,IAAI;AAAA,UAC1B,CAAC;AAAA,QACH,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,MAAM,sDAAiD;AAAA,YACrE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,YAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH;AAAA,MACF,GAAG;AACH,WAAK,2BAA2B;AAChC,UAAI;AAAE,cAAM;AAAA,MAAQ,UAAE;AAAU,aAAK,2BAA2B;AAAA,MAAK;AAAA,IACvE;AAEA,UAAM,SAASC,0BAAyB;AAAA,MACtC,cAAc,KAAK;AAAA,MACnB,KAAK;AAAA,MACL,aAAa,KAAK;AAAA,MAClB,SAAS;AAAA,MACT,SAASD;AAAA,MACT,OAAO,OAA2B;AAAA,QAChC,SAAS;AAAA,QACT,eAAe;AAAA,QACf,iBAAiB;AAAA,QACjB,sBAAsB;AAAA,MACxB;AAAA,IACF,CAAC;AAED,UAAM,WAA+D;AAAA,MACnE,WAAW,OAAO;AAAA,MAClB,YAAY,OAAO,EAAE,UAAU,QAAQ,MAAM;AAC3C,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,gBAAgB,SAAS,OAAO;AAC1C,cAAM,OAAO,UAAU;AACvB,aAAK,aAAa,cAAc,UAAU;AAAA,UACxC;AAAA,UACA,eAAe,MAAM,YAAY,UAAW,MAAM,iBAAiB,IAAK,KAAK,IAAI;AAAA,UACjF,eAAe,KAAK,IAAI;AAAA,QAC1B,CAAC;AAAA,MACH;AAAA,MACA,aAAa,OAAO,EAAE,SAAS,MAAM;AACnC,YAAI,aAAa,KAAK,GAAI,QAAO;AACjC,cAAM,OAAO,YAAY;AACzB,eAAO,UAAU,GAAG,mBAAmB;AAAA,MACzC;AAAA,MACA,aAAa,OAAO,EAAE,UAAU,SAAS,MAAM;AAC7C,YAAI,aAAa,KAAK,GAAI;AAI1B,cAAM,UAAU,UAAU,GAAG,mBAAmB;AAAA,UAC9C,YAAY;AAAA,UACZ,kBAAkB;AAAA,UAClB,uBAAuB;AAAA,QACzB;AACA,cAAM,SAA+B;AAAA,UACnC,YAAY,SAAS,cAAc,QAAQ;AAAA,UAC3C,kBAAkB,SAAS,oBAAoB,QAAQ;AAAA,UACvD,uBAAuB,SAAS,yBAAyB,QAAQ;AAAA,QACnE;AAIA,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,aAAgE;AAAA,UACpE,2BAA2B,OAAO;AAAA,UAClC,gCAAgC,OAAO;AAAA,QACzC;AACA,YAAI,OAAO,WAAW,SAAS,EAAG,YAAW,iBAAiB,OAAO;AACrE,cAAM,IAAI,wBAAwB,SAAS,UAAU;AAGrD,cAAM,kBAAkB;AAAA,MAC1B;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,wBAAwB,QAAQ;AAC3D,SAAK,IAAI,OAAO,KAAK,wCAAwC;AAAA,MAC3D,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,IAC5B,CAAC;AAAA,EACH;AAAA,EAEQ,yBAA+B;AACrC,QAAI,KAAK,cAAe;AACxB,UAAM,QAAQ,KAAK,cAAiC;AACpD,QAAI,CAAC,MAAM,OAAQ;AACnB,SAAK,gBAAgB;AACrB;AACE,YAAM,cAAyD;AAAA,QAC7D,MAAM,OAAO,EAAE,UAAU,KAAK,MAAM,MAAM,MAAM,MAAM;AACpD,cAAI,aAAa,KAAK,GAAI;AAC1B,gBAAM,KAAK;AAAA,YAAO;AAAA,YAAK;AAAA,YAAM;AAAA,YAAM;AAAA;AAAA,YAAwB;AAAA,UAAK;AAAA,QAClE;AAAA,QACA,gBAAgB,OAAO,EAAE,UAAU,KAAK,MAAM,MAAM,MAAM,MAAM;AAC9D,cAAI,aAAa,KAAK,GAAI;AAC1B,gBAAM,KAAK;AAAA,YAAO;AAAA,YAAK;AAAA,YAAM;AAAA,YAAM;AAAA;AAAA,YAAwB;AAAA,UAAI;AAAA,QACjE;AAAA,QACA,MAAM,OAAO,EAAE,SAAS,MAAM;AAC5B,cAAI,aAAa,KAAK,GAAI;AAC1B,gBAAM,MAAM,MAAM,KAAK,UAAU;AACjC,gBAAM,UAAU,KAAK,WAAW;AAKhC,cAAI;AAAE,kBAAM,IAAI,IAAI,SAAS,EAAE,QAAQ,QAAQ,SAAS,KAAK,CAAC;AAAA,UAAE,QAAQ;AAAA,UAAe;AAAA,QACzF;AAAA,QACA,YAAY,OAAO,EAAE,SAAS,MAAM;AAClC,cAAI,aAAa,KAAK,GAAI,QAAO,CAAC;AAClC,cAAI;AACF,kBAAM,MAAM,MAAM,KAAK,UAAU;AACjC,kBAAM,UAAU,KAAK,WAAW;AAChC,kBAAM,UAAU,MAAM,IAAI,cAAc,OAAO;AAC/C,mBAAO,QAAQ,IAAI,CAAC,OAAO,EAAE,IAAI,OAAO,EAAE,EAAE,GAAG,MAAM,EAAE,KAAK,EAAE;AAAA,UAChE,QAAQ;AAAE,mBAAO,CAAC;AAAA,UAAE;AAAA,QACtB;AAAA,QACA,YAAY,OAAO,EAAE,UAAU,SAAS,MAAM;AAC5C,cAAI,aAAa,KAAK,GAAI;AAC1B,gBAAM,MAAM,MAAM,KAAK,UAAU;AACjC,gBAAM,UAAU,KAAK,WAAW;AAChC,gBAAM,KAAK,OAAO,QAAQ;AAC1B,cAAI,CAAC,OAAO,SAAS,EAAE,EAAG,OAAM,IAAI,MAAM,qBAAqB,QAAQ,EAAE;AACzE,gBAAM,IAAI,gBAAgB,SAAS,EAAE;AAAA,QACvC;AAAA,QACA,QAAQ,OAAO,EAAE,SAAS,MAAM;AAC9B,cAAI,aAAa,KAAK,GAAI;AAI1B,cAAI;AACF,kBAAM,MAAM,MAAM,KAAK,UAAU;AACjC,kBAAM,UAAU,KAAK,WAAW;AAChC,kBAAM,IAAI,gBAAgB,SAAS,CAAC;AAAA,UACtC,QAAQ;AAAA,UAAe;AAAA,QACzB;AAAA,QACA,aAAa,OAAO,EAAE,SAAS,MAAM;AACnC,cAAI,aAAa,KAAK,GAAI,QAAO,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,EAAE;AAG5D,iBAAO,EAAE,KAAK,GAAG,MAAM,GAAG,MAAM,EAAE;AAAA,QACpC;AAAA,MACF;AACA,WAAK,IAAI,kBAAkB,eAAe,WAAW;AAAA,IACvD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYA,MAAc,OACZ,KACA,MACA,MACA,OACA,YACe;AACf,UAAM,MAAM,MAAM,KAAK,UAAU;AACjC,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,OAAuE,CAAC;AAC9E,QAAI,QAAQ,UAAa,KAAK,IAAI,GAAG,IAAI,KAAM,MAAK,KAAK,MAAM,IAAI,UAAU,MAAM;AACnF,QAAI,SAAS,UAAa,KAAK,IAAI,IAAI,IAAI,KAAM,MAAK,KAAK,OAAO,IAAI,OAAO,MAAM;AACnF,QAAI,SAAS,UAAa,KAAK,IAAI,IAAI,IAAI,KAAM,MAAK,KAAK,OAAO,IAAI,WAAW,SAAS;AAC1F,QAAI,KAAK,WAAW,EAAG;AAEvB,UAAM,gBAAgB,UAAU,SAC5B,KACA,KAAK,IAAI,GAAG,KAAK,IAAI,IAAI,KAAK,MAAM,QAAQ,EAAE,CAAC,CAAC;AACpD,eAAW,WAAW,MAAM;AAC1B,UAAI;AACF,cAAM,IAAI,IAAI,SAAS;AAAA,UACrB,QAAQ;AAAA,UACR;AAAA,UACA,OAAO;AAAA,UACP,GAAI,aAAa,CAAC,IAAI,EAAE,YAAY,IAAI;AAAA,QAC1C,CAAC;AAAA,MACH,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,+BAA+B;AAAA,UAClD,MAAM,EAAE,UAAU,KAAK,IAAI,QAAQ;AAAA,UACnC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA,EAIA,MAAM,mBAA0D;AAC9D,UAAM,UAAU,eAAe,KAAK,aAAa,CAAC;AAClD,WAAO,QAAQ,IAAI,CAAC,GAAG,OAA0B;AAAA,MAC/C,IAAI,EAAE;AAAA,MACN,OAAO,EAAE;AAAA,MACT,UAAU;AAAA,MACV,aAAa,IAAI,MAAM,IAAI,SAAS;AAAA,IACtC,EAAE;AAAA,EACJ;AAAA,EAEQ,eAAuB;AAC7B,WAAO,KAAK,OAAO,IAAI,aAAa,GAAG,gBAAgB;AAAA,EACzD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAsCA,MAAM,kBAAiC;AAUrC,QAAI,KAAK,aAAa,KAAK,UAAU;AACnC,WAAK,IAAI,OAAO,MAAM,0DAAqD;AAAA,QACzE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AACA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU;AAAA,IAC7B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,oEAA+D;AAAA,QACnF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,QAAI;AAQJ,UAAM,UAAU,KAAK,WAAW;AAChC,UAAM,aAAa,KAAK,WAAW;AACnC,UAAM,eAAe,UAAU,EAAE,SAAS,YAAY,OAAO,KAAK,IAAI;AACtE,QAAI;AACF,sBAAgB,eACZ,MAAM,IAAI,wBAAwB,YAAY,IAC9C,MAAM,IAAI,wBAAwB;AAAA,IACxC,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,mDAAmD;AAAA,QACtE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,UAAM,eAAe,KAAK,aAAa;AACvC,UAAM,UAAU,oBAAI,IAUjB;AA2BH,eAAW,KAAK,cAAc,eAAe;AAC3C,YAAM,kBAAkB,UAAU,aAAa;AAC/C,YAAM,KAAK,iBAAiB,UAAU,EAAE,WAAW,iBAAiB,EAAE,SAAS,YAAY;AAC3F,YAAM,IAAI,EAAE;AAKZ,YAAM,QAAQ,mBAAmB,GAAG,YAAY,KAAK;AACrD,YAAM,MAAM,yBAAyB;AAAA,QACnC,cAAc,GAAG,gBAAgB;AAAA,QACjC,eAAe,GAAG,SAAS,OAAO;AAAA,MACpC,CAAC;AACD,YAAM,QAUF;AAAA,QACF,aAAa;AAAA,QACb,MAAM;AAAA,QACN,OAAO,EAAE,QAAQ,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO,YAAY,GAAG,QAAQ;AAAA,QACrE,KAAK,oBAAoB,EAAE;AAAA,QAC3B,cAAc;AAAA,QACd,GAAI,KAAK,EAAE,QAAQ,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,QAC/F,GAAI,KAAK,EAAE,YAAY,IAAI,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC;AAAA,QACnD;AAAA,QACA,UAAU,EAAE,IAAI;AAAA,MAClB;AACA,cAAQ,IAAI,IAAI,KAAK;AAAA,IACvB;AAIA,UAAM,aAAa,CACjB,MACA,YACS;AACT,UAAI,QAAQ,WAAW,EAAG;AAC1B,YAAM,aAAa,SAAS,SAAS,cAAc;AACnD,YAAM,kBAAkB,UAAU,aAAa;AAC/C,iBAAW,KAAK,SAAS;AACvB,cAAM,KAAK,iBAAiB,MAAM,EAAE,WAAW,iBAAiB,EAAE,SAAS,YAAY;AACvF,YAAI,QAAQ,IAAI,EAAE,EAAG;AACrB,cAAM,IAAI,EAAE;AACZ,gBAAQ,IAAI,IAAI;AAAA,UACd,aAAa;AAAA,UACb,MAAM;AAAA,UACN,OAAO,EAAE,QAAQ,EAAE,KAAK,SAAS,IAAI,EAAE,OAAO,YAAY,GAAG,IAAI;AAAA,UACjE,KAAK,EAAE;AAAA,UACP,cAAc;AAAA,UACd,GAAI,KAAK,EAAE,QAAQ,KAAK,EAAE,SAAS,IAAI,EAAE,YAAY,EAAE,OAAO,EAAE,OAAO,QAAQ,EAAE,OAAO,EAAE,IAAI,CAAC;AAAA,UAC/F,GAAI,KAAK,EAAE,YAAY,IAAI,EAAE,KAAK,EAAE,UAAU,IAAI,CAAC;AAAA,UACnD,GAAI,mBAAmB,GAAG,YAAY,IAAI,EAAE,OAAO,mBAAmB,GAAG,YAAY,EAAG,IAAI,CAAC;AAAA,QAC/F,CAAC;AAAA,MACH;AAAA,IACF;AACA,eAAW,QAAQ,cAAc,WAAW;AAC5C,eAAW,QAAQ,cAAc,WAAW;AAE5C,QAAI,QAAQ,SAAS,GAAG;AACtB,WAAK,IAAI,OAAO,KAAK,wEAAmE;AAAA,QACtF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AAKA,UAAM,UAAoB,CAAC;AAC3B,eAAW,MAAM,KAAK,oBAAoB;AACxC,UAAI,CAAC,QAAQ,IAAI,EAAE,EAAG,SAAQ,KAAK,EAAE;AAAA,IACvC;AACA,eAAW,MAAM,SAAS;AACxB,UAAI;AACF,cAAM,KAAK,IAAI,IAAI,aAAa,oBAAoB,OAAO,EAAE,UAAU,KAAK,IAAI,aAAa,GAAG,CAAC;AAAA,MACnG,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,MAAM,sCAAsC;AAAA,UAC1D,MAAM,EAAE,UAAU,KAAK,IAAI,aAAa,GAAG;AAAA,UAC3C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AACA,WAAK,mBAAmB,OAAO,EAAE;AAAA,IACnC;AAEA,eAAW,KAAK,QAAQ,OAAO,GAAG;AAChC,YAAM,KAAK,WAAW,CAAC;AAAA,IACzB;AAAA,EACF;AAAA,EAEA,MAAc,WAAW,OAUP;AAChB,QAAI;AACF,YAAM,KAAK,IAAI,IAAI,aAAa,oBAAoB,OAAO;AAAA,QACzD,UAAU,KAAK;AAAA,QACf,aAAa,MAAM;AAAA,QACnB,MAAM,MAAM;AAAA,QACZ,OAAO,MAAM;AAAA,QACb,GAAI,MAAM,MAAM,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,QACtC,GAAI,MAAM,aAAa,EAAE,YAAY,MAAM,WAAW,IAAI,CAAC;AAAA,QAC3D,GAAI,MAAM,QAAQ,SAAY,EAAE,KAAK,MAAM,IAAI,IAAI,CAAC;AAAA,QACpD,GAAI,MAAM,UAAU,SAAY,EAAE,OAAO,MAAM,MAAM,IAAI,CAAC;AAAA,QAC1D,GAAI,MAAM,WAAW,EAAE,UAAU,MAAM,SAAS,IAAI,CAAC;AAAA,QACrD,gBAAgB,CAAC,GAAG,KAAK,QAAQ;AAAA,QACjC,cAAc,MAAM;AAAA,MACtB,CAAC;AACD,WAAK,mBAAmB,IAAI,MAAM,WAAW;AAAA,IAC/C,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,KAAK,8BAA8B;AAAA,QACjD,MAAM,EAAE,UAAU,KAAK,IAAI,aAAa,MAAM,YAAY;AAAA,QAC1D,MAAM,EAAE,MAAM,MAAM,MAAM,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MACpF,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAM,wBAAwB,aAAoC;AAChE,UAAM,QAAQ,iBAAiB,aAAa,KAAK,WAAW,CAAC;AAC7D,QAAI,CAAC,SAAS,MAAM,SAAS,UAAU;AAGrC;AAAA,IACF;AACA,UAAM,SAAS,MAAM,KAAK,oBAAoB,aAAa,MAAM,SAAS,MAAM,OAAO;AACvF,QAAI,CAAC,QAAQ;AACX,WAAK,IAAI,OAAO,KAAK,8DAA8D;AAAA,QACjF,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,MACzC,CAAC;AACD;AAAA,IACF;AACA,UAAM,OAAO,GAAG,mBAAmB,OAAO,QAAQ,CAAC,IAAI,mBAAmB,OAAO,QAAQ,CAAC;AAC1F,UAAM,MAAM,SAAS,IAAI,IAAI,OAAO,IAAI,IAAI,OAAO,IAAI;AAMvD,UAAM,QAAQ,OAAO,cAAc,SAAS,SAAS;AACrD,UAAM,KAAK,WAAW;AAAA,MACpB;AAAA,MACA,MAAM;AAAA,MACN,OAAO;AAAA,MACP;AAAA,MACA;AAAA,MACA,cAAc;AAAA,MACd,UAAU,EAAE,KAAK,OAAO,IAAI;AAAA,IAC9B,CAAC;AACD,SAAK,IAAI,OAAO,KAAK,uDAAuD;AAAA,MAC1E,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,MACvC,MAAM,EAAE,KAAK,SAAS,OAAO,IAAI,IAAI,OAAO,IAAI,GAAG;AAAA,IACrD,CAAC;AAAA,EACH;AAAA,EAEA,MAAe,eAA8B;AAC3C,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,SAAK,IAAI,OAAO,KAAK,2BAA2B,EAAE,MAAM,EAAE,UAAU,KAAK,SAAS,EAAE,CAAC;AACrF,UAAM,MAAM,CAAC,GAAG,KAAK,kBAAkB;AACvC,UAAM,QAAQ;AAAA,MACZ,IAAI;AAAA,QAAI,CAAC,OACP,KAAK,IAAI,IAAI,aAAa,oBACvB,OAAO,EAAE,UAAU,KAAK,IAAI,aAAa,GAAG,CAAC,EAC7C,MAAM,CAAC,QAAiB;AACvB,eAAK,IAAI,OAAO,MAAM,8BAA8B;AAAA,YAClD,MAAM,EAAE,UAAU,KAAK,IAAI,aAAa,GAAG;AAAA,YAC3C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH,CAAC;AAAA,MACL;AAAA,IACF;AACA,SAAK,mBAAmB,MAAM;AAC9B,UAAM,KAAK,cAAc;AAAA,EAC3B;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAM,oBAAoB,aAAqB,SAAiB,SAAmE;AAQjI,UAAM,WAAW,KAAK,OAAO,IAAI,WAAW;AAC5C,QAAI,UAAU,QAAQ,QAAQ,UAAW,QAAO,SAAS;AACzD,QAAI,UAAU;AACZ,WAAK,IAAI,OAAO,KAAK,oDAA+C;AAAA,QAClE,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,MACzC,CAAC;AACD,UAAI;AAAE,cAAM,SAAS,OAAO,MAAM,UAAU;AAAA,MAAE,QAAQ;AAAA,MAAuB;AAC7E,WAAK,OAAO,OAAO,WAAW;AAAA,IAChC;AAEA,QAAI;AACJ,QAAI;AACF,YAAM,MAAM,KAAK,UAAU;AAAA,IAC7B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,kCAAkC;AAAA,QACtD,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,QACvC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAKD,aAAO;AAAA,IACT;AAMA,UAAM,WAAW,MAAM,KAAK,EAAE;AAC9B,UAAM,WAAW,YAAY,EAAE;AAE/B,QAAI;AACJ,QAAI;AACF,eAAS,MAAM,uBAAuB;AAAA,QACpC;AAAA;AAAA;AAAA,QAGA,oBAAoB;AAAA,QACpB;AAAA,QACA;AAAA,QACA,MAAM;AAAA,QACN;AAAA,QACA;AAAA,QACA,aAAa;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASb,UAAU,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,QAKf,aAAa;AAAA;AAAA;AAAA,QAGb,QAAQ,KAAK,iBAAiB;AAAA,MAChC,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,0CAA0C;AAAA,QAC9D,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,QACvC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD,aAAO;AAAA,IACT;AAEA,UAAM,QAAQ,EAAE,aAAa,SAAS,KAAK,oBAAoB,WAAW,GAAG,OAAO;AACpF,SAAK,OAAO,IAAI,aAAa,KAAK;AASlC,WAAO,OAAO,KAAK,SAAS,MAAM;AAChC,YAAM,UAAU,KAAK,OAAO,IAAI,WAAW;AAC3C,UAAI,SAAS,WAAW,QAAQ;AAC9B,aAAK,OAAO,OAAO,WAAW;AAC9B,aAAK,IAAI,OAAO,MAAM,sDAAiD;AAAA,UACrE,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,QACzC,CAAC;AAAA,MACH;AAAA,IACF,CAAC;AAED,SAAK,IAAI,OAAO,KAAK,6BAA6B;AAAA,MAChD,MAAM,EAAE,UAAU,KAAK,IAAI,YAAY;AAAA,MACvC,MAAM,EAAE,MAAM,OAAO,MAAM,MAAM,OAAO,MAAM,WAAW,OAAO,WAAW,OAAO,OAAO,OAAO,SAAS,KAAK;AAAA,IAChH,CAAC;AAED,WAAO;AAAA,EACT;AAAA;AAAA,EAGQ,oBAAoB,aAAiC;AAI3D,QAAI,YAAY,SAAS,MAAM,KAAK,YAAY,SAAS,MAAM,EAAG,QAAO;AACzE,QAAI,YAAY,SAAS,MAAM,KAAK,YAAY,SAAS,MAAM,EAAG,QAAO;AACzE,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOQ,mBAA4B;AAClC,UAAM,MAAM,KAAK,IAAI,OAAO,MAAM,UAAU;AAC5C,UAAM,OAAO,CAAC,UAA+C,IAAI,SAAoB;AACnF,YAAM,UAAU,KAAK,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,CAAC,CAAE,EAAE,KAAK,GAAG;AACzF,UAAI,KAAK,EAAE,SAAS,CAAC,CAAC;AAAA,IACxB;AAEA,WAAO,EAAE,KAAK,KAAK,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,MAAM,KAAK,MAAM,GAAG,OAAO,KAAK,OAAO,GAAG,OAAO,KAAK,OAAO,EAAE;AAAA,EACjH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,mBAAmB,QAA+B;AAC9D,UAAM,UAAU,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC;AACxC,SAAK,OAAO,MAAM;AAClB,QAAI,QAAQ,WAAW,EAAG;AAC1B,SAAK,IAAI,OAAO,MAAM,mCAAmC;AAAA,MACvD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC1B,MAAM,EAAE,OAAO,QAAQ,QAAQ,OAAO;AAAA,IACxC,CAAC;AACD,eAAW,SAAS,SAAS;AAC3B,UAAI;AAAE,cAAM,MAAM,OAAO,MAAM,UAAU;AAAA,MAAE,QAAQ;AAAA,MAAoB;AAAA,IACzE;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAuB;AAC7B,QAAI,KAAK,eAAgB;AACzB,QAAI,CAAC,KAAK,UAAW;AAQrB,QAAI,KAAK,WAAW,EAAG;AAIvB,UAAM,UAAU;AAChB,SAAK,iBAAiB,YAAY,MAAM;AACtC,YAAM,MAAM,KAAK;AACjB,UAAI,CAAC,IAAK;AACV,UAAI;AACF,cAAM,SAAS,IAAI,eAAe,EAAE,SAAS,KAAK,WAAW,EAAE,CAAC;AAChE,YAAI,OAAO,UAAU,cAAc,CAAC,KAAK,UAAU;AACjD,eAAK,MAAM,QAAQ,WAAW;AAC9B,eAAK,IAAI,OAAO,KAAK,2DAAsD;AAAA,YACzE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,YAC1B,MAAM,EAAE,QAAQ,OAAO,OAAO;AAAA,UAChC,CAAC;AACD,eAAK,KAAK,mBAAmB,YAAY,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC9E,WAAW,OAAO,UAAU,WAAW,KAAK,UAAU;AACpD,eAAK,MAAM,QAAQ,WAAW;AAC9B,eAAK,IAAI,OAAO,MAAM,6BAA6B,EAAE,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,CAAC;AAIlF,eAAK,KAAK,iBAAiB,YAAY,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC5E;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,MAAM,oBAAoB;AAAA,UACxC,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF,GAAG,OAAO;AACV,QAAI,OAAO,KAAK,eAAe,UAAU,WAAY,MAAK,eAAe,MAAM;AAAA,EACjF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,gBAAgB;AACvB,oBAAc,KAAK,cAAc;AACjC,WAAK,iBAAiB;AAAA,IACxB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAoBA,uBAAuB,KAAgE;AACrF,SAAK,iBAAiB,IAAI,IAAI,MAAM,GAAG;AAAA,EACzC;AAAA;AAAA;AAAA;AAAA,EAKA,MAAc,qBAAqB,QAAqD;AACtF,QAAI,KAAK,iBAAiB,SAAS,EAAG;AACtC,QAAI,KAAK,aAAa,KAAK,YAAY,WAAW,YAAY;AAI5D;AAAA,IACF;AACA,UAAM,OAAO,CAAC,GAAG,KAAK,iBAAiB,OAAO,CAAC;AAC/C,UAAM,UAAoB,CAAC;AAC3B,UAAM,WAA4B,CAAC;AACnC,eAAW,OAAO,MAAM;AACtB,UAAI,IAAI,aAAa,GAAG;AACtB,gBAAQ,KAAK,IAAI,IAAI;AACrB;AAAA,MACF;AACA,eAAS,KAAK,IAAI,iBAAiB,EAAE,MAAM,MAAM;AAAA,MAAgC,CAAC,CAAC;AAAA,IACrF;AACA,UAAM,QAAQ,WAAW,QAAQ;AACjC,QAAI,QAAQ,SAAS,GAAG;AACtB,WAAK,IAAI,OAAO,MAAM,4CAA4C;AAAA,QAChE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,QAAQ,QAAQ;AAAA,MAC1B,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA,EAIQ,uBAA6B;AACnC,QAAI,KAAK,cAAe;AACxB,QAAI,KAAK,UAAW;AACpB,UAAM,UAAU;AAChB,SAAK,gBAAgB,YAAY,MAAM;AACrC,WAAK,KAAK,qBAAqB,UAAU;AAAA,IAC3C,GAAG,OAAO;AACV,QAAI,OAAO,KAAK,cAAc,UAAU,WAAY,MAAK,cAAc,MAAM;AAAA,EAC/E;AAAA,EAEQ,sBAA4B;AAClC,QAAI,KAAK,eAAe;AACtB,oBAAc,KAAK,aAAa;AAChC,WAAK,gBAAgB;AAAA,IACvB;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAc,iBAAiB,QAAsD;AACnF,SAAK,IAAI,OAAO,MAAM,sDAAiD;AAAA,MACrE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC1B,MAAM,EAAE,OAAO;AAAA,IACjB,CAAC;AACD,UAAM,QAAQ,WAAW;AAAA,MACvB,KAAK,qBAAqB,MAAM;AAAA,MAChC,KAAK,8BAA8B;AAAA,IACrC,CAAC;AAAA,EACH;AAAA;AAAA,EAIA,OAAwB,mBAAmB;AAAA,EAC3C,OAAwB,oBAAoB;AAAA,EAC5C,OAAwB,0BAA0B;AAAA,EAClD,OAAwB,2BAA2B,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAWhD,iBAAuB;AAC7B,SAAK,cAAc;AACnB,SAAK,oBAAoB,KAAK,IAAI;AAClC,SAAK,0BAA0B;AAE/B,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,oBAAoB,YAAY,MAAM;AACzC,aAAK,KAAK,gBAAgB,EAAE,MAAM,MAAM;AAAA,QAAsB,CAAC;AAAA,MACjE,GAAG,eAAc,gBAAgB;AACjC,UAAI,OAAO,KAAK,kBAAkB,UAAU,WAAY,MAAK,kBAAkB,MAAM;AAAA,IACvF;AAYA,QAAI,CAAC,KAAK,WAAW;AACnB,WAAK,qBAAqB,YAAY,MAAM;AAC1C,aAAK,KAAK,iBAAiB,EAAE,MAAM,MAAM;AAAA,QAAsB,CAAC;AAAA,MAClE,GAAG,eAAc,uBAAuB;AACxC,UAAI,OAAO,KAAK,mBAAmB,UAAU,WAAY,MAAK,mBAAmB,MAAM;AAAA,IACzF;AAAA,EACF;AAAA,EAEQ,gBAAsB;AAC5B,QAAI,KAAK,mBAAmB;AAC1B,oBAAc,KAAK,iBAAiB;AACpC,WAAK,oBAAoB;AAAA,IAC3B;AACA,QAAI,KAAK,oBAAoB;AAC3B,oBAAc,KAAK,kBAAkB;AACrC,WAAK,qBAAqB;AAAA,IAC5B;AACA,SAAK,qBAAqB;AAC1B,SAAK,0BAA0B;AAAA,EACjC;AAAA,EAEA,MAAc,kBAAiC;AAC7C,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,CAAC,IAAI,OAAO,kBAAkB,GAAG;AAGnC,WAAK,0BAA0B;AAC/B;AAAA,IACF;AACA,QAAI;AACF,YAAM,IAAI,KAAK;AACf,WAAK,0BAA0B;AAAA,IACjC,SAAS,KAAK;AACZ,WAAK;AACL,YAAM,cAAc,2BAA2B,GAAG;AAClD,WAAK,IAAI,OAAO,MAAM,uBAAuB;AAAA,QAC3C,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM;AAAA,UACJ,UAAU,KAAK;AAAA,UACf;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AAAA,MACF,CAAC;AACD,UAAI,KAAK,2BAA2B,eAAc,mBAAmB;AACnE,aAAK,IAAI,OAAO,KAAK,iEAA4D;AAAA,UAC/E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,UAAU,KAAK,wBAAwB;AAAA,QACjD,CAAC;AACD,aAAK,0BAA0B;AAC/B,aAAK,kBAAkB,eAAe;AAAA,MACxC;AAAA,IACF;AAAA,EACF;AAAA,EAEA,MAAc,mBAAkC;AAC9C,UAAM,MAAM,KAAK;AACjB,QAAI,CAAC,IAAK;AACV,QAAI,CAAC,IAAI,OAAO,kBAAkB,KAAK,CAAC,IAAI,OAAO,SAAU;AAE7D,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,YAAY,KAAK,cAAc,IAAI,KAAK,cAAc,KAAK;AACjE,UAAM,SAAS,MAAM;AACrB,QAAI,SAAS,eAAc,yBAA0B;AAErD,SAAK,IAAI,OAAO,KAAK,4DAA4D;AAAA,MAC/E,MAAM,EAAE,QAAQ,aAAa,KAAK,YAAY;AAAA,IAChD,CAAC;AACD,QAAI;AACF,YAAM,KAAK,wBAAwB,KAAK,UAAU;AAGlD,WAAK,oBAAoB,KAAK,IAAI;AAAA,IACpC,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,8CAA8C;AAAA,QAClE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBA,MAAc,wBACZ,KACA,QACe;AACf,QAAI;AACF,YAAM,IAAI,eAAe,KAAK,sBAAsB;AAAA,IACtD,QAAQ;AAAA,IAIR;AACA,QAAI,CAAC,IAAI,OAAO,kBAAkB,KAAK,CAAC,IAAI,OAAO,UAAU;AAC3D,WAAK,IAAI,OAAO,MAAM,iEAA4D;AAAA,QAChF,MAAM,EAAE,QAAQ,WAAW,IAAI,OAAO,kBAAkB,GAAG,UAAU,IAAI,OAAO,SAAS;AAAA,MAC3F,CAAC;AACD;AAAA,IACF;AACA,UAAM,IAAI,cAAc,KAAK,sBAAsB;AACnD,SAAK,IAAI,OAAO,MAAM,kCAAkC,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAAA,EAC9E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBQ,wBAA8B;AACpC,SAAK,qBAAqB;AAC1B,SAAK,+BAA+B;AACpC,SAAK,yBAAyB;AAC9B,SAAK,wBAAwB,YAAY,MAAM;AAC7C,YAAM,YAAY;AAChB,cAAM,MAAM,KAAK;AACjB,YAAI,CAAC,IAAK;AACV,YAAI,CAAC,IAAI,OAAO,kBAAkB,KAAK,CAAC,IAAI,OAAO,SAAU;AAK7D,cAAM,MAAM,KAAK,IAAI;AACrB,YAAI,MAAM,KAAK,uBAAwB;AACvC,cAAM,SAAS,OAAO,KAAK,cAAc,IAAI,KAAK,cAAc,KAAK;AACrE,YAAI,SAAS,eAAc,yBAA0B;AAKrD,cAAM,OAAO;AAAA,UACX;AAAA,UACA,aAAa,KAAK;AAAA,UAClB,aAAa,KAAK,+BAA+B;AAAA,QACnD;AACA,YAAI,KAAK,iCAAiC,GAAG;AAC3C,eAAK,IAAI,OAAO,KAAK,wDAAwD,EAAE,KAAK,CAAC;AAAA,QACvF,OAAO;AACL,eAAK,IAAI,OAAO,MAAM,wDAAwD,EAAE,KAAK,CAAC;AAAA,QACxF;AACA,YAAI;AACF,gBAAM,KAAK,wBAAwB,KAAK,oBAAoB;AAC5D,eAAK,oBAAoB,KAAK,IAAI;AAAA,QACpC,SAAS,KAAK;AACZ,eAAK,IAAI,OAAO,MAAM,4CAA4C;AAAA,YAChE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH;AAKA,aAAK,gCAAgC;AACrC,cAAM,YAAY,4BAA4B,KAAK,4BAA4B;AAC/E,aAAK,yBAAyB,KAAK,IAAI,IAAI;AAAA,MAC7C,GAAG;AAAA,IACL,GAAG,GAAM;AACT,QAAI,OAAO,KAAK,sBAAsB,UAAU,YAAY;AAC1D,WAAK,sBAAsB,MAAM;AAAA,IACnC;AAAA,EACF;AAAA,EAEQ,uBAA6B;AACnC,QAAI,KAAK,uBAAuB;AAC9B,oBAAc,KAAK,qBAAqB;AACxC,WAAK,wBAAwB;AAAA,IAC/B;AAAA,EACF;AAAA;AAAA,EAGA,MAAM,gBAA+B;AACnC,SAAK,cAAc;AACnB,SAAK,oBAAoB;AACzB,SAAK,cAAc;AACnB,UAAM,UAAU,CAAC,GAAG,KAAK,OAAO,OAAO,CAAC;AACxC,SAAK,OAAO,MAAM;AAClB,eAAW,SAAS,SAAS;AAC3B,UAAI;AAAE,cAAM,MAAM,OAAO,MAAM,gBAAgB;AAAA,MAAE,QAAQ;AAAA,MAAoB;AAAA,IAC/E;AACA,QAAI,KAAK,KAAK;AACZ,UAAI;AAAE,cAAM,KAAK,IAAI,MAAM,EAAE,QAAQ,kBAAkB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAoB;AACtF,WAAK,MAAM;AAAA,IACb;AACA,SAAK,WAAW,KAAK;AAAA,EACvB;AAAA;AAAA,EAIS,sBAAgD;AACvD,UAAM,QAAQ,KAAK,OAAO,IAAI,aAAa;AAC3C,UAAM,aAAa,OAAO,kBAAkB,CAAC;AAC7C,UAAM,UAAU,OAAO,iBAAiB,CAAC;AAOzC,QAAI,KAAK,2BAA2B,GAAG;AACrC,YAAM,MAAM,KAAK,IAAI;AACrB,UAAI,MAAM,KAAK,8BAA8B,eAAc,gCAAgC;AACzF,aAAK,8BAA8B;AACnC,aAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,QAAoB,CAAC;AAAA,MAC7E;AAAA,IACF;AACA,UAAM,SAAyB;AAAA,MAC7B,MAAM;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAYJ,EAAE,IAAI,WAAW,OAAO,WAAW,MAAM,YAAY,OAAO,EAAE;AAAA,QAC9D,EAAE,IAAI,SAAS,OAAO,SAAS,MAAM,SAAS,OAAO,GAAG;AAAA,QACxD,EAAE,IAAI,SAAS,OAAO,SAAS,MAAM,UAAU,OAAO,GAAG;AAAA,QACzD,EAAE,IAAI,YAAY,OAAO,YAAY,MAAM,SAAS,OAAO,GAAG;AAAA,MAChE;AAAA,MACA,UAAU;AAAA;AAAA;AAAA,QAGR;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN,EAAE,MAAM,QAAQ,KAAK,QAAQ,OAAO,aAAa,UAAU,MAAM,aAAa,gBAAgB;AAAA,YAC9F,EAAE,MAAM,UAAU,KAAK,QAAQ,OAAO,QAAQ,SAAS,KAAM,KAAK,GAAG,KAAK,OAAO,MAAM,EAAE;AAAA,YACzF,EAAE,MAAM,QAAQ,KAAK,YAAY,OAAO,YAAY,SAAS,QAAQ;AAAA,YACrE,EAAE,MAAM,YAAY,KAAK,YAAY,OAAO,YAAY,UAAU,MAAM,YAAY,KAAK;AAAA,UAC3F;AAAA,QACF;AAAA;AAAA,QAEA;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS,WAAW,WAAW;AAAA,cAC/B,OAAO;AAAA,cACP,MAAM;AAAA,YACR;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,WAAW,eAAe;AAAA,cACnC,WAAW;AAAA,cACX,aAAa;AAAA,cACb,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,GAAK,OAAO,iBAAiB,MAAM,cAAc,SAAS,IACtD,CAAC;AAAA,UACC,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAS;AAAA,YACP,CAAC,UAAW,uBAAwB,QAAQ;AAAA,YAC5C,CAAC,WAAW,wBAAwB,SAAS;AAAA,YAC7C,CAAC,WAAW,uBAAwB,QAAQ;AAAA,YAC5C,CAAC,QAAW,qBAAwB,MAAM;AAAA,YAC1C,CAAC,WAAW,wBAAwB,SAAS;AAAA,UAC/C,EACG,OAAO,CAAC,CAAC,IAAI,MAAM,MAAM,cAAe,SAAS,IAAI,CAAC,EACtD,IAAI,CAAC,CAAC,MAAM,KAAK,KAAK,OAAO;AAAA,YAC5B,MAAM;AAAA,YACN;AAAA,YACA,OAAO,GAAG,KAAK;AAAA,YACf,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,YACN,SAAS,MAAM,wBAAwB,IAAI,GAAG,eAAe;AAAA,YAC7D,WAAW;AAAA,YACX,aAAa;AAAA,UACf,EAAE;AAAA,QACN,CAAC,IACD,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOL;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,QAAQ,UAAU;AAAA,cAC3B,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,QAAQ,YAAY;AAAA,cAC7B,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,QAAQ,cAAc;AAAA,cAC/B,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,QAAQ,OAAO;AAAA,cACxB,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,cACX,aAAa;AAAA,cACb,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA;AAAA,QAEA,GAAI,QAAQ,aAAa,SACrB,CAAC;AAAA,UACC,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS,QAAQ,YAAY;AAAA,YAC/B;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS;AAAA,YACX;AAAA,UACF;AAAA,QACF,CAAC,IACD,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QA2BL;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS;AAAA,cACT,SAAS;AAAA,gBACP,EAAE,OAAO,QAAQ,OAAO,wBAAwB;AAAA,gBAChD,EAAE,OAAO,MAAQ,OAAO,YAAY;AAAA,gBACpC,EAAE,OAAO,OAAQ,OAAO,aAAa;AAAA,cACvC;AAAA,YACF;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAOA;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS,OAAO,cAAc,WAAW;AAAA,cACzC,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAMA,GAAI,OAAO,mBAAmB,cAAc,OACxC,CAAC;AAAA,UACC,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,SAAS,MAAM,mBAAmB,WAAW;AAAA,cAC7C,OAAO;AAAA,YACT;AAAA,UACF;AAAA,QACF,CAAC,IACD,CAAC;AAAA;AAAA,QAEL;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS;AAAA,cACT,WAAW;AAAA,cACX,aAAa;AAAA,cACb,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQA;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,OAAO,oBAAoB,UAC/B,MAAM,oBAAoB,SAAS,IACpC;AAAA,cACJ,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,UACF;AAAA,QACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QASA,GAAI,KAAK,cAAiC,EAAE,gBAAgB,OACxD,CAAC;AAAA,UACC,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UACF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,KAAK,OAAO,IAAI,0BAA0B,KAAK;AAAA,cACxD,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,KAAK,OAAO,IAAI,sBAAsB,KAAK;AAAA,cACpD,WAAW;AAAA,cACX,aAAa;AAAA,YACf;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,KAAK;AAAA,cACL,KAAK;AAAA,cACL,MAAM;AAAA,cACN,SAAS,KAAK,OAAO,IAAI,cAAc,KAAK;AAAA,cAC5C,WAAW;AAAA,cACX,aAAa;AAAA,cACb,MAAM;AAAA,YACR;AAAA,UACF;AAAA,QACF,CAAC,IACD,CAAC;AAAA,QACL;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aACE;AAAA,UAGF,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS;AAAA,cACT,OAAO;AAAA,YACT;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS,CAAC;AAAA,cACV,SAAS;AAAA,gBACP,EAAE,OAAO,aAAa,OAAO,8BAA8B;AAAA,gBAC3D,EAAE,OAAO,qBAAqB,OAAO,qDAAqD;AAAA,gBAC1F,EAAE,OAAO,mBAAmB,OAAO,sDAAsD;AAAA,gBACzF,EAAE,OAAO,aAAa,OAAO,+BAA+B;AAAA,gBAC5D,EAAE,OAAO,eAAe,OAAO,6CAAwC;AAAA,cACzE;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,QACA,GAAG,KAAK,yBAAyB;AAAA,MACnC;AAAA,IACF;AACA,UAAM,SAAkC;AAAA,MACtC,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5B,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5B,UAAU,KAAK,OAAO,IAAI,UAAU;AAAA,MACpC,UAAU,KAAK,OAAO,IAAI,UAAU;AAAA,MACpC,cAAc,KAAK,OAAO,IAAI,cAAc,KAAK;AAAA,MACjD,iBAAiB,KAAK,OAAO,IAAI,iBAAiB,KAAK,CAAC;AAAA,MACxD,eAAe,KAAK,OAAO,IAAI,eAAe,KAAK,WAAW,WAAW;AAAA,MACzE,mBAAmB,KAAK,OAAO,IAAI,mBAAmB,KAAK,WAAW,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAMrF,qBAAqB,KAAK,OAAO,IAAI,qBAAqB,KAAK,OAAO,wBAAwB,QAAQ,GAAG,eAAe;AAAA,MACxH,sBAAsB,KAAK,OAAO,IAAI,sBAAsB,KAAK,OAAO,wBAAwB,SAAS,GAAG,eAAe;AAAA,MAC3H,qBAAqB,KAAK,OAAO,IAAI,qBAAqB,KAAK,OAAO,wBAAwB,SAAS,GAAG,eAAe;AAAA,MACzH,mBAAmB,KAAK,OAAO,IAAI,mBAAmB,KAAK,OAAO,wBAAwB,MAAM,GAAG,eAAe;AAAA,MAClH,sBAAsB,KAAK,OAAO,IAAI,sBAAsB,KAAK,OAAO,wBAAwB,SAAS,GAAG,eAAe;AAAA;AAAA;AAAA;AAAA;AAAA,MAK3H,WAAW,KAAK,OAAO,IAAI,WAAW,KAAK,QAAQ,UAAU;AAAA,MAC7D,aAAa,KAAK,OAAO,IAAI,aAAa,KAAK,QAAQ,YAAY;AAAA,MACnE,eAAe,KAAK,OAAO,IAAI,eAAe,KAAK,QAAQ,cAAc;AAAA,MACzE,QAAQ,KAAK,OAAO,IAAI,QAAQ,KAAK,QAAQ,OAAO;AAAA,MACpD,YAAY,KAAK,OAAO,IAAI,YAAY,KAAK;AAAA,MAC7C,aAAa,KAAK,OAAO,IAAI,aAAa,KAAK,QAAQ,YAAY;AAAA,MACnE,aAAa,KAAK,OAAO,IAAI,aAAa,KAAK;AAAA,MAC/C,eAAe,KAAK,OAAO,IAAI,eAAe,KAAK;AAAA,MACnD,oBAAoB,KAAK,OAAO,IAAI,oBAAoB,KAAK;AAAA,MAC7D,aAAa,KAAK,OAAO,IAAI,aAAa,KAAK;AAAA,MAC/C,yBAAyB,KAAK,OAAO,IAAI,yBAAyB,KAAK;AAAA,MACvE,oBAAoB,KAAK,OAAO,IAAI,oBAAoB,KAAK;AAAA;AAAA;AAAA;AAAA;AAAA,MAK7D,mBAAmB,KAAK,OAAO,IAAI,mBAAmB,KAAK,OAAO,aAAa,YAAY,WAAW;AAAA,MACtG,qBAAqB,KAAK,OAAO,IAAI,qBAAqB,KAAK,OAAO,aAAa,YAAY,aAAa;AAAA,MAC5G,kBAAkB,KAAK,OAAO,IAAI,kBAAkB,KAAK,OAAO,aAAa,WAAW,WAAW;AAAA,MACnG,oBAAoB,KAAK,OAAO,IAAI,oBAAoB,KAAK,OAAO,aAAa,WAAW,aAAa;AAAA,MACzG,oBAAoB,KAAK,OAAO,IAAI,oBAAoB,KAAK,OAAO,aAAa,UAAU;AAAA;AAAA,MAE3F,oBAAoB,KAAK,OAAO,IAAI,oBAAoB,KAAK,OAAO,cAAc,WAAW;AAAA,MAC7F,iBAAiB,KAAK,OAAO,IAAI,iBAAiB,MAC5C,OAAO,oBAAoB,UAAW,MAAM,oBAAoB,SAAS,IAAK;AAAA,MACpF,kBAAkB,KAAK,OAAO,IAAI,kBAAkB,KAAK,OAAO,mBAAmB,WAAW;AAAA;AAAA;AAAA;AAAA,MAI9F,0BAA0B,KAAK,OAAO,IAAI,0BAA0B,KAAK;AAAA,MACzE,sBAAsB,KAAK,OAAO,IAAI,sBAAsB,KAAK;AAAA,MACjE,cAAc,KAAK,OAAO,IAAI,cAAc,KAAK;AAAA,IACnD;AACA,WAAOE,eAAc,QAAQ,MAAM;AAAA,EACrC;AAAA,EAGA,MAAe,mBAAmB,OAA+C;AAM/E,UAAM,EAAE,kBAAkB,GAAG,KAAK,IAAI;AACtC,QAAI,qBAAqB,QAAW;AAClC,YAAM,KAAK,wBAAwB,EAAE,MAAM,CAAC,QAAQ;AAClD,aAAK,IAAI,OAAO,KAAK,kCAAkC;AAAA,UACrD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,QAAI,OAAO,KAAK,IAAI,EAAE,WAAW,EAAG;AAIpC,UAAM,KAAK,OAAO,OAAO,IAAI;AAC7B,UAAM,aAAa;AACnB,QAAI,WAAW,QAAQ,WAAW,QAAQ,WAAW,YAAY,WAAW,UAAU;AACpF,YAAM,KAAK,cAAc;AACzB;AAAA,IACF;AAQA,QAAI,kBAAkB,SAAS,qBAAqB,OAAO;AACzD,UAAI,KAAK,KAAK;AACZ,YAAI;AAAE,gBAAM,KAAK,IAAI,MAAM,EAAE,QAAQ,wBAAwB,CAAC;AAAA,QAAE,QAAQ;AAAA,QAAoB;AAC5F,aAAK,MAAM;AAAA,MACb;AAAA,IACF;AASA,QAAI,mBAAmB,SAAS,uBAAuB,OAAO;AAC5D,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,MAAM,KAAK,OAAO,IAAI,aAAa,GAAG,kBAAkB,CAAC;AAC/D,cAAM,UAAU,WAAW,iBAAiB,IAAI,WAAW;AAC3D,cAAM,kBAAkB,WAAW,qBAAqB,IAAI,eAAe;AAC3E,cAAM,iBAAiB,OAAO,oBAAoB,WAAW,KAAK,kBAAkB;AACpF,cAAM,UAAU,KAAK,WAAW;AAChC,cAAM,IAAI,eAAe,SAAS,gBAAgB,OAAO;AAGzD,aAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,QAAC,CAAC;AAAA,MAC1D,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,4BAA4B;AAAA,UAC/C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAQA,UAAM,WAAuD;AAAA,MAC3D,CAAC,uBAAwB,QAAQ;AAAA,MACjC,CAAC,wBAAwB,SAAS;AAAA,MAClC,CAAC,uBAAwB,SAAS;AAAA,MAClC,CAAC,qBAAwB,MAAM;AAAA,MAC/B,CAAC,wBAAwB,SAAS;AAAA,IACpC;AACA,QAAI,YAAY;AAChB,eAAW,CAAC,OAAO,MAAM,KAAK,UAAU;AACtC,UAAI,EAAE,SAAS,OAAQ;AACvB,YAAM,IAAK,WAAuC,KAAK;AACvD,UAAI,OAAO,MAAM,SAAU;AAC3B,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,eAAe,KAAK,WAAW,GAAG,QAAQ,CAAC;AACrD,oBAAY;AAAA,MACd,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,4BAA4B;AAAA,UAC/C,MAAM,EAAE,QAAQ,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAC1E,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,WAAW;AACb,WAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,MAAC,CAAC;AAAA,IAC1D;AAOA,UAAM,YAAY,CAAC,aAAa,eAAe,iBAAiB,UAAU,YAAY;AACtF,QAAI,UAAU,KAAK,CAAC,MAAM,KAAK,KAAK,GAAG;AACrC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,aAAiD,CAAC;AACxD,YAAI,OAAO,WAAW,cAAc,SAAU,YAAW,SAAS,WAAW;AAC7E,YAAI,OAAO,WAAW,gBAAgB,SAAU,YAAW,WAAW,WAAW;AACjF,YAAI,OAAO,WAAW,kBAAkB,SAAU,YAAW,aAAa,WAAW;AACrF,YAAI,OAAO,WAAW,WAAW,SAAU,YAAW,MAAM,WAAW;AACvE,YAAI,OAAO,WAAW,eAAe,SAAU,YAAW,UAAU,WAAW;AAC/E,YAAI,OAAO,KAAK,UAAU,EAAE,SAAS,GAAG;AACtC,gBAAM,IAAI,SAAS,KAAK,WAAW,GAAG,UAAU;AAGhD,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,qBAAqB;AAAA,UACxC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAMA,UAAM,UAAU,CAAC,eAAe,eAAe,UAAU,kBAAkB,sBAAsB;AACjG,QAAI,QAAQ,KAAK,CAAC,MAAM,KAAK,KAAK,GAAG;AACnC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,WAA6C,CAAC;AACpD,YAAI,OAAO,WAAW,gBAAgB,YAAY,WAAW,YAAa,UAAS,WAAW,WAAW;AACzG,YAAI,OAAO,WAAW,gBAAgB,YAAY,WAAW,YAAa,UAAS,WAAW,WAAW;AACzG,YAAI,OAAO,WAAW,mBAAmB,SAAU,UAAS,cAAc,WAAW;AACrF,YAAI,WAAW,WAAW,KAAK,WAAW,WAAW,EAAG,UAAS,MAAM,WAAW;AAClF,YAAI,OAAO,WAAW,yBAAyB,SAAU,UAAS,oBAAoB,WAAW;AACjG,YAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,GAAG;AACpC,gBAAM,IAAI,OAAO,KAAK,WAAW,GAAG,QAAQ;AAC5C,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,mBAAmB;AAAA,UACtC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAKA,QAAI,mBAAmB,SAAS,wBAAwB,OAAO;AAC7D,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,UAAiD,CAAC;AACxD,YAAI,WAAW,kBAAkB,OAAW,SAAQ,UAAU,WAAW;AACzE,YAAI,OAAO,WAAW,uBAAuB,SAAU,SAAQ,eAAe,WAAW;AACzF,YAAI,OAAO,KAAK,OAAO,EAAE,SAAS,EAAG,OAAM,IAAI,YAAY,KAAK,WAAW,GAAG,OAAO;AAAA,MACvF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,yBAAyB;AAAA,UAC5C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAKA,UAAM,YAAY,CAAC,eAAe,2BAA2B,oBAAoB;AACjF,QAAI,UAAU,KAAK,CAAC,MAAM,KAAK,KAAK,GAAG;AACrC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,aAAoD,CAAC;AAC3D,YAAI,OAAO,WAAW,gBAAgB,SAAU,YAAW,SAAS,WAAW;AAC/E,YAAI,OAAO,WAAW,4BAA4B,SAAU,YAAW,qBAAqB,WAAW;AACvG,YAAI,OAAO,WAAW,uBAAuB,SAAU,YAAW,gBAAgB,WAAW;AAC7F,YAAI,OAAO,KAAK,UAAU,EAAE,SAAS,EAAG,OAAM,IAAI,YAAY,KAAK,WAAW,GAAG,UAAU;AAAA,MAC7F,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,yBAAyB;AAAA,UAC5C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAUA,UAAM,UAAU;AAAA,MACd;AAAA,MAAqB;AAAA,MACrB;AAAA,MAAoB;AAAA,MACpB;AAAA,IACF;AACA,QAAI,QAAQ,KAAK,CAAC,MAAM,KAAK,KAAK,GAAG;AACnC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,WAA6C,CAAC;AACpD,YAAI,OAAO,WAAW,uBAAuB,WAAW;AACtD,mBAAS,QAAQ,WAAW,qBAAqB,IAAI;AAAA,QACvD;AACA,cAAM,YAAyE,CAAC;AAChF,YAAI,OAAO,WAAW,sBAAsB,SAAU,WAAU,UAAU,WAAW;AACrF,YAAI,OAAO,WAAW,wBAAwB,SAAU,WAAU,YAAY,WAAW;AACzF,YAAI,OAAO,KAAK,SAAS,EAAE,SAAS,EAAG,UAAS,aAAa;AAC7D,cAAM,WAAuE,CAAC;AAC9E,YAAI,OAAO,WAAW,qBAAqB,SAAU,UAAS,UAAU,WAAW;AACnF,YAAI,OAAO,WAAW,uBAAuB,SAAU,UAAS,YAAY,WAAW;AACvF,YAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,EAAG,UAAS,YAAY;AAC3D,YAAI,OAAO,KAAK,QAAQ,EAAE,SAAS,GAAG;AACpC,gBAAM,IAAI,OAAO,KAAK,WAAW,GAAG,QAAQ;AAC5C,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,mBAAmB;AAAA,UACtC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAGA,QAAI,wBAAwB,OAAO;AACjC,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,WAAW;AACrB,YAAI,OAAO,MAAM,WAAW;AAC1B,gBAAM,IAAI,QAAQ,KAAK,WAAW,GAAG,EAAE,QAAQ,EAAE,CAAC;AAClD,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,oBAAoB;AAAA,UACvC,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAKA,QAAI,qBAAqB,OAAO;AAC9B,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,WAAW;AACrB,YAAI,OAAO,MAAM,UAAU;AACzB,gBAAM,IAAI,cAAc,KAAK,WAAW,GAAG,CAAC;AAC5C,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,2BAA2B;AAAA,UAC9C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAKA,QAAI,sBAAsB,OAAO;AAC/B,UAAI;AACF,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,WAAW;AACrB,YAAI,OAAO,MAAM,WAAW;AAC1B,gBAAM,IAAI,aAAa,KAAK,WAAW,GAAG,IAAI,IAAI,CAAC;AACnD,eAAK,KAAK,8BAA8B,EAAE,MAAM,MAAM;AAAA,UAAC,CAAC;AAAA,QAC1D;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,KAAK,0BAA0B;AAAA,UAC7C,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUA,SAAS,KAA+B;AACtC,SAAK,MAAM;AACX,SAAK,oBAAoB;AACzB,QAAI,OAAO,GAAG,SAAS,MAAM,KAAK,kBAAkB,OAAO,CAAC;AAC5D,QAAI,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC9B,WAAK,IAAI,OAAO,KAAK,qDAAgD;AAAA,QACnE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD,WAAK,kBAAkB,OAAO;AAAA,IAChC,CAAC;AAGD,UAAM,sBAAsB,KAAK,OAAO,IAAI,aAAa,GAAG;AAC5D,QAAI,wBAAwB,eAAe;AACzC,WAAK,YAAY;AACjB,UAAI,kBAAkB,IAAI;AAC1B,WAAK,eAAe;AACpB,WAAK,2BAA2B;AAIhC,WAAK,KAAK,sBAAsB;AAAA,IAClC,OAAO;AAIL,WAAK,qBAAqB;AAAA,IAC5B;AAKA,SAAK,KAAK,wBAAwB,KAAK,UAAU,EAAE,MAAM,CAAC,QAAiB;AACzE,WAAK,IAAI,OAAO,MAAM,mDAAmD;AAAA,QACvE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH,CAAC;AAED,SAAK,eAAe;AACpB,SAAK,sBAAsB;AAM3B,QAAI,CAAC,KAAK,OAAO,IAAI,aAAa,GAAG,UAAU;AAC7C,WAAK,KAAK,wBAAwB,GAAG,EAAE,MAAM,CAAC,QAAiB;AAC7D,aAAK,IAAI,OAAO,MAAM,uDAAuD;AAAA,UAC3E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EAMF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,eAAwB;AACtB,UAAM,KAAK,KAAK,aAAa,YAAiD,eAAe;AAC7F,WAAO,IAAI,QAAQ,YAAY,MAAM;AAAA,EACvC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,aAAsB;AACpB,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAM,YAAyC;AAS7C,QAAI,KAAK,WAAW,GAAG;AACrB,YAAM,SAAS,MAAM,KAAK,aAAa;AACvC,UAAI,CAAC,QAAQ;AACX,cAAM,IAAI,MAAM,iBAAiB,KAAK,EAAE,gBAAgB,KAAK,cAAc,qBAAqB;AAAA,MAClG;AACA,aAAO,OAAO,OAAO;AAAA,IACvB;AAEA,QAAI,KAAK,IAAK,QAAO,KAAK;AAC1B,QAAI,KAAK,aAAc,QAAO,KAAK;AAEnC,UAAM,OAAO,KAAK,OAAO,IAAI,MAAM;AACnC,UAAM,OAAO,KAAK,OAAO,IAAI,MAAM;AACnC,UAAM,WAAW,KAAK,OAAO,IAAI,UAAU;AAC3C,UAAM,WAAW,KAAK,OAAO,IAAI,UAAU;AAC3C,UAAM,YAAY,KAAK,OAAO,IAAI,WAAW;AAC7C,UAAM,MAAM,KAAK,OAAO,IAAI,KAAK;AACjC,UAAM,qBAAqB,KAAK,OAAO,IAAI,oBAAoB;AAE/D,SAAK,IAAI,OAAO,KAAK,yBAAyB;AAAA,MAC5C,MAAM,EAAE,UAAU,KAAK,IAAI,MAAM,OAAO,IAAI,EAAE;AAAA,MAC9C,MAAM,EAAE,MAAM,WAAW,QAAQ,QAAQ,GAAG,EAAE;AAAA,IAChD,CAAC;AAED,UAAM,eAAe,KAAK,wBAAwB;AAElD,SAAK,gBAAgB,YAAY;AAC/B,YAAM,MAAM,IAAI,mBAAmB;AAAA,QACjC;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA;AAAA,QACA,GAAI,MAAM,EAAE,IAAI,IAAI,CAAC;AAAA,QACrB,GAAI,qBAAqB,EAAE,mBAAmB,IAAI,CAAC;AAAA,QACnD,GAAI,eAAe,EAAE,aAAa,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,QAQvC,QAAQ,KAAK,iBAAiB;AAAA,MAChC,CAAC;AACD,UAAI;AACF,cAAM,IAAI,MAAM;AAAA,MAClB,SAAS,KAAK;AACZ,aAAK,eAAe;AACpB,cAAM;AAAA,MACR;AACA,WAAK,MAAM;AACX,WAAK,eAAe;AACpB,WAAK,oBAAoB;AAOzB,YAAM,sBAAsB,KAAK,OAAO,IAAI,aAAa,GAAG;AAC5D,UAAI,wBAAwB,eAAe;AACzC,aAAK,YAAY;AACjB,YAAI,kBAAkB,IAAI;AAC1B,aAAK,IAAI,OAAO,KAAK,sFAAiF;AAAA,UACpG,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC5B,CAAC;AAAA,MACH,WAAW,wBAAwB,QAAW;AAC5C,YAAI;AACF,gBAAM,IAAI,eAAe,CAAC;AAC1B,eAAK,YAAY;AACjB,cAAI,kBAAkB,IAAI;AAC1B,eAAK,IAAI,OAAO,KAAK,iFAA4E;AAAA,YAC/F,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC5B,CAAC;AAGD,eAAK,KAAK,gBAAgB,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC/D,QAAQ;AACN,eAAK,YAAY;AAAA,QACnB;AAAA,MACF,OAAO;AACL,aAAK,YAAY;AAAA,MACnB;AAIA,UAAI,KAAK,WAAW;AAClB,aAAK,eAAe;AAGpB,aAAK,2BAA2B;AAOhC,aAAK,KAAK,sBAAsB;AAAA,MAClC;AAGA,WAAK,eAAe;AAKpB,WAAK,KAAK,wBAAwB,GAAG,EAAE,MAAM,CAAC,QAAiB;AAC7D,aAAK,IAAI,OAAO,MAAM,4CAA4C;AAAA,UAChE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAOD,UAAI,OAAO,GAAG,SAAS,MAAM,KAAK,kBAAkB,OAAO,CAAC;AAC5D,UAAI,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC9B,aAAK,IAAI,OAAO,KAAK,qDAAgD;AAAA,UACnE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AACD,aAAK,kBAAkB,OAAO;AAAA,MAChC,CAAC;AAMD,WAAK,KAAK,wBAAwB,KAAK,OAAO,EAAE,MAAM,CAAC,QAAiB;AACtE,aAAK,IAAI,OAAO,MAAM,2CAA2C;AAAA,UAC/D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AACD,WAAK,sBAAsB;AAE3B,aAAO;AAAA,IACT,GAAG;AAEH,WAAO,KAAK;AAAA,EACd;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAwBQ,cAAiF;AACvF,WAAO,EAAE,MAAM,UAAU,IAAI,KAAK,IAAI,SAAS,kBAAkB,UAAU,KAAK,GAAG;AAAA,EACrF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,0BAA+D;AACrE,UAAM,UAAU,KAAK,OAAO,IAAI,cAAc,MAAM;AACpD,UAAM,cAAe,KAAK,OAAO,IAAI,iBAAiB,KAAK,CAAC;AAC5D,UAAM,OAAgC,CAAC;AACvC,QAAI,QAAS,MAAK,UAAU;AAC5B,eAAW,QAAQ,YAAa,MAAK,IAAI,IAAI;AAC7C,WAAO,OAAO,KAAK,IAAI,EAAE,SAAS,IAAI,OAAO;AAAA,EAC/C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,kBAAkB,OAAoE;AAEpF,SAAK,cAAc,KAAK,IAAI;AAG5B,SAAK,+BAA+B;AACpC,SAAK,yBAAyB;AAC9B,UAAM,cAAc,KAAK,YAAY;AAcrC,QAAI,MAAM,SAAS,WAAW;AAC5B,WAAK,IAAI,OAAO,KAAK,gCAAgC;AAAA,QACnD,MAAM;AAAA,UACJ,MAAM,MAAM;AAAA,UACZ,SAAS,MAAM;AAAA,UACf,WAAW,MAAM;AAAA,QACnB;AAAA,MACF,CAAC;AAAA,IACH;AASA,QAAI,MAAM,SAAS,UAAU;AAC3B,YAAM,MAAM,KAAK,IAAI;AACrB,WAAK,IAAI,SAAS,KAAK;AAAA,QACrB,cAAc;AAAA,QACd;AAAA,QACA,EAAE,UAAU,KAAK,IAAI,UAAU,MAAM,WAAW,KAAK,QAAQ,UAAU;AAAA,MACzE,CAAC;AACD;AAAA,IACF;AAOA,UAAM,UAAU,aAAa,MAAM,IAAI;AACvC,QAAI,YAAY,QAAW;AACzB,YAAM,MAAM,KAAK,IAAI;AACrB,WAAK,IAAI,SAAS,KAAK;AAAA,QACrB,cAAc;AAAA,QACd;AAAA,QACA;AAAA,UACE,UAAU,KAAK;AAAA,UACf,QAAQ;AAAA,UACR,YAAY,CAAC,EAAE,OAAO,SAAS,WAAW,IAAI,CAAC;AAAA,QACjD;AAAA,MACF,CAAC;AACD,WAAK,IAAI,SAAS,KAAK;AAAA,QACrB,cAAc;AAAA,QACd;AAAA,QACA,EAAE,UAAU,KAAK,IAAI,UAAU,MAAM,WAAW,KAAK,QAAQ,UAAU;AAAA,MACzE,CAAC;AACD;AAAA,IACF;AAOA,QAAI,MAAM,SAAS,SAAS;AAC1B,YAAM,cAAc,KAAK;AAQzB,UAAI,KAAK,yBAAyB,KAAK,GAAG;AAOxC,YAAI,KAAK,UAAW,MAAK,MAAM,QAAQ,WAAW;AAClD,YAAI,aAAa;AACf,eAAK,IAAI,OAAO,KAAK,0BAA0B,EAAE,MAAM,EAAE,UAAU,KAAK,GAAG,EAAE,CAAC;AAC9E,eAAK,IAAI,SAAS,KAAK;AAAA,YACrB,cAAc;AAAA,YACd;AAAA,YACA,EAAE,UAAU,KAAK,IAAI,YAAY,kBAAkB,QAAQ,QAAQ;AAAA,UACrE,CAAC;AAOD,cAAI,KAAK,mBAAmB,SAAS,GAAG;AACtC,iBAAK,KAAK,gBAAgB,EAAE,MAAM,CAAC,QAAiB;AAClD,mBAAK,IAAI,OAAO,MAAM,kCAAkC;AAAA,gBACtD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,gBAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,cAClE,CAAC;AAAA,YACH,CAAC;AAAA,UACH;AAcA,eAAK,KAAK,iBAAiB,cAAc,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC9E;AAAA,MACF;AACA;AAAA,IACF;AAMA,QAAI,MAAM,SAAS,YAAY,MAAM,SAAS,WAAW;AACvD,YAAM,OAAO,MAAM,SAAS;AAC5B,UAAI,SAAS,KAAK,QAAQ;AACxB,aAAK,WAAW,IAAI;AACpB,aAAK,IAAI,SAAS,KAAK;AAAA,UACrB,OAAO,cAAc,eAAe,cAAc;AAAA,UAClD;AAAA,UACA,OACI,EAAE,UAAU,KAAK,IAAI,YAAY,kBAAkB,cAAc,GAAG,QAAQ,gBAAgB,IAC5F,EAAE,UAAU,KAAK,IAAI,YAAY,kBAAkB,QAAQ,gBAAgB;AAAA,QACjF,CAAC;AAAA,MACH;AACA;AAAA,IACF;AAEA,QAAI,MAAM,SAAS,YAAY;AAC7B,YAAM,cAAc,KAAK;AACzB,UAAI,KAAK,yBAAyB,IAAI,GAAG;AACvC,YAAI,KAAK,UAAW,MAAK,MAAM,QAAQ,WAAW;AAClD,YAAI,CAAC,aAAa;AAChB,eAAK,IAAI,SAAS,KAAK;AAAA,YACrB,cAAc;AAAA,YACd;AAAA,YACA,EAAE,UAAU,KAAK,IAAI,YAAY,kBAAkB,QAAQ,WAAW;AAAA,UACxE,CAAC;AAAA,QACH;AAMA,YAAI,KAAK,OAAO,OAAO,GAAG;AACxB,eAAK,IAAI,OAAO,KAAK,8DAAyD;AAAA,YAC5E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,YAC1B,MAAM,EAAE,eAAe,KAAK,OAAO,KAAK;AAAA,UAC1C,CAAC;AAKD,eAAK,KAAK,mBAAmB,UAAU,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC5E,OAAO;AACL,eAAK,IAAI,OAAO,MAAM,uDAAuD;AAAA,YAC3E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,UAC5B,CAAC;AAAA,QACH;AAAA,MAKF;AACA;AAAA,IACF;AAIA,QAAI,MAAM,SAAS,aAAa,MAAM,SAAS;AAC7C,WAAK,mBAAmB,MAAM,OAAyD;AACvF;AAAA,IACF;AAIA,QAAI,MAAM,SAAS,YAAY;AAC7B,WAAK,oBAAoB,KAAK,IAAI,CAAC;AACnC;AAAA,IACF;AAIA,SAAK,IAAI,OAAO,MAAM,wCAAwC;AAAA,MAC5D,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC1B,MAAM,EAAE,MAAM,MAAM,MAAM,SAAS,MAAM,SAAS,GAAI,MAAM,UAAU,EAAE,SAAS,MAAM,QAAQ,IAAI,CAAC,EAAG;AAAA,IACzG,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeA,MAAc,wBAAwB,KAAwC;AAC5E,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AACJ,QAAI;AAEJ,QAAI;AAAE,qBAAe,MAAM,IAAI,gBAAgB;AAAA,IAAE,QAAQ;AAAA,IAAe;AAOxE,QAAI;AACF,YAAM,EAAE,aAAa,IAAI,MAAM,IAAI,sBAAsB,KAAK,WAAW,CAAC;AAC1E,eAAS,aAAa;AACtB,oBAAc,aAAa;AAC3B,oBAAc,aAAa;AAC3B,sBAAgB,aAAa;AAC7B,iBAAW,aAAa;AACxB,mBAAa,aAAa;AAC1B,qBAAe,aAAa;AAC5B,qBAAe,aAAa;AAAA,IAC9B,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,2DAAsD;AAAA,QAC1E,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAOA,UAAM,oBACJ,WAAW,UACR,gBAAgB,UAChB,gBAAgB,UAChB,kBAAkB,UAClB,aAAa,UACb,eAAe,UACf,iBAAiB,UACjB,iBAAiB,UACjB,iBAAiB;AAGtB,QAAI,CAAC,mBAAmB;AACtB,WAAK,IAAI,OAAO,MAAM,iEAA4D;AAAA,QAChF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AACD;AAAA,IACF;AAKA,UAAM,OAAO,KAAK,aAAa,YAAgC,eAAe;AAC9E,UAAM,YAAa,MAAM,SAAS,CAAC;AACnC,UAAM,sBAAsB,KAAK,OAAO,IAAI,aAAa,GAAG;AAC5D,UAAM,qBAAoC,wBACpC,KAAK,YAAY,gBAAgB;AACvC,UAAM,iBAAiB,KAAK,OAAO,IAAI,aAAa,GAAG,SAAS;AAEhE,UAAM,QAA2B;AAAA,MAC/B,GAAG;AAAA;AAAA;AAAA;AAAA;AAAA,MAKH,YAAY,cAAc,UAAU,cAAc,KAAK;AAAA,MACvD,GAAI,WAAW,SAAY,EAAE,OAAO,IAAI,CAAC;AAAA,MACzC,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,gBAAgB,SAAY,EAAE,YAAY,IAAI,CAAC;AAAA,MACnD,GAAI,kBAAkB,SAAY,EAAE,cAAc,IAAI,CAAC;AAAA,MACvD,GAAI,aAAa,SAAY,EAAE,SAAS,IAAI,CAAC;AAAA,MAC7C,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,MACrD,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,IACvD;AAEA,UAAM,MAAM,KAAK,IAAI;AACrB,UAAM,OAA2B;AAAA,MAC/B;AAAA,MACA,YAAY;AAAA,MACZ,OAAO;AAAA,MACP,cAAc,gBAAgB,MAAM,gBAAgB;AAAA,MACpD,cAAc;AAAA,MACd,eAAe;AAAA,IACjB;AACA,SAAK,aAAa,YAAY,iBAAiB,IAAI;AAKnD,QAAI;AACF,YAAM,WAAW,KAAK,OAAO,IAAI,aAAa,KAAK,CAAC;AACpD,YAAM,KAAK,OAAO,OAAO;AAAA,QACvB,aAAa;AAAA,UACX,GAAG;AAAA,UACH,GAAI,qBAAqB,EAAE,YAAY,mBAAmB,IAAI,CAAC;AAAA,UAC/D,GAAI,iBAAiB,SAAY,EAAE,aAAa,IAAI,CAAC;AAAA,QACvD;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,mDAAmD;AAAA,QACvE,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAGA,SAAK,uBAAuB;AAC5B,SAAK,gCAAgC;AACrC,SAAK,4BAA4B;AAQjC,SAAK,IAAI,OAAO,KAAK,uCAAuC;AAAA,MAC1D,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC1B,MAAM,EAAE,OAAO,cAAc,YAAY,mBAAmB;AAAA,IAC9D,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,6BAA6B,KAAwC;AACjF,UAAM,SAAS,KAAK,OAAO,IAAI,KAAK;AAMpC,UAAM,UAAU,KAAK,mBAAmB,OAAO,KAAK,WAAW,IAAI;AACnE,UAAM,wBAAwB,KAAK,SAAS;AAAA,MAC1C,IAAI,KAAK;AAAA,MACT,KAAK,KAAK;AAAA,MACV,KAAK,OAAO,WAAW,YAAY,OAAO,SAAS,IAAI,SAAS;AAAA,MAChE,qBAAqB,OAAO,eAAe;AACzC,cAAM,WAAW,KAAK,OAAO,IAAI,aAAa,KAAK,CAAC;AACpD,cAAM,KAAK,OAAO,OAAO,EAAE,aAAa,EAAE,GAAG,UAAU,GAAG,WAAW,EAAE,CAAC;AAAA,MAC1E;AAAA,IACF,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAmBQ,kBAAkB,QAAsB;AAC9C,QAAI,KAAK,eAAgB;AACzB,QAAI,KAAK,WAAW;AAQlB,WAAK,MAAM;AACX,WAAK,IAAI,OAAO,MAAM,oEAA+D;AAAA,QACnF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,QAC1B,MAAM,EAAE,QAAQ,UAAU,KAAK,SAAS;AAAA,MAC1C,CAAC;AACD;AAAA,IACF;AACA,UAAM,UAAU,EAAE,KAAK;AACvB,UAAM,YAAY,KAAK,IAAI,KAAQ,MAAQ,KAAK,KAAK,IAAI,UAAU,GAAG,CAAC,CAAC;AACxE,SAAK,IAAI,OAAO,KAAK,gCAAgC;AAAA,MACnD,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC1B,MAAM,EAAE,QAAQ,SAAS,UAAU;AAAA,IACrC,CAAC;AAOD,SAAK,MAAM;AAEX,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AAItB,WAAK,IAAI,OAAO,MAAM,kEAA6D;AAAA,QACjF,MAAM,EAAE,UAAU,KAAK,GAAG;AAAA,MAC5B,CAAC;AAAA,IACH,GAAG,SAAS;AACZ,QAAI,OAAO,KAAK,eAAe,UAAU,WAAY,MAAK,eAAe,MAAM;AAAA,EACjF;AACF;AAeA,SAAS,UAAU,GAAiC;AAClD,MAAI,OAAO,MAAM,YAAY,MAAM,KAAM,QAAO;AAChD,QAAM,MAAM;AACZ,SAAO,OAAO,IAAI,OAAO,YAAY,OAAO,IAAI,WAAW;AAC7D;AAYA,SAAS,4BAA4B,aAA6B;AAChE,MAAI,eAAe,EAAG,QAAO;AAC7B,MAAI,gBAAgB,EAAG,QAAO,IAAI;AAClC,MAAI,gBAAgB,EAAG,QAAO,IAAI;AAClC,SAAO,KAAK;AACd;;;AgB5sIA,SAAS,sBAAAC,2BAAmD;AAC5D;AAAA,EACE,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,cAAAC;AAAA,EACA,iBAAAC;AAAA,EACA,iBAAAC;AAAA,EAMA;AAAA,EACA,oBAAAC;AAAA,OAIK;AAaP,IAAM,mCAAmC;AACzC,IAAM,wBAAwB;AAC9B,IAAM,uBAAuB;AA0BtB,IAAM,aAAN,MAAM,oBAAmBC,YAA8D;AAAA,EACnF,WAAqC,CAACC,eAAc,UAAU;AAAA;AAAA,EAG/D,MAAiC;AAAA,EACjC,kBAAsD;AAAA,EACtD,oBAAoB;AAAA,EACpB,iBAAuD;AAAA,EACvD,oBAAoB;AAAA;AAAA,EAGpB,mBAA4C;AAAA,EAC5C,0BAAgD;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAUhD,oBAAoB,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAO5C,kBAAkB,oBAAI,IAAoB;AAAA;AAAA;AAAA;AAAA;AAAA,EAMlD,OAAwB,qBAAqB,KAAK;AAAA;AAAA,EAGjC,yBAAyB,CAAC,OAAiC;AAC1E,SAAK,iBAAiB,EAAE;AAAA,EAC1B;AAAA,EAEA,YAAY,KAAoB;AAC9B,UAAM,KAAK,kBAAkB,EAAE,MAAMC,YAAW,IAAI,CAAC;AACrD,SAAK,wBAAwB;AAC7B,SAAK,eAAe;AAAA,EACtB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAeQ,iBAAuB;AAC7B,UAAM,WAAyD;AAAA,MAC7D,QAAQ,OAAO,EAAE,SAAS,MAAM;AAC9B,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,2CAA2C,KAAK,EAAE,SAAS,QAAQ,EAAE;AAAA,QACvF;AACA,cAAM,MAAM,MAAM,KAAK,UAAU;AACjC,cAAM,IAAI,OAAO;AACjB,eAAO,EAAE,SAAS,KAAc;AAAA,MAClC;AAAA,IACF;AACA,SAAK,IAAI,kBAAkBC,mBAAkB,QAAQ;AAAA,EACvD;AAAA,EAEA,MAAe,aAA4B;AAIzC,SAAK,KAAK,UAAU,EAAE,MAAM,CAAC,QAAQ;AACnC,WAAK,IAAI,OAAO,KAAK,wDAAmD;AAAA,QACtE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH,CAAC;AAqBD,SAAK,mBAAmB;AAAA,MACtB,KAAK,IAAI,SAAS;AAAA,QAChB,EAAE,UAAUC,eAAc,mBAAmB;AAAA,QAC7C,CAAC,UAAU;AACT,gBAAM,OAAO,MAAM;AAInB,cAAI,KAAK,mBAAmB,KAAK,GAAI;AACrC,gBAAM,MAAM,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AAChE,eAAK,IAAI,OAAO,KAAK,0EAAqE;AAAA,YACxF,GAAI,QAAQ,OAAO,EAAE,MAAM,EAAE,UAAU,IAAI,EAAE,IAAI,CAAC;AAAA,UACpD,CAAC;AACD,eAAK,KAAK,2BAA2B,EAAE,MAAM,MAAM;AAAA,UAAoB,CAAC;AAAA,QAC1E;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA,EAEQ,qBAAwC,CAAC;AAAA,EAEjD,MAAe,eAA8B;AAC3C,SAAK,oBAAoB;AACzB,QAAI,KAAK,gBAAgB;AACvB,mBAAa,KAAK,cAAc;AAChC,WAAK,iBAAiB;AAAA,IACxB;AACA,eAAW,SAAS,KAAK,oBAAoB;AAC3C,UAAI;AAAE,cAAM;AAAA,MAAE,QAAQ;AAAA,MAAoB;AAAA,IAC5C;AACA,SAAK,qBAAqB,CAAC;AAC3B,QAAI,KAAK,KAAK;AACZ,UAAI;AAAE,cAAM,KAAK,IAAI,MAAM,EAAE,QAAQ,eAAe,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAoB;AACnF,WAAK,MAAM;AAAA,IACb;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAYS,sBAAgD;AACvD,UAAM,QAAQ,KAAK,OAAO,IAAI,aAAa;AAG3C,UAAM,OAAO,KAAK;AAClB,UAAM,UAAU,SAAS,QAAS,KAAK,IAAI,IAAI,KAAK,KAAM;AAC1D,QAAI,SAAS;AACX,WAAK,KAAK,wBAAwB,EAAE,MAAM,CAAC,QAAQ;AACjD,aAAK,IAAI,OAAO,MAAM,8BAA8B;AAAA,UAClD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,UAAM,SAAyB;AAAA,MAC7B,MAAM;AAAA,QACJ,EAAE,IAAI,WAAW,OAAO,WAAW,MAAM,YAAY,OAAO,EAAE;AAAA,QAC9D,EAAE,IAAI,YAAY,OAAO,YAAY,MAAM,SAAS,OAAO,GAAG;AAAA,MAChE;AAAA,MACA,UAAU;AAAA,QACR,GAAG,yBAAyB,MAAM,EAAE,OAAO,YAAY,OAAO,eAAe,CAAC;AAAA,QAC9E;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN,EAAE,MAAM,QAAY,KAAK,QAAY,OAAO,UAAY,UAAU,MAAM,aAAa,gBAAgB;AAAA,YACrG,EAAE,MAAM,UAAY,KAAK,QAAY,OAAO,QAAY,SAAS,KAAM,KAAK,GAAG,KAAK,OAAO,MAAM,EAAE;AAAA,YACnG,EAAE,MAAM,QAAY,KAAK,YAAY,OAAO,YAAY,SAAS,QAAQ;AAAA,YACzE,EAAE,MAAM,YAAY,KAAK,YAAY,OAAO,YAAY,UAAU,MAAM,YAAY,KAAK;AAAA,UAC3F;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN,EAAE,MAAM,QAAQ,KAAK,UAAiB,OAAO,SAAkB,SAAS,OAAO,SAAS,WAAqB,UAAU,KAAK;AAAA,YAC5H,EAAE,MAAM,QAAQ,KAAK,iBAAiB,OAAO,kBAAkB,SAAS,OAAO,OAAO,gBAAgB,CAAC,GAAM,UAAU,KAAK;AAAA,UAC9H;AAAA,QACF;AAAA,QACA;AAAA,UACE,IAAI;AAAA,UACJ,KAAK;AAAA,UACL,OAAO;AAAA,UACP,aAAa;AAAA,UACb,SAAS;AAAA,UACT,QAAQ;AAAA,YACN;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS;AAAA,cACT,OAAO;AAAA,YACT;AAAA,YACA;AAAA,cACE,MAAM;AAAA,cACN,KAAK;AAAA,cACL,OAAO;AAAA,cACP,aAAa;AAAA,cACb,SAAS,CAAC;AAAA,cACV,SAAS;AAAA,gBACP,EAAE,OAAO,aAAqB,OAAO,8BAA8B;AAAA,gBACnE,EAAE,OAAO,qBAAqB,OAAO,qDAAqD;AAAA,gBAC1F,EAAE,OAAO,mBAAqB,OAAO,sDAAsD;AAAA,gBAC3F,EAAE,OAAO,aAAqB,OAAO,+BAA+B;AAAA,gBACpE,EAAE,OAAO,eAAqB,OAAO,6CAAwC;AAAA,cAC/E;AAAA,YACF;AAAA,UACF;AAAA,QACF;AAAA,MACF;AAAA,IACF;AACA,UAAM,SAAkC;AAAA,MACtC,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5B,MAAM,KAAK,OAAO,IAAI,MAAM;AAAA,MAC5B,UAAU,KAAK,OAAO,IAAI,UAAU;AAAA,MACpC,UAAU,KAAK,OAAO,IAAI,UAAU;AAAA,MACpC,cAAc,KAAK,OAAO,IAAI,cAAc,KAAK;AAAA,MACjD,iBAAiB,KAAK,OAAO,IAAI,iBAAiB,KAAK,CAAC;AAAA,MACxD,QAAQ,OAAO,SAAS;AAAA,MACxB,eAAe,OAAO,OAAO,gBAAgB,CAAC;AAAA,IAChD;AACA,WAAOC,eAAc,QAAQ,MAAM;AAAA,EACrC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,0BAAyC;AACrD,QAAI,KAAK,wBAAyB,QAAO,KAAK;AAC9C,UAAM,WAAW,YAA2B;AAC1C,UAAI;AACJ,UAAI;AACF,cAAM,MAAM,KAAK,UAAU;AAAA,MAC7B,SAAS,KAAK;AACZ,aAAK,mBAAmB;AAAA,UACtB,IAAI,KAAK,IAAI;AAAA,UACb,aAAa;AAAA,UACb,YAAY,CAAC;AAAA,UACb,mBAAmB,CAAC;AAAA,UACpB,UAAU;AAAA,UACV,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AACA;AAAA,MACF;AACA,WAAK,mBAAmB,MAAM,wBAAwB,KAAK;AAAA,QACzD,QAAQ,KAAK,IAAI;AAAA,QACjB,UAAU,KAAK;AAAA,MACjB,CAAC;AAAA,IACH,GAAG,EAAE,QAAQ,MAAM;AACjB,WAAK,0BAA0B;AAAA,IACjC,CAAC;AACD,SAAK,0BAA0B;AAC/B,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAe,mBAAmB,OAA+C;AAG/E,QAAI,sBAAsB,OAAO;AAC/B,YAAM,KAAK,wBAAwB,EAAE,MAAM,CAAC,QAAQ;AAClD,aAAK,IAAI,OAAO,KAAK,kCAAkC;AAAA,UACrD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AACA,UAAM,WAAoC,CAAC;AAC3C,eAAW,CAAC,GAAG,CAAC,KAAK,OAAO,QAAQ,KAAK,GAAG;AAC1C,UAAI,EAAE,WAAW,GAAG,EAAG;AACvB,eAAS,CAAC,IAAI;AAAA,IAChB;AACA,QAAI,qBAAqB,YAAY,MAAM,QAAQ,SAAS,iBAAiB,CAAC,GAAG;AAE/E,eAAS,iBAAiB,IAAK,SAAS,iBAAiB,EACtD;AAAA,QAAO,CAAC,MACP,OAAO,MAAM,YACZ,6BAA6B,UAAU,CAAC,EAAG;AAAA,MAC9C;AAAA,IACJ;AACA,QAAI,OAAO,KAAK,QAAQ,EAAE,WAAW,EAAG;AACxC,UAAM,KAAK,OAAO,OAAO,QAAQ;AAKjC,UAAM,cAAc,CAAC,QAAQ,QAAQ,YAAY,YAAY,WAAW,EAAE,KAAK,CAAC,MAAM,KAAK,QAAQ;AACnG,UAAM,eAAe,kBAAkB,YAAY,qBAAqB;AACxE,QAAI,eAAe,cAAc;AAC/B,UAAI,KAAK,KAAK;AACZ,YAAI;AAAE,gBAAM,KAAK,IAAI,MAAM,EAAE,QAAQ,mBAAmB,CAAC;AAAA,QAAE,QAAQ;AAAA,QAAoB;AACvF,aAAK,MAAM;AAAA,MACb;AACA,WAAK,WAAW,KAAK;AAGrB,WAAK,KAAK,UAAU,EAAE,MAAM,CAAC,QAAQ;AACnC,aAAK,IAAI,OAAO,KAAK,oDAAoD;AAAA,UACvE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAM,SAAsC;AAC1C,WAAO,KAAK,UAAU;AAAA,EACxB;AAAA;AAAA,EAIQ,0BAAgC;AACtC,UAAM,WAAkE;AAAA,MACtE,WAAW,OAAO,EAAE,SAAS,MAA6C;AACxE,YAAI,aAAa,KAAK,GAAI,QAAO;AACjC,eAAO,KAAK,aAAa,YAAmC,kBAAkB,KAAK;AAAA,MACrF;AAAA,MACA,gBAAgB,OAAO,EAAE,SAAS,MAAiD;AACjF,YAAI,aAAa,KAAK,GAAI,QAAO,CAAC;AAClC,cAAM,QAAQ,KAAK,aAAa,YAAmC,kBAAkB;AACrF,eAAO,OAAO,cAAc,CAAC;AAAA,MAC/B;AAAA,MACA,kBAAkB,OAAO,EAAE,SAAS,MAAiD;AACnF,YAAI,aAAa,KAAK,GAAI,QAAO,CAAC;AAClC,cAAM,KAAK,2BAA2B;AACtC,cAAM,QAAQ,KAAK,aAAa,YAAmC,kBAAkB;AACrF,eAAO,OAAO,cAAc,CAAC;AAAA,MAC/B;AAAA,MACA,aAAa,OAAO,EAAE,UAAU,eAAe,KAAK,MAAuD;AACzG,YAAI,aAAa,KAAK,IAAI;AACxB,gBAAM,IAAI,MAAM,6CAAwC,QAAQ,cAAc,KAAK,EAAE,EAAE;AAAA,QACzF;AACA,eAAO,KAAK,qBAAqB,eAAe,IAAI;AAAA,MACtD;AAAA,MACA,eAAe,OAAO,EAAE,UAAU,cAAc,MAAqB;AACnE,YAAI,aAAa,KAAK,GAAI;AAC1B,cAAM,KAAK,uBAAuB,aAAa;AAAA,MACjD;AAAA,IACF;AACA,SAAK,IAAI,kBAAkB,2BAA2B,QAAQ;AAK9D,SAAK,YAAY,2BAA2B;AAAA,MAC1C,YAAY,CAAC;AAAA,MACb,iBAAiB;AAAA,MACjB,WAAW;AAAA,MACX,eAAe;AAAA,IACjB,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAcA,MAAc,6BAA4C;AACxD,QAAI,YAA2B;AAC/B,QAAI,aAAsC,CAAC;AAC3C,UAAM,YAAY,KAAK,IAAI;AAE3B,SAAK,IAAI,OAAO,KAAK,0CAA0C;AAAA,MAC7D,MAAM,EAAE,QAAQ,OAAO,WAAW,iCAAiC;AAAA,IACrE,CAAC;AAED,QAAI;AACF,YAAM,MAAM,MAAM,KAAK,UAAU;AACjC,YAAM,UAAU,MAAM,IAAI,sBAAsB;AAAA,QAC9C,QAAQ;AAAA,QACR,WAAW;AAAA,MACb,CAAC;AAGD,YAAM,mBAAmB,MAAM,KAAK,6BAA6B;AAEjE,mBAAa,QAAQ,QAAQ,IAAI,CAAC,MAA6B;AAC7D,cAAM,gBAAgB,qBAAqB,KAAK,UAAU,EAAE,SAAS,EAAE,GAAG;AAC1E,cAAM,kBAAkB,iBAAiB,IAAI,EAAE,OAAO,KAAK;AAC3D,eAAO;AAAA,UACL;AAAA,UACA,MAAM,EAAE,QAAQ,WAAW,EAAE,OAAO;AAAA;AAAA;AAAA,UAGpC,MAAMH,YAAW;AAAA,UACjB,QAAQ,oBAAoB,CAAC;AAAA,UAC7B,UAAU;AAAA,YACR,GAAI,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAAA,YACpC,GAAI,EAAE,eAAe,EAAE,cAAc,EAAE,aAAa,IAAI,CAAC;AAAA,YACzD,GAAI,EAAE,MAAM,EAAE,KAAK,EAAE,IAAI,IAAI,CAAC;AAAA,YAC9B,aAAa,EAAE;AAAA,YACf,WAAW,EAAE,cAAc;AAAA,YAC3B,YAAY,EAAE,eAAe;AAAA,YAC7B,cAAc,EAAE,iBAAiB;AAAA,UACnC;AAAA,UACA,gBAAgB,oBAAoB;AAAA,UACpC;AAAA,QACF;AAAA,MACF,CAAC;AAAA,IACH,SAAS,KAAK;AACZ,kBAAY,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAC3D,WAAK,IAAI,OAAO,KAAK,wCAAwC;AAAA,QAC3D,MAAM,EAAE,OAAO,UAAU;AAAA,MAC3B,CAAC;AAAA,IACH;AAEA,UAAM,QAA2D;AAAA,MAC/D;AAAA,MACA,iBAAiB,KAAK,IAAI;AAAA,MAC1B;AAAA,MACA,eAAe,KAAK,IAAI;AAAA,IAC1B;AACA,SAAK,aAAa,YAAY,0BAA0B,MAAM,KAAK;AAEnE,QAAI,cAAc,MAAM;AACtB,YAAM,eAAe,WAAW,OAAO,CAAC,MAAM,EAAE,cAAc,EAAE;AAChE,YAAM,iBAAiB,WAAW,SAAS;AAC3C,YAAM,cAAc,WAAW,OAAO,CAAC,MAAM,EAAE,WAAW,QAAQ,EAAE;AACpE,YAAM,gBAAgB,WAAW,OAAO,CAAC,MAAM,EAAE,WAAW,UAAU,EAAE;AACxE,WAAK,IAAI,OAAO,KAAK,2CAA2C;AAAA,QAC9D,MAAM;AAAA,UACJ,YAAY,KAAK,IAAI,IAAI;AAAA,UACzB,OAAO,WAAW;AAAA,UAClB,SAAS;AAAA,UACT,WAAW;AAAA,UACX,QAAQ;AAAA,UACR,UAAU;AAAA,UACV,UAAU,WACP,IAAI,CAAC,OAAO;AAAA,YACX,IAAI,EAAE,SAAS;AAAA,YACf,MAAM,EAAE;AAAA,YACR,OAAO,EAAE,SAAS,SAAS;AAAA,YAC3B,QAAQ,EAAE;AAAA,YACV,SAAS,EAAE;AAAA,UACb,EAAE;AAAA,QACN;AAAA,MACF,CAAC;AAAA,IACH;AAIA,SAAK,kBAAkB,MAAM;AAC7B,eAAW,SAAS,YAAY;AAC9B,YAAM,KAAK,MAAM,SAAS;AAC1B,UAAI,OAAO,OAAO,YAAY,MAAM,oBAAoB,MAAM;AAC5D,aAAK,kBAAkB,IAAI,IAAI,MAAM,eAAe;AAAA,MACtD;AAAA,IACF;AAMA,QAAI,cAAc,MAAM;AACtB,YAAM,KAAK,4BAA4B,UAAU;AAAA,IACnD;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAaA,MAAc,4BAA4B,YAA6D;AACrG,UAAM,MAAM,KAAK,IAAI;AAErB,UAAM,eAAe,oBAAI,IAAY;AACrC,eAAW,SAAS,YAAY;AAC9B,YAAM,KAAK,MAAM,SAAS;AAC1B,UAAI,OAAO,OAAO,YAAY,MAAM,oBAAoB,KAAM;AAC9D,mBAAa,IAAI,EAAE;AACnB,WAAK,gBAAgB,IAAI,MAAM,iBAAiB,GAAG;AAAA,IACrD;AAGA,QAAI;AACJ,QAAI;AACF,iBAAW,MAAM,KAAK,IAAI,QAAQ,YAAY,KAAK,EAAE;AAAA,IACvD,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,mDAAmD;AAAA,QACvE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD;AAAA,IACF;AACA,eAAW,SAAS,UAAU;AAC5B,YAAM,MAAO,MAAM,OAA+B;AAClD,YAAM,KAAK,KAAK;AAChB,UAAI,OAAO,OAAO,SAAU;AAI5B,UAAI,MAAM,SAASA,YAAW,OAAQ;AAEtC,YAAM,aAAa,aAAa,IAAI,EAAE;AACtC,YAAM,WAAW,KAAK,gBAAgB,IAAI,MAAM,EAAE,KAAK;AACvD,YAAM,YAAY,aAAa,IAAK,WAAW,IAAI,MAAM,WAAW,OAAO;AAI3E,YAAM,OAAO;AAEb,UAAI,YAAY;AACd,YAAI,KAAK,WAAW,MAAM;AACxB,eAAK,WAAW,IAAI;AACpB,eAAK,IAAI,OAAO,KAAK,0CAA0C;AAAA,YAC7D,MAAM,EAAE,UAAU,MAAM,IAAI,YAAY,MAAM,KAAK;AAAA,YACnD,MAAM,EAAE,SAAS,GAAG;AAAA,UACtB,CAAC;AAAA,QACH;AACA;AAAA,MACF;AACA,UAAI,aAAa,YAAW,oBAAoB;AAC9C,YAAI,KAAK,WAAW,OAAO;AACzB,eAAK,WAAW,KAAK;AACrB,eAAK,IAAI,OAAO,KAAK,sEAAsE;AAAA,YACzF,MAAM,EAAE,UAAU,MAAM,IAAI,YAAY,MAAM,KAAK;AAAA,YACnD,MAAM,EAAE,SAAS,IAAI,UAAU;AAAA,UACjC,CAAC;AAAA,QACH;AAAA,MACF;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQA,MAAc,+BAA6D;AACzE,UAAM,SAAS,oBAAI,IAAoB;AACvC,UAAM,WAAW,MAAM,KAAK,IAAI,QAAQ,YAAY,KAAK,EAAE;AAC3D,eAAW,SAAS,UAAU;AAG5B,YAAM,MAAO,MAAM,OAA+B;AAClD,YAAM,KAAK,KAAK;AAChB,UAAI,OAAO,OAAO,SAAU,QAAO,IAAI,IAAI,MAAM,EAAE;AAAA,IACrD;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,MAAc,qBAAqB,eAAuB,cAAwE;AAChI,UAAM,QAAQ,KAAK,aAAa,YAAmC,kBAAkB;AACrF,UAAM,QAAQ,OAAO,WAAW,KAAK,CAAC,MAAM,EAAE,kBAAkB,aAAa;AAC7E,QAAI,CAAC,OAAO;AACV,YAAM,IAAI,MAAM,gBAAgB,aAAa,8CAAyC;AAAA,IACxF;AACA,QAAI,MAAM,kBAAkB,MAAM,oBAAoB,MAAM;AAE1D,YAAM,MAAM,MAAM,KAAK,IAAI,QAAQ,OAAO;AAC1C,YAAM,WAAW,IAAI,KAAK,CAAC,MAAe,EAAE,OAAO,MAAM,eAAe;AACxE,UAAI,SAAU,QAAO,EAAE,UAAU,SAAS,IAAI,UAAU,SAAS,SAAS;AAAA,IAC5E;AACA,UAAM,UAAU,MAAM,SAAS;AAC/B,QAAI,OAAO,YAAY,UAAU;AAC/B,YAAM,IAAI,MAAM,gBAAgB,aAAa,sDAAiD;AAAA,IAChG;AAEA,UAAM,SAAS,KAAK,OAAO;AAC3B,UAAM,cAAc;AAAA;AAAA,MAElB,MAAM,OAAO;AAAA,MACb,MAAM,OAAO;AAAA,MACb,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB,WAAW,OAAO;AAAA,MAClB,GAAI,MAAM,SAAS,MAAM,EAAE,KAAK,MAAM,SAAS,IAAI,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA,MAKxD;AAAA,MACA,aAAa;AAAA,QACX,YAAY;AAAA,QACZ,cAAc;AAAA,QACd,GAAI,MAAM,SAAS,QAAQ,EAAE,OAAO,MAAM,SAAS,MAAM,IAAI,CAAC;AAAA,QAC9D,GAAI,MAAM,SAAS,cAAc,SAAY,EAAE,YAAY,MAAM,SAAS,UAAU,IAAI,CAAC;AAAA,QACzF,GAAI,MAAM,SAAS,eAAe,SAAY,EAAE,aAAa,MAAM,SAAS,WAAW,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAS9F;AAAA,IACF;AAEA,UAAM,WAAW,GAAG,KAAK,QAAQ,IAAI,aAAa;AAClD,SAAK,IAAI,OAAO,KAAK,+BAA+B;AAAA,MAClD,MAAM;AAAA,QACJ;AAAA,QACA;AAAA,QACA,MAAM,gBAAgB,MAAM;AAAA,QAC5B,OAAO,MAAM,SAAS,SAAS;AAAA,QAC/B,WAAW,MAAM,SAAS,cAAc;AAAA,QACxC,YAAY,MAAM,SAAS,eAAe;AAAA,QAC1C;AAAA,MACF;AAAA,IACF,CAAC;AACD,UAAM,UAAU,MAAM,KAAK,IAAI,QAAQ;AAAA,MACrC;AAAA,MACA;AAAA,MACA;AAAA,MACA,KAAK;AAAA,MACL,EAAE,MAAMA,YAAW,QAAQ,MAAM,gBAAgB,MAAM,KAAK;AAAA,IAC9D;AAMA,SAAK,IAAI,OAAO,KAAK,8BAA8B;AAAA,MACjD,MAAM,EAAE,UAAU,QAAQ,GAAG;AAAA,MAC7B,MAAM,EAAE,eAAe,SAAS,UAAU,UAAU,CAAC,GAAG,QAAQ,QAAQ,EAAE;AAAA,IAC5E,CAAC;AAID,SAAK,kBAAkB,IAAI,SAAS,QAAQ,EAAE;AAM9C,UAAM,KAAK,2BAA2B,EAAE,MAAM,MAAM;AAAA,IAAoB,CAAC;AAEzE,WAAO,EAAE,UAAU,QAAQ,IAAI,SAAS;AAAA,EAC1C;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,MAAc,uBAAuB,eAAsC;AACzE,UAAM,MAAM,MAAM,KAAK,IAAI,QAAQ,OAAO;AAC1C,UAAM,QAAQ,IAAI,KAAK,CAAC,MAAe,EAAE,OAAO,aAAa;AAC7D,QAAI,CAAC,SAAS,MAAM,mBAAmB,KAAK,IAAI;AAC9C,YAAM,IAAI,MAAM,kBAAkB,aAAa,0BAA0B,KAAK,EAAE,EAAE;AAAA,IACpF;AACA,SAAK,IAAI,OAAO,KAAK,gCAAgC;AAAA,MACnD,MAAM,EAAE,UAAU,cAAc;AAAA,MAChC,MAAM,EAAE,UAAU,MAAM,UAAU,MAAM,MAAM,KAAK;AAAA,IACrD,CAAC;AACD,UAAM,KAAK,IAAI,QAAQ,OAAO,aAAa;AAI3C,eAAW,CAAC,IAAI,GAAG,KAAK,KAAK,kBAAkB,QAAQ,GAAG;AACxD,UAAI,QAAQ,cAAe,MAAK,kBAAkB,OAAO,EAAE;AAAA,IAC7D;AACA,SAAK,IAAI,OAAO,KAAK,+BAA+B;AAAA,MAClD,MAAM,EAAE,UAAU,cAAc;AAAA,IAClC,CAAC;AAID,UAAM,KAAK,2BAA2B,EAAE,MAAM,MAAM;AAAA,IAAoB,CAAC;AAAA,EAC3E;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASQ,iBAAiB,IAA8B;AACrD,UAAM,UAAU,IAAI;AACpB,QAAI,OAAO,YAAY,SAAU;AACjC,UAAM,WAAW,KAAK,kBAAkB,IAAI,OAAO;AACnD,QAAI,aAAa,OAAW;AAC5B,SAAK,KAAK,IAAI,QAAQ,OAAO,EAC1B,KAAK,CAAC,QAAQ,IAAI,KAAK,CAAC,MAAM,EAAE,OAAO,QAAQ,CAAC,EAChD,KAAK,CAAC,UAAU;AACf,UAAI,EAAE,iBAAiB,eAAgB;AACvC,YAAM,kBAAkB,EAAE;AAAA,IAC5B,CAAC,EACA,MAAM,CAAC,QAAQ;AACd,WAAK,IAAI,OAAO,MAAM,mCAAmC;AAAA,QACvD,MAAM;AAAA,UACJ;AAAA,UACA;AAAA,UACA,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,QACxD;AAAA,MACF,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AAAA;AAAA,EAIA,MAAc,YAAyC;AACrD,QAAI,KAAK,OAAO,KAAK,IAAI,OAAO,kBAAkB,KAAK,KAAK,IAAI,OAAO,UAAU;AAC/E,aAAO,KAAK;AAAA,IACd;AACA,QAAI,KAAK,gBAAiB,QAAO,KAAK;AACtC,SAAK,kBAAkB,KAAK,QAAQ,EAAE,QAAQ,MAAM;AAClD,WAAK,kBAAkB;AAAA,IACzB,CAAC;AACD,WAAO,KAAK;AAAA,EACd;AAAA,EAEA,MAAc,UAAuC;AACnD,QAAI,KAAK,KAAK;AACZ,UAAI;AAAE,cAAM,KAAK,IAAI,MAAM,EAAE,QAAQ,gBAAgB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAoB;AACpF,WAAK,MAAM;AAAA,IACb;AACA,UAAM,MAAM,KAAK,OAAO;AACxB,QAAI,CAAC,IAAI,KAAM,OAAM,IAAI,MAAM,kCAAkC;AACjE,QAAI,CAAC,IAAI,SAAU,OAAM,IAAI,MAAM,sCAAsC;AAEzE,UAAM,MAAM,IAAII,oBAAmB;AAAA,MACjC,MAAM,IAAI;AAAA,MACV,MAAM,IAAI;AAAA,MACV,UAAU,IAAI;AAAA,MACd,UAAU,IAAI;AAAA,MACd,WAAW,IAAI;AAAA,MACf,GAAI,IAAI,MAAM,EAAE,KAAK,IAAI,IAAI,IAAI,CAAC;AAAA,IACpC,CAAC;AACD,SAAK,IAAI,OAAO,KAAK,0BAA0B;AAAA,MAC7C,MAAM,EAAE,MAAM,IAAI,MAAM,MAAM,IAAI,MAAM,WAAW,IAAI,UAAU;AAAA,IACnE,CAAC;AACD,UAAM,IAAI,MAAM;AAKhB,QAAI;AACF,YAAM,IAAI,eAAe,KAAK,sBAAsB;AAAA,IACtD,QAAQ;AAAA,IAAiD;AACzD,UAAM,IAAI,cAAc,KAAK,sBAAsB;AAEnD,QAAI,OAAO,GAAG,SAAS,MAAM,KAAK,kBAAkB,cAAc,CAAC;AACnE,QAAI,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC9B,WAAK,IAAI,OAAO,KAAK,wDAAmD;AAAA,QACtE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD,WAAK,kBAAkB,cAAc;AAAA,IACvC,CAAC;AAED,SAAK,MAAM;AACX,SAAK,oBAAoB;AACzB,SAAK,WAAW,IAAI;AAIpB,SAAK,KAAK,2BAA2B,EAAE,MAAM,MAAM;AAAA,IAAoB,CAAC;AAMxE,SAAK,KAAK,6BAA6B,GAAG,EAAE,MAAM,MAAM;AAAA,IAAoB,CAAC;AAE7E,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA,EAMA,MAAc,6BAA6B,KAAwC;AACjF,UAAM,SAAS,KAAK,OAAO,OAAO,KAAK;AAMvC,UAAM,wBAAwB,KAAK,QAAW;AAAA,MAC5C,IAAI,KAAK;AAAA,MACT,KAAK,KAAK;AAAA,MACV,KAAK,OAAO,WAAW,YAAY,OAAO,SAAS,IAAI,SAAS;AAAA,MAChE,qBAAqB,OAAO,eAAe;AACzC,cAAM,WAAW,KAAK,OAAO,IAAI,aAAa,KAAK,CAAC;AACpD,cAAM,KAAK,OAAO,OAAO,EAAE,aAAa,EAAE,GAAG,UAAU,GAAG,WAAW,EAAE,CAAC;AAAA,MAC1E;AAAA,IACF,CAAC;AAAA,EACH;AAAA,EAEQ,kBAAkB,QAAsB;AAC9C,QAAI,KAAK,kBAAmB;AAC5B,SAAK,MAAM;AACX,SAAK,WAAW,KAAK;AACrB,QAAI,KAAK,eAAgB;AACzB,UAAM,UAAU,KAAK;AAAA,MACnB,wBAAwB,KAAK,KAAK;AAAA,MAClC;AAAA,IACF;AACA,SAAK,qBAAqB;AAC1B,SAAK,IAAI,OAAO,KAAK,mCAAmC;AAAA,MACtD,MAAM,EAAE,QAAQ,SAAS,KAAK,mBAAmB,WAAW,QAAQ;AAAA,IACtE,CAAC;AACD,SAAK,iBAAiB,WAAW,MAAM;AACrC,WAAK,iBAAiB;AACtB,WAAK,KAAK,UAAU,EAAE,MAAM,MAAM;AAAA,MAA4B,CAAC;AAAA,IACjE,GAAG,OAAO;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,SAAS,KAA+B;AACtC,SAAK,MAAM;AACX,SAAK,oBAAoB;AACzB,SAAK,WAAW,IAAI;AACpB,QAAI,OAAO,GAAG,SAAS,MAAM,KAAK,kBAAkB,cAAc,CAAC;AACnE,QAAI,OAAO,GAAG,SAAS,CAAC,QAAQ;AAC9B,WAAK,IAAI,OAAO,KAAK,oCAAoC;AAAA,QACvD,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AACD,WAAK,kBAAkB,cAAc;AAAA,IACvC,CAAC;AACD,SAAK,IAAI,eAAe,KAAK,sBAAsB,EAChD,MAAM,MAAM;AAAA,IAAwB,CAAC,EACrC,KAAK,MAAM,IAAI,cAAc,KAAK,sBAAsB,CAAC,EACzD,KAAK,MAAM,KAAK,2BAA2B,CAAC,EAC5C,MAAM,CAAC,QAAQ;AACd,WAAK,IAAI,OAAO,KAAK,2CAA2C;AAAA,QAC9D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH,CAAC;AAAA,EACL;AACF;AAUA,SAAS,qBAAqB,gBAAwB,SAAiB,KAAsB;AAC3F,OAAK;AACL,MAAI,OAAO,IAAI,SAAS,EAAG,QAAO,OAAO,GAAG;AAC5C,SAAO,WAAW,OAAO;AAC3B;AAUA,SAAS,oBAAoB,GAAoF;AAC/G,MAAI,EAAE,WAAW,MAAO,QAAO;AAC/B,MAAI,EAAE,aAAa,KAAM,QAAO;AAChC,MAAI,EAAE,WAAW,KAAM,QAAO;AAC9B,MAAI,OAAO,EAAE,UAAU,YAAY,EAAE,MAAM,SAAS,EAAG,QAAO;AAC9D,SAAO;AACT;;;AC/6BO,SAAS,0BAA0C;AACxD,SAAO;AAAA,IACL,UAAU;AAAA,MACR;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV,aAAa;AAAA,UACf;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aAAa;AAAA,QACb,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,SAAS;AAAA,YACT,UAAU;AAAA,UACZ;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV,YAAY;AAAA,UACd;AAAA,QACF;AAAA,MACF;AAAA,MACA;AAAA,QACE,IAAI;AAAA,QACJ,OAAO;AAAA,QACP,aACE;AAAA,QACF,SAAS;AAAA,QACT,QAAQ;AAAA,UACN;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,SAAS;AAAA,YACT,aACE;AAAA,YACF,SAAS;AAAA,cACP,EAAE,OAAO,QAAQ,OAAO,qBAAqB;AAAA,cAC7C,EAAE,OAAO,OAAO,OAAO,WAAW;AAAA,cAClC,EAAE,OAAO,OAAO,OAAO,6BAA6B;AAAA,YACtD;AAAA,UACF;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,UAAU;AAAA,YACV,aAAa;AAAA,YACb,aACE;AAAA,YACF,WAAW;AAAA,UACb;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,SAAS;AAAA,YACT,KAAK;AAAA,YACL,KAAK;AAAA,YACL,MAAM;AAAA,YACN,aAAa;AAAA,YACb,UAAU,EAAE,OAAO,aAAa,WAAW,MAAM;AAAA,UACnD;AAAA,UACA;AAAA,YACE,MAAM;AAAA,YACN,KAAK;AAAA,YACL,OAAO;AAAA,YACP,aAAa;AAAA,YACb,aACE;AAAA,YACF,UAAU,EAAE,OAAO,aAAa,WAAW,MAAM;AAAA,UACnD;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAAA,EACF;AACF;;;AChHA,SAAS,kBAAkB;AA+B3B,IAAM,iBAAiB;AAEhB,IAAM,kBAAN,MAAsB;AAAA,EACV;AAAA,EACA;AAAA,EACA,UAA6C,oBAAI,IAAI;AAAA,EAEtE,YAAY,UAAkC,CAAC,GAAG;AAChD,SAAK,QAAQ,QAAQ,SAAS;AAC9B,SAAK,MAAM,QAAQ,OAAO,KAAK;AAAA,EACjC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EASA,OAAO,OAAO,OAMH;AACT,UAAM,WAAW,WAAW,QAAQ,EAAE,OAAO,MAAM,QAAQ,EAAE,OAAO,KAAK,EAAE,MAAM,GAAG,EAAE;AACtF,WAAO;AAAA,MACL,MAAM,KAAK,KAAK,EAAE,YAAY;AAAA,MAC9B,MAAM;AAAA,MACN;AAAA,MACA,MAAM,KAAK,KAAK,KAAK;AAAA,MACrB,MAAM;AAAA,IACR,EAAE,KAAK,GAAG;AAAA,EACZ;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAA0C;AAC5C,UAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,QAAI,MAAM,aAAa,KAAK,IAAI,GAAG;AACjC,WAAK,QAAQ,OAAO,GAAG;AACvB,WAAK,KAAK,WAAW,MAAM,MAAM;AACjC,aAAO;AAAA,IACT;AACA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,IAAI,KAAa,QAAgC;AAC/C,UAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAClC,QAAI,OAAO;AACT,WAAK,KAAK,WAAW,MAAM,MAAM;AAAA,IACnC;AACA,SAAK,QAAQ,IAAI,KAAK;AAAA,MACpB;AAAA,MACA,WAAW,KAAK,IAAI,IAAI,KAAK;AAAA,IAC/B,CAAC;AAAA,EACH;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAOA,KAAK,KAAsC;AACzC,UAAM,QAAQ,KAAK,QAAQ,IAAI,GAAG;AAClC,QAAI,CAAC,MAAO,QAAO;AACnB,SAAK,QAAQ,OAAO,GAAG;AACvB,QAAI,MAAM,aAAa,KAAK,IAAI,GAAG;AACjC,WAAK,KAAK,WAAW,MAAM,MAAM;AACjC,aAAO;AAAA,IACT;AACA,WAAO,MAAM;AAAA,EACf;AAAA;AAAA,EAGA,MAAM,UAAyB;AAC7B,UAAM,MAAM,CAAC,GAAG,KAAK,QAAQ,OAAO,CAAC;AACrC,SAAK,QAAQ,MAAM;AACnB,UAAM,QAAQ,IAAI,IAAI,IAAI,CAAC,MAAM,KAAK,WAAW,EAAE,MAAM,CAAC,CAAC;AAAA,EAC7D;AAAA;AAAA,EAGA,IAAI,OAAe;AACjB,WAAO,KAAK,QAAQ;AAAA,EACtB;AAAA,EAEA,MAAc,WAAW,QAAyC;AAChE,QAAI;AACF,YAAM,OAAO,IAAI,MAAM,EAAE,QAAQ,yBAAyB,CAAC;AAAA,IAC7D,QAAQ;AAAA,IAER;AAAA,EACF;AACF;;;AnB3GA,SAAS,yBAAyB,cAMhC;AACA,QAAM,MAAM,CAAC,UAA+C,IAAI,SAAoB;AAClF,UAAM,UAAU,KAAK,IAAI,CAAC,MAAO,OAAO,MAAM,WAAW,IAAI,KAAK,UAAU,CAAC,CAAE,EAAE,KAAK,GAAG;AACzF,iBAAa,KAAK,EAAE,OAAO;AAAA,EAC7B;AACA,SAAO;AAAA,IACL,KAAK,IAAI,MAAM;AAAA,IACf,MAAM,IAAI,MAAM;AAAA,IAChB,MAAM,IAAI,MAAM;AAAA,IAChB,OAAO,IAAI,OAAO;AAAA,IAClB,OAAO,IAAI,OAAO;AAAA,EACpB;AACF;AAEA,SAAS,UAAU,KAA8B,KAAqB;AACpE,QAAM,IAAI,IAAI,GAAG;AACjB,SAAO,OAAO,MAAM,WAAW,IAAI;AACrC;AAEA,SAAS,UAAU,KAA8B,KAAa,UAA0B;AACtF,QAAM,IAAI,IAAI,GAAG;AACjB,SAAO,OAAO,MAAM,YAAY,OAAO,SAAS,CAAC,IAAI,IAAI;AAC3D;AAIA,SAAS,iBAAiB,KAA6C;AACrE,QAAM,IAAI,IAAI,WAAW;AACzB,SAAO,MAAM,SAAS,MAAM,SAAS,MAAM,SAAS,IAAI;AAC1D;AAEA,SAAS,sBAAsB,MAO7B;AACA,QAAM,OAAO,UAAU,MAAM,MAAM,EAAE,KAAK;AAC1C,QAAM,YAAY,UAAU,MAAM,UAAU,KAAK,SAAS,KAAK;AAC/D,QAAM,WAAW,UAAU,MAAM,UAAU;AAC3C,QAAM,SAAS,UAAU,MAAM,KAAK,EAAE,KAAK;AAC3C,QAAM,MAAM,OAAO,SAAS,IAAI,SAAS;AACzC,QAAM,YAAY,iBAAiB,IAAI;AACvC,QAAM,OAAO,UAAU,MAAM,QAAQ,GAAI;AACzC,SAAO,EAAE,MAAM,UAAU,UAAU,KAAK,WAAW,KAAK;AAC1D;AAEA,SAAS,sBAAsB,QAAoC;AACjE,QAAM,SAAmB,CAAC;AAC1B,QAAM,QAAQ,OAAO,YAAY;AACjC,MAAI,SAAS,MAAM,SAAS,EAAG,QAAO,KAAK,KAAK;AAChD,SAAO,KAAK,sBAAsB,OAAO,IAAI,CAAC;AAC9C,MAAI,OAAO,SAAS,SAAS,OAAO,OAAO,eAAe,YAAY,OAAO,aAAa,GAAG;AAC3F,WAAO,KAAK,GAAG,OAAO,UAAU,WAAW;AAAA,EAC7C;AACA,SAAO,KAAK,cAAc,OAAO,UAAU,YAAY,CAAC,EAAE;AAC1D,MAAI,OAAO,cAAc,SAAS,OAAO,oBAAoB;AAC3D,WAAO,KAAK,WAAW,OAAO,kBAAkB,EAAE;AAAA,EACpD;AACA,MAAI,OAAO,OAAO,OAAO,IAAI,SAAS,KAAK,OAAO,cAAc,OAAO;AACrE,WAAO,KAAK,OAAO,OAAO,IAAI,MAAM,GAAG,CAAC,CAAC,SAAI,OAAO,IAAI,MAAM,EAAE,CAAC,EAAE;AAAA,EACrE;AACA,SAAO;AACT;AAEA,SAAS,sBAAsB,MAAwC;AACrE,UAAQ,MAAM;AAAA,IACZ,KAAK;AAAU,aAAO;AAAA,IACtB,KAAK;AAAc,aAAO;AAAA,IAC1B,KAAK;AAAe,aAAO;AAAA,IAC3B,KAAK;AAAO,aAAO;AAAA,IACnB,KAAK;AAAc,aAAO;AAAA,EAC5B;AACF;AAEA,SAAS,oBAAoB,OAA6B;AACxD,MAAI,MAAM,aAAa,qBAAsB,QAAO;AACpD,QAAM,OAAO,MAAM;AACnB,SAAO,KAAK,SAAS,MAAM,mBAAmB,KAAK,OAAO,MAAM;AAClE;AAQA,SAAS,uBAAuB,GAAoB;AAClD,MAAI,CAAC,KAAK,EAAE,SAAS,EAAG,QAAO;AAC/B,QAAM,WAAW,IAAI,IAAI,EAAE,YAAY,EAAE,MAAM,EAAE,EAAE,OAAO,CAAC,MAAM,MAAM,OAAO,MAAM,GAAG,CAAC;AACxF,SAAO,SAAS,QAAQ;AAC1B;AA+BA,eAAe,iCACb,WACA,QACe;AACf,QAAM,MAAM,UAAU;AAStB,MAAI;AACF,UAAM,WAAW,MAAM,IAAI,UAAU,QAAW,EAAE,WAAW,IAAK,CAAC;AACnE,QAAI,aAAa,SAAS,QAAQ,SAAS,eAAe;AACxD,YAAM,eAAe,UAAU,YAAY,QAAQ;AACnD,YAAM,iBAAiB,UAAU,YAAY,gBAAgB;AAC7D,gBAAU,aAAa,EAAE,GAAI,UAAU,cAAc,CAAC,GAAI,GAAG,SAAS;AACtE,UAAI,iBAAiB,SAAS,QAAQ,mBAAmB,SAAS,cAAc;AAC9E,eAAO,KAAK,wEAAwE;AAAA,UAClF,MAAM;AAAA,YACJ;AAAA,YACA,cAAc,SAAS,QAAQ;AAAA,YAC/B;AAAA,YACA,gBAAgB,SAAS,gBAAgB;AAAA,UAC3C;AAAA,QACF,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF,SAAS,KAAK;AACZ,WAAO,MAAM,oDAAoD;AAAA,MAC/D,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,IAClE,CAAC;AAAA,EACH;AAIA,MAAI,CAAC,UAAU,iBAAiB,KAAK;AACnC,QAAI;AACF,YAAM,MAAM,MAAM,IAAI,iBAAiB,QAAW,EAAE,WAAW,IAAK,CAAC;AACrE,UAAI,KAAK,OAAO,KAAK,IAAI;AACvB,kBAAU,kBAAkB,EAAE,GAAI,UAAU,mBAAmB,CAAC,GAAI,GAAG,IAAI;AAC3E,eAAO,KAAK,yEAAyE;AAAA,UACnF,MAAM,EAAE,IAAI,IAAI,MAAM,WAAW,KAAK,IAAI,OAAO,UAAU;AAAA,QAC7D,CAAC;AAAA,MACH;AAAA,IACF,SAAS,KAAK;AACZ,aAAO,MAAM,sDAAsD;AAAA,QACjE,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAClE,CAAC;AAAA,IACH;AAAA,EACF;AACF;AAEO,IAAM,uBAAN,cAAmC,mBAAmB;AAAA,EACxC,UAAU;AAAA,EACV,eAAe;AAAA,EACf,gBAAyE;AAAA,IAC1F,CAACC,YAAW,MAAM,GAAG;AAAA,IACrB,CAACA,YAAW,GAAG,GAAG;AAAA,EACpB;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAQiB,kBAAkB,IAAI,gBAAgB;AAAA,EAEvD,cAAc;AAAE,UAAM,CAAC,CAAC;AAAA,EAAE;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EA4BP,iBAAiB,OAAmB,QAA0C;AAC/F,UAAM,MAAM,UAAU,CAAC;AACvB,UAAM,QAAS,IAAI,aAAa,KAA6C,CAAC;AAC9E,UAAM,SAAS,OAAO,MAAM,KAAK,MAAM,WAAW,MAAM,KAAK,EAAE,KAAK,IAAI;AACxE,UAAM,MAAM,OAAO,QAAQ,iBAAiB,EAAE,EAAE,YAAY;AAI5D,QAAI,uBAAuB,GAAG,EAAG,QAAO,OAAO,GAAG;AAClD,UAAM,OAAO,OAAO,IAAI,MAAM,MAAM,WAAW,IAAI,MAAM,EAAE,KAAK,IAAI;AACpE,QAAI,KAAK,SAAS,GAAG;AAGnB,YAAM,OAAO,KAAK,YAAY,EAAE,QAAQ,eAAe,GAAG,EAAE,QAAQ,YAAY,EAAE;AAClF,UAAI,KAAK,SAAS,EAAG,QAAO,QAAQ,IAAI;AAAA,IAC1C;AAIA,UAAM,YAAY,QAAQ;AAC1B,UAAM,SAAS,yBAAyB,SAAS;AACjD,QAAI;AACF,YAAM,MAAM,KAAK,IAAI;AACrB,WAAK,KAAK,IAAI,KAAK,QAAQ,MAAM,SAAS;AAAA,QACxC,IAAI,uBAAuB,GAAG;AAAA,QAC9B,UAAU;AAAA,QACV,UAAU;AAAA,QACV,OAAO,kCAAkC,SAAS;AAAA,QAClD,SAAS;AAAA,QACT,QAAQ;AAAA,QACR,MAAM;AAAA,QACN,WAAW;AAAA,QACX,WAAW;AAAA,QACX,QAAQ,EAAE,MAAM,SAAS,IAAI,KAAK,QAAQ;AAAA,QAC1C,UAAU,EAAE,MAAM,UAAU;AAAA,MAC9B,CAAC;AAAA,IACH,QAAQ;AAAA,IAAoD;AAC5D,UAAM,IAAI,MAAM,YAAY,MAAM,EAAE;AAAA,EACtC;AAAA,EAEA,MAAyB,aAA4B;AACnD,UAAM,KAAK,gBAAgB,QAAQ;AAAA,EACrC;AAAA,EAEA,MAAyB,eAAgD;AACvE,UAAM,OAAO,MAAM,MAAM,aAAa;AAGtC,SAAK;AAAA,MACH,EAAE,UAAUC,eAAc,iBAAiB;AAAA,MAC3C,CAAC,UAAU;AACT,YAAI,CAAC,oBAAoB,KAAK,EAAG;AACjC,aAAK,KAAK,aAAa,EAAE,MAAM,CAAC,QAAiB;AAC/C,eAAK,IAAI,OAAO,KAAK,2DAA2D;AAAA,YAC9E,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AAgBA,SAAK;AAAA,MACH,EAAE,UAAUA,eAAc,yCAAyC;AAAA,MACnE,CAAC,UAAU;AACT,cAAM,OAAO,MAAM;AACnB,cAAM,WAAW,OAAO,KAAK,aAAa,WAAW,KAAK,WAAW;AACrE,YAAI,aAAa,KAAM;AACvB,aAAK,KAAK,qBAAqB,UAAU,KAAK,WAAW,EAAE,MAAM,CAAC,QAAiB;AACjF,eAAK,IAAI,OAAO,KAAK,oCAAoC;AAAA,YACvD,MAAM,EAAE,SAAS;AAAA,YACjB,MAAM;AAAA,cACJ,aAAa,KAAK,eAAe;AAAA,cACjC,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,YACxD;AAAA,UACF,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AAEA,WAAO;AAAA,EACT;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAiBA,MAAc,qBAAqB,UAAkB,aAAgD;AACnG,UAAM,MAAM,KAAK,IAAI,OAAO,gBAAgB,QAAQ,QAAQ;AAC5D,QAAI,CAAC,OAAO,EAAE,eAAe,eAAgB;AAC7C,QAAI,aAAa;AACf,WAAK,IAAI,OAAO,KAAK,0EAAqE;AAAA,QACxF,MAAM,EAAE,SAAS;AAAA,QACjB,MAAM,EAAE,YAAY;AAAA,MACtB,CAAC;AACD,YAAM,IAAI,wBAAwB,WAAW;AAC7C;AAAA,IACF;AACA,SAAK,IAAI,OAAO,KAAK,yEAAoE;AAAA,MACvF,MAAM,EAAE,SAAS;AAAA,IACnB,CAAC;AACD,UAAM,IAAI,gBAAgB;AAAA,EAC5B;AAAA;AAAA,EAIA,MAAgB,oBAAoB,MAAkD;AAKpF,QAAI,SAASD,YAAW,UAAU,SAASA,YAAW,IAAK,QAAO;AAClE,WAAO,wBAAwB;AAAA,EACjC;AAAA,EAEA,MAAgB,eAAe,MAAkB,QAA4D;AAC3G,QAAI,SAASA,YAAW,UAAU,SAASA,YAAW,KAAK;AACzD,YAAM,IAAI,MAAM,kDAAkD,IAAI,EAAE;AAAA,IAC1E;AAEA,UAAM,OAAO,UAAU,QAAQ,MAAM,EAAE,KAAK;AAC5C,UAAM,SAAS,sBAAsB,MAAM;AAE3C,QAAI,CAAC,KAAM,OAAM,IAAI,MAAM,yBAAyB;AACpD,QAAI,CAAC,OAAO,SAAU,OAAM,IAAI,MAAM,sBAAsB;AAC5D,QAAI,CAAC,OAAO,QAAQ,OAAO,cAAc,OAAO;AAC9C,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AACA,QAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,KAAK;AAC/B,YAAM,IAAI,MAAM,oCAAoC;AAAA,IACtD;AAKA,UAAM,WAAW,gBAAgB,OAAO,MAAM;AAC9C,QAAI,YAAY,KAAK,gBAAgB,KAAK,QAAQ;AAClD,QAAI,CAAC,WAAW;AACd,WAAK,IAAI,OAAO,KAAK,oDAA+C;AAAA,QAClE,MAAM,EAAE,MAAM,OAAO,QAAQ,aAAa;AAAA,QAC1C,MAAM,EAAE,WAAW,OAAO,WAAW,QAAQ,QAAQ,OAAO,GAAG,EAAE;AAAA,MACnE,CAAC;AACD,kBAAY,MAAM,qBAAqB;AAAA,QACrC,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,GAAI,OAAO,MAAM,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,QACxC,MAAM,OAAO;AAAA,MACf,CAAC;AAAA,IACH,OAAO;AACL,WAAK,IAAI,OAAO,KAAK,2CAAsC;AAAA,QACzD,MAAM,EAAE,MAAM,OAAO,QAAQ,aAAa;AAAA,QAC1C,MAAM,EAAE,MAAM,UAAU,MAAM,WAAW,UAAU,UAAU;AAAA,MAC/D,CAAC;AAAA,IACH;AAOA,SAAK,IAAI,OAAO,KAAK,uBAAuB;AAAA,MAC1C,MAAM,EAAE,MAAM,OAAO,QAAQ,aAAa;AAAA,MAC1C,MAAM;AAAA,QACJ,MAAM,UAAU;AAAA,QAChB,WAAW,UAAU;AAAA,QACrB,YAAY,UAAU,cAAc;AAAA,QACpC,OAAO,UAAU,YAAY,QAAQ;AAAA,QACrC,QAAQ,QAAQ,UAAU,GAAG;AAAA,MAC/B;AAAA,IACF,CAAC;AAED,QAAI,UAAU,SAAS,cAAc;AACnC,UAAI;AAAE,cAAM,UAAU,IAAI,MAAM,EAAE,QAAQ,mBAAmB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAe;AACvF,YAAM,IAAI;AAAA,QACR;AAAA,MACF;AAAA,IACF;AAEA,UAAM,KAAK,uBAAuB,WAAW,OAAO,QAAQ,YAAY;AAWxE,UAAM,iCAAiC,WAAW,KAAK,IAAI,MAAM;AAEjE,UAAM,YAA2B,UAAU;AAC3C,UAAM,cAAc,UAAU,OAAO,UAAU,IAAI,SAAS,IAAI,UAAU,MAAM,OAAO;AACvF,UAAM,eAAe,UAAU,SAAS,SAAS,OAAO,UAAU,eAAe,WAC7E,KAAK,IAAI,GAAG,UAAU,UAAU,IAChC;AAEJ,UAAM,eAAe,OAAO,QAAQ,UAAU,iBAAiB,MAAM,IAAI,KAAK;AAC9E,QAAI,CAAC,aAAa;AAChB,UAAI;AAAE,cAAM,UAAU,IAAI,MAAM,EAAE,QAAQ,qBAAqB,CAAC;AAAA,MAAE,QAAQ;AAAA,MAAe;AACzF,YAAM,IAAI,MAAM,kEAAkE;AAAA,IACpF;AAQA,QAAI,UAAU,SAAS,OAAO;AAC5B,YAAM,YAAY,iBAAiB,MAAM;AAAA,QACvC,MAAM;AAAA,QACN,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB;AAAA,QACA,GAAI,cAAc,EAAE,KAAK,YAAY,IAAI,CAAC;AAAA,QAC1C,aAAa;AAAA,UACX,YAAY;AAAA,UACZ;AAAA,UACA,GAAI,UAAU,YAAY,OAAO,EAAE,OAAO,UAAU,WAAW,KAAK,IAAI,CAAC;AAAA,UACzE,GAAI,UAAU,YAAY,eAAe,EAAE,cAAc,UAAU,WAAW,aAAa,IAAI,CAAC;AAAA,UAChG,GAAI,UAAU,YAAY,kBAAkB,EAAE,iBAAiB,UAAU,WAAW,gBAAgB,IAAI,CAAC;AAAA,UACzG,GAAI,UAAU,YAAY,kBAAkB,EAAE,iBAAiB,UAAU,WAAW,gBAAgB,IAAI,CAAC;AAAA,UACzG,GAAI,UAAU,iBAAiB,MAAM,EAAE,KAAK,UAAU,gBAAgB,IAAI,IAAI,CAAC;AAAA,UAC/E,UAAU,KAAK,IAAI;AAAA,QACrB;AAAA,MACF,CAAC;AACD,YAAME,eAAc,UAAU;AAC9B,aAAO;AAAA,QACL,MAAM,EAAE,MAAMF,YAAW,KAAK,KAAK;AAAA,QACnC,QAAQ;AAAA,QACR,eAAe,OAAO,WAAoB;AACxC,cAAI,kBAAkB,YAAY;AAChC,mBAAO,SAASE,YAAW;AAAA,UAC7B;AAAA,QACF;AAAA,MACF;AAAA,IACF;AAEA,UAAM,SAAS,oBAAoB,MAAM;AAAA,MACvC,MAAM;AAAA,MACN,MAAM,OAAO;AAAA,MACb,UAAU,OAAO;AAAA,MACjB,UAAU,OAAO;AAAA,MACjB;AAAA,MACA,GAAI,cAAc,EAAE,KAAK,YAAY,IAAI,CAAC;AAAA,MAC1C,GAAI,UAAU,qBAAqB,EAAE,oBAAoB,UAAU,mBAAmB,IAAI,CAAC;AAAA,MAC3F,aAAa;AAAA,QACX,YAAY,UAAU;AAAA,QACtB;AAAA,QACA,YAAY,UAAU,eAAe;AAAA,QACrC,GAAI,UAAU,YAAY,OAAO,EAAE,OAAO,UAAU,WAAW,KAAK,IAAI,CAAC;AAAA,QACzE,GAAI,UAAU,YAAY,eAAe,EAAE,cAAc,UAAU,WAAW,aAAa,IAAI,CAAC;AAAA,QAChG,GAAI,UAAU,YAAY,kBAAkB,EAAE,iBAAiB,UAAU,WAAW,gBAAgB,IAAI,CAAC;AAAA,QACzG,GAAI,UAAU,YAAY,kBAAkB,EAAE,iBAAiB,UAAU,WAAW,gBAAgB,IAAI,CAAC;AAAA,QACzG,GAAI,UAAU,iBAAiB,MAAM,EAAE,KAAK,UAAU,gBAAgB,IAAI,IAAI,CAAC;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,MAYjF;AAAA,IACF,CAAC;AAQD,UAAM,cAAc,UAAU;AAC9B,UAAM,MAAM,KAAK;AAEjB,WAAO;AAAA,MACL,MAAM,EAAE,MAAMF,YAAW,QAAQ,KAAK;AAAA,MACtC,QAAQ;AAAA,MACR,eAAe,OAAO,WAAoB;AACxC,YAAI,kBAAkB,eAAe;AACnC,iBAAO,SAAS,WAAW;AAAA,QAC7B;AAGA,aAAK,IAAI,KAAK,sBAAsB,0BAA0B,OAAO;AAAA,UACnE,UAAU,OAAO;AAAA,UACjB,OAAO,EAAE,eAAe,CAAC,SAAS,EAAE;AAAA,QACtC,CAAC,EAAE,MAAM,CAAC,QAAiB;AACzB,cAAI,OAAO,MAAM,sDAAsD;AAAA,YACrE,MAAM,EAAE,UAAU,OAAO,GAAG;AAAA,YAC5B,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,UAClE,CAAC;AAAA,QACH,CAAC;AAAA,MACH;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAkBA,MAAc,uBACZ,WACA,WACe;AACf,QAAI,UAAU,SAAS,YAAY,UAAU,SAAS,cAAe;AACrE,QAAI;AACF,YAAM,UAAU,MAAM,UAAU,IAAI,sBAAsB,EAAE,QAAQ,OAAO,WAAW,IAAK,CAAC;AAC5F,UAAI,QAAQ,SAAS,SAAS,GAAG;AAC/B,aAAK,IAAI,OAAO,KAAK,uDAAkD;AAAA,UACrE,MAAM,EAAE,MAAM,UAAU;AAAA,UACxB,MAAM;AAAA,YACJ,SAAS,UAAU;AAAA,YACnB,eAAe,UAAU,cAAc;AAAA,YACvC,aAAa,QAAQ;AAAA,YACrB,gBAAgB,QAAQ,QAAQ;AAAA,UAClC;AAAA,QACF,CAAC;AACA,QAAC,UAAoD,OAAO;AAC5D,QAAC,UAAoD,aAAa,QAAQ,SAAS;AAAA,MACtF;AAAA,IACF,SAAS,KAAK;AACZ,WAAK,IAAI,OAAO,MAAM,yDAAyD;AAAA,QAC7E,MAAM,EAAE,MAAM,UAAU;AAAA,QACxB,MAAM,EAAE,SAAS,UAAU,MAAM,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,MAC3F,CAAC;AAAA,IACH;AAAA,EACF;AAAA;AAAA,EAIA,MAAe,kBAAkB,OAKH;AAC5B,QAAI,MAAM,QAAQ,QAAQ;AACxB,aAAO,EAAE,QAAQ,MAAM,QAAQ,CAAC,oBAAoB,EAAE;AAAA,IACxD;AAEA,UAAM,OAAO,MAAM,cAAc,EAAE,MAAM,OAAO,MAAM,UAAU,WAAW,MAAM,QAAQ,GAAG;AAC5F,UAAM,SAAS,sBAAsB,IAAI;AAEzC,QAAI,CAAC,OAAO,UAAU;AACpB,aAAO,EAAE,QAAQ,SAAS,OAAO,2CAA2C;AAAA,IAC9E;AACA,QAAI,CAAC,OAAO,QAAQ,CAAC,OAAO,KAAK;AAC/B,aAAO,EAAE,QAAQ,SAAS,OAAO,kDAAkD;AAAA,IACrF;AAQA,UAAM,iBAAiB,OAAO,KAAK,iBAAiB,MAAM,WACrD,KAAK,iBAAiB,IACvB;AAEJ,UAAM,cAAc,iBAChB,KAAK,IAAI,OAAO,MAAM,YAAY,EAAE,SAAS,EAAE,WAAW,eAAe,CAAC,IAC1E,KAAK,IAAI,OAAO,MAAM,YAAY;AAEtC,QAAI;AACF,YAAM,SAAS,MAAM,qBAAqB;AAAA,QACxC,MAAM,OAAO;AAAA,QACb,UAAU,OAAO;AAAA,QACjB,UAAU,OAAO;AAAA,QACjB,GAAI,OAAO,MAAM,EAAE,KAAK,OAAO,IAAI,IAAI,CAAC;AAAA,QACxC,MAAM,OAAO;AAAA,QACb,QAAQ,yBAAyB,WAAW;AAAA,MAC9C,CAAC;AAOD,YAAM,KAAK,uBAAuB,QAAQ,OAAO,QAAQ,YAAY;AACrE,YAAM,WAAW,gBAAgB,OAAO,MAAM;AAC9C,WAAK,gBAAgB,IAAI,UAAU,MAAM;AACzC,aAAO,EAAE,QAAQ,MAAM,QAAQ,sBAAsB,MAAM,EAAE;AAAA,IAC/D,SAAS,KAAK;AACZ,aAAO;AAAA,QACL,QAAQ;AAAA,QACR,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG;AAAA,MACxD;AAAA,IACF;AAAA,EACF;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA;AAAA,EAqBA,MAAc,eAA8B;AAC1C,UAAM,MAAM,KAAK,IAAI,OAAO,gBAAgB,eAAe,KAAK,OAAO,KAAK,CAAC;AAC7E,QAAI,YAAY;AAChB,eAAW,OAAO,KAAK;AACrB,UAAI,EAAE,eAAe,eAAgB;AACrC,UAAI;AACF,cAAM,IAAI,gBAAgB;AAC1B;AAAA,MACF,SAAS,KAAK;AACZ,aAAK,IAAI,OAAO,MAAM,0CAA0C;AAAA,UAC9D,MAAM,EAAE,UAAU,IAAI,GAAG;AAAA,UACzB,MAAM,EAAE,OAAO,eAAe,QAAQ,IAAI,UAAU,OAAO,GAAG,EAAE;AAAA,QAClE,CAAC;AAAA,MACH;AAAA,IACF;AACA,QAAI,YAAY,GAAG;AACjB,WAAK,IAAI,OAAO,KAAK,iDAAiD;AAAA,QACpE,MAAM,EAAE,WAAW,OAAO,IAAI,OAAO;AAAA,MACvC,CAAC;AAAA,IACH;AAAA,EACF;AACF;","names":["DeviceType","EventCategory","BaseDevice","DeviceType","DeviceFeature","AccessoryKind","hydrateSchema","createRuntimeStateBridge","AccessoryKind","z","z","z","BaseDevice","DeviceType","DeviceFeature","hydrateSchema","switchCapability","motionTriggerCapability","createRuntimeStateBridge","z","STALE_MS","COOLDOWN_MS","BaseDevice","DeviceType","DeviceFeature","switchCapability","motionTriggerCapability","createRuntimeStateBridge","hydrateSchema","z","BaseDevice","DeviceType","hydrateSchema","switchCapability","z","COOLDOWN_MS","BaseDevice","DeviceType","switchCapability","hydrateSchema","AccessoryKind","z","errMsg","BaseDevice","DeviceType","DeviceFeature","AccessoryKind","STALE_MS","createRuntimeStateBridge","hydrateSchema","ReolinkBaichuanApi","BaseDevice","DeviceFeature","DeviceType","EventCategory","hydrateSchema","rebootCapability","BaseDevice","DeviceFeature","DeviceType","rebootCapability","EventCategory","hydrateSchema","ReolinkBaichuanApi","DeviceType","EventCategory","detectedApi"]}