/* Starboard · DataTable
   Config-driven ERP table: sorting, selection + bulk toolbar, sticky
   header/column, resizable columns, column visibility, expandable
   rows, density, loading/error/empty states, footers, and automatic
   row virtualization above 200 rows.
   Requires: datatable.css, a11y.js, position.js, React 18.
   Load with: <script type="text/babel" src="DataTable.jsx"></script> */

/* ── Virtualization hook ─────────────────────────────────────────
   Window of visible rows from the container's scroll position.
   Returns { start, end, padTop, padBottom }. */
const adUseVirtualRows = (containerRef, rowCount, rowHeight, overscan = 6) => {
  const [win, setWin] = React.useState({ start: 0, end: Math.min(rowCount, 30) });
  React.useEffect(() => {
    const el = containerRef.current;
    if (!el) return;
    const update = () => {
      const start = Math.max(0, Math.floor(el.scrollTop / rowHeight) - overscan);
      const end = Math.min(rowCount, Math.ceil((el.scrollTop + el.clientHeight) / rowHeight) + overscan);
      setWin((w) => (w.start === start && w.end === end ? w : { start, end }));
    };
    update();
    el.addEventListener('scroll', update, { passive: true });
    window.addEventListener('resize', update);
    return () => {
      el.removeEventListener('scroll', update);
      window.removeEventListener('resize', update);
    };
  }, [rowCount, rowHeight, overscan]);
  return {
    start: win.start,
    end: win.end,
    padTop: win.start * rowHeight,
    padBottom: Math.max(0, (rowCount - win.end) * rowHeight),
  };
};

/* ── Shared chevrons ── */
const AdDtCaret = () => (
  <svg className="ad-dt__sort-caret" width="12" height="12" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2.5" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m6 9 6 6 6-6"/></svg>
);
const AdDtChevronRight = () => (
  <svg width="14" height="14" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="m9 18 6-6-6-6"/></svg>
);

/* ── Columns visibility menu ── */
const AdDtColumnsMenu = ({ columns, hidden, onToggle }) => {
  const [open, setOpen] = React.useState(false);
  const btnRef = React.useRef(null);
  const menuRef = React.useRef(null);
  const wrapRef = React.useRef(null);
  const { style } = adUseAnchorPosition(btnRef, menuRef, open, { placement: 'bottom-end', offset: 6 });
  adUseDismiss(wrapRef, open, () => setOpen(false));
  return (
    <span ref={wrapRef}>
      <button ref={btnRef} type="button" className="ad-dt__cols-btn" aria-haspopup="true" aria-expanded={open} onClick={() => setOpen(!open)}>
        <svg width="13" height="13" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" aria-hidden="true"><rect x="3" y="4" width="18" height="16" rx="2"/><path d="M9 4v16M15 4v16"/></svg>
        Columns
      </button>
      {open && (
        <div ref={menuRef} className="ad-dt__colmenu" style={style} role="group" aria-label="Toggle columns">
          <div className="ad-dt__colmenu-label">Show columns</div>
          {columns.map((c) => (
            <label key={c.key} className="ad-dt__colmenu-item">
              <input
                type="checkbox"
                className="ad-dt__check"
                checked={!hidden.has(c.key)}
                onChange={() => onToggle(c.key)}
              />
              {c.header}
            </label>
          ))}
        </div>
      )}
    </span>
  );
};

/* ── DataTable ── */
const DataTable = ({
  columns = [],
  data = [],
  rowKey = 'id',
  title,
  subtitle,
  selectable = false,
  bulkActions,          // [{label, icon?, danger?, onClick(selectedKeys)}]
  renderExpanded,       // (row) => node — enables the expander column
  density = 'comfortable',
  stickyHeader = true,
  columnVisibility = false,
  loading = false,
  error = null,         // string — shows inline error block
  onRetry,
  empty,                // {title, sub, cta?, onCta?}
  summary,              // node|string — "Showing 1–25 of 412 voyages"
  pagination,           // node — pagination slot in the summary bar
  maxHeight,
  onSelectionChange,
}) => {
  const getKey = (row) => (typeof rowKey === 'function' ? rowKey(row) : row[rowKey]);
  const rowHeight = density === 'compact' ? 40 : 52;

  const [sort, setSort] = React.useState({ key: null, dir: null });
  const [selected, setSelected] = React.useState(() => new Set());
  const [expanded, setExpanded] = React.useState(() => new Set());
  const [hidden, setHidden] = React.useState(() => new Set());
  const [widths, setWidths] = React.useState({});
  const [sortEpoch, setSortEpoch] = React.useState(0);

  const scrollRef = React.useRef(null);
  const headCheckRef = React.useRef(null);
  const tableId = adUseId('ad-dt');

  /* Visible columns */
  const cols = columns.filter((c) => !hidden.has(c.key));
  const hasSticky = cols.some((c) => c.sticky);

  /* Sorting */
  const cycleSort = (key) => {
    setSort((s) => {
      if (s.key !== key) return { key, dir: 'asc' };
      if (s.dir === 'asc') return { key, dir: 'desc' };
      return { key: null, dir: null };
    });
    setSortEpoch((e) => e + 1);
  };
  const sortedData = React.useMemo(() => {
    if (!sort.key) return data;
    const col = columns.find((c) => c.key === sort.key);
    const get = (row) => (col && col.sortValue ? col.sortValue(row) : row[sort.key]);
    const copy = [...data];
    copy.sort((a, b) => {
      const va = get(a), vb = get(b);
      const r = typeof va === 'number' && typeof vb === 'number'
        ? va - vb
        : String(va ?? '').localeCompare(String(vb ?? ''), undefined, { numeric: true });
      return sort.dir === 'desc' ? -r : r;
    });
    return copy;
  }, [data, sort, columns]);

  /* Selection */
  const allKeys = sortedData.map(getKey);
  const allSelected = allKeys.length > 0 && allKeys.every((k) => selected.has(k));
  const someSelected = selected.size > 0 && !allSelected;
  React.useEffect(() => {
    if (headCheckRef.current) headCheckRef.current.indeterminate = someSelected;
  }, [someSelected]);
  const commitSelection = (next) => {
    setSelected(next);
    adAnnounce(`${next.size} row${next.size === 1 ? '' : 's'} selected`);
    if (onSelectionChange) onSelectionChange([...next]);
  };
  const toggleRow = (key) => {
    const next = new Set(selected);
    next.has(key) ? next.delete(key) : next.add(key);
    commitSelection(next);
  };
  const toggleAll = () => commitSelection(allSelected ? new Set() : new Set(allKeys));

  /* Sticky header scroll shadow — fades over the first 24px */
  React.useEffect(() => {
    const el = scrollRef.current;
    if (!el) return;
    const onScroll = () => {
      el.classList.toggle('is-scrolled', el.scrollTop > 0);
      el.style.setProperty('--ad-dt-scrollfade', String(Math.min(el.scrollTop / 24, 1)));
      el.style.setProperty('--ad-dt-xfade', String(Math.min(el.scrollLeft / 24, 1)));
    };
    onScroll();
    el.addEventListener('scroll', onScroll, { passive: true });
    return () => el.removeEventListener('scroll', onScroll);
  }, []);

  /* Column resize — drag, double-click auto-fit, keyboard ±8px */
  const startResize = (e, key) => {
    e.preventDefault();
    const th = e.currentTarget.closest('th');
    const startX = e.clientX;
    const startW = th.getBoundingClientRect().width;
    const handle = e.currentTarget;
    handle.classList.add('is-resizing');
    const col = columns.find((c) => c.key === key);
    const min = (col && col.minWidth) || 64;
    const onMove = (ev) => setWidths((w) => ({ ...w, [key]: Math.max(min, Math.round(startW + ev.clientX - startX)) }));
    const onUp = () => {
      handle.classList.remove('is-resizing');
      document.removeEventListener('pointermove', onMove);
      document.removeEventListener('pointerup', onUp);
    };
    document.addEventListener('pointermove', onMove);
    document.addEventListener('pointerup', onUp);
  };
  const keyResize = (e, key) => {
    if (e.key !== 'ArrowLeft' && e.key !== 'ArrowRight') return;
    e.preventDefault();
    const th = e.currentTarget.closest('th');
    const col = columns.find((c) => c.key === key);
    const min = (col && col.minWidth) || 64;
    const current = widths[key] || th.getBoundingClientRect().width;
    setWidths((w) => ({ ...w, [key]: Math.max(min, Math.round(current + (e.key === 'ArrowRight' ? 8 : -8))) }));
  };
  const autoFit = (key) => setWidths((w) => { const n = { ...w }; delete n[key]; return n; });

  /* Virtualization — automatic above 200 rows */
  const virtual = sortedData.length > 200;
  const vWin = adUseVirtualRows(scrollRef, sortedData.length, rowHeight + 1, 8);
  const visibleRows = virtual ? sortedData.slice(vWin.start, vWin.end) : sortedData;
  const baseIndex = virtual ? vWin.start : 0;

  const hasFooters = cols.some((c) => c.footer !== undefined);
  const leadCols = (selectable ? 1 : 0) + (renderExpanded ? 1 : 0);
  const totalCols = cols.length + leadCols;
  const staggerCap = 8;

  const alignClass = (c) => (c.align === 'right' ? ' ad-dt__td--right' : c.align === 'center' ? ' ad-dt__td--center' : '');

  /* ── Render ── */
  return (
    <div className={`ad-dt ad-dt--${density}${sort.key ? ' is-sorted' : ''}`}>
      {/* Header bar ⇄ bulk toolbar */}
      {selected.size > 0 ? (
        <div className="ad-dt__bulk" role="toolbar" aria-label="Bulk actions">
          <span className="ad-dt__bulk-count">{selected.size} selected</span>
          <span className="ad-dt__bulk-sep" aria-hidden="true" />
          {(bulkActions || []).map((a, i) => (
            <button key={i} type="button" className={`ad-dt__bulk-act${a.danger ? ' ad-dt__bulk-act--danger' : ''}`} onClick={() => a.onClick && a.onClick([...selected])}>
              {a.icon}{a.label}
            </button>
          ))}
          <button type="button" className="ad-dt__bulk-clear" onClick={() => commitSelection(new Set())}>Clear selection</button>
        </div>
      ) : (title || subtitle || columnVisibility) ? (
        <div className="ad-dt__bar">
          <div>
            {title && <div className="ad-dt__title">{title}</div>}
            {subtitle && <div className="ad-dt__sub">{subtitle}</div>}
          </div>
          <div className="ad-dt__bar-actions">
            {columnVisibility && (
              <AdDtColumnsMenu
                columns={columns}
                hidden={hidden}
                onToggle={(key) => setHidden((h) => { const n = new Set(h); n.has(key) ? n.delete(key) : n.add(key); return n; })}
              />
            )}
          </div>
        </div>
      ) : null}

      {/* Error block */}
      {error && (
        <div className="ad-dt__error" role="alert">
          <svg width="16" height="16" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="2" strokeLinecap="round" aria-hidden="true"><circle cx="12" cy="12" r="9"/><path d="M12 8v4M12 16h.01"/></svg>
          <div>
            <div className="ad-dt__error-title">Couldn't load rows</div>
            {error}
          </div>
          {onRetry && <button type="button" className="ad-dt__error-retry" onClick={onRetry}>Retry</button>}
        </div>
      )}

      {/* Table */}
      {!error && (
        <div ref={scrollRef} className="ad-dt__scroll" style={maxHeight ? { maxHeight } : undefined}>
          <table className="ad-dt__table" id={tableId}>
            <thead>
              <tr>
                {selectable && (
                  <th scope="col" className={`ad-dt__th ad-dt__th--check${hasSticky ? ' ad-dt__th--sticky' : ''}`} style={!stickyHeader ? { position: 'static' } : undefined}>
                    <input
                      ref={headCheckRef}
                      type="checkbox"
                      className="ad-dt__check"
                      checked={allSelected}
                      onChange={toggleAll}
                      aria-label="Select all rows"
                    />
                  </th>
                )}
                {renderExpanded && <th scope="col" className="ad-dt__th ad-dt__th--expander" style={!stickyHeader ? { position: 'static' } : undefined}><VisuallyHidden>Expand row</VisuallyHidden></th>}
                {cols.map((c) => {
                  const isSortCol = sort.key === c.key;
                  const ariaSort = isSortCol ? (sort.dir === 'asc' ? 'ascending' : 'descending') : (c.sortable ? 'none' : undefined);
                  return (
                    <th
                      key={c.key}
                      scope="col"
                      aria-sort={ariaSort}
                      className={`ad-dt__th${alignClass(c).replace('__td', '__th')}${isSortCol ? ` is-sort-active${sort.dir === 'desc' ? ' is-desc' : ''}` : ''}${c.sticky ? ' ad-dt__th--sticky' : ''}`}
                      style={{
                        ...(widths[c.key] ? { width: widths[c.key], minWidth: widths[c.key], maxWidth: widths[c.key] } : c.width ? { width: c.width } : {}),
                        ...(c.minWidth && !widths[c.key] ? { minWidth: c.minWidth } : {}),
                        ...(!stickyHeader && !c.sticky ? { position: 'static' } : {}),
                      }}
                    >
                      {c.sortable ? (
                        <button type="button" className="ad-dt__sort" onClick={() => cycleSort(c.key)}>
                          {c.header}
                          <AdDtCaret />
                        </button>
                      ) : c.header}
                      {c.resizable && (
                        <button
                          type="button"
                          className="ad-dt__rz"
                          role="separator"
                          aria-orientation="vertical"
                          aria-label={`Resize ${c.header} column. Use left and right arrow keys.`}
                          onPointerDown={(e) => startResize(e, c.key)}
                          onDoubleClick={() => autoFit(c.key)}
                          onKeyDown={(e) => keyResize(e, c.key)}
                        />
                      )}
                    </th>
                  );
                })}
              </tr>
            </thead>
            <tbody>
              {/* Loading — 5 shimmer skeleton rows */}
              {loading && Array.from({ length: 5 }).map((_, i) => (
                <tr key={`bone-${i}`} className="ad-dt__row" aria-hidden="true">
                  {selectable && <td className="ad-dt__td ad-dt__td--check"><div className="ad-dt__bone" style={{ width: 16, height: 16 }} /></td>}
                  {renderExpanded && <td className="ad-dt__td ad-dt__td--expander" />}
                  {cols.map((c, j) => (
                    <td key={c.key} className="ad-dt__td">
                      <div className="ad-dt__bone" style={{ width: `${[72, 48, 84, 56, 64][(i + j) % 5]}%` }} />
                    </td>
                  ))}
                </tr>
              ))}

              {/* Empty */}
              {!loading && sortedData.length === 0 && (
                <tr>
                  <td className="ad-dt__td" colSpan={totalCols} style={{ borderBottom: 0 }}>
                    <div className="ad-dt__empty">
                      <div className="ad-dt__empty-icon">
                        <svg width="20" height="20" viewBox="0 0 24 24" fill="none" stroke="currentColor" strokeWidth="1.8" strokeLinecap="round" strokeLinejoin="round" aria-hidden="true"><path d="M3 12h6l3-7 3 14 3-7h3"/></svg>
                      </div>
                      <div className="ad-dt__empty-title">{(empty && empty.title) || 'No voyages yet'}</div>
                      <div className="ad-dt__empty-sub">{(empty && empty.sub) || 'When voyages are scheduled they will appear here.'}</div>
                      {empty && empty.cta && <button type="button" className="ad-btn ad-btn--secondary ad-btn--sm" onClick={empty.onCta}>{empty.cta}</button>}
                    </div>
                  </td>
                </tr>
              )}

              {/* Virtual top pad */}
              {!loading && virtual && vWin.padTop > 0 && (
                <tr aria-hidden="true"><td colSpan={totalCols} style={{ height: vWin.padTop, padding: 0, border: 0 }} /></tr>
              )}

              {/* Rows */}
              {!loading && visibleRows.map((row, i) => {
                const key = getKey(row);
                const isSel = selected.has(key);
                const isOpen = expanded.has(key);
                const idx = baseIndex + i;
                return (
                  <React.Fragment key={`${sortEpoch}-${key}`}>
                    <tr
                      className={`ad-dt__row${isSel ? ' is-selected' : ''}${!virtual ? ' is-entering' : ''}`}
                      style={!virtual ? { animationDelay: `calc(${Math.min(idx, staggerCap)} * var(--stagger-step))` } : undefined}
                      aria-selected={selectable ? isSel : undefined}
                    >
                      {selectable && (
                        <td className={`ad-dt__td ad-dt__td--check${hasSticky ? ' ad-dt__td--sticky' : ''}`}>
                          <input
                            type="checkbox"
                            className="ad-dt__check"
                            checked={isSel}
                            onChange={() => toggleRow(key)}
                            aria-label={`Select row ${key}`}
                          />
                        </td>
                      )}
                      {renderExpanded && (
                        <td className="ad-dt__td ad-dt__td--expander">
                          <button
                            type="button"
                            className={`ad-dt__exp-btn${isOpen ? ' is-open' : ''}`}
                            aria-expanded={isOpen}
                            aria-label={`${isOpen ? 'Collapse' : 'Expand'} row ${key}`}
                            onClick={() => setExpanded((s) => { const n = new Set(s); n.has(key) ? n.delete(key) : n.add(key); return n; })}
                          >
                            <AdDtChevronRight />
                          </button>
                        </td>
                      )}
                      {cols.map((c) => (
                        <td key={c.key} className={`ad-dt__td${alignClass(c)}${c.sticky ? ' ad-dt__td--sticky' : ''}`}>
                          {c.cell ? c.cell(row) : row[c.key]}
                        </td>
                      ))}
                    </tr>
                    {renderExpanded && (
                      <tr aria-hidden={!isOpen}>
                        <td className="ad-dt__expand-td" colSpan={totalCols}>
                          <div className={`ad-dt__expand${isOpen ? ' is-open' : ''}`}>
                            <div>{isOpen && <div className="ad-dt__expand-body">{renderExpanded(row)}</div>}</div>
                          </div>
                        </td>
                      </tr>
                    )}
                  </React.Fragment>
                );
              })}

              {/* Virtual bottom pad */}
              {!loading && virtual && vWin.padBottom > 0 && (
                <tr aria-hidden="true"><td colSpan={totalCols} style={{ height: vWin.padBottom, padding: 0, border: 0 }} /></tr>
              )}
            </tbody>

            {/* Column footers (totals) */}
            {hasFooters && !loading && sortedData.length > 0 && (
              <tfoot>
                <tr>
                  {selectable && <td className="ad-dt__tf-td" />}
                  {renderExpanded && <td className="ad-dt__tf-td" />}
                  {cols.map((c, i) => (
                    <td key={c.key} className={`ad-dt__tf-td${alignClass(c).replace('__td--', '__td--')}`} style={c.align === 'right' ? { textAlign: 'right' } : undefined}>
                      {c.footer !== undefined
                        ? (typeof c.footer === 'function' ? c.footer(sortedData) : c.footer)
                        : (i === 0 ? <span className="ad-dt__tf-label">Totals</span> : null)}
                    </td>
                  ))}
                </tr>
              </tfoot>
            )}
          </table>
        </div>
      )}

      {/* Summary bar + pagination slot */}
      {(summary || pagination) && !error && (
        <div className="ad-dt__summary">
          {summary && <div className="ad-dt__summary-text">{summary}</div>}
          {pagination}
        </div>
      )}
    </div>
  );
};

Object.assign(window, { DataTable, adUseVirtualRows });
