import { computed, reactive, watch } from 'vue';
import type { ComputedRef } from 'vue';
import type {
    KviewCrudTableColumn,
    KviewCrudTableFilterState,
    KviewCrudTableLabels,
} from './types';

export const NUMBER_OPERATORS = ['=', '!=', '>', '>=', '<', '<='].map((value) => ({
    value,
    label: value === '!=' ? '<>' : value,
}));

type FilterType = 'text' | 'number' | 'date' | 'multiselect';

interface NormalizedFilterOption {
    value: string;
    label: string;
    disabled: boolean;
}

interface UseKviewCrudTableFiltersOptions {
    labels: ComputedRef<KviewCrudTableLabels>;
    /** Column lookup shared with `useKviewCrudTableColumns`. */
    columnByKey: ComputedRef<Map<string, KviewCrudTableColumn>>;
    activeFilters: ComputedRef<KviewCrudTableFilterState>;
    onFiltersChange: (filters: KviewCrudTableFilterState) => void;
}

/**
 * Per-column filter drafts and operators for `KviewCrudTable`. Filters are
 * server-owned (applying emits an intent the host resolves through a visit),
 * but the popover's draft state — the in-progress operator/value before
 * Apply — is owned here so typing does not round-trip to the server on every
 * keystroke. Operators mirror the Blade engine's column-filter popovers.
 */
export const useKviewCrudTableFilters = (options: UseKviewCrudTableFiltersOptions) => {
    const { labels, columnByKey, activeFilters, onFiltersChange } = options;

    const TEXT_OPERATORS = computed(() => (
        ['contains', 'equals', 'starts', 'ends', 'not_contains', 'not_equals'] as const
    ).map((value) => ({ value, label: labels.value.operators[value] })));

    const filterOptions = (column: KviewCrudTableColumn): NormalizedFilterOption[] => {
        const filter = column.filter;
        const filterOptionsSource = filter && typeof filter === 'object' ? filter.options : undefined;

        if (Array.isArray(filterOptionsSource)) {
            return filterOptionsSource.flatMap((option) => {
                if (typeof option === 'string' || typeof option === 'number') {
                    const value = String(option);

                    return [{ value, label: value, disabled: false }];
                }

                if (!option || typeof option !== 'object' || !('value' in option)) {
                    return [];
                }

                const value = String(option.value);
                const label = 'label' in option ? String(option.label) : value;

                return [{ value, label, disabled: Boolean(option.disabled) }];
            });
        }

        if (filterOptionsSource && typeof filterOptionsSource === 'object') {
            return Object.entries(filterOptionsSource).map(([value, label]) => ({
                value,
                label: String(label),
                disabled: false,
            }));
        }

        return [];
    };

    const filterType = (column: KviewCrudTableColumn): FilterType => {
        const filter = column.filter;

        if (filter && typeof filter === 'object') {
            if (filter.type === 'select' || filter.type === 'multiselect' || filter.options !== undefined) {
                return 'multiselect';
            }

            if (filter.type === 'number') {
                return 'number';
            }

            if (filter.type === 'date') {
                return 'date';
            }
        }

        return 'text';
    };

    const filterPlaceholder = (column: KviewCrudTableColumn): string => {
        const filter = column.filter;

        return filter && typeof filter === 'object' && typeof filter.placeholder === 'string'
            ? filter.placeholder
            : labels.value.filterPlaceholder;
    };

    const defaultOperator = (column: KviewCrudTableColumn): string => {
        const type = filterType(column);

        if (type === 'multiselect') {
            return 'in';
        }

        return type === 'number' || type === 'date' ? '=' : 'contains';
    };

    const filterDrafts = reactive<Record<string, { operator: string; value: string }>>({});

    const draftFor = (column: KviewCrudTableColumn): { operator: string; value: string } => {
        if (!filterDrafts[column.key]) {
            const active = activeFilters.value[column.key];
            filterDrafts[column.key] = {
                operator: active?.operator ?? defaultOperator(column),
                value: active?.value ?? '',
            };
        }

        return filterDrafts[column.key];
    };

    watch(activeFilters, (filters) => {
        Object.keys(filterDrafts).forEach((key) => {
            const column = columnByKey.value.get(key);
            filterDrafts[key] = {
                operator: filters[key]?.operator ?? (column ? defaultOperator(column) : 'contains'),
                value: filters[key]?.value ?? '',
            };
        });
    });

    const isFiltered = (key: string): boolean => Boolean(activeFilters.value[key]);

    const selectedFilterValues = (column: KviewCrudTableColumn): Set<string> => new Set(
        draftFor(column).value
            .split(',')
            .map((value) => value.trim())
            .filter(Boolean),
    );

    const isFilterOptionSelected = (column: KviewCrudTableColumn, value: string): boolean =>
        selectedFilterValues(column).has(value);

    const orderedFilterValues = (column: KviewCrudTableColumn): string[] => {
        const selected = selectedFilterValues(column);

        return filterOptions(column)
            .map((option) => option.value)
            .filter((value) => selected.has(value));
    };

    const toggleFilterOption = (column: KviewCrudTableColumn, value: string, event: Event): void => {
        const checkbox = event.target as HTMLInputElement;
        const selected = selectedFilterValues(column);

        if (checkbox.checked) {
            selected.add(value);
        } else {
            selected.delete(value);
        }

        draftFor(column).operator = 'in';
        draftFor(column).value = filterOptions(column)
            .map((option) => option.value)
            .filter((optionValue) => selected.has(optionValue))
            .join(',');
    };

    /** Commits the draft to the server-owned filter state. Popover closing is
     * a UI-local concern owned by the caller (KviewCrudTableFilterPanel). */
    const applyFilter = (column: KviewCrudTableColumn): void => {
        const draft = draftFor(column);
        const next: KviewCrudTableFilterState = { ...activeFilters.value };
        const multiselect = filterType(column) === 'multiselect';
        const value = multiselect ? orderedFilterValues(column).join(',') : draft.value.trim();

        if (value === '') {
            delete next[column.key];
        } else {
            next[column.key] = { operator: multiselect ? 'in' : draft.operator, value };
        }

        onFiltersChange(next);
    };

    const clearFilter = (column: KviewCrudTableColumn): void => {
        filterDrafts[column.key] = { operator: defaultOperator(column), value: '' };

        if (isFiltered(column.key)) {
            const next: KviewCrudTableFilterState = { ...activeFilters.value };
            delete next[column.key];
            onFiltersChange(next);
        }
    };

    return {
        TEXT_OPERATORS,
        NUMBER_OPERATORS,
        filterOptions,
        filterType,
        filterPlaceholder,
        defaultOperator,
        draftFor,
        isFiltered,
        isFilterOptionSelected,
        toggleFilterOption,
        applyFilter,
        clearFilter,
    };
};
