/**
 * Operators available per filter type
 */
const operatorsByType = {
    text: ['contains', 'notContains', 'equals', 'notEquals', 'startsWith', 'endsWith', 'isEmpty', 'isNotEmpty'],
    number: ['equals', 'notEquals', 'greaterThan', 'lessThan', 'greaterThanOrEqual', 'lessThanOrEqual', 'between', 'isEmpty', 'isNotEmpty'],
    date: ['equals', 'before', 'after', 'between', 'isEmpty', 'isNotEmpty'],
    select: ['equals', 'notEquals', 'isEmpty', 'isNotEmpty'],
    multiSelect: ['contains', 'equals', 'isEmpty', 'isNotEmpty'],
    boolean: ['isTrue', 'isFalse'],
};
/**
 * Default operator per filter type
 */
const defaultOperatorByType = {
    text: 'contains',
    number: 'equals',
    date: 'equals',
    select: 'equals',
    multiSelect: 'contains',
    boolean: 'isTrue',
};
/**
 * Human-readable labels for filter operators
 */
const operatorLabels = {
    equals: 'Equals',
    notEquals: 'Not equals',
    contains: 'Contains',
    notContains: 'Not contains',
    startsWith: 'Starts with',
    endsWith: 'Ends with',
    greaterThan: 'Greater than',
    lessThan: 'Less than',
    greaterThanOrEqual: 'Greater or equal',
    lessThanOrEqual: 'Less or equal',
    isEmpty: 'Is empty',
    isNotEmpty: 'Is not empty',
    isTrue: 'Is true',
    isFalse: 'Is false',
    before: 'Before',
    after: 'After',
    between: 'Between',
};
/**
 * Infer the filter type for a column based on its configuration and data.
 * Priority: explicit filterType > filterOptions presence > data sampling
 */
function inferFilterType(column, data) {
    // 1. Explicit filterType always wins
    if (column.filterType)
        return column.filterType;
    // 2. If column has filterOptions, it's a select
    if (column.filterOptions && column.filterOptions.length > 0)
        return 'select';
    // 3. Sample first non-null value from data
    if (data && data.length > 0) {
        for (const row of data) {
            const value = typeof column.accessor === 'function'
                ? column.accessor(row)
                : row[column.accessor];
            if (value == null)
                continue;
            if (typeof value === 'boolean')
                return 'boolean';
            if (typeof value === 'number')
                return 'number';
            if (value instanceof Date)
                return 'date';
            if (typeof value === 'string') {
                // Check if it looks like an ISO date string (YYYY-MM-DD or full ISO)
                if (/^\d{4}-\d{2}-\d{2}(T|\s|$)/.test(value))
                    return 'date';
            }
            // Default to text
            return 'text';
        }
    }
    return 'text';
}
/**
 * Get the valid operators for a given filter type
 */
function getOperatorsForFilterType(filterType) {
    const operators = operatorsByType[filterType] || operatorsByType.text;
    return operators.map((op) => ({
        id: op,
        label: operatorLabels[op],
        value: op,
    }));
}
/**
 * Get the default operator for a given filter type
 */
function getDefaultOperator(filterType) {
    return defaultOperatorByType[filterType] || 'contains';
}
/**
 * Returns true if the operator requires no value input (unary operators)
 */
function isUnaryOperator(operator) {
    return ['isEmpty', 'isNotEmpty', 'isTrue', 'isFalse'].includes(operator);
}
/**
 * Returns true if the operator needs two value inputs (between)
 */
function isBetweenOperator(operator) {
    return operator === 'between';
}
/**
 * Resolve a column accessor to extract a cell value from a row
 */
function getCellValue(row, column) {
    if (typeof column.accessor === 'function') {
        return column.accessor(row);
    }
    return row[column.accessor];
}
/**
 * Evaluate a single filter condition against a row value
 */
function evaluateFilter(cellValue, filter, filterType) {
    const operator = filter.operator || 'contains';
    const filterValue = filter.value;
    // Handle unary operators first
    if (operator === 'isEmpty') {
        return cellValue == null || cellValue === '' || (Array.isArray(cellValue) && cellValue.length === 0);
    }
    if (operator === 'isNotEmpty') {
        return cellValue != null && cellValue !== '' && !(Array.isArray(cellValue) && cellValue.length === 0);
    }
    if (operator === 'isTrue') {
        return cellValue === true || cellValue === 'true' || cellValue === 1;
    }
    if (operator === 'isFalse') {
        return cellValue === false || cellValue === 'false' || cellValue === 0;
    }
    // For non-unary operators, if no filter value is set, skip (pass-through)
    if (filterValue == null || filterValue === '')
        return true;
    // Type-aware comparison
    switch (filterType) {
        case 'number': {
            const numCell = Number(cellValue);
            const numFilter = Number(filterValue);
            if (isNaN(numCell))
                return false;
            switch (operator) {
                case 'equals': return numCell === numFilter;
                case 'notEquals': return numCell !== numFilter;
                case 'greaterThan': return numCell > numFilter;
                case 'lessThan': return numCell < numFilter;
                case 'greaterThanOrEqual': return numCell >= numFilter;
                case 'lessThanOrEqual': return numCell <= numFilter;
                case 'between': {
                    if (Array.isArray(filterValue) && filterValue.length === 2) {
                        const [min, max] = filterValue.map(Number);
                        return numCell >= min && numCell <= max;
                    }
                    return true;
                }
                default: return true;
            }
        }
        case 'date': {
            const dateCell = cellValue instanceof Date ? cellValue : new Date(String(cellValue));
            if (isNaN(dateCell.getTime()))
                return false;
            if (operator === 'between') {
                if (Array.isArray(filterValue) && filterValue.length === 2) {
                    const [minDate, maxDate] = filterValue.map((v) => new Date(v));
                    return dateCell >= minDate && dateCell <= maxDate;
                }
                return true;
            }
            const dateFilter = new Date(String(filterValue));
            if (isNaN(dateFilter.getTime()))
                return true;
            // Normalize to date-only comparison (strip time)
            const cellDateOnly = new Date(dateCell.getFullYear(), dateCell.getMonth(), dateCell.getDate());
            const filterDateOnly = new Date(dateFilter.getFullYear(), dateFilter.getMonth(), dateFilter.getDate());
            switch (operator) {
                case 'equals': return cellDateOnly.getTime() === filterDateOnly.getTime();
                case 'before': return cellDateOnly < filterDateOnly;
                case 'after': return cellDateOnly > filterDateOnly;
                default: return true;
            }
        }
        case 'boolean':
            // Boolean operators are handled above as unary
            return true;
        case 'multiSelect': {
            const cellArr = Array.isArray(cellValue) ? cellValue : [cellValue];
            const filterArr = Array.isArray(filterValue) ? filterValue : [filterValue];
            switch (operator) {
                case 'contains': return filterArr.some((fv) => cellArr.includes(fv));
                case 'equals': return filterArr.length === cellArr.length && filterArr.every((fv) => cellArr.includes(fv));
                default: return true;
            }
        }
        case 'select': {
            const strCell = String(cellValue).toLowerCase();
            const strFilter = String(filterValue).toLowerCase();
            switch (operator) {
                case 'equals': return strCell === strFilter;
                case 'notEquals': return strCell !== strFilter;
                default: return true;
            }
        }
        case 'text':
        default: {
            const strCell = String(cellValue).toLowerCase();
            const strFilter = String(filterValue).toLowerCase();
            switch (operator) {
                case 'contains': return strCell.includes(strFilter);
                case 'notContains': return !strCell.includes(strFilter);
                case 'equals': return strCell === strFilter;
                case 'notEquals': return strCell !== strFilter;
                case 'startsWith': return strCell.startsWith(strFilter);
                case 'endsWith': return strCell.endsWith(strFilter);
                default: return strCell.includes(strFilter);
            }
        }
    }
}
/**
 * Pure filtering function — filters data based on columns, filter configs, and logic.
 * Returns filtered data array.
 */
function filterData(data, columns, filters, logic = 'and') {
    // Remove filters with no column or that are effectively empty
    const activeFilters = filters.filter((f) => {
        if (!f.columnId)
            return false;
        // Unary operators don't need a value
        if (f.operator && isUnaryOperator(f.operator))
            return true;
        // Non-unary operators need a value
        return f.value != null && f.value !== '';
    });
    if (activeFilters.length === 0)
        return data;
    // Build a column lookup for quick access
    const columnMap = new Map(columns.map((col) => [col.id, col]));
    return data.filter((row) => {
        const results = activeFilters.map((filter) => {
            const column = columnMap.get(filter.columnId);
            if (!column)
                return true;
            const cellValue = getCellValue(row, column);
            const filterType = inferFilterType(column, data);
            return evaluateFilter(cellValue, filter, filterType);
        });
        return logic === 'and'
            ? results.every(Boolean)
            : results.some(Boolean);
    });
}

export { filterData, getDefaultOperator, getOperatorsForFilterType, inferFilterType, isBetweenOperator, isUnaryOperator, operatorLabels };
