import { computed, reactive, ref } from 'vue';
import type { ComputedRef, Ref } from 'vue';
import {
    clearTableViewState,
    readTableViewState,
    writeTableViewState,
} from '../js/table-view-state';
import type { KviewCrudTableColumn, KviewCrudTableFilterState } from './types';

const MIN_COLUMN_WIDTH = 88;
const MAX_COLUMN_WIDTH = 800;

interface UseKviewCrudTableColumnsOptions {
    /** `${storagePrefix}${id}`, the shared localStorage key with the Blade engine. */
    storageKey: ComputedRef<string>;
    columns: ComputedRef<KviewCrudTableColumn[]>;
    /** The active filter payload, so a full reset can clear it too. */
    activeFilters: ComputedRef<KviewCrudTableFilterState>;
    /** Selection reserves a real leading gutter; row actions do not. */
    hasLeadingSelectionGutter: ComputedRef<boolean>;
    /** `containerWidth` from `useKviewCrudTableScroll`. */
    containerWidth: Ref<number>;
    onFiltersReset: () => void;
}

/**
 * Column view state for `KviewCrudTable`: visibility, drag/keyboard reorder,
 * and pointer/keyboard resize, persisted through the same `table-view-state`
 * localStorage contract as the Blade engine so a saved view survives the
 * Blade → Vue cutover. Also owns proportional width fitting — the Vue
 * equivalent of the Blade engine's `fitColumnWidths()`: saved or declared
 * widths are preferred sizes, and leftover container width is redistributed
 * so the grid fills its viewport instead of truncating headers beside empty
 * space; when preferred widths exceed the viewport the data region scrolls
 * horizontally instead.
 */
export const useKviewCrudTableColumns = (options: UseKviewCrudTableColumnsOptions) => {
    const {
        storageKey, columns, activeFilters, hasLeadingSelectionGutter,
        containerWidth, onFiltersReset,
    } = options;

    const columnKeys = computed(() => columns.value.map((column) => column.key));
    const serverHidden = computed(() => columns.value.filter((column) => column.hidden).map((column) => column.key));

    const viewState = reactive(readTableViewState(storageKey.value, columnKeys.value, serverHidden.value));

    const persistViewState = (): void => {
        writeTableViewState(storageKey.value, {
            hidden: [...viewState.hidden],
            order: [...viewState.order],
            widths: { ...viewState.widths },
        });
    };

    const columnByKey = computed(() => new Map(columns.value.map((column) => [column.key, column])));

    const orderedColumns = computed(() => viewState.order
        .map((key) => columnByKey.value.get(key))
        .filter((column): column is KviewCrudTableColumn => Boolean(column)));

    const visibleColumns = computed(() => orderedColumns.value
        .filter((column) => !viewState.hidden.includes(column.key)));

    const isColumnVisible = (key: string): boolean => !viewState.hidden.includes(key);

    const toggleColumn = (column: KviewCrudTableColumn): void => {
        if (column.hideable === false) {
            return;
        }

        if (viewState.hidden.includes(column.key)) {
            viewState.hidden = viewState.hidden.filter((key) => key !== column.key);
        } else if (visibleColumns.value.length > 1) {
            viewState.hidden = [...viewState.hidden, column.key];
        }

        persistViewState();
    };

    const dragKey = ref<string | null>(null);

    const handleColumnDragStart = (key: string, event: DragEvent): void => {
        dragKey.value = key;
        event.dataTransfer?.setData('text/plain', key);

        if (event.dataTransfer) {
            event.dataTransfer.effectAllowed = 'move';
        }
    };

    const handleColumnDrop = (targetKey: string): void => {
        const sourceKey = dragKey.value;
        dragKey.value = null;

        if (!sourceKey || sourceKey === targetKey) {
            return;
        }

        const order = viewState.order.filter((key) => key !== sourceKey);
        const targetIndex = order.indexOf(targetKey);
        order.splice(targetIndex < 0 ? order.length : targetIndex, 0, sourceKey);
        viewState.order = order;
        persistViewState();
    };

    /** Resets column visibility, order, widths, and any active filters. */
    const resetView = (): void => {
        clearTableViewState(storageKey.value);
        viewState.hidden = [...serverHidden.value];
        viewState.order = [...columnKeys.value];
        viewState.widths = {};

        if (Object.keys(activeFilters.value).length) {
            onFiltersReset();
        }
    };

    const parseWidth = (value: string | number | null | undefined, fallback: number): number => {
        const parsed = Number.parseFloat(String(value ?? ''));

        return Number.isFinite(parsed) && parsed > 0 ? parsed : fallback;
    };

    const columnMinimumWidth = (column: KviewCrudTableColumn): number =>
        Math.min(MAX_COLUMN_WIDTH, Math.max(MIN_COLUMN_WIDTH, parseWidth(column.minWidth, MIN_COLUMN_WIDTH)));

    const clampColumnWidth = (column: KviewCrudTableColumn, width: number): number =>
        Math.min(MAX_COLUMN_WIDTH, Math.max(columnMinimumWidth(column), Math.round(width)));

    // Keep the stored preference authoritative for resize input. The rendered
    // width may be proportionally expanded to fill the viewport, but both
    // stored values and their ARIA representation always share the same real
    // bounds.
    const preferredWidth = (column: KviewCrudTableColumn): number =>
        clampColumnWidth(column, parseWidth(viewState.widths[column.key], parseWidth(column.width, 160)));

    const effectiveWidths = computed<Record<string, number>>(() => {
        const visible = visibleColumns.value;
        const preferred = visible.map((column) => ({ column, key: column.key, width: preferredWidth(column) }));

        // Selection owns a real leading gutter so its checkbox can never cover
        // the first data value. Row actions remain a zero-width trailing
        // overlay.
        const fixedEdges = hasLeadingSelectionGutter.value ? 44 : 0;
        const available = Math.floor(containerWidth.value - fixedEdges - 2);
        const totalPreferred = preferred.reduce((sum, column) => sum + column.width, 0);
        const widths: Record<string, number> = {};

        if (available > totalPreferred && totalPreferred > 0) {
            const scale = available / totalPreferred;
            preferred.forEach(({ column, key, width }) => {
                widths[key] = clampColumnWidth(column, Math.floor(width * scale));
            });
        } else {
            preferred.forEach(({ key, width }) => {
                widths[key] = width;
            });
        }

        return widths;
    });

    const columnStyle = (column: KviewCrudTableColumn): Record<string, string> => {
        const minimum = columnMinimumWidth(column);

        return {
            width: `${effectiveWidths.value[column.key] ?? preferredWidth(column)}px`,
            minWidth: `${minimum}px`,
            maxWidth: `${MAX_COLUMN_WIDTH}px`,
        };
    };

    const resizing = ref<{
        key: string;
        startX: number;
        startWidth: number;
        minWidth: number;
    } | null>(null);

    const handleResizeMove = (event: PointerEvent): void => {
        if (!resizing.value) {
            return;
        }

        const delta = event.clientX - resizing.value.startX;
        viewState.widths = {
            ...viewState.widths,
            [resizing.value.key]: Math.min(
                MAX_COLUMN_WIDTH,
                Math.max(resizing.value.minWidth, Math.round(resizing.value.startWidth + delta)),
            ),
        };
    };

    const stopResize = (): void => {
        if (!resizing.value) {
            return;
        }

        resizing.value = null;
        window.removeEventListener('pointermove', handleResizeMove);
        window.removeEventListener('pointerup', stopResize);
        persistViewState();
    };

    const startResize = (column: KviewCrudTableColumn, event: PointerEvent): void => {
        if (column.resizable === false) {
            return;
        }

        resizing.value = {
            key: column.key,
            startX: event.clientX,
            startWidth: preferredWidth(column),
            minWidth: columnMinimumWidth(column),
        };
        window.addEventListener('pointermove', handleResizeMove);
        window.addEventListener('pointerup', stopResize);
        event.preventDefault();
    };

    /** Keyboard resize on the separator handle: Left/Right nudge, Home resets. */
    const handleResizeKeydown = (column: KviewCrudTableColumn, event: KeyboardEvent): void => {
        if (column.resizable === false) {
            return;
        }

        const step = event.shiftKey ? 40 : 12;

        if (event.key === 'ArrowLeft') {
            event.preventDefault();
            viewState.widths = { ...viewState.widths, [column.key]: clampColumnWidth(column, preferredWidth(column) - step) };
            persistViewState();
        } else if (event.key === 'ArrowRight') {
            event.preventDefault();
            viewState.widths = { ...viewState.widths, [column.key]: clampColumnWidth(column, preferredWidth(column) + step) };
            persistViewState();
        } else if (event.key === 'Home') {
            event.preventDefault();
            const widths = { ...viewState.widths };
            delete widths[column.key];
            viewState.widths = widths;
            persistViewState();
        }
    };

    /**
     * Keyboard reorder inside the column manager: Alt+Up / Alt+Down move the
     * focused column, mirroring the pointer drag without requiring a mouse.
     */
    const moveColumn = (key: string, direction: -1 | 1): void => {
        const order = [...viewState.order];
        const from = order.indexOf(key);
        const to = from + direction;

        if (from < 0 || to < 0 || to >= order.length) {
            return;
        }

        order.splice(to, 0, order.splice(from, 1)[0]);
        viewState.order = order;
        persistViewState();
    };

    const handleColumnManagerKeydown = (key: string, event: KeyboardEvent): void => {
        if (!event.altKey || (event.key !== 'ArrowUp' && event.key !== 'ArrowDown')) {
            return;
        }

        event.preventDefault();
        moveColumn(key, event.key === 'ArrowUp' ? -1 : 1);
    };

    return {
        columnByKey,
        orderedColumns,
        visibleColumns,
        effectiveWidths,
        isColumnVisible,
        toggleColumn,
        handleColumnDragStart,
        handleColumnDrop,
        resetView,
        columnMinimumWidth,
        preferredWidth,
        columnStyle,
        startResize,
        stopResize,
        handleResizeKeydown,
        moveColumn,
        handleColumnManagerKeydown,
        MAX_COLUMN_WIDTH,
    };
};
