'use client';

import React, { useState, useMemo, useId, useEffect, useRef } from 'react';
import { Table, TableProps, Button, Input, ConfigProvider, theme } from 'antd';
import type { ColumnsType, TablePaginationConfig } from 'antd/es/table';
import { FastColor } from '@ant-design/fast-color';
import { RotateCw, Search as SearchIcon } from 'lucide-react';
import { ICON_SIZE_MD } from '@/components/icons';
import { PaginatedResponse } from '@/types';
import { useAparencia } from '@/hooks/useAparencia';
import {
    buildListingSkeletonRows,
    listingSkeletonRowCount,
    isListingSkeletonRow,
    wrapColumnsWithListingSkeleton,
} from '@/components/tables/listingSkeletonRows';
import { applyListingTableColumnMinWidths } from '@/components/tables/listingTableColumnMinWidths';
import { withListingTableSingleActiveSortIconColumns } from '@/components/tables/listingTableSingleActiveSortIcon';
import { parseListingRowSelection } from '@/components/listings/listingRowSelectionTypes';
import { patchListingColumnsForMobileSelection } from '@/components/listings/patchListingColumnsForMobileSelection';
import { useIsMobile } from '@/hooks/useIsMobile';
import {
    ListingMobileCardList,
    buildListingMobileView,
    type ListingMobileViewProps,
} from '@/components/listings/mobile';
import styles from './DataTable.module.scss';

const { Search } = Input;

const PAGINATION_NAV_ARIA: Record<string, string> = {
    prev: 'Página anterior',
    next: 'Página seguinte',
    'jump-prev': 'Retroceder cinco páginas',
    'jump-next': 'Avançar cinco páginas',
};

export function listingPaginationItemRender(
    _page: number,
    type: string,
    originalElement: React.ReactNode,
): React.ReactNode {
    const label = PAGINATION_NAV_ARIA[type];
    if (!label || !React.isValidElement(originalElement)) {
        return originalElement;
    }
    const element = originalElement as React.ReactElement<{
        disabled?: boolean;
        'aria-label'?: string;
    }>;
    const disabled = Boolean(element.props?.disabled);
    const accessibleLabel = disabled ? `${label} (desativado)` : label;
    return React.cloneElement(element, {
        'aria-label': accessibleLabel,
        ...(disabled ? { 'aria-disabled': true as const } : {}),
    });
}

const visuallyHidden: React.CSSProperties = {
    position: 'absolute',
    width: 1,
    height: 1,
    padding: 0,
    margin: -1,
    overflow: 'hidden',
    clip: 'rect(0, 0, 0, 0)',
    whiteSpace: 'nowrap',
    border: 0,
};

/** Tons do cabeçalho em `corPrimaria`: hover/ordenado baseados em `FastColor.shade` (sem cinza neutro). */
function derivePrimaryHeaderTableTokens(corPrimaria: string) {
    const base = new FastColor(corPrimaria);
    const sortActive = base.shade(7).toRgbString();
    const sortHover = new FastColor(corPrimaria).shade(14).toRgbString();
    const filterHover = new FastColor(corPrimaria).shade(10).toRgbString();
    return {
        headerSortActiveBg: sortActive,
        headerSortHoverBg: sortHover,
        fixedHeaderSortActiveBg: sortHover,
        headerFilterHoverBg: filterHover,
        /** Divisor entre colunas legível sobre fundo colorido */
        headerSplitColor: 'rgba(255, 255, 255, 0.22)',
    };
}

/** Tema local do `Table` quando `primaryHeader` (padrão: ligado). */
function PrimaryHeaderTableProvider({ children }: { children: React.ReactNode }) {
    const { token } = theme.useToken();
    const { corPrimaria } = useAparencia();
    const derived = useMemo(() => derivePrimaryHeaderTableTokens(corPrimaria), [corPrimaria]);
    const tableTheme = useMemo(
        () => ({
            components: {
                Table: {
                    headerBg: corPrimaria,
                    headerColor: token.colorTextLightSolid,
                    ...derived,
                },
            },
        }),
        [corPrimaria, derived, token.colorTextLightSolid],
    );
    return (
        <ConfigProvider theme={tableTheme}>
            <div className={styles.primaryHeaderTable}>{children}</div>
        </ConfigProvider>
    );
}

interface DataTableProps<T = unknown> extends Omit<TableProps<T>, 'dataSource' | 'columns'> {
    columns: ColumnsType<T>;
    data?: T[];
    paginatedData?: PaginatedResponse<T>;
    loading?: boolean;
    /** Fetch em andamento (ex.: após ordenar ou mudar página): corpo em Skeleton em vez do overlay da tabela. */
    fetching?: boolean;
    searchable?: boolean;
    searchPlaceholder?: string;
    onSearch?: (value: string) => void;
    onRefresh?: () => void;
    /**
     * Botão "Atualizar" acima da tabela. Listagens Waygest usam `ListPageLayout.onRefresh` / barra mobile;
     * active apenas quando necessário (ex.: tabela isolada sem layout de página).
     * @default false
     */
    showRefresh?: boolean;
    pageSize?: number;
    onPageChange?: (page: number, pageSize?: number) => void;
    // Suporte para seleção de linhas
    rowSelection?: TableProps<T>['rowSelection'];
    // Suporte para expansão de linhas
    expandable?: TableProps<T>['expandable'];
    /** Rótulo da região semântica (leitor de tela). */
    ariaRegionLabel?: string;
    /** Texto curto sobre scroll horizontal quando a tabela usa colunas largas. */
    horizontalScrollHint?: string;
    /** Incrementar para limpar o texto da busca local (ex.: após "Limpar filtros" no pai). */
    searchResetKey?: number;
    /**
     * Cabeçalho (`th`) com fundo = cor primária da aparência (`GET …/configuracoes-aparencia`).
     * Usa `ConfigProvider` local (não depende de `cssVar: true` no tema global).
     * Ícones de ordenação: colunas com `sorter` e sem `sortIcon` próprio passam a mostrar só a seta
     * da coluna ativa (padrão visual de `/suporte/chamados`). O contraste no cabeçalho primário está em
     * `DataTable.module.scss` (`.primaryHeaderTable`). Para ícone duplo clássico do Ant, defina `sortIcon` na coluna.
     * @default true — use `false` para cabeçalho neutro (`headerBg` global).
     */
    primaryHeader?: boolean;
    /** Tabela inline no `ListPageLayout` sem cartão envolvente (§6.2 guia listagens). */
    listingInline?: boolean;
    /**
     * Vista mobile nativa (cards) em ≤768px — evita scroll horizontal da tabela.
     * Use `cardConfig` ou `renderCard`; quando omitido, mantém tabela com scroll contido.
     */
    mobileView?: ListingMobileViewProps<T>;
    /**
     * Atalho: gera `mobileView` automaticamente a partir de `columns` (listagens CRUD padrão).
     * Tem prioridade inferior a `mobileView` explícito.
     */
    autoMobileFromColumns?: Omit<
        import('@/components/listings/mobile/buildListingMobileView').BuildListingMobileViewOptions<T>,
        'columns'
    >;
    /**
     * Quando true, desativa geração automática de cards mobile a partir das colunas.
     * @default false — listagens com ≥3 colunas passam a cards em ≤768px sem config extra.
     */
    disableAutoMobileCards?: boolean;
}

export default function DataTable<T = unknown>({
    columns,
    data,
    paginatedData,
    loading = false,
    fetching = false,
    searchable = false,
    searchPlaceholder = 'Buscar...',
    onSearch,
    onRefresh,
    showRefresh = false,
    pageSize = 10,
    onPageChange,
    rowSelection,
    expandable,
    ariaRegionLabel = 'Tabela de listagem',
    horizontalScrollHint = 'Esta tabela pode ter mais colunas à direita. Use scroll horizontal ou deslize para ver todo o conteúdo.',
    searchResetKey,
    primaryHeader = true,
    listingInline = false,
    mobileView,
    autoMobileFromColumns,
    disableAutoMobileCards = false,
    ...tablePropsRest
}: DataTableProps<T>) {
    /** Borda nas células (padrão listagens); pode desativar com `bordered={false}`. */
    const {
        scroll: userScroll,
        bordered: borderedProp,
        rowKey: rowKeyProp,
        onChange: tableOnChange,
        ...tableProps
    } = tablePropsRest;
    const bordered = borderedProp ?? !listingInline;
    const [searchValue, setSearchValue] = useState('');
    const prevSearchResetKey = useRef(searchResetKey ?? 0);
    const scrollHintId = useId();
    const liveStatusId = useId();
    const [liveMessage, setLiveMessage] = useState('');

    const tableData = useMemo(() => {
        if (paginatedData) {
            // Respostas parciais/erro de contrato podem omitir `data` — nunca devolver undefined
            // (evita crash em `tableData.length` nas listagens).
            return Array.isArray(paginatedData.data) ? paginatedData.data : [];
        }
        return data || [];
    }, [data, paginatedData]);

    useEffect(() => {
        if (searchResetKey == null) return;
        if (searchResetKey !== prevSearchResetKey.current) {
            prevSearchResetKey.current = searchResetKey;
            setSearchValue('');
        }
    }, [searchResetKey]);

    const busy = loading || fetching;
    const prevBusyRef = useRef(busy);
    useEffect(() => {
        const wasBusy = prevBusyRef.current;
        prevBusyRef.current = busy;
        if (wasBusy && !busy) {
            const n = tableData.length;
            const total = paginatedData?.meta?.total;
            const suffix =
                total != null ? `${n} linhas nesta página, ${total} no total.` : `${n} linhas nesta página.`;
            setLiveMessage(`Resultados atualizados. ${suffix}`);
            const t = window.setTimeout(() => setLiveMessage(''), 2500);
            return () => window.clearTimeout(t);
        }
        return undefined;
    }, [busy, tableData.length, paginatedData?.meta?.total]);

    const showSkeletonRows = Boolean(
        fetching || (loading && tableData.length === 0)
    );

    const skeletonN = useMemo(
        () =>
            listingSkeletonRowCount(
                tableData.length,
                paginatedData?.meta?.per_page ?? pageSize
            ),
        [tableData.length, paginatedData?.meta?.per_page, pageSize]
    );

    const displayData = useMemo(() => {
        if (!showSkeletonRows) {
            return tableData;
        }
        return buildListingSkeletonRows(skeletonN) as unknown as T[];
    }, [showSkeletonRows, skeletonN, tableData]);

    const columnsWithListingMinWidths = useMemo(
        () =>
            withListingTableSingleActiveSortIconColumns(
                applyListingTableColumnMinWidths(
                    columns as unknown as ColumnsType<Record<string, unknown>>,
                ),
            ) as unknown as ColumnsType<T>,
        [columns],
    );

    const displayColumns = useMemo(() => {
        if (!showSkeletonRows) {
            return columnsWithListingMinWidths;
        }
        return wrapColumnsWithListingSkeleton(
            columnsWithListingMinWidths as ColumnsType<Record<string, unknown>>
        ) as ColumnsType<T>;
    }, [columnsWithListingMinWidths, showSkeletonRows]);

    const { tableRowSelection, mobileMenuHeader } = useMemo(
        () => parseListingRowSelection(rowSelection),
        [rowSelection],
    );

    const tableColumns = useMemo(
        () =>
            patchListingColumnsForMobileSelection(
                displayColumns as ColumnsType<T>,
                mobileMenuHeader,
            ),
        [displayColumns, mobileMenuHeader],
    );

    const paginationConfig: TablePaginationConfig = useMemo(() => {
        const meta = paginatedData?.meta;
        if (meta) {
            return {
                current: meta.current_page ?? 1,
                pageSize: meta.per_page ?? pageSize,
                total: meta.total ?? paginatedData?.data?.length ?? 0,
                showSizeChanger: true,
                showTotal: (total, range) => `${range[0]}-${range[1]} de ${total} itens`,
                itemRender: listingPaginationItemRender,
                onChange: (page, size) => {
                    // Com `Table.onChange` (listagem servidor), paginação já vem em `extra.action === 'paginate'`.
                    if (!tableOnChange) {
                        onPageChange?.(page, size);
                    }
                },
            };
        }
        return {
            pageSize,
            showSizeChanger: true,
            showTotal: (total, range) => `${range[0]}-${range[1]} de ${total} itens`,
            itemRender: listingPaginationItemRender,
            onChange: tableOnChange
                ? undefined
                : (page, size) => {
                      onPageChange?.(page, size);
                  },
        };
    }, [paginatedData, pageSize, onPageChange, tableOnChange]);

    /** Atualiza só o texto local; não dispara fetch a cada tecla. */
    const handleSearchInputChange = (e: React.ChangeEvent<HTMLInputElement>) => {
        const v = e.target.value;
        setSearchValue(v);
        if (v === '') {
            onSearch?.('');
        }
    };

    /** Enter ou botão de pesquisa: aplica o termo no pai (ex.: query API). */
    const handleSearchSubmit = (value: string) => {
        setSearchValue(value);
        onSearch?.(value);
    };

    const handleRefresh = () => {
        setSearchValue('');
        onRefresh?.();
    };

    const resolveRowKey = (record: T): React.Key => {
        if (typeof rowKeyProp === 'function') {
            return rowKeyProp(record, 0);
        }
        if (typeof rowKeyProp === 'string') {
            const field = rowKeyProp as keyof T;
            const k = record[field] as React.Key | undefined;
            if (k !== undefined && k !== null && k !== '') {
                return k;
            }
        }
        const r = record as T & { id?: string | number; key?: string | number };
        const k = r.id ?? r.key;
        if (k !== undefined && k !== null && k !== '') {
            return k as React.Key;
        }
        if (isListingSkeletonRow(record)) {
            return record.id;
        }
        return `row-${JSON.stringify(record).slice(0, 120)}`;
    };

    const isMobile = useIsMobile();

    const resolvedMobileView = useMemo(() => {
        if (mobileView) return mobileView;
        if (autoMobileFromColumns) {
            return buildListingMobileView<T>({
                columns,
                ...autoMobileFromColumns,
            });
        }
        if (
            !disableAutoMobileCards &&
            columns.length >= 3 &&
            (paginatedData != null || (data != null && data.length > 0))
        ) {
            const slug = ariaRegionLabel
                .toLowerCase()
                .normalize('NFD')
                .replace(/[\u0300-\u036f]/g, '')
                .replace(/[^a-z0-9]+/g, '-')
                .replace(/^-|-$/g, '');
            return buildListingMobileView<T>({
                columns,
                testIdPrefix: slug || 'listing',
                listHeading: ariaRegionLabel,
            });
        }
        return undefined;
    }, [
        mobileView,
        autoMobileFromColumns,
        disableAutoMobileCards,
        columns,
        paginatedData,
        data,
        ariaRegionLabel,
    ]);

    const useMobileCards =
        isMobile &&
        resolvedMobileView != null &&
        resolvedMobileView.enabled !== false &&
        (resolvedMobileView.renderCard != null || resolvedMobileView.cardConfig != null);

    const tableElement = (
        <Table<T>
            aria-describedby={scrollHintId}
            bordered={bordered}
            columns={tableColumns as ColumnsType<T>}
            dataSource={displayData as T[]}
            loading={showSkeletonRows ? false : loading}
            pagination={paginationConfig}
            rowKey={resolveRowKey}
            rowSelection={showSkeletonRows ? undefined : tableRowSelection}
            expandable={showSkeletonRows ? undefined : expandable}
            onChange={tableOnChange}
            {...tableProps}
            scroll={{
                ...(userScroll ?? {}),
                x: 'max-content',
            }}
        />
    );

    const regionClassName = listingInline
        ? `${styles.listingInline} ${styles.dataTable} listing-inline-table`
        : styles.dataTable;

    const mobileCardsContent =
        useMobileCards && resolvedMobileView ? (
            <ListingMobileCardList
                rows={tableData}
                paginatedData={paginatedData}
                loading={loading}
                fetching={fetching}
                onPageChange={onPageChange}
                resolveRowKey={resolveRowKey}
                dataTestId={resolvedMobileView.dataTestId ?? 'listing-mobile-cards'}
                listHeading={resolvedMobileView.listHeading ?? ariaRegionLabel}
                cardConfig={resolvedMobileView.cardConfig}
                renderCard={resolvedMobileView.renderCard}
            />
        ) : null;

    return (
        <div role="region" aria-label={ariaRegionLabel} className={regionClassName}>
            <p id={scrollHintId} style={visuallyHidden}>
                {horizontalScrollHint}
            </p>
            <div id={liveStatusId} style={visuallyHidden} aria-live="polite" aria-atomic="true">
                {liveMessage}
            </div>
            {(searchable || showRefresh) && (
                <div
                    style={{
                        marginBottom: 16,
                        display: 'flex',
                        justifyContent: 'space-between',
                        gap: 16,
                    }}
                >
                    {searchable && (
                        <Search
                            placeholder={searchPlaceholder}
                            allowClear
                            enterButton={<SearchIcon size={ICON_SIZE_MD} aria-hidden />}
                            size="large"
                            value={searchValue}
                            onChange={handleSearchInputChange}
                            onSearch={handleSearchSubmit}
                            style={{ maxWidth: 400 }}
                        />
                    )}
                    {showRefresh && (
                        <Button
                            icon={<RotateCw size={ICON_SIZE_MD} aria-hidden />}
                            onClick={handleRefresh}
                            loading={loading || fetching}
                        >
                            Atualizar
                        </Button>
                    )}
                </div>
            )}

            {/* MOB-005: scroll horizontal apenas dentro do wrapper; página sem overflow */}
            {useMobileCards ? (
                mobileCardsContent
            ) : (
            <div
                style={{
                    overflowX: 'auto',
                    maxWidth: '100%',
                    minWidth: 0,
                    WebkitOverflowScrolling: 'touch',
                }}
            >
                {primaryHeader ? (
                    <PrimaryHeaderTableProvider>{tableElement}</PrimaryHeaderTableProvider>
                ) : (
                    tableElement
                )}
            </div>
            )}
        </div>
    );
}
