/**
 * Pure, immutable helpers for walking and mutating the BlockManager instance
 * tree (the nested `elements` structure where container instances carry a
 * `children` array).
 *
 * Every function that mutates returns NEW array/object references for any node
 * along the path it changed, so the composer's "reassign `this.instances`"
 * pattern triggers Vue 2 reactivity exactly as the pre-nesting code relied on.
 * Nodes off the mutation path may be shared by reference — they are never
 * mutated in place, so this is safe.
 *
 * Convention: a node is a CONTAINER iff it has a `children` array
 * (`children !== undefined`). A leaf has no `children` key. These helpers never
 * introduce a `children` key on a leaf, and never strip one from a container.
 */

/**
 * Depth-first search for the node with the given instance_id.
 * @returns {object|null} the node (by reference) or null.
 */
export function findInstance(tree, id) {
    for (const node of tree) {
        if (node.instance_id === id) return node;
        if (node.children !== undefined) {
            const hit = findInstance(node.children, id);
            if (hit) return hit;
        }
    }
    return null;
}

/**
 * Return a new tree where the node matching `id` is replaced with
 * `{ ...node, ...patch }`. Recurses to any depth. Branches that don't contain
 * the target keep their original references.
 */
export function updateInstance(tree, id, patch) {
    return tree.map((node) => {
        if (node.instance_id === id) {
            return { ...node, ...patch };
        }
        if (node.children !== undefined) {
            const newChildren = updateInstance(node.children, id, patch);
            if (newChildren !== node.children) {
                return { ...node, children: newChildren };
            }
        }
        return node;
    });
}

/**
 * Return a new tree with the node matching `id` removed, wherever it lives.
 * Only the ancestor arrays on the path to it are rebuilt.
 */
export function removeInstance(tree, id) {
    let changed = false;
    const out = [];
    for (const node of tree) {
        if (node.instance_id === id) {
            changed = true;
            continue;
        }
        if (node.children !== undefined) {
            const newChildren = removeInstance(node.children, id);
            if (newChildren !== node.children) {
                changed = true;
                out.push({ ...node, children: newChildren });
                continue;
            }
        }
        out.push(node);
    }
    return changed ? out : tree;
}

/**
 * Return a new tree where the container matching `containerId` has its
 * `children` replaced by `children`. Recurses to any depth. This is the atomic
 * primitive the canvas/structure drag buffers flush through.
 */
export function replaceChildren(tree, containerId, children) {
    return tree.map((node) => {
        if (node.instance_id === containerId) {
            return { ...node, children };
        }
        if (node.children !== undefined) {
            const newChildren = replaceChildren(node.children, containerId, children);
            if (newChildren !== node.children) {
                return { ...node, children: newChildren };
            }
        }
        return node;
    });
}

/**
 * Depth-first flatten of the tree into an ordered list, PARENT BEFORE CHILDREN.
 * Mirrors PHP TwigGenerator::flattenPlan() so derived field ordering/dedup
 * matches the shipped block.
 * @returns {object[]}
 */
export function flattenTree(tree) {
    const flat = [];
    for (const node of tree) {
        flat.push(node);
        if (node.children !== undefined) {
            flat.push(...flattenTree(node.children));
        }
    }
    return flat;
}
