import { useEffect, useMemo, useState, type ReactNode } from 'react';

import {
    formControlClassName,
    formHelpClassName,
    mediaPreviewClassName,
    statusBadgeActiveClassName,
    statusBadgeInactiveClassName,
    tableActionClassName,
    tableCellClassName,
    tableCellTruncateClassName,
    tableCellTruncateInnerClassName,
    tableClassName,
    tableCompactClassName,
    tableEmptyClassName,
    tablePaginationClassName,
    tableWrapperClassName,
} from '@admin/components/ui/field-styles';
import { cn } from '@admin/lib/utils';
import { CREATE_SIDEBAR_PAGE_SIZE, INDEX_TABLE_PAGE_SIZE } from '@admin/lib/crud-constants';

export type DataTableColumn<T extends Record<string, unknown>> = {
    header: string;
    key: keyof T & string;
    render?: (row: T) => ReactNode;
    /**
     * Ellipsis long text (CSS `truncate` + `title` tooltip).
     * Omitted = auto-truncate default text cells; set `false` to opt out.
     */
    truncate?: boolean;
    /** Optional width hint for table-fixed layouts, e.g. `w-[40%]` */
    className?: string;
};

/** Keys that must never auto-truncate (badges, media, narrow counts). */
const TABLE_CELL_NO_TRUNCATE_KEYS = new Set([
    'status',
    'image',
    'photo',
    'video',
    'icon',
    'sort_order',
    'permissions_count',
    'users_count',
    'is_featured',
    'is_protected',
    'featured_image',
    'cover_image',
]);

function resolveColumnTruncate<T extends Record<string, unknown>>(column: DataTableColumn<T>): boolean {
    if (column.truncate === true) {
        return true;
    }

    if (column.truncate === false || TABLE_CELL_NO_TRUNCATE_KEYS.has(column.key)) {
        return false;
    }

    if (column.render) {
        return false;
    }

    return true;
}

type DataTableProps<T extends Record<string, unknown>> = {
    data: T[];
    columns: DataTableColumn<T>[];
    title?: string;
    itemsPerPage?: number;
    showSearch?: boolean;
    showRowNumber?: boolean;
    compact?: boolean;
    noDataText?: string;
    onEdit?: (row: T) => void;
    onDelete?: (row: T) => void;
    /** When false, hides the edit action even if onEdit is provided */
    showEdit?: boolean;
    /** When false, hides the delete action even if onDelete is provided */
    showDelete?: boolean;
    /** When provided, hides edit per row (e.g. system permission groups) */
    canEditRow?: (row: T) => boolean;
    /** When provided, hides delete per row (e.g. protected system roles) */
    canDeleteRow?: (row: T) => boolean;
};

function defaultCellValue<T extends Record<string, unknown>>(row: T, key: string): ReactNode {
    const value = row[key];

    if (key === 'status') {
        const active = value === true || value === 1 || value === 'active';

        return (
            <span className={active ? statusBadgeActiveClassName : statusBadgeInactiveClassName}>
                {active ? 'Active' : 'Inactive'}
            </span>
        );
    }

    if ((key === 'image' || key === 'photo') && value) {
        return (
            <img
                src={`/storage/${String(value)}`}
                alt=""
                className={cn('h-10 w-16 object-cover', mediaPreviewClassName)}
            />
        );
    }

    if (key === 'video' && value) {
        return <span className={formHelpClassName}>Video attached</span>;
    }

    if (value === null || value === undefined || value === '') {
        return '—';
    }

    return String(value);
}

export function DataTable<T extends Record<string, unknown>>({
    data,
    columns,
    title = 'Records',
    itemsPerPage,
    showSearch = true,
    showRowNumber = true,
    compact = false,
    noDataText = 'No records found',
    onEdit,
    onDelete,
    showEdit = true,
    showDelete = true,
    canDeleteRow,
    canEditRow,
}: DataTableProps<T>) {
    const showEditAction = showEdit && onEdit !== undefined;
    const showDeleteAction = showDelete && onDelete !== undefined;
    const rowShowsEdit = (row: T) => showEditAction && (canEditRow === undefined || canEditRow(row));
    const rowShowsDelete = (row: T) => showDeleteAction && (canDeleteRow === undefined || canDeleteRow(row));
    const anyRowEdit = showEditAction && (canEditRow === undefined || data.some(canEditRow));
    const anyRowDelete = showDeleteAction && (canDeleteRow === undefined || data.some(canDeleteRow));
    const showActionsColumn = anyRowEdit || anyRowDelete;
    const resolvedItemsPerPage = itemsPerPage ?? (compact ? CREATE_SIDEBAR_PAGE_SIZE : INDEX_TABLE_PAGE_SIZE);
    const [currentPage, setCurrentPage] = useState(1);
    const [searchTerm, setSearchTerm] = useState('');

    const filteredData = useMemo(() => {
        if (!searchTerm) {
            return data;
        }

        const needle = searchTerm.toLowerCase();

        return data.filter((row) => Object.values(row).some((value) => String(value).toLowerCase().includes(needle)));
    }, [data, searchTerm]);

    const totalPages = Math.max(1, Math.ceil(filteredData.length / resolvedItemsPerPage));
    const startIndex = (currentPage - 1) * resolvedItemsPerPage;
    const currentData = filteredData.slice(startIndex, startIndex + resolvedItemsPerPage);

    useEffect(() => {
        if (currentData.length === 0 && currentPage > 1) {
            setCurrentPage((page) => Math.max(page - 1, 1));
        }
    }, [currentData.length, currentPage]);

    useEffect(() => {
        setCurrentPage(1);
    }, [searchTerm]);

    const resolvedShowSearch = compact ? false : showSearch;
    const resolvedShowRowNumber = compact ? false : showRowNumber;
    const tableLayoutClassName = compact ? tableCompactClassName : tableClassName;

    return (
        <div className="box">
            <div className="box-header flex flex-wrap items-center justify-between gap-3">
                <h5 className="box-title">{title}</h5>
                {resolvedShowSearch && (
                    <input
                        type="search"
                        className={cn(formControlClassName, 'max-w-xs p-2 pe-10')}
                        placeholder="Search..."
                        value={searchTerm}
                        onChange={(event) => setSearchTerm(event.target.value)}
                    />
                )}
            </div>
            <div className="box-body p-0">
                <div className={tableWrapperClassName}>
                    <table className={tableLayoutClassName}>
                        <thead>
                            <tr>
                                {resolvedShowRowNumber && <th scope="col">#</th>}
                                {columns.map((column) => (
                                    <th key={column.key} scope="col" className={column.className}>
                                        {column.header}
                                    </th>
                                ))}
                                {showActionsColumn && (
                                    <th scope="col" className="!text-end w-24 shrink-0 whitespace-nowrap">
                                        Action
                                    </th>
                                )}
                            </tr>
                        </thead>
                        <tbody>
                            {currentData.length > 0 ? (
                                currentData.map((row, index) => (
                                    <tr key={String(row.id ?? index)}>
                                        {resolvedShowRowNumber && (
                                            <td className={tableCellClassName}>{startIndex + index + 1}</td>
                                        )}
                                        {columns.map((column) => {
                                            const shouldTruncate = resolveColumnTruncate(column);
                                            const cellContent = column.render
                                                ? column.render(row)
                                                : defaultCellValue(row, column.key);
                                            const rawValue = row[column.key];
                                            const titleText =
                                                shouldTruncate && rawValue != null && rawValue !== ''
                                                    ? String(rawValue)
                                                    : undefined;

                                            return (
                                                <td
                                                    key={column.key}
                                                    className={cn(
                                                        tableCellClassName,
                                                        column.className,
                                                        shouldTruncate && tableCellTruncateClassName,
                                                    )}
                                                    title={titleText}
                                                >
                                                    {shouldTruncate ? (
                                                        <span className={tableCellTruncateInnerClassName}>
                                                            {cellContent}
                                                        </span>
                                                    ) : (
                                                        cellContent
                                                    )}
                                                </td>
                                            );
                                        })}
                                        {showActionsColumn && (
                                            <td className="!text-end w-24 shrink-0 whitespace-nowrap">
                                                <div className="flex justify-end gap-3">
                                                {rowShowsEdit(row) && (
                                                    <button
                                                        type="button"
                                                        className={cn(tableActionClassName, 'text-primary')}
                                                        onClick={() => onEdit?.(row)}
                                                    >
                                                        <i className="ri-edit-line" />
                                                    </button>
                                                )}
                                                    {rowShowsDelete(row) && (
                                                        <button
                                                            type="button"
                                                            className={cn(tableActionClassName, 'text-danger')}
                                                            onClick={() => onDelete?.(row)}
                                                        >
                                                            <i className="ri-delete-bin-line" />
                                                        </button>
                                                    )}
                                                </div>
                                            </td>
                                        )}
                                    </tr>
                                ))
                            ) : (
                                <tr>
                                    <td
                                        colSpan={
                                            columns.length + (resolvedShowRowNumber ? 1 : 0) + (showActionsColumn ? 1 : 0)
                                        }
                                        className={tableEmptyClassName}
                                    >
                                        {noDataText}
                                    </td>
                                </tr>
                            )}
                        </tbody>
                    </table>
                </div>

                {filteredData.length > resolvedItemsPerPage && (
                    <nav className="p-4 overflow-auto">
                        <ul className={tablePaginationClassName}>
                            <li className={currentPage === 1 ? 'opacity-50 pointer-events-none' : ''}>
                                <button type="button" className="page-link" onClick={() => setCurrentPage((page) => Math.max(page - 1, 1))}>
                                    <span aria-hidden="true">«</span>
                                    <span className="sr-only">Previous</span>
                                </button>
                            </li>
                            {Array.from({ length: totalPages }, (_, index) => index + 1).map((page) => (
                                <li key={page} className={currentPage === page ? 'active' : ''}>
                                    <button type="button" className="page-link" aria-current={currentPage === page ? 'page' : undefined} onClick={() => setCurrentPage(page)}>
                                        {page}
                                    </button>
                                </li>
                            ))}
                            <li className={currentPage === totalPages ? 'opacity-50 pointer-events-none' : ''}>
                                <button type="button" className="page-link" onClick={() => setCurrentPage((page) => Math.min(page + 1, totalPages))}>
                                    <span className="sr-only">Next</span>
                                    <span aria-hidden="true">»</span>
                                </button>
                            </li>
                        </ul>
                    </nav>
                )}
            </div>
        </div>
    );
}
