/**
 * Copyright Aquera Inc 2025
 *
 * This source code is licensed under the BSD-3-Clause license found in the
 * LICENSE file in the root directory of this source tree.
 */
export function isGroup(item) {
    return !!item && typeof item === 'object' && item.type === 'group' && Array.isArray(item.options);
}
export function hasGroups(data) {
    if (!Array.isArray(data))
        return false;
    for (const item of data) {
        if (isGroup(item))
            return true;
    }
    return false;
}
export function countOptionsDeep(group) {
    let n = 0;
    for (const child of group.options) {
        if (isGroup(child))
            n += countOptionsDeep(child);
        else
            n += 1;
    }
    return n;
}
export function flattenRows(data) {
    const rows = [];
    const walk = (items, depth, parentIds) => {
        for (const item of items) {
            if (isGroup(item)) {
                rows.push({
                    kind: 'header',
                    id: item.id,
                    label: item.label,
                    prefix: item.prefix,
                    depth,
                    optionCount: countOptionsDeep(item),
                });
                walk(item.options, depth + 1, [...parentIds, item.id]);
            }
            else {
                rows.push({
                    kind: 'option',
                    item: item,
                    depth,
                    parentIds,
                });
            }
        }
    };
    walk(Array.isArray(data) ? data : [], 0, []);
    return rows;
}
export function getOptionRows(rows) {
    const out = [];
    for (const r of rows)
        if (r.kind === 'option')
            out.push(r);
    return out;
}
/**
 * Filter rows by a query.
 *
 * Rules:
 *  - An option row is kept if its searchText matches.
 *  - If a group's label matches, the entire subtree (header + all descendant
 *    options + nested headers) is kept.
 *  - Otherwise a header is kept only if at least one descendant option matches.
 *  - Empty groups (no surviving descendants) are dropped.
 */
export function filterRows(data, query, getSearchText) {
    const q = (query || '').trim().toLowerCase();
    if (!q) {
        const rows = flattenRows(data);
        return { rows, visibleOptionCount: getOptionRows(rows).length };
    }
    const matchedOption = (item) => {
        const text = (getSearchText(item) || '').toString().toLowerCase();
        return text.includes(q);
    };
    const walk = (item, ancestorLabelMatched) => {
        if (isGroup(item)) {
            const labelMatched = item.label.toLowerCase().includes(q);
            const keepAll = labelMatched || ancestorLabelMatched;
            const children = [];
            for (const child of item.options) {
                const sub = walk(child, keepAll);
                if (sub)
                    children.push(sub);
            }
            if (children.length === 0 && !keepAll)
                return null;
            // If keepAll but nothing came back (empty group), still emit so user sees label.
            return { kind: 'group', group: item, children };
        }
        if (ancestorLabelMatched || matchedOption(item)) {
            return { kind: 'option', item: item };
        }
        return null;
    };
    const tops = [];
    for (const top of data) {
        const r = walk(top, false);
        if (r)
            tops.push(r);
    }
    // Materialize back to flat rows.
    const rows = [];
    const emit = (results, depth, parentIds) => {
        for (const r of results) {
            if (!r)
                continue;
            if (r.kind === 'group') {
                rows.push({
                    kind: 'header',
                    id: r.group.id,
                    label: r.group.label,
                    prefix: r.group.prefix,
                    depth,
                    optionCount: countOptionsDeep(r.group),
                });
                emit(r.children, depth + 1, [...parentIds, r.group.id]);
            }
            else {
                rows.push({
                    kind: 'option',
                    item: r.item,
                    depth,
                    parentIds,
                });
            }
        }
    };
    emit(tops, 0, []);
    return { rows, visibleOptionCount: getOptionRows(rows).length };
}
//# sourceMappingURL=group-utils.js.map