import { useEffect, useMemo, useRef, useState } from 'react';
import { router } from '@inertiajs/react';
import { ArrowDown, ArrowUp, ChevronDown, ChevronsUpDown, Package, Pin, RotateCcw, Search, SlidersHorizontal } from 'lucide-react';
import AppLayout from '@/Layouts/AppLayout';
import { cn } from '@/lib/utils';
import { ListFooter } from '@/Components/Table/ListFooter';
import { StatusBadge } from '@/Components/Proto/UI/StatusBadge';
import { CheckBox } from '@/Components/Proto/UI/CheckBox';
import { statusTone } from '@/lib/sampleOrderStatusTones';
import { SelectPill, DateRangePill } from '@/Components/MenuQuotations/QuotationListPage/QuotationListPills';
import { useResizableColumns, ColumnResizeGrip } from '@/lib/useResizableColumns';
import { useStickyColShadow } from '@/lib/useStickyColShadow';
import { ExportButton } from '@/lib/excel/ExportButton';

const NA = () => <span className="font-normal text-muted-foreground/55">-</span>;
const orNA = (v) => (v === null || v === undefined || v === '') ? <NA /> : v;
// qty arrives as an already-formatted string ("5", "2.5") from GeneralFunction::number.
const qtyNum = (v) => (v === null || v === undefined || v === '') ? <NA /> : v;

// Quotation-style table shell (mirrors the View Details page): sticky header, sticky
// first column, zebra striping, rounded pill corners, horizontal scroll shadow.
const TABLE_CLASS = 'w-full table-fixed border-separate border-spacing-0 [&_tbody_td]:overflow-hidden [&_td]:whitespace-nowrap [&_td]:px-3.5 [&_td]:py-[16px] [&_td]:text-left [&_td]:text-[12px] [&_th]:whitespace-nowrap [&_th]:px-3.5 [&_th]:py-3 [&_th]:text-left [&_th]:text-[11px] [&_th.col-name]:sticky [&_th.col-name]:left-0 [&_th.col-name]:z-3 [&_thead_th]:sticky [&_thead_th]:top-0 [&_thead_th]:z-2 [&_thead_th]:bg-[color-mix(in_srgb,var(--color-secondary)_50%,var(--color-card))] [&_thead_th]:text-[11px] [&_thead_th]:font-semibold [&_thead_th]:uppercase [&_thead_th]:tracking-wide [&_thead_th]:text-muted-foreground [&_thead_tr:first-child_th:first-child]:rounded-l-full [&_thead_tr:first-child_th:first-child]:pl-7 [&_thead_tr:first-child_th:last-child]:rounded-r-full [&_thead_tr:first-child_th:last-child]:pr-5 [&_tbody_td]:border-b [&_tbody_td]:border-border/60 [&_tbody_td:last-child]:pr-5 [&_tbody_td]:px-3.5 [&_tbody_td]:py-[16px] [&_tbody_td]:align-middle [&_tbody_td]:text-[12px] [&_tbody_td]:text-card-foreground [&_tbody_td.col-name]:sticky [&_tbody_td.col-name]:left-0 [&_tbody_td.col-name]:z-1 [&_tbody_td.col-name]:bg-card [&_tbody_td.col-name]:font-semibold [&_tbody_td.col-name]:text-card-foreground [&_tbody_td:first-child]:pl-7 [&_tbody_tr:hover_td]:bg-secondary/60 [&_tbody_tr:hover_td.col-name]:bg-[color-mix(in_srgb,var(--color-secondary)_60%,var(--color-card))] [&_tbody_tr:nth-child(even)_td]:bg-secondary/25 [&_tbody_tr:nth-child(even)_td.col-name]:bg-[color-mix(in_srgb,var(--color-secondary)_25%,var(--color-card))]';
const SCROLL_SHADOW = 'overflow-x-auto rounded-xl [background:linear-gradient(to_right,var(--card)_30%,transparent),linear-gradient(to_right,transparent,var(--card)_70%)_right,radial-gradient(farthest-side_at_0_50%,rgba(0,0,0,0.12),transparent),radial-gradient(farthest-side_at_100%_50%,rgba(0,0,0,0.12),transparent)_right] [background-attachment:local,local,scroll,scroll] [background-repeat:no-repeat] [background-size:40px_100%,40px_100%,14px_100%,14px_100%]';

// Columns mirror the legacy listsampledetailsvsstock.php report.
const COLUMN_DEFS = [
    // `numericSort` sorts by soId numerically (#100 after #55) without right-aligning the sticky column.
    { id: 'soId', label: 'SO No', sticky: true, numericSort: true },
    { id: 'company', label: 'Company' },
    { id: 'status', label: 'Status' },
    { id: 'creator', label: 'Creator' },
    { id: 'sales', label: 'Sales' },
    { id: 'division', label: 'Division' },
    { id: 'industry', label: 'Industry' },
    { id: 'companyCategory', label: 'Company Category' },
    { id: 'tanggal', label: 'Tanggal' },
    { id: 'contactPerson', label: 'Contact Person' },
    { id: 'principal', label: 'Principal' },
    { id: 'namaBarang', label: 'Nama Barang' },
    { id: 'lotNumberRequest', label: 'Lot Number Req' },
    { id: 'qty', label: 'Quantity', numeric: true },
    { id: 'stock', label: 'Stock Barang', sortable: false },
];

// Default per-column pixel widths for the resizable table-fixed layout (overridable by drag).
const COL_W = {
    soId: 90, company: 200, status: 130, creator: 130, sales: 130,
    division: 130, industry: 150, companyCategory: 165, tanggal: 120,
    contactPerson: 160, principal: 190, namaBarang: 180, lotNumberRequest: 140,
    qty: 130, stock: 130,
};
const COL_W_FALLBACK = 150;

// Fields that get a filter pill (string select); options derive from the loaded rows.
// key = the `filterOptions` key the server returns; param = the query-string name it reads.
const PILL_FIELDS = [
    { key: 'status', label: 'Status', param: 'status' },
    { key: 'company', label: 'Company', param: 'company' },
    { key: 'division', label: 'Division', param: 'division' },
    { key: 'industry', label: 'Industry', param: 'industry' },
    { key: 'sales', label: 'Sales', param: 'sales' },
    { key: 'creator', label: 'Creator', param: 'creator' },
    { key: 'principal', label: 'Principal', param: 'principal' },
    { key: 'namaBarang', label: 'Product', param: 'product' },
];

const PER_PAGE_OPTIONS = [10, 20, 50, 100];

// Stock lots — compact "N lots" chip; the full list shows in a hover popover
// (click pins it), same interaction as the Sample List column on the SO lists.
// Keeps every row one line tall no matter how many lots a product has.
function StockCell({ lots }) {
    const [pos, setPos] = useState(null);
    const [pinned, setPinned] = useState(false);
    const ref = useRef(null);
    const pinnedRef = useRef(false);
    const timer = useRef(null);
    pinnedRef.current = pinned;

    useEffect(() => () => { if (timer.current) clearTimeout(timer.current); }, []);
    useEffect(() => {
        if (!pinned) return undefined;
        const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) { setPinned(false); setPos(null); } };
        const onEsc = (e) => { if (e.key === 'Escape') { setPinned(false); setPos(null); } };
        document.addEventListener('mousedown', onDoc);
        document.addEventListener('keydown', onEsc);
        return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onEsc); };
    }, [pinned]);

    if (!lots?.length) return <span className="italic text-muted-foreground/55">No stock</span>;

    const width = 300;
    const computePos = (el) => {
        const r = el.getBoundingClientRect();
        const left = Math.max(8, Math.min(r.left, window.innerWidth - width - 8));
        const spaceBelow = window.innerHeight - r.bottom - 12;
        const spaceAbove = r.top - 12;
        if (spaceBelow < 260 && spaceAbove > spaceBelow) {
            return { left, bottom: window.innerHeight - r.top + 6, maxH: Math.max(140, Math.min(320, spaceAbove)) };
        }
        return { left, top: r.bottom + 6, maxH: Math.max(140, Math.min(320, spaceBelow)) };
    };
    const cancelClose = () => { if (timer.current) { clearTimeout(timer.current); timer.current = null; } };
    const scheduleClose = () => { cancelClose(); timer.current = setTimeout(() => { if (!pinnedRef.current) setPos(null); }, 150); };
    const onEnter = (e) => { cancelClose(); if (!pinnedRef.current) setPos(computePos(e.currentTarget)); };
    const onClick = (e) => {
        e.stopPropagation();
        cancelClose();
        if (pinned) { setPinned(false); setPos(null); return; }
        const next = computePos(e.currentTarget);
        setPinned(true);
        setPos(next);
    };

    return (
        <div ref={ref} className="relative flex items-center">
            <button type="button" onMouseEnter={onEnter} onMouseLeave={scheduleClose} onClick={onClick}
                className={`inline-flex shrink-0 items-center gap-1.5 rounded-full border px-2.5 py-0.5 text-[11px] font-semibold tabular-nums transition-colors ${pos ? 'border-primary text-primary' : 'border-border bg-card text-muted-foreground hover:border-primary hover:text-primary'}`}>
                <Package className="size-3" aria-hidden="true" /> {lots.length} lot{lots.length !== 1 ? 's' : ''}
                <ChevronDown className={`size-3 transition-transform ${pos ? 'rotate-180' : ''}`} aria-hidden="true" />
            </button>
            {pos && (
                <div onMouseEnter={cancelClose} onMouseLeave={scheduleClose} onClick={(e) => e.stopPropagation()}
                    className="fixed z-[100] rounded-lg border border-border bg-card p-2.5 text-left shadow-modal"
                    style={{ left: pos.left, top: pos.top, bottom: pos.bottom, width, maxWidth: '85vw' }}>
                    <div className="mb-1.5 flex items-center justify-between gap-2 border-b border-border/60 pb-1.5">
                        <span className="text-[10px] font-bold uppercase tracking-wide text-muted-foreground">Stock Barang ({lots.length})</span>
                        {pinned && (
                            <span className="inline-flex items-center gap-0.5 text-[9px] font-semibold text-primary">
                                <Pin className="size-2.5" aria-hidden="true" />Pinned
                            </span>
                        )}
                    </div>
                    <ul className="m-0 flex list-none flex-col overflow-y-auto pr-1" style={{ maxHeight: pos.maxH }}>
                        {lots.map((l, i) => (
                            <li key={i} className="flex items-baseline justify-between gap-3 border-b border-border/50 py-1.5 leading-snug last:border-b-0">
                                <span className="min-w-0 truncate text-[12px] font-semibold text-foreground" title={l.lot || '—'}>{l.lot || '—'}</span>
                                <span className="shrink-0 text-[11px] tabular-nums text-muted-foreground">{l.qty}{l.satuan ? ` ${l.satuan}` : ''}</span>
                            </li>
                        ))}
                    </ul>
                </div>
            )}
        </div>
    );
}

function renderCell(r, colId, rowNumber) {
    switch (colId) {
        case 'soId': return <span className="font-semibold text-primary">#{r.soId}</span>;
        case 'company': return <span className="text-[12px] font-semibold text-foreground">{orNA(r.company)}</span>;
        case 'status': return r.status ? <StatusBadge tone={statusTone(r.status)}>{r.status}</StatusBadge> : <NA />;
        case 'creator': return orNA(r.creator);
        case 'sales': return orNA(r.sales);
        case 'division': return orNA(r.division);
        case 'industry': return orNA(r.industry);
        case 'companyCategory': return orNA(r.companyCategory);
        case 'tanggal': return <span className="tabular-nums text-muted-foreground">{orNA(r.tanggal)}</span>;
        case 'contactPerson': return <span className="block max-w-[200px] truncate" title={r.contactPerson || ''}>{orNA(r.contactPerson)}</span>;
        case 'principal': return orNA(r.principal);
        case 'namaBarang': return <span className="font-semibold text-card-foreground">{orNA(r.namaBarang)}</span>;
        case 'lotNumberRequest': return orNA(r.lotNumberRequest);
        case 'qty': return <span className="tabular-nums">{qtyNum(r.qty)}{r.satuan ? <span className="text-muted-foreground"> {r.satuan}</span> : null}</span>;
        case 'stock': return <StockCell lots={r.stock || []} />;
        default: return null;
    }
}

const defaultOrder = () => COLUMN_DEFS.map((d) => d.id);
function loadOrder(storageKey) {
    try {
        const raw = localStorage.getItem(storageKey);
        if (!raw) return defaultOrder();
        const parsed = JSON.parse(raw).filter((id) => COLUMN_DEFS.some((d) => d.id === id));
        COLUMN_DEFS.forEach((d, i) => { if (!parsed.includes(d.id)) parsed.splice(i, 0, d.id); });
        return parsed;
    } catch {
        return defaultOrder();
    }
}

export default function SampleOrderRequestVsStockList({
    details = { data: [] },
    title = 'Request vs Stock Sample',
    filterOptions = {},
    filters = {},
}) {
    // Server-driven since the #254 rollout — see ViewDetails/List.jsx for the contract.
    const [q, setQ] = useState(filters.search || '');
    const [activePill, setActivePill] = useState(null);
    const searchDebounce = useRef(null);

    const rows = details.data || [];
    const sortKey = filters.sort || 'soId';
    const sortDir = filters.dir || 'desc';
    const perPage = Number(filters.per_page) || 10;
    const currentPage = details.current_page || 1;
    const totalPages = details.last_page || 1;

    // Sticky-column divider appears only while scrolled horizontally. Must live HERE:
    // the table below consumes scrollRef/shadowClass, and this call previously sat in
    // StockCell, where both were out of scope — the ReferenceError blanked the page.
    const { scrollRef, shadowClass } = useStickyColShadow();

    const colDefById = useMemo(() => new Map(COLUMN_DEFS.map((d) => [d.id, d])), []);
    const storageKey = 'sampleOrderRequestVsStockColumns_v1';
    const [order, setOrder] = useState(() => loadOrder(storageKey));
    // Column show/hide (⚙ panel) — SO No stays locked as the row anchor.
    const visibleKey = 'sampleOrderRequestVsStockColumnsVisible_v1';
    const [visibleSet, setVisibleSet] = useState(() => {
        try {
            const raw = JSON.parse(localStorage.getItem(visibleKey) ?? 'null');
            if (Array.isArray(raw)) return new Set(['soId', ...raw.filter((id) => COLUMN_DEFS.some((d) => d.id === id))]);
        } catch { /* SSR / corrupt */ }
        return new Set(COLUMN_DEFS.map((d) => d.id));
    });
    const [colsOpen, setColsOpen] = useState(false);
    const colsRef = useRef(null);
    useEffect(() => {
        if (!colsOpen) return undefined;
        const h = (e) => { if (colsRef.current && !colsRef.current.contains(e.target)) setColsOpen(false); };
        document.addEventListener('mousedown', h);
        return () => document.removeEventListener('mousedown', h);
    }, [colsOpen]);
    const toggleColumn = (id) => {
        if (id === 'soId') return;
        setVisibleSet((prev) => {
            const next = new Set(prev);
            next.has(id) ? next.delete(id) : next.add(id);
            try { localStorage.setItem(visibleKey, JSON.stringify([...next])); } catch { /* private mode */ }
            return next;
        });
    };
    const resetVisible = () => {
        try { localStorage.removeItem(visibleKey); } catch { /* private mode */ }
        setVisibleSet(new Set(COLUMN_DEFS.map((d) => d.id)));
    };
    const [dragColId, setDragColId] = useState(null);
    const [dragOverColId, setDragOverColId] = useState(null);
    const visibleCols = useMemo(() => order.map((id) => colDefById.get(id)).filter((d) => d && visibleSet.has(d.id)), [order, colDefById, visibleSet]);
    const isDefaultOrder = order.length === COLUMN_DEFS.length && order.every((id, i) => id === COLUMN_DEFS[i].id);

    // Resizable columns — drag a header's right edge (matches the View Details list).
    const { widthOf, startResize, resizingId, resizeRef } = useResizableColumns(COL_W, COL_W_FALLBACK);
    const tableWidth = useMemo(() => visibleCols.reduce((s, c) => s + widthOf(c.id), 0), [visibleCols, widthOf]);

    const persist = (next) => { try { localStorage.setItem(storageKey, JSON.stringify(next)); } catch { /* ignore */ } };
    const reorderCols = (fromId, toId) => {
        if (!fromId || fromId === toId) return;
        setOrder((prev) => {
            const fi = prev.indexOf(fromId); const ti = prev.indexOf(toId);
            if (fi < 0 || ti < 0) return prev;
            const next = [...prev];
            const [moved] = next.splice(fi, 1);
            next.splice(ti, 0, moved);
            persist(next);
            return next;
        });
    };
    const resetCols = () => { setOrder(defaultOrder()); try { localStorage.removeItem(storageKey); } catch { /* ignore */ } };

    const buildParams = (overrides = {}) => {
        const params = {};
        const search = overrides.search ?? q;
        const pp = overrides.per_page ?? perPage;
        const page = overrides.page ?? currentPage;
        const sort = overrides.sort ?? sortKey;
        const dir = overrides.dir ?? sortDir;
        if (search) params.search = search;
        if (pp && Number(pp) !== 10) params.per_page = pp;
        if (page && Number(page) !== 1) params.page = page;
        if (sort && sort !== 'soId') params.sort = sort;
        if (dir && dir !== 'desc') params.dir = dir;
        PILL_FIELDS.forEach(({ param }) => {
            const v = overrides[param] ?? filters[param] ?? '';
            if (v) params[param] = v;
        });
        ['date_from', 'date_to'].forEach((k) => {
            const v = overrides[k] ?? filters[k] ?? '';
            if (v) params[k] = v;
        });
        return params;
    };

    // The export must cover exactly what the screen covers, so its params come from the
    // SAME live filter state go() builds from — never read back from the URL, which omits
    // default-valued filters and would export a wider set than the list shows. Paging is
    // dropped: the export hook walks the whole set itself at the server's chunk size.
    const exportParams = { ...buildParams() };
    delete exportParams.page;
    delete exportParams.per_page;

    // only: — the filterOptions closure runs eight scoped DISTINCT scans; without this
    // they re-run on every keystroke. `filters` must be listed (sort arrows + page size).
    const go = (overrides) => router.get(route('sample-orders.request-vs-stock'), buildParams(overrides), {
        only: ['details', 'filters'],
        preserveState: true, preserveScroll: true, replace: true,
    });

    const onSearchChange = (val) => {
        setQ(val);
        clearTimeout(searchDebounce.current);
        searchDebounce.current = setTimeout(() => go({ search: val, page: 1 }), 300);
    };
    const onSort = (colId) => go({
        sort: colId,
        dir: sortKey === colId && sortDir === 'asc' ? 'desc' : 'asc',
        page: 1,
    });
    const setPill = (param, val) => { setActivePill(null); go({ [param]: val, page: 1 }); };
    const setDates = (from, to) => { setActivePill(null); go({ date_from: from || '', date_to: to || '', page: 1 }); };
    const togglePill = (key) => setActivePill(activePill === key ? null : key);
    const hasActiveFilters = Boolean(filters.search)
        || PILL_FIELDS.some(({ param }) => filters[param])
        || Boolean(filters.date_from) || Boolean(filters.date_to);
    const clearFilters = () => {
        setQ('');
        setActivePill(null);
        go(Object.fromEntries([
            ...PILL_FIELDS.map(({ param }) => [param, '']),
            ['date_from', ''], ['date_to', ''], ['search', ''], ['page', 1],
        ]));
    };

    return (
        <section className="flex min-w-0 flex-col gap-[18px]">
            {/* Export lives in the PAGE header, outside the table card (user 2026-08-24).
                Same shape Complain & Returns View Request uses: breadcrumb + h1 on the left,
                a right-aligned action group on the right. Inside the toolbar it was landing on
                a second row here — eight filter pills already fill the first one. */}
            <header className="flex items-start justify-between gap-4">
                <div>
                    <p className="m-0 mb-1.5 flex items-center gap-2 text-xs font-semibold text-muted-foreground">
                        <span>Sample Orders</span><span aria-hidden="true">›</span><span className="text-foreground">{title}</span>
                    </p>
                    <h1 className="m-0 text-xl font-bold leading-[1.2] text-card-foreground">{title}</h1>
                </div>
                <div className="flex flex-wrap items-center justify-end gap-2">
                    <ExportButton specKey="sampleRequestVsStockExport"
                        url={route('sample-orders.request-vs-stock.export-data')}
                        params={exportParams} label="Export" className="h-9 px-4 text-[11px]" />
                </div>
            </header>

            {activePill && <div className="fixed inset-0 z-49" onClick={() => setActivePill(null)} />}

            <article className="overflow-hidden rounded-2xl border border-border bg-card shadow-sm">
                <div className="flex flex-wrap items-center gap-2.5 border-b border-border/50 px-5 py-4">
                    <label className="relative inline-flex h-8 min-w-[200px] max-w-[320px] flex-1 items-center gap-2 rounded-full border border-transparent bg-muted/60 px-3.5 text-muted-foreground transition-colors hover:bg-muted focus-within:border-primary/40 focus-within:bg-card">
                        <Search aria-hidden="true" className="size-3.5 shrink-0" />
                        <input type="search" placeholder="Search SO No, Company, Principal, Product, Status…" value={q} onChange={(e) => onSearchChange(e.target.value)}
                            className="min-w-0 flex-1 bg-transparent text-[12.5px] font-medium text-foreground outline-none placeholder:text-muted-foreground/70" />
                    </label>

                    <div className="flex flex-wrap items-center gap-2.5">
                        {PILL_FIELDS.map((f) => (
                            <SelectPill key={f.key} label={f.label} value={filters[f.param] || ''} options={filterOptions[f.key] || []}
                                open={activePill === f.key} onToggle={() => togglePill(f.key)} onPick={(v) => setPill(f.param, v)} />
                        ))}
                        <DateRangePill label="Tanggal" from={filters.date_from} to={filters.date_to}
                            open={activePill === 'dateRange'} onToggle={() => togglePill('dateRange')} onApply={setDates} />
                        {hasActiveFilters && (
                            <button type="button" onClick={clearFilters}
                                title="Reset all active filters"
                                className="inline-flex h-8 cursor-pointer items-center gap-1.5 rounded-full px-2.5 text-[12.5px] font-semibold text-muted-foreground transition-colors hover:bg-danger/10 hover:text-danger-text">
                                <RotateCcw className="size-3.5" />
                                <span>Reset filters</span>
                            </button>
                        )}
                    </div>

                    <div className="ml-auto inline-flex items-center gap-2">
                        {!isDefaultOrder && (
                            <button type="button" onClick={resetCols} title="Reset column order"
                                className="inline-flex h-8 items-center gap-1.5 rounded-full px-2.5 text-[12px] font-bold text-muted-foreground transition-colors hover:text-primary">
                                <RotateCcw className="size-3.5" /> Reset
                            </button>
                        )}
                        <div ref={colsRef} className="relative">
                            <button type="button" title="Configure columns" aria-label="Configure columns" aria-pressed={colsOpen}
                                onClick={() => setColsOpen((o) => !o)}
                                className={`grid size-7 place-items-center rounded-md transition-colors ${colsOpen ? 'bg-accent text-primary' : 'text-muted-foreground hover:bg-muted hover:text-foreground'}`}>
                                <SlidersHorizontal className="size-3.5" strokeWidth={2.5} />
                            </button>
                            {colsOpen && (
                                <div className="absolute right-0 top-full z-50 mt-1.5 max-h-[340px] w-[210px] overflow-y-auto rounded-xl border border-border bg-surface py-2 shadow-modal">
                                    <div className="flex items-center justify-between px-3.5 pb-1.5">
                                        <span className="text-[10px] font-extrabold uppercase tracking-wider text-muted-foreground">Columns</span>
                                        <button type="button" onClick={resetVisible} className="text-[11px] font-bold text-primary hover:underline">Reset</button>
                                    </div>
                                    {COLUMN_DEFS.map((d) => (
                                        <label key={d.id} className={`flex w-full items-center gap-2.5 px-3.5 py-1.5 text-[12.5px] font-medium ${d.id === 'soId' ? 'cursor-default text-muted-foreground/60' : 'cursor-pointer text-foreground hover:bg-surface-tint'}`}
                                            title={d.id === 'soId' ? 'Required column' : undefined}>
                                            <CheckBox size="sm" checked={visibleSet.has(d.id)} disabled={d.id === 'soId'} onChange={() => toggleColumn(d.id)} ariaLabel={`Show column ${d.label}`} />
                                            {d.label}
                                        </label>
                                    ))}
                                </div>
                            )}
                        </div>
                    </div>
                </div>

                <div className="px-5 pb-5 pt-4">
                    <div ref={scrollRef} className={cn(SCROLL_SHADOW, 'max-h-[62vh] overflow-y-auto')}>
                        <table style={{ minWidth: tableWidth }} className={cn(TABLE_CLASS, shadowClass)}>
                            <colgroup>
                                {visibleCols.map((col) => <col key={col.id} style={{ width: widthOf(col.id) }} />)}
                            </colgroup>
                            <thead>
                                <tr>
                                    {visibleCols.map((col) => {
                                        const sortable = col.sortable !== false;
                                        return (
                                            <th key={col.id} draggable
                                                onDragStart={(e) => { if (resizeRef.current) { e.preventDefault(); return; } setDragColId(col.id); }}
                                                onDragOver={(e) => { e.preventDefault(); setDragOverColId(col.id); }}
                                                onDrop={() => { reorderCols(dragColId, col.id); setDragColId(null); setDragOverColId(null); }}
                                                onDragEnd={() => { setDragColId(null); setDragOverColId(null); }}
                                                className={cn(col.sticky && 'col-name', col.numeric && 'text-right', dragColId === col.id && 'opacity-45',
                                                    dragOverColId === col.id && dragColId !== col.id && 'bg-accent text-primary',
                                                    'group/col relative cursor-grab select-none active:cursor-grabbing')}>
                                                {sortable ? (
                                                    <button type="button" onClick={() => onSort(col.id)} className="inline-flex items-center gap-1 border-none bg-transparent p-0 text-left font-[inherit] text-[inherit]">
                                                        <span>{col.label}</span>
                                                        {sortKey === col.id
                                                            ? (sortDir === 'asc' ? <ArrowUp className="size-3 text-muted-foreground" /> : <ArrowDown className="size-3 text-muted-foreground" />)
                                                            : <ChevronsUpDown className="size-3 opacity-40" />}
                                                    </button>
                                                ) : <span className="align-middle">{col.label}</span>}
                                                <ColumnResizeGrip onMouseDown={(e) => startResize(e, col.id)} active={resizingId === col.id} />
                                            </th>
                                        );
                                    })}
                                </tr>
                            </thead>
                            <tbody>
                                {rows.length === 0 ? (
                                    <tr><td colSpan={visibleCols.length} className="py-10 !text-center text-[13px] text-muted-foreground">{hasActiveFilters ? 'No detail lines match your filters.' : 'No detail lines found.'}</td></tr>
                                ) : rows.map((r, i) => (
                                    <tr key={r.detailId}>
                                        {visibleCols.map((col) => (
                                            <td key={col.id} className={cn(col.sticky && 'col-name', col.numeric && '!text-right tabular-nums', col.cellClass)}>
                                                {renderCell(r, col.id, (details.from || 1) + i)}
                                            </td>
                                        ))}
                                    </tr>
                                ))}
                            </tbody>
                        </table>
                    </div>

                    <ListFooter page={currentPage} totalPages={totalPages} onPage={(p) => go({ page: p })}
                        pageSize={perPage} onPageSize={(n) => go({ per_page: n, page: 1 })}
                        pageSizeOptions={PER_PAGE_OPTIONS} total={details.total}
                        from={details.from} to={details.to} itemLabel="entries"
                        className="border-t-0" />
                </div>
            </article>
        </section>
    );
}

SampleOrderRequestVsStockList.layout = [AppLayout];
