// Copyright 2026 The Lynx Authors. All rights reserved. // Licensed under the Apache License Version 2.0 that can be found in the // LICENSE file in the root directory of this source tree. import type { BackgroundElementTemplateInstance } from '../background/instance.js'; import { ElementTemplateUpdateOps } from '../protocol/opcodes.js'; import type { ElementTemplateUpdateOp } from '../protocol/opcodes.js'; import type { ElementTemplateUpdateCommandStream } from '../protocol/types.js'; export type FormattedElementTemplateUpdateCommand = | { op: 'createTemplate'; handleId: number; templateKey: string; bundleUrl: string | null | undefined; attributeSlots: unknown; childSlots: unknown; } | { op: 'createTypedElement'; handleId: number; type: string; attributes: unknown; childSlots: unknown; options: unknown; } | { op: 'setAttribute'; targetId: number; attrSlotIndex: number; value: unknown; } | { op: 'setMainThreadEvent' | 'setMainThreadRef'; targetId: number; attrSlotIndex: number; value: unknown; } | { op: 'insertTypedListItem'; targetId: number; item: unknown; beforeId: number; } | { op: 'removeTypedListItem'; targetId: number; itemId: number; removedSubtreeHandleIds: number[]; } | { op: 'updateTypedListItem'; targetId: number; item: unknown; } | { op: 'insertNode'; targetId: number; childSlotIndex: number; childId: number; referenceId: number; attachedSubtreeHandleIds: number[] | null; } | { op: 'removeNode'; targetId: number; childSlotIndex: number; childId: number; removedSubtreeHandleIds: number[]; } | { op: 'unknown'; opcode: unknown; index: number; remaining: unknown[]; }; export function formatElementTemplateUpdateCommands( stream: ElementTemplateUpdateCommandStream | undefined, ): FormattedElementTemplateUpdateCommand[] { if (!Array.isArray(stream)) { return []; } const result: FormattedElementTemplateUpdateCommand[] = []; for (let index = 0; index < stream.length;) { const opIndex = index; const op = stream[index++] as ElementTemplateUpdateOp; switch (op) { case ElementTemplateUpdateOps.createTemplate: { result.push({ op: 'createTemplate', handleId: stream[index++] as number, templateKey: stream[index++] as string, bundleUrl: stream[index++] as string | null | undefined, attributeSlots: stream[index++], childSlots: stream[index++], }); break; } case ElementTemplateUpdateOps.setAttribute: result.push({ op: 'setAttribute', targetId: stream[index++] as number, attrSlotIndex: stream[index++] as number, value: stream[index++], }); break; case ElementTemplateUpdateOps.setMainThreadEvent: case ElementTemplateUpdateOps.setMainThreadRef: result.push({ op: op === ElementTemplateUpdateOps.setMainThreadEvent ? 'setMainThreadEvent' : 'setMainThreadRef', targetId: stream[index++] as number, attrSlotIndex: stream[index++] as number, value: stream[index++], }); break; case ElementTemplateUpdateOps.createTypedElement: result.push({ op: 'createTypedElement', handleId: stream[index++] as number, type: stream[index++] as string, attributes: stream[index++], childSlots: stream[index++], options: stream[index++], }); break; case ElementTemplateUpdateOps.insertTypedListItem: result.push({ op: 'insertTypedListItem', targetId: stream[index++] as number, item: stream[index++], beforeId: stream[index++] as number, }); break; case ElementTemplateUpdateOps.removeTypedListItem: result.push({ op: 'removeTypedListItem', targetId: stream[index++] as number, itemId: stream[index++] as number, removedSubtreeHandleIds: stream[index++] as number[], }); break; case ElementTemplateUpdateOps.updateTypedListItem: result.push({ op: 'updateTypedListItem', targetId: stream[index++] as number, item: stream[index++], }); break; case ElementTemplateUpdateOps.insertNode: result.push({ op: 'insertNode', targetId: stream[index++] as number, childSlotIndex: stream[index++] as number, childId: stream[index++] as number, referenceId: stream[index++] as number, attachedSubtreeHandleIds: stream[index++] as number[] | null, }); break; case ElementTemplateUpdateOps.removeNode: result.push({ op: 'removeNode', targetId: stream[index++] as number, childSlotIndex: stream[index++] as number, childId: stream[index++] as number, removedSubtreeHandleIds: stream[index++] as number[], }); break; default: result.push({ op: 'unknown', opcode: op, index: opIndex, remaining: stream.slice(index), }); index = stream.length; break; } } return result; } export function printElementTemplateTreeToString( root: BackgroundElementTemplateInstance | null | undefined, ): string { if (!root) { return ''; } const lines: string[] = []; appendInstance(lines, root, 0); return lines.join('\n'); } function appendInstance( lines: string[], instance: BackgroundElementTemplateInstance, depth: number, ): void { const indent = ' '.repeat(depth); const type = instance.type ?? ''; const instanceId = instance.instanceId ?? ''; lines.push(`${indent}${type}#${instanceId}`); if (Array.isArray(instance.attributeSlots) && instance.attributeSlots.length > 0) { lines.push(`${indent} attributeSlots: ${JSON.stringify(instance.attributeSlots)}`); } const childSlots = Array.isArray(instance.childSlots) ? instance.childSlots : []; for (let slotIndex = 0; slotIndex < childSlots.length; slotIndex += 1) { const children = childSlots[slotIndex]; if (!children || children.length === 0) { continue; } lines.push( `${indent} childSlots[${slotIndex}]: [${children.map(child => child.instanceId).join(', ')}]`, ); } let child = instance.firstChild; while (child) { appendInstance(lines, child, depth + 1); child = child.nextSibling; } }