// 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 { elementTemplateRegistry } from './template/registry.js';
import { ElementTemplateUpdateOps } from '../protocol/opcodes.js';
export function applyElementTemplateUpdateCommands(stream) {
    let i = 0;
    while (i < stream.length) {
        const op = stream[i++];
        switch (op) {
            case ElementTemplateUpdateOps.createTemplate: {
                const handleId = stream[i++];
                const templateKey = stream[i++];
                const bundleUrl = stream[i++];
                const attributeSlots = stream[i++];
                const elementSlots = stream[i++];
                if (__DEV__) {
                    const createError = validateCreateTemplatePayload(handleId, attributeSlots, elementSlots);
                    if (createError) {
                        lynx.reportError(createError);
                        continue;
                    }
                }
                const resolvedElementSlots = resolveElementSlots(elementSlots);
                if (__DEV__ && resolvedElementSlots.hasError) {
                    continue;
                }
                const nativeRef = __CreateElementTemplate(templateKey, bundleUrl, normalizeAttributeSlots(attributeSlots), resolvedElementSlots.value, handleId);
                if (nativeRef) {
                    elementTemplateRegistry.set(handleId, nativeRef);
                }
                break;
            }
            case ElementTemplateUpdateOps.setAttribute: {
                const targetId = stream[i++];
                const attrSlotIndex = stream[i++];
                const value = stream[i++];
                const nativeRef = resolveHandle(targetId, 'target');
                if (!nativeRef) {
                    continue;
                }
                __SetAttributeOfElementTemplate(nativeRef, attrSlotIndex, value, null);
                break;
            }
            case ElementTemplateUpdateOps.insertNode: {
                const targetId = stream[i++];
                const elementSlotIndex = stream[i++];
                const childId = stream[i++];
                const referenceId = stream[i++];
                const nativeRef = resolveHandle(targetId, 'target');
                const childRef = resolveHandle(childId, 'child');
                if (!nativeRef || !childRef) {
                    continue;
                }
                const referenceRef = referenceId === 0 ? null : resolveHandle(referenceId, 'reference');
                if (referenceId !== 0 && !referenceRef) {
                    continue;
                }
                __InsertNodeToElementTemplate(nativeRef, elementSlotIndex, childRef, referenceRef);
                break;
            }
            case ElementTemplateUpdateOps.removeNode: {
                const targetId = stream[i++];
                const elementSlotIndex = stream[i++];
                const childId = stream[i++];
                const removedSubtreeHandleIds = stream[i++];
                const nativeRef = resolveHandle(targetId, 'target');
                const childRef = resolveHandle(childId, 'child');
                if (!nativeRef || !childRef) {
                    continue;
                }
                __RemoveNodeFromElementTemplate(nativeRef, elementSlotIndex, childRef);
                // The native API only detaches from the slot. Releasing ET runtime's
                // strong refs after a successful detach lets JS GC reclaim the subtree.
                for (const handleId of removedSubtreeHandleIds) {
                    elementTemplateRegistry.delete(handleId);
                }
                break;
            }
            default: {
                lynx.reportError(new Error(`ElementTemplate update opcode ${String(op)} is not supported.`));
            }
        }
    }
}
function resolveElementSlots(elementSlots) {
    if (!Array.isArray(elementSlots)) {
        return { hasError: false, value: null };
    }
    let hasError = false;
    const value = elementSlots.map((children, slotIndex) => {
        if (!Array.isArray(children)) {
            if (__DEV__) {
                lynx.reportError(new Error(`ElementTemplate create slot ${slotIndex} must be an array of child handles.`));
                hasError = true;
            }
            return [];
        }
        return children
            .map((childId) => {
            const childRef = __DEV__
                ? resolveHandle(childId, 'child')
                : (elementTemplateRegistry.get(childId) ?? null);
            if (__DEV__ && childRef === null) {
                hasError = true;
            }
            return childRef;
        })
            .filter((childRef) => childRef !== null);
    });
    return { hasError, value };
}
function resolveHandle(id, role) {
    const nativeRef = elementTemplateRegistry.get(id);
    if (!nativeRef) {
        lynx.reportError(new Error(`ElementTemplate update ${role} handle ${id} not found.`));
        return null;
    }
    return nativeRef;
}
function isValidHandleId(handleId) {
    return Number.isInteger(handleId) && handleId !== 0;
}
function validateCreateTemplatePayload(handleId, attributeSlots, elementSlots) {
    if (!isValidHandleId(handleId)) {
        return new Error(`ElementTemplate update has invalid handleId ${String(handleId)}.`);
    }
    if (elementTemplateRegistry.get(handleId)) {
        return new Error(`ElementTemplate update received duplicate handleId ${handleId}.`);
    }
    if (attributeSlots != null && !Array.isArray(attributeSlots)) {
        return new Error('ElementTemplate update create attributeSlots must be an array, null, or undefined.');
    }
    if (elementSlots != null && !Array.isArray(elementSlots)) {
        return new Error('ElementTemplate update create elementSlots must be an array, null, or undefined.');
    }
    return null;
}
function normalizeAttributeSlots(attributeSlots) {
    if (!Array.isArray(attributeSlots)) {
        return attributeSlots;
    }
    return attributeSlots.map((value) => (value === undefined ? null : value));
}
//# sourceMappingURL=patch.js.map