/**
 * Client-side search for .data-table bodies.
 * @param {HTMLTableSectionElement} tbody
 * @param {string} query
 * @param {(row: HTMLTableRowElement) => string} getRowText
 * @returns {number} visible row count (excludes placeholder rows)
 */
export function filterTableBodyRows(tbody, query, getRowText) {
    if (!tbody) return 0;
    const q = query.trim().toLowerCase();
    let visible = 0;
    tbody.querySelectorAll('tr').forEach(row => {
        if (row.dataset.filterPlaceholder === '1') {
            row.style.display = q ? 'none' : '';
            return;
        }
        const text = getRowText(row).toLowerCase();
        const show = !q || text.includes(q);
        row.style.display = show ? '' : 'none';
        if (show) visible++;
    });
    return visible;
}

/**
 * @param {HTMLInputElement} input
 * @param {HTMLTableSectionElement} tbody
 * @param {(row: HTMLTableRowElement) => string} getRowText
 * @param {HTMLElement|null} countEl
 * @param {{ total: number, noneLabel?: string }} [countOpts]
 */
export function wireTableSearch(input, tbody, getRowText, countEl, countOpts = {}) {
    if (!input || !tbody) return;
    const update = () => {
        const visible = filterTableBodyRows(tbody, input.value, getRowText);
        const total = countOpts.total ?? tbody.querySelectorAll('tr:not([data-filter-placeholder])').length;
        if (!countEl) return;
        const q = input.value.trim();
        if (!q) {
            countEl.textContent = countOpts.noneLabel
                ? `${total} ${countOpts.noneLabel}`
                : '';
            return;
        }
        const noun = countOpts.noneLabel || 'rows';
        countEl.textContent = visible
            ? `${visible} of ${total} ${noun}`
            : `No ${noun} match`;
    };
    input.addEventListener('input', update);
    update();
}
