'use client';

import React, { useState, useCallback, useMemo, useRef, useLayoutEffect, useEffect } from 'react';
import { Card, Button, Tag, Typography, Empty, Dropdown, Modal, Form, Input, Tooltip } from 'antd';
import { Ellipsis, Pencil, Plus, Trash2 } from 'lucide-react';
import { ICON_SIZE_MD } from '@/components/icons';
import type { MenuProps } from 'antd';
import {
    DndContext,
    type DragCancelEvent,
    type DragEndEvent,
    type DragOverEvent,
    DragOverlay,
    type DragStartEvent,
    type UniqueIdentifier,
    closestCorners,
    useSensor,
    useSensors,
    KeyboardCode,
    KeyboardSensor,
    PointerSensor,
    useDroppable,
} from '@dnd-kit/core';
import {
    SortableContext,
    arrayMove,
    sortableKeyboardCoordinates,
    useSortable,
    verticalListSortingStrategy,
} from '@dnd-kit/sortable';
import { CSS } from '@dnd-kit/utilities';
import { KanbanColumnVirtualBody } from './KanbanColumnVirtualBody';
import { shouldVirtualizeKanbanColumn } from './kanbanColumnVirtual';
import type { KanbanColumn, KanbanItem } from './kanbanBoardTypes';
import styles from './KanbanBoard.module.scss';

export type { KanbanColumn, KanbanItem } from './kanbanBoardTypes';

const { Text } = Typography;
const { TextArea } = Input;

interface KanbanBoardProps {
    columns: KanbanColumn[];
    onItemMove?: (itemId: string | number, newStatus: string) => void;
    onItemClick?: (item: KanbanItem) => void;
    onItemAdd?: (status: string) => void;
    onItemEdit?: (item: KanbanItem) => void;
    onItemDelete?: (itemId: string | number) => void;
    renderItem?: (item: KanbanItem) => React.ReactNode;
    loading?: boolean;
    /**
     * Altura máxima da lista de cartões por coluna (scroll interno).
     * Colunas com ≥48 cartões usam react-window (`KanbanColumnVirtualBody`).
     */
    columnBodyMaxHeight?: number;
    /** Limiar para lista virtual por coluna; `0` desativa. @default 48 */
    columnVirtualizeThreshold?: number;
    /**
     * Prefixo para data-testid estáveis (E2E): `{prefix}-board`, `{prefix}-column`, `{prefix}-item`, `{prefix}-edit-modal`.
     * @default 'kanban'
     */
    dataTestId?: string;
    /** Variante visual — `crm` aplica colunas estilo SaaS comercial (funil CRM). */
    variant?: 'default' | 'crm';
    /** Singular da entidade nos textos de coluna vazia e aria-label (ex.: `tarefa`, `negócio`). */
    entitySingular?: string;
    /** Descrição customizada quando a coluna está vazia (variante `crm`). */
    emptyColumnDescription?: string;
    /** Gera aria-label do botão "Adicionar" por coluna. */
    addItemAriaLabel?: (columnTitle: string) => string;
    /** aria-label do wrapper sortable quando `renderItem` customiza o cartão (evita duplicar `role="button"`). */
    getRenderItemAriaLabel?: (item: KanbanItem) => string;
    /** Carregar mais cartões numa coluna com paginação parcial. */
    onColumnLoadMore?: (columnId: string) => void;
    /**
     * Conteúdo extra no cabeçalho da coluna (ex.: ícone ⚙ de configuração), inserido após o
     * título/badge e antes do botão "Adicionar". Não substitui `onItemAdd`; ambos podem coexistir.
     */
    renderColumnHeaderExtra?: (column: KanbanColumn) => React.ReactNode;
}

/** Copy de coluna vazia (variante CRM). Exportada para testes / reuso. */
export function crmEmptyColumnDescription(entitySingular: string, custom?: string): string {
    if (custom) {
        return custom;
    }
    if (entitySingular === 'negócio') {
        // ACH-076-02 / TASK-NEG-101 — empty não pode sugerir só "arrastar"
        return 'Nenhum negócio nesta etapa — arraste cartões, use o atalho de teclado ou abra o negócio para mudar de etapa; também pode criar um novo';
    }
    return `Nenhuma ${entitySingular} nesta etapa — arraste ${entitySingular}s para cá ou crie uma nova`;
}

interface KanbanItemCardProps {
    item: KanbanItem;
    onClickItem: (item: KanbanItem) => void;
    onEditItem: (item: KanbanItem) => void;
    onDeleteItem: (itemId: string | number) => void;
    renderItem?: (item: KanbanItem) => React.ReactNode;
    getRenderItemAriaLabel?: (item: KanbanItem) => string;
    testIdPrefix: string;
    variant: 'default' | 'crm';
}

function kanbanColumnsLayoutEqual(a: KanbanColumn[], b: KanbanColumn[]): boolean {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
        if (a[i].id !== b[i].id) return false;
        const aItems = a[i].items;
        const bItems = b[i].items;
        if (aItems.length !== bItems.length) return false;
        for (let j = 0; j < aItems.length; j++) {
            if (aItems[j].id !== bItems[j].id) return false;
        }
    }
    return true;
}

function findColumnIdForDndId(columns: KanbanColumn[], id: UniqueIdentifier): string | undefined {
    if (columns.some((col) => col.id === id)) {
        return String(id);
    }
    return columns.find((col) => col.items.some((item) => item.id === id))?.id;
}

function moveKanbanItemPreview(
    columns: KanbanColumn[],
    activeId: UniqueIdentifier,
    overId: UniqueIdentifier,
): KanbanColumn[] {
    const activeColumnId = findColumnIdForDndId(columns, activeId);
    const overColumnId = findColumnIdForDndId(columns, overId);
    if (!activeColumnId || !overColumnId) return columns;

    const activeColumn = columns.find((col) => col.id === activeColumnId);
    const overColumn = columns.find((col) => col.id === overColumnId);
    if (!activeColumn || !overColumn) return columns;

    const activeIndex = activeColumn.items.findIndex((item) => item.id === activeId);
    if (activeIndex < 0) return columns;

    const overIndex =
        overId === overColumnId
            ? overColumn.items.length
            : overColumn.items.findIndex((item) => item.id === overId);

    if (activeColumnId === overColumnId) {
        if (overIndex < 0 || activeIndex === overIndex) return columns;
        return columns.map((col) =>
            col.id === activeColumnId
                ? { ...col, items: arrayMove(col.items, activeIndex, overIndex) }
                : col,
        );
    }

    const activeItem = activeColumn.items[activeIndex];
    const insertIndex = overIndex >= 0 ? overIndex : overColumn.items.length;

    return columns.map((col) => {
        if (col.id === activeColumnId) {
            return { ...col, items: col.items.filter((item) => item.id !== activeId) };
        }
        if (col.id === overColumnId) {
            const nextItems = [...col.items];
            nextItems.splice(insertIndex, 0, { ...activeItem, status: col.id });
            return { ...col, items: nextItems };
        }
        return col;
    });
}

interface KanbanColumnProps {
    column: KanbanColumn;
    onClickItem: (item: KanbanItem) => void;
    onEditItem: (item: KanbanItem) => void;
    onDeleteItem: (itemId: string | number) => void;
    onItemAdd?: (status: string) => void;
    renderItem?: (item: KanbanItem) => React.ReactNode;
    getRenderItemAriaLabel?: (item: KanbanItem) => string;
    testIdPrefix: string;
    columnBodyMaxHeight?: number;
    variant: 'default' | 'crm';
    entitySingular: string;
    emptyColumnDescription?: string;
    addItemAriaLabel?: (columnTitle: string) => string;
    onColumnLoadMore?: (columnId: string) => void;
    columnVirtualizeThreshold?: number;
    renderColumnHeaderExtra?: (column: KanbanColumn) => React.ReactNode;
}

function kanbanItemsShallowEqual(a: KanbanItem[], b: KanbanItem[]): boolean {
    if (a.length !== b.length) return false;
    for (let i = 0; i < a.length; i++) {
        const x = a[i];
        const y = b[i];
        if (x.id !== y.id) return false;
        if (x.title !== y.title) return false;
        if (x.description !== y.description) return false;
        if (x.status !== y.status) return false;
    }
    return true;
}

function kanbanColumnPropsEqual(prev: KanbanColumnProps, next: KanbanColumnProps): boolean {
    if (prev.column.id !== next.column.id) return false;
    if (prev.column.title !== next.column.title) return false;
    if (prev.column.color !== next.column.color) return false;
    if (!kanbanItemsShallowEqual(prev.column.items, next.column.items)) return false;
    if (prev.testIdPrefix !== next.testIdPrefix) return false;
    if (prev.columnBodyMaxHeight !== next.columnBodyMaxHeight) return false;
    if (prev.variant !== next.variant) return false;
    if (prev.renderItem !== next.renderItem) return false;
    if (prev.getRenderItemAriaLabel !== next.getRenderItemAriaLabel) return false;
    if (prev.onClickItem !== next.onClickItem) return false;
    if (prev.onEditItem !== next.onEditItem) return false;
    if (prev.onDeleteItem !== next.onDeleteItem) return false;
    if (prev.onItemAdd !== next.onItemAdd) return false;
    if (prev.entitySingular !== next.entitySingular) return false;
    if (prev.emptyColumnDescription !== next.emptyColumnDescription) return false;
    if (prev.addItemAriaLabel !== next.addItemAriaLabel) return false;
    if (prev.onColumnLoadMore !== next.onColumnLoadMore) return false;
    if (prev.columnVirtualizeThreshold !== next.columnVirtualizeThreshold) return false;
    if (prev.renderColumnHeaderExtra !== next.renderColumnHeaderExtra) return false;
    if (prev.column.totalItemCount !== next.column.totalItemCount) return false;
    if (prev.column.summaryPartial !== next.column.summaryPartial) return false;
    if (prev.column.hasMoreItems !== next.column.hasMoreItems) return false;
    if (prev.column.loadMoreLoading !== next.column.loadMoreLoading) return false;
    if (prev.column.loadMoreError !== next.column.loadMoreError) return false;
    if (prev.column.loadMoreExhaustedHint !== next.column.loadMoreExhaustedHint) return false;
    const prevSummary = prev.column.summaryLines ?? [];
    const nextSummary = next.column.summaryLines ?? [];
    if (prevSummary.length !== nextSummary.length) return false;
    for (let i = 0; i < prevSummary.length; i += 1) {
        if (
            prevSummary[i].label !== nextSummary[i].label ||
            prevSummary[i].value !== nextSummary[i].value ||
            prevSummary[i].emphasis !== nextSummary[i].emphasis
        ) {
            return false;
        }
    }
    return true;
}

const KanbanColumnComponent = React.memo(function KanbanColumnComponent({
    column,
    onClickItem,
    onEditItem,
    onDeleteItem,
    onItemAdd,
    renderItem,
    getRenderItemAriaLabel,
    testIdPrefix,
    columnBodyMaxHeight,
    variant,
    entitySingular,
    emptyColumnDescription,
    addItemAriaLabel,
    onColumnLoadMore,
    columnVirtualizeThreshold = 48,
    renderColumnHeaderExtra,
}: KanbanColumnProps) {
    const { setNodeRef, isOver } = useDroppable({ id: column.id });
    const isCrm = variant === 'crm';
    const useVirtualList = shouldVirtualizeKanbanColumn(
        column.items.length,
        columnVirtualizeThreshold,
    );

    const itemIds = useMemo(() => column.items.map((item) => item.id), [column.items]);

    const listWrapStyle: React.CSSProperties = useMemo(
        () => ({
            ...(columnBodyMaxHeight != null && !isCrm && !useVirtualList
                ? { maxHeight: columnBodyMaxHeight, overflowY: 'auto' }
                : {}),
            ...(useVirtualList ? { flex: 1, minHeight: 0, display: 'flex', flexDirection: 'column' } : {}),
        }),
        [columnBodyMaxHeight, isCrm, useVirtualList]
    );

    const columnClassName = [
        styles.column,
        isCrm ? styles.columnCrm : styles.columnDefault,
        isCrm && isOver ? styles.columnOver : '',
    ]
        .filter(Boolean)
        .join(' ');

    const columnSurfaceStyle: React.CSSProperties | undefined = isCrm
        ? { borderTop: column.color ? `4px solid ${column.color}` : undefined }
        : {
              minWidth: 300,
              maxWidth: 300,
              backgroundColor: isOver ? '#e6f7ff' : '#f5f5f5',
              borderRadius: 8,
              padding: 16,
              transition: 'background-color 0.2s',
          };

    const countBadge = isCrm ? (
        <span
            className={`${styles.countBadge} ${styles.countBadgeCrm}`}
            style={{ backgroundColor: column.color ?? 'var(--text-subtle, #9ca3af)' }}
        >
            {column.totalItemCount ?? column.items.length}
        </span>
    ) : (
        <Tag className={styles.countBadge}>{column.totalItemCount ?? column.items.length}</Tag>
    );

    const hiddenCount =
        column.totalItemCount != null
            ? Math.max(0, column.totalItemCount - column.items.length)
            : 0;
    const hasMoreItems =
        column.hasMoreItems != null ? column.hasMoreItems : hiddenCount > 0;
    const showLoadMore = Boolean(onColumnLoadMore) && (hasMoreItems || column.loadMoreError);
    const showExhaustedHint =
        !hasMoreItems &&
        !column.loadMoreError &&
        Boolean(column.loadMoreExhaustedHint) &&
        column.items.length > 0 &&
        (column.totalItemCount == null || column.items.length >= column.totalItemCount);

    const loadMoreLabel = column.loadMoreError
        ? 'Tentar novamente'
        : column.loadMoreLoading
          ? 'Carregando…'
          : hiddenCount > 0
            ? `Carregar mais (${hiddenCount})`
            : 'Carregar mais';

    const loadMoreAria = column.loadMoreError
        ? `Erro ao carregar mais ${entitySingular}s em ${column.title}. Tentar novamente.`
        : `Carregar mais ${entitySingular}s em ${column.title}.${
              hiddenCount > 0 ? ` ${hiddenCount} restantes.` : ''
          }`;

    return (
        <div
            ref={setNodeRef}
            data-testid={`${testIdPrefix}-column`}
            data-kanban-column-id={String(column.id)}
            className={columnClassName}
            style={columnSurfaceStyle}
        >
            <div
                className={[styles.columnHeader, isCrm ? styles.columnHeaderCrm : ''].filter(Boolean).join(' ')}
            >
                <div className={styles.columnHeaderLeft}>
                    {column.color && (
                        <div
                            className={[styles.stageDot, !isCrm ? styles.stageDotDefault : ''].filter(Boolean).join(' ')}
                            style={{ backgroundColor: column.color }}
                        />
                    )}
                    <Text strong className={styles.columnTitle}>
                        {column.title}
                    </Text>
                    {countBadge}
                    {renderColumnHeaderExtra?.(column)}
                </div>
                {onItemAdd && (
                    <Tooltip title={`Criar ${entitySingular} em ${column.title}`}>
                        <Button
                            type="text"
                            icon={<Plus size={ICON_SIZE_MD} aria-hidden />}
                            size="small"
                            className={styles.addButton}
                            onClick={() => onItemAdd(column.id)}
                            aria-label={
                                addItemAriaLabel?.(column.title) ??
                                `Adicionar ${entitySingular} em ${column.title}`
                            }
                        />
                    </Tooltip>
                )}
            </div>

            {column.summaryLines && column.summaryLines.length > 0 ? (
                (() => {
                    const summaryBody = (
                        <div
                            className={styles.columnSummary}
                            data-testid={`${testIdPrefix}-column-summary`}
                            aria-label={`Resumo da coluna ${column.title}`}
                        >
                            {column.summaryLines.map((line) => (
                                <div key={line.label} className={styles.columnSummaryRow}>
                                    <Text type="secondary" className={styles.columnSummaryLabel}>
                                        {line.label}
                                    </Text>
                                    <Text
                                        className={styles.columnSummaryValue}
                                        type={
                                            line.emphasis === 'warning'
                                                ? 'warning'
                                                : line.emphasis === 'danger'
                                                  ? 'danger'
                                                  : undefined
                                        }
                                    >
                                        {line.value}
                                    </Text>
                                </div>
                            ))}
                        </div>
                    );
                    return isCrm ? (
                        <Tooltip
                            title={
                                column.summaryPartial
                                    ? 'Valores e contadores com base nos negócios carregados nesta página; pode haver mais resultados filtrados.'
                                    : 'Valor total, valor ponderado (probabilidade), fechamentos atrasados e negócios sem follow-up recente (7 dias).'
                            }
                        >
                            {summaryBody}
                        </Tooltip>
                    ) : (
                        summaryBody
                    );
                })()
            ) : null}

            <SortableContext items={itemIds} strategy={verticalListSortingStrategy}>
                <div
                    className={[
                        styles.columnBody,
                        isCrm ? styles.columnBodyCrm : '',
                        useVirtualList ? styles.columnBodyVirtualized : '',
                    ]
                        .filter(Boolean)
                        .join(' ')}
                    style={listWrapStyle}
                >
                    {column.items.length === 0 ? (
                        <Empty
                            image={Empty.PRESENTED_IMAGE_SIMPLE}
                            description={
                                isCrm
                                    ? crmEmptyColumnDescription(entitySingular, emptyColumnDescription)
                                    : 'Sem itens'
                            }
                            className={isCrm ? styles.emptyStateCrm : styles.emptyState}
                        />
                    ) : useVirtualList ? (
                        <KanbanColumnVirtualBody
                            items={column.items}
                            className={isCrm ? styles.columnBodyVirtualCrm : styles.columnBodyVirtualDefault}
                            SortableItemCard={KanbanItemCard}
                            onClickItem={onClickItem}
                            onEditItem={onEditItem}
                            onDeleteItem={onDeleteItem}
                            renderItem={renderItem}
                            getRenderItemAriaLabel={getRenderItemAriaLabel}
                            testIdPrefix={testIdPrefix}
                            variant={variant}
                        />
                    ) : (
                        column.items.map((item) => (
                            <KanbanItemCard
                                key={item.id}
                                item={item}
                                onClickItem={onClickItem}
                                onEditItem={onEditItem}
                                onDeleteItem={onDeleteItem}
                                renderItem={renderItem}
                                getRenderItemAriaLabel={getRenderItemAriaLabel}
                                testIdPrefix={testIdPrefix}
                                variant={variant}
                            />
                        ))
                    )}
                    {showLoadMore && onColumnLoadMore ? (
                        <Button
                            type="link"
                            size="small"
                            className={styles.loadMoreButton}
                            loading={column.loadMoreLoading}
                            disabled={column.loadMoreLoading}
                            onClick={() => onColumnLoadMore(column.id)}
                            aria-label={loadMoreAria}
                            data-testid={`${testIdPrefix}-column-load-more-${column.id}`}
                        >
                            {loadMoreLabel}
                        </Button>
                    ) : null}
                    {showExhaustedHint ? (
                        <Text
                            type="secondary"
                            className={styles.loadMoreExhausted}
                            data-testid={`${testIdPrefix}-column-load-exhausted-${column.id}`}
                        >
                            Todos os {column.totalItemCount ?? column.items.length} carregados
                        </Text>
                    ) : null}
                </div>
            </SortableContext>
        </div>
    );
}, kanbanColumnPropsEqual);

function kanbanItemCardPropsEqual(prev: KanbanItemCardProps, next: KanbanItemCardProps): boolean {
    if (prev.item.id !== next.item.id) return false;
    if (prev.item.title !== next.item.title) return false;
    if (prev.item.description !== next.item.description) return false;
    if (prev.item.status !== next.item.status) return false;
    if (prev.testIdPrefix !== next.testIdPrefix) return false;
    if (prev.variant !== next.variant) return false;
    if (prev.renderItem !== next.renderItem) return false;
    if (prev.getRenderItemAriaLabel !== next.getRenderItemAriaLabel) return false;
    if (prev.onClickItem !== next.onClickItem) return false;
    if (prev.onEditItem !== next.onEditItem) return false;
    if (prev.onDeleteItem !== next.onDeleteItem) return false;
    return true;
}

const KanbanItemCard = React.memo(function KanbanItemCard({
    item,
    onClickItem,
    onEditItem,
    onDeleteItem,
    renderItem,
    getRenderItemAriaLabel,
    testIdPrefix,
    variant,
}: KanbanItemCardProps) {
    const { attributes, listeners, setNodeRef, transform, transition, isDragging } = useSortable({
        id: item.id,
    });

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

    const ghostClassName = [
        styles.dragGhost,
        variant === 'crm' ? styles.dragGhostCrm : '',
    ]
        .filter(Boolean)
        .join(' ');

    const menuItems: MenuProps['items'] = useMemo(
        () => [
            {
                key: 'edit',
                icon: <Pencil size={ICON_SIZE_MD} aria-hidden />,
                label: 'Editar',
                onClick: () => onEditItem(item),
            },
            {
                key: 'delete',
                icon: <Trash2 size={ICON_SIZE_MD} aria-hidden />,
                label: 'Excluir',
                danger: true,
                onClick: () => onDeleteItem(item.id),
            },
        ],
        [item, onEditItem, onDeleteItem]
    );

    if (isDragging) {
        return (
            <div
                ref={setNodeRef}
                style={style}
                className={ghostClassName}
                data-testid={`${testIdPrefix}-item-ghost`}
                data-kanban-item-id={String(item.id)}
                aria-hidden
            />
        );
    }

    const dragKeyDown = listeners?.onKeyDown as
        | React.KeyboardEventHandler<HTMLElement>
        | undefined;

    /** `Enter` no próprio cartão abre; as restantes teclas seguem para o sensor de arrasto (`Space`). */
    const handleKeyDown = (event: React.KeyboardEvent<HTMLElement>) => {
        if (event.key === 'Enter' && event.target === event.currentTarget) {
            event.preventDefault();
            onClickItem(item);
            return;
        }
        dragKeyDown?.(event);
    };

    if (renderItem) {
        const sortableAriaLabel = getRenderItemAriaLabel?.(item);

        return (
            <div
                ref={setNodeRef}
                style={style}
                data-testid={`${testIdPrefix}-item`}
                data-kanban-item-id={String(item.id)}
                {...attributes}
                {...listeners}
                onClick={() => onClickItem(item)}
                onKeyDown={handleKeyDown}
                aria-label={sortableAriaLabel}
            >
                {renderItem(item)}
            </div>
        );
    }

    return (
        <Card
            ref={setNodeRef}
            data-testid={`${testIdPrefix}-item`}
            data-kanban-item-id={String(item.id)}
            style={style}
            size="small"
            hoverable
            onClick={() => onClickItem(item)}
            {...attributes}
            {...listeners}
            onKeyDown={handleKeyDown}
            actions={[
                <Dropdown menu={{ items: menuItems }} trigger={['click']} key="more">
                    <Button type="text" icon={<Ellipsis size={ICON_SIZE_MD} aria-hidden />} onClick={(e) => e.stopPropagation()} />
                </Dropdown>,
            ]}
        >
            <div>
                <Text strong style={{ display: 'block', marginBottom: 4 }}>
                    {item.title}
                </Text>
                {item.description && (
                    <Text type="secondary" style={{ fontSize: 12 }}>
                        {item.description}
                    </Text>
                )}
            </div>
        </Card>
    );
}, kanbanItemCardPropsEqual);

function KanbanBoardCrmShell({
    children,
    testIdPrefix,
    boardClassName,
}: {
    children: React.ReactNode;
    testIdPrefix: string;
    boardClassName: string;
}) {
    const boardRef = useRef<HTMLDivElement>(null);
    const topScrollRef = useRef<HTMLDivElement>(null);
    const topSpacerRef = useRef<HTMLDivElement>(null);
    const scrollSyncSource = useRef<'board' | 'top' | null>(null);

    const syncTopSpacerWidth = useCallback(() => {
        const board = boardRef.current;
        const spacer = topSpacerRef.current;
        if (!board || !spacer) return;
        spacer.style.width = `${board.scrollWidth}px`;
    }, []);

    useLayoutEffect(() => {
        syncTopSpacerWidth();
    }, [syncTopSpacerWidth, children]);

    useEffect(() => {
        const board = boardRef.current;
        if (!board) return;

        const observer = new ResizeObserver(() => syncTopSpacerWidth());
        observer.observe(board);
        return () => observer.disconnect();
    }, [syncTopSpacerWidth]);

    const handleBoardScroll = useCallback(() => {
        if (scrollSyncSource.current === 'top') return;
        scrollSyncSource.current = 'board';
        const board = boardRef.current;
        const top = topScrollRef.current;
        if (board && top) {
            top.scrollLeft = board.scrollLeft;
        }
        scrollSyncSource.current = null;
    }, []);

    const handleTopScroll = useCallback(() => {
        if (scrollSyncSource.current === 'board') return;
        scrollSyncSource.current = 'top';
        const board = boardRef.current;
        const top = topScrollRef.current;
        if (board && top) {
            board.scrollLeft = top.scrollLeft;
        }
        scrollSyncSource.current = null;
    }, []);

    return (
        <div className={styles.boardCrmShell} data-testid={`${testIdPrefix}-board-shell`}>
            <div
                ref={topScrollRef}
                className={styles.boardTopScroll}
                onScroll={handleTopScroll}
                aria-label="Deslocar funil horizontalmente"
                data-testid={`${testIdPrefix}-board-top-scroll`}
            >
                <div ref={topSpacerRef} className={styles.boardTopScrollInner} aria-hidden />
            </div>
            <div
                ref={boardRef}
                data-testid={`${testIdPrefix}-board`}
                className={boardClassName}
                onScroll={handleBoardScroll}
            >
                {children}
            </div>
        </div>
    );
}

export default function KanbanBoard({
    columns,
    onItemMove,
    onItemClick,
    onItemAdd,
    onItemEdit,
    onItemDelete,
    renderItem,
    getRenderItemAriaLabel,
    loading: _loading = false,
    columnBodyMaxHeight,
    dataTestId = 'kanban',
    variant = 'default',
    entitySingular = 'negócio',
    emptyColumnDescription,
    addItemAriaLabel,
    onColumnLoadMore,
    columnVirtualizeThreshold = 48,
    renderColumnHeaderExtra,
}: KanbanBoardProps) {
    const testIdPrefix = dataTestId;
    const [activeId, setActiveId] = useState<string | number | null>(null);
    const [dragColumns, setDragColumns] = useState<KanbanColumn[] | null>(null);
    const [editingItem, setEditingItem] = useState<KanbanItem | null>(null);
    const [form] = Form.useForm();

    const sensors = useSensors(
        useSensor(PointerSensor, {
            activationConstraint: {
                distance: 8,
            },
        }),
        useSensor(KeyboardSensor, {
            coordinateGetter: sortableKeyboardCoordinates,
            /**
             * `Enter` fica reservado para abrir o item; sem isto o cartão entrava em modo arrasto
             * e o usuário de teclado não tinha forma de o abrir (só `Escape` para sair).
             */
            keyboardCodes: {
                start: [KeyboardCode.Space],
                cancel: [KeyboardCode.Esc],
                end: [KeyboardCode.Space, KeyboardCode.Enter],
            },
        })
    );

    const displayedColumns = dragColumns ?? columns;

    const resetDragState = useCallback(() => {
        setActiveId(null);
        setDragColumns(null);
    }, []);

    const handleDragStart = useCallback((event: DragStartEvent) => {
        setActiveId(event.active.id);
        setDragColumns(null);
    }, []);

    const handleDragOver = useCallback(
        (event: DragOverEvent) => {
            const { active, over } = event;
            if (!over || active.id === over.id) return;

            setDragColumns((current) => {
                const base = current ?? columns;
                const next = moveKanbanItemPreview(base, active.id, over.id);
                if (kanbanColumnsLayoutEqual(base, next)) {
                    return current;
                }
                return next;
            });
        },
        [columns],
    );

    const handleDragEnd = useCallback(
        (event: DragEndEvent) => {
            const { active, over } = event;
            const previewColumns = dragColumns ?? columns;
            resetDragState();

            if (!over) return;

            const itemId = active.id;
            const sourceColumn = columns.find((col) => col.items.some((i) => i.id === itemId));
            const targetColumnId = findColumnIdForDndId(previewColumns, itemId);

            if (sourceColumn && targetColumnId && sourceColumn.id !== targetColumnId && onItemMove) {
                onItemMove(itemId, targetColumnId);
            }
        },
        [columns, dragColumns, onItemMove, resetDragState],
    );

    const handleDragCancel = useCallback(
        (_event: DragCancelEvent) => {
            resetDragState();
        },
        [resetDragState],
    );

    const handleItemClick = useCallback(
        (item: KanbanItem) => {
            onItemClick?.(item);
        },
        [onItemClick]
    );

    const handleItemEditOpen = useCallback(
        (item: KanbanItem) => {
            setEditingItem(item);
            form.setFieldsValue({
                title: item.title,
                description: item.description,
            });
        },
        [form]
    );

    const handleItemDelete = useCallback(
        (itemId: string | number) => {
            onItemDelete?.(itemId);
        },
        [onItemDelete]
    );

    const handleAddItem = useCallback(
        (status: string) => {
            onItemAdd?.(status);
        },
        [onItemAdd]
    );

    const handleSaveEdit = useCallback(() => {
        if (!editingItem) return;

        form.validateFields().then((values) => {
            onItemEdit?.({ ...editingItem, ...values });
            setEditingItem(null);
            form.resetFields();
        });
    }, [editingItem, form, onItemEdit]);

    const activeItem = activeId
        ? displayedColumns.flatMap((col) => col.items).find((item) => item.id === activeId)
        : null;

    const boardClassName = [
        styles.board,
        variant === 'crm' ? styles.boardCrm : styles.boardDefault,
        displayedColumns.length === 1 ? styles.boardSingleColumn : '',
    ]
        .filter(Boolean)
        .join(' ');

    const columnNodes = displayedColumns.map((column) => (
        <KanbanColumnComponent
            key={column.id}
            column={column}
            onClickItem={handleItemClick}
            onEditItem={handleItemEditOpen}
            onDeleteItem={handleItemDelete}
            onItemAdd={onItemAdd ? handleAddItem : undefined}
            renderItem={renderItem}
            getRenderItemAriaLabel={getRenderItemAriaLabel}
            testIdPrefix={testIdPrefix}
            columnBodyMaxHeight={columnBodyMaxHeight}
            variant={variant}
            entitySingular={entitySingular}
            emptyColumnDescription={emptyColumnDescription}
            addItemAriaLabel={addItemAriaLabel}
            onColumnLoadMore={onColumnLoadMore}
            columnVirtualizeThreshold={columnVirtualizeThreshold}
            renderColumnHeaderExtra={renderColumnHeaderExtra}
        />
    ));

    const boardElement =
        variant === 'crm' ? (
            <KanbanBoardCrmShell testIdPrefix={testIdPrefix} boardClassName={boardClassName}>
                {columnNodes}
            </KanbanBoardCrmShell>
        ) : (
            <div data-testid={`${testIdPrefix}-board`} className={boardClassName}>
                {columnNodes}
            </div>
        );

    return (
        <>
            <DndContext
                sensors={sensors}
                collisionDetection={closestCorners}
                onDragStart={handleDragStart}
                onDragOver={handleDragOver}
                onDragEnd={handleDragEnd}
                onDragCancel={handleDragCancel}
            >
                {boardElement}

                <DragOverlay dropAnimation={null}>
                    {activeItem ? (
                        renderItem ? (
                            <div
                                style={{
                                    width: variant === 'crm' ? 340 : 280,
                                    cursor: 'grabbing',
                                    opacity: 0.96,
                                    boxShadow: '0 12px 32px rgba(15, 23, 42, 0.18)',
                                }}
                            >
                                {renderItem(activeItem)}
                            </div>
                        ) : (
                            <Card
                                size="small"
                                style={{
                                    width: 280,
                                    transform: 'rotate(2deg)',
                                    opacity: 0.96,
                                    cursor: 'grabbing',
                                    boxShadow: '0 12px 32px rgba(15, 23, 42, 0.18)',
                                }}
                            >
                                <Text strong>{activeItem.title}</Text>
                            </Card>
                        )
                    ) : null}
                </DragOverlay>
            </DndContext>

            <Modal
                title="Editar Item"
                open={!!editingItem}
                onOk={handleSaveEdit}
                onCancel={() => {
                    setEditingItem(null);
                    form.resetFields();
                }}
                data-testid={`${testIdPrefix}-edit-modal`}
            >
                <Form form={form} layout="vertical">
                    <Form.Item name="title" label="Título" rules={[{ required: true }]}>
                        <Input />
                    </Form.Item>
                    <Form.Item name="description" label="Descrição">
                        <TextArea rows={3} />
                    </Form.Item>
                </Form>
            </Modal>
        </>
    );
}
