'use client';

import { Columns3, GripVertical, Search } from 'lucide-react';
import { ICON_SIZE_MD } from '@/components/icons';
import React, { useCallback, useEffect, useId, useMemo, useRef, useState } from 'react';
import { Button, Checkbox, Flex, Input, Modal, Tag, Typography } from 'antd';
import {
    DndContext,
    closestCenter,
    KeyboardSensor,
    PointerSensor,
    TouchSensor,
    useSensor,
    useSensors,
    type DragEndEvent,
} from '@dnd-kit/core';
import {
    SortableContext,
    arrayMove,
    sortableKeyboardCoordinates,
    useSortable,
    verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { message } from '@/lib/feedback/message';
import { useResponsiveModalProps } from '@/hooks/useResponsiveModalProps';
import {
    mergeWaygestFormModalBodyStyles,
    useWaygestFormModalProps,
} from '@/hooks/useWaygestFormModalProps';
import type { ListingColumnCatalogEntry } from './listingColumnCatalogTypes';
import {
    getDefaultColumnPrefs,
    type ListingColumnPrefsV2,
} from './useListingVisibleColumns';
import styles from './ListingColumnCustomizeModal.module.scss';

const SEARCH_THRESHOLD = 6;

function isObrigatoria(col: ListingColumnCatalogEntry): boolean {
    return col.obrigatoria === true || col.locked === true;
}

function prefsEqual(a: ListingColumnPrefsV2, b: ListingColumnPrefsV2): boolean {
    if (a.order.length !== b.order.length || a.visible.length !== b.visible.length) {
        return false;
    }
    if (a.order.some((key, i) => key !== b.order[i])) {
        return false;
    }
    const visibleB = new Set(b.visible);
    return a.visible.every((key) => visibleB.has(key)) && b.visible.every((key) => a.visible.includes(key));
}

type SortableColumnRowProps = {
    col: ListingColumnCatalogEntry;
    checked: boolean;
    locked: boolean;
    dndEnabled: boolean;
    onToggle: (key: string, checked: boolean) => void;
};

function SortableColumnRow({ col, checked, locked, dndEnabled, onToggle }: SortableColumnRowProps) {
    const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
        id: col.key,
        disabled: !dndEnabled,
    });

    const style: React.CSSProperties = {
        transform: CSS.Transform.toString(transform),
        transition,
    };

    return (
        <div
            ref={setNodeRef}
            style={style}
            className={[
                styles.columnItem,
                locked ? styles.locked : '',
                isDragging ? styles.dragging : '',
                !dndEnabled ? styles.dndDisabled : '',
            ]
                .filter(Boolean)
                .join(' ')}
            role="listitem"
        >
            <button
                type="button"
                className={styles.dragHandle}
                aria-label={`Reordenar coluna ${col.label}`}
                title={dndEnabled ? 'Arrastar para reordenar' : 'Limpe a busca para reordenar as colunas'}
                disabled={!dndEnabled}
                {...(dndEnabled ? { ...attributes, ...listeners } : {})}
            >
                <GripVertical size={ICON_SIZE_MD} aria-hidden />
            </button>
            <Checkbox
                checked={checked || locked}
                disabled={locked}
                onChange={(e) => onToggle(col.key, e.target.checked)}
                aria-label={
                    locked ? `${col.label} (obrigatória, não pode ocultar)` : col.label
                }
            >
                <Flex align="center" gap={6} wrap="wrap">
                    <span>{col.label}</span>
                    {locked ? (
                        <Tag bordered={false} color="default" style={{ margin: 0 }}>
                            Obrigatória
                        </Tag>
                    ) : null}
                </Flex>
            </Checkbox>
        </div>
    );
}

export type ListingColumnCustomizeModalProps = {
    open: boolean;
    onClose: () => void;
    catalog: ListingColumnCatalogEntry[];
    /** Keys visíveis atuais (ordem da tabela). */
    value: string[];
    /** Ordem completa do catálogo (inclui ocultas). Se omitido, deriva de `value` + catálogo. */
    order?: string[];
    /** Persiste `{ order, visible }` (preferir `setColumnPrefs` do hook). */
    onApply: (prefs: ListingColumnPrefsV2) => void;
    /** Shell visual premium (`waygestFormModal`) — alinhado ao modal de endereço PF. */
    premiumModalShell?: boolean;
    'data-testid'?: string;
};

export function ListingColumnCustomizeModal({
    open,
    onClose,
    catalog,
    value,
    order: orderProp,
    onApply,
    premiumModalShell = false,
    'data-testid': dataTestId = 'listing-colunas-visiveis-modal',
}: ListingColumnCustomizeModalProps) {
    const responsiveModalLayout = useResponsiveModalProps();
    const premiumModalLayout = useWaygestFormModalProps();
    const colunasModalLayout = premiumModalShell ? premiumModalLayout : responsiveModalLayout;
    const listInstructionsId = useId();
    const liveRegionId = useId();

    const configuraveis = useMemo(
        () =>
            catalog
                .filter((c) => c.configuravel !== false)
                .sort((a, b) => (a.ordem ?? 0) - (b.ordem ?? 0)),
        [catalog],
    );
    const configuraveisByKey = useMemo(() => {
        const map = new Map<string, ListingColumnCatalogEntry>();
        configuraveis.forEach((c) => map.set(c.key, c));
        return map;
    }, [configuraveis]);

    const defaultPrefs = useMemo(() => getDefaultColumnPrefs(catalog), [catalog]);
    const lockedKeys = useMemo(
        () => new Set(configuraveis.filter((c) => isObrigatoria(c)).map((c) => c.key)),
        [configuraveis],
    );

    const buildInitialDraft = useCallback((): ListingColumnPrefsV2 => {
        const catalogKeys = configuraveis.map((c) => c.key);
        const safeVisible = Array.isArray(value) ? value.filter((k) => configuraveisByKey.has(k)) : [];
        const safeOrder = Array.isArray(orderProp)
            ? orderProp.filter((k) => configuraveisByKey.has(k))
            : [];
        const seen = new Set<string>();
        const order: string[] = [];
        for (const key of safeOrder.length > 0 ? safeOrder : [...safeVisible, ...catalogKeys]) {
            if (configuraveisByKey.has(key) && !seen.has(key)) {
                order.push(key);
                seen.add(key);
            }
        }
        for (const key of catalogKeys) {
            if (!seen.has(key)) {
                order.push(key);
                seen.add(key);
            }
        }
        const visible = [
            ...new Set([...lockedKeys, ...safeVisible.filter((k) => configuraveisByKey.has(k))]),
        ].filter((k) => order.includes(k));
        return { order, visible };
    }, [configuraveis, configuraveisByKey, lockedKeys, orderProp, value]);

    const [draft, setDraft] = useState<ListingColumnPrefsV2>(buildInitialDraft);
    const [search, setSearch] = useState('');
    const [liveMessage, setLiveMessage] = useState('');
    const baselineRef = useRef<ListingColumnPrefsV2>(buildInitialDraft());

    useEffect(() => {
        if (open) {
            const initial = buildInitialDraft();
            baselineRef.current = initial;
            setDraft(initial);
            setSearch('');
            setLiveMessage('');
        }
    }, [open, buildInitialDraft]);

    const searchActive = search.trim().length > 0;
    const dndEnabled = !searchActive;
    const isDirty = !prefsEqual(draft, baselineRef.current);

    const columnLabel = useCallback(
        (id: string | number) => configuraveisByKey.get(String(id))?.label ?? String(id),
        [configuraveisByKey],
    );

    const dndAccessibility = useMemo(
        () => ({
            screenReaderInstructions: {
                draggable:
                    'Para pegar uma coluna arrastável, pressione Espaço ou Enter. Enquanto arrasta, use as setas para mover. Pressione Espaço ou Enter de novo para soltar, ou Escape para cancelar.',
            },
            announcements: {
                onDragStart({ active }: { active: { id: string | number } }) {
                    return `Pegou a coluna ${columnLabel(active.id)}.`;
                },
                onDragOver({
                    active,
                    over,
                }: {
                    active: { id: string | number };
                    over: { id: string | number } | null;
                }) {
                    if (over) {
                        return `Coluna ${columnLabel(active.id)} sobre ${columnLabel(over.id)}.`;
                    }
                    return `Coluna ${columnLabel(active.id)} sem alvo de soltura.`;
                },
                onDragEnd({
                    active,
                    over,
                }: {
                    active: { id: string | number };
                    over: { id: string | number } | null;
                }) {
                    if (over) {
                        return `Soltou a coluna ${columnLabel(active.id)} na posição de ${columnLabel(over.id)}.`;
                    }
                    return `Soltou a coluna ${columnLabel(active.id)}.`;
                },
                onDragCancel({ active }: { active: { id: string | number } }) {
                    return `Cancelou o arraste da coluna ${columnLabel(active.id)}.`;
                },
            },
        }),
        [columnLabel],
    );

    const filteredKeys = useMemo(() => {
        const query = search.trim().toLocaleLowerCase('pt');
        if (!query) {
            return draft.order;
        }
        return draft.order.filter((key) => {
            const col = configuraveisByKey.get(key);
            return col?.label.toLocaleLowerCase('pt').includes(query);
        });
    }, [draft.order, search, configuraveisByKey]);

    const toggleableFilteredKeys = useMemo(
        () => filteredKeys.filter((key) => !lockedKeys.has(key)),
        [filteredKeys, lockedKeys],
    );

    const visibleCount = useMemo(() => {
        const visible = new Set(draft.visible);
        return configuraveis.filter((col) => visible.has(col.key) || isObrigatoria(col)).length;
    }, [configuraveis, draft.visible]);

    const allToggleableSelected = toggleableFilteredKeys.every((key) => draft.visible.includes(key));
    const onlyLockedSelected =
        toggleableFilteredKeys.length === 0 ||
        toggleableFilteredKeys.every((key) => !draft.visible.includes(key));

    const sensors = useSensors(
        useSensor(PointerSensor, { activationConstraint: { distance: 8 } }),
        useSensor(TouchSensor, { activationConstraint: { delay: 200, tolerance: 5 } }),
        useSensor(KeyboardSensor, { coordinateGetter: sortableKeyboardCoordinates }),
    );

    const handleToggle = (key: string, checked: boolean) => {
        if (lockedKeys.has(key)) {
            return;
        }
        setDraft((prev) => {
            const visible = checked
                ? [...new Set([...prev.visible, key])]
                : prev.visible.filter((k) => k !== key);
            return { ...prev, visible };
        });
    };

    const handleSelectAll = () => {
        setDraft((prev) => ({
            ...prev,
            visible: [...new Set([...prev.visible, ...toggleableFilteredKeys, ...lockedKeys])],
        }));
    };

    const handleClear = () => {
        const filteredSet = new Set(toggleableFilteredKeys);
        setDraft((prev) => ({
            ...prev,
            visible: prev.visible.filter((k) => lockedKeys.has(k) || !filteredSet.has(k)),
        }));
    };

    const handleRestoreDefault = () => {
        setDraft({ ...defaultPrefs });
        setSearch('');
    };

    const handleDragEnd = (event: DragEndEvent) => {
        if (!dndEnabled) {
            return;
        }
        const { active, over } = event;
        if (!over || active.id === over.id) {
            return;
        }
        setDraft((prev) => {
            const oldIndex = prev.order.indexOf(String(active.id));
            const newIndex = prev.order.indexOf(String(over.id));
            if (oldIndex < 0 || newIndex < 0) {
                return prev;
            }
            const order = arrayMove(prev.order, oldIndex, newIndex);
            const label = columnLabel(active.id);
            setLiveMessage(`Coluna ${label} movida para a posição ${newIndex + 1}`);
            return { ...prev, order };
        });
    };

    const handleOk = () => {
        const visible = draft.order.filter(
            (k) => draft.visible.includes(k) || lockedKeys.has(k),
        );
        if (visible.length === 0) {
            message.warning('Selecione pelo menos uma coluna para exibir.');
            return;
        }
        onApply({ order: draft.order, visible });
        onClose();
        message.success('Colunas atualizadas.');
    };

    const handleRequestClose = () => {
        if (!isDirty) {
            onClose();
            return;
        }
        Modal.confirm({
            title: 'Descartar alterações?',
            content: 'Há alterações de colunas ou ordem que ainda não foram aplicadas.',
            okText: 'Descartar',
            cancelText: 'Continuar editando',
            okButtonProps: { danger: true },
            centered: true,
            onOk: () => onClose(),
        });
    };

    return (
        <Modal
            title={
                <Flex align="center" gap={8}>
                    <Columns3 size={ICON_SIZE_MD} aria-hidden />
                    <span>Colunas visíveis</span>
                </Flex>
            }
            data-testid={dataTestId}
            open={open}
            onOk={handleOk}
            onCancel={handleRequestClose}
            okText="Aplicar"
            cancelText="Cancelar"
            width={colunasModalLayout.width ?? 520}
            centered={colunasModalLayout.centered ?? true}
            className={premiumModalShell ? premiumModalLayout.className : undefined}
            styles={
                premiumModalShell
                    ? mergeWaygestFormModalBodyStyles(premiumModalLayout)
                    : colunasModalLayout.styles
            }
            zIndex={premiumModalShell ? premiumModalLayout.zIndex : undefined}
            destroyOnHidden
            keyboard
            focusTriggerAfterClose
            maskClosable={!isDirty}
        >
            <div className={styles.root}>
                <Typography.Text type="secondary" className={styles.description}>
                    Escolha quais colunas exibir e arraste para definir a ordem na tabela.
                </Typography.Text>

                {configuraveis.length >= SEARCH_THRESHOLD ? (
                    <Input
                        allowClear
                        prefix={<Search size={14} aria-hidden />}
                        placeholder="Buscar coluna…"
                        value={search}
                        onChange={(e) => setSearch(e.target.value)}
                        className={styles.search}
                        aria-label="Buscar coluna"
                    />
                ) : null}

                {searchActive ? (
                    <Typography.Text type="secondary" className={styles.searchHint}>
                        Limpe a busca para reordenar as colunas. Marcar todas e Limpar aplicam só às
                        colunas filtradas.
                    </Typography.Text>
                ) : null}

                <div className={styles.toolbar}>
                    <div className={styles.toolbarActions}>
                        <Button
                            type="link"
                            size="small"
                            disabled={allToggleableSelected || toggleableFilteredKeys.length === 0}
                            onClick={handleSelectAll}
                        >
                            Marcar todas
                        </Button>
                        <Button
                            type="link"
                            size="small"
                            disabled={onlyLockedSelected}
                            onClick={handleClear}
                        >
                            Limpar
                        </Button>
                        <Button type="link" size="small" onClick={handleRestoreDefault}>
                            Restaurar padrão
                        </Button>
                    </div>
                    <Typography.Text type="secondary" className={styles.counter}>
                        {visibleCount} de {configuraveis.length} visíveis
                    </Typography.Text>
                </div>

                <p id={listInstructionsId} className={styles.srOnly}>
                    Use o manipulador para reordenar. Colunas obrigatórias podem mudar de posição, mas
                    não podem ser ocultadas.
                </p>
                <div
                    id={liveRegionId}
                    className={styles.srOnly}
                    aria-live="polite"
                    aria-atomic="true"
                >
                    {liveMessage}
                </div>

                <DndContext
                    sensors={sensors}
                    collisionDetection={closestCenter}
                    onDragEnd={handleDragEnd}
                    accessibility={dndAccessibility}
                >
                    <SortableContext
                        items={dndEnabled ? draft.order : []}
                        strategy={verticalListSortingStrategy}
                    >
                        <div
                            className={styles.list}
                            role="list"
                            aria-label="Colunas da tabela"
                            aria-describedby={listInstructionsId}
                        >
                            {filteredKeys.length === 0 ? (
                                <div className={styles.emptySearch}>
                                    <Typography.Text type="secondary">
                                        Nenhuma coluna encontrada para &quot;{search.trim()}&quot;.
                                    </Typography.Text>
                                    <Button type="link" size="small" onClick={() => setSearch('')}>
                                        Limpar busca
                                    </Button>
                                </div>
                            ) : (
                                filteredKeys.map((key) => {
                                    const col = configuraveisByKey.get(key);
                                    if (!col) {
                                        return null;
                                    }
                                    const locked = isObrigatoria(col);
                                    return (
                                        <SortableColumnRow
                                            key={col.key}
                                            col={col}
                                            checked={draft.visible.includes(col.key)}
                                            locked={locked}
                                            dndEnabled={dndEnabled}
                                            onToggle={handleToggle}
                                        />
                                    );
                                })
                            )}
                        </div>
                    </SortableContext>
                </DndContext>
            </div>
        </Modal>
    );
}
