/* flex.jsx - reusable UI-flexibility primitives.
   - useColumns / ColumnsMenu / ConfigurableTable: per-list column show/hide + reorder, persisted.
   - useDashLayout: dashboard widget order/visibility, persisted.
   All persistence is localStorage (e8-*-v1), matching the config screens.
   Note: the DS MenuButton takes an `items` array (no children), so ColumnsMenu is a custom popover. */
const DSf = window.Stand8DesignSystem_b5c975;

/* ---- Column configuration ------------------------------------------------ */
// registry item: { key, label, render?, width?, secondary?, locked?, align?, defaultHidden? }
// defaultHidden: opt-in column - stays hidden (even for existing saved configs that predate
// it) until the user shows it in the columns menu, which writes it into the saved order.
// saved shape (backward compatible with order/hidden-only v1 records):
// localStorage['e8-cols-<listKey>-v1'] = { order, hidden, sort, widths, density }
function readColsConfig(listKey) {
  try {
    const parsed = JSON.parse(localStorage.getItem('e8-cols-' + listKey + '-v1'));
    return parsed && typeof parsed === 'object' && !Array.isArray(parsed) ? parsed : null;
  }
  catch (e) { return null; }
}

function columnIsSortable(column, rows = []) {
  if (column.sortable === false) return false;
  if (column.sortValue || !column.render) return true;
  return rows.some((row) => {
    const value = row && row[column.key];
    return value != null && typeof value !== 'object' && typeof value !== 'function';
  });
}

function normalizeColsConfig(registry, raw, fallbackDensity = 'comfortable', rows = []) {
  const byKey = Object.fromEntries(registry.map((column, index) => [column.key, { ...column, locked: !!column.locked || index === 0 }]));
  const identityKey = registry[0] && registry[0].key;
  const savedOrder = Array.isArray(raw && raw.order) ? raw.order.filter((key, index, all) => byKey[key] && key !== identityKey && all.indexOf(key) === index) : [];
  const order = (identityKey ? [identityKey] : []).concat(savedOrder, registry.map((column) => column.key).filter((key) => key !== identityKey && !savedOrder.includes(key)));
  const hidden = Array.isArray(raw && raw.hidden) ? raw.hidden.filter((key, index, all) => byKey[key] && !byKey[key].locked && all.indexOf(key) === index) : [];
  registry.forEach((column) => { if (column.defaultHidden && !savedOrder.includes(column.key) && !hidden.includes(column.key)) hidden.push(column.key); });
  const widths = {};
  if (raw && raw.widths && typeof raw.widths === 'object' && !Array.isArray(raw.widths)) {
    Object.keys(raw.widths).forEach((key) => {
      const value = Number(raw.widths[key]);
      if (byKey[key] && Number.isFinite(value) && value > 0) {
        widths[key] = Math.round(Math.max(byKey[key].minWidth || 96, Math.min(byKey[key].maxWidth || 640, value)));
      }
    });
  }
  const sort = raw && byKey[raw.sort && raw.sort.key] && columnIsSortable(byKey[raw.sort.key], rows) && (raw.sort.dir === 'asc' || raw.sort.dir === 'desc')
    ? { key: raw.sort.key, dir: raw.sort.dir }
    : { key: null, dir: null };
  const density = raw && (raw.density === 'compact' || raw.density === 'comfortable')
    ? raw.density
    : (fallbackDensity === 'compact' ? 'compact' : 'comfortable');
  return { order, hidden, widths, sort, density };
}

function sortRowsForTable(rows, columns, sort) {
  if (DSf.sortTableRows) return DSf.sortTableRows(rows, columns, sort);
  if (!sort || !sort.key || !sort.dir) return rows.slice();
  const column = columns.find((candidate) => candidate.key === sort.key);
  if (!column) return rows.slice();
  const direction = sort.dir === 'desc' ? -1 : 1;
  return rows.map((row, index) => ({ row, index })).sort((a, b) => {
    const aValue = column.sortValue ? column.sortValue(a.row) : a.row[column.key];
    const bValue = column.sortValue ? column.sortValue(b.row) : b.row[column.key];
    if (aValue == null && bValue == null) return a.index - b.index;
    if (aValue == null) return 1;
    if (bValue == null) return -1;
    const compared = column.sortComparator
      ? column.sortComparator(aValue, bValue, a.row, b.row)
      : typeof aValue === 'number' && typeof bValue === 'number'
        ? aValue - bValue
        : String(aValue).localeCompare(String(bValue), undefined, { numeric: true, sensitivity: 'base' });
    return compared === 0 ? a.index - b.index : compared * direction;
  }).map((entry) => entry.row);
}

function useColumns(listKey, registry, fallbackDensity = 'comfortable', rows = []) {
  const [config, setConfigState] = React.useState(() => readColsConfig(listKey));
  React.useEffect(() => {
    const onViewsReset = () => setConfigState(readColsConfig(listKey));
    window.addEventListener('e8-workspace-views-reset', onViewsReset);
    return () => window.removeEventListener('e8-workspace-views-reset', onViewsReset);
  }, [listKey]);
  const setConfig = (next) => {
    const normalized = normalizeColsConfig(registry, next, fallbackDensity, rows);
    setConfigState(normalized);
    try { localStorage.setItem('e8-cols-' + listKey + '-v1', JSON.stringify(normalized)); } catch (e) {}
  };
  const reset = () => {
    setConfigState(null);
    try { localStorage.removeItem('e8-cols-' + listKey + '-v1'); } catch (e) {}
  };
  const effectiveRegistry = registry.map((column, index) => ({ ...column, locked: !!column.locked || index === 0 }));
  const normalized = normalizeColsConfig(effectiveRegistry, config, fallbackDensity, rows);
  const byKey = Object.fromEntries(effectiveRegistry.map((c) => [c.key, c]));
  const order = normalized.order;
  const hidden = new Set(normalized.hidden);
  const columns = order.filter((k) => !hidden.has(k)).map((k) => ({ ...byKey[k], sortable: columnIsSortable(byKey[k], rows) }));
  const allColumns = order.map((k) => byKey[k]).filter(Boolean);
  const snapshot = () => ({ order, hidden: Array.from(hidden), widths: normalized.widths, sort: normalized.sort, density: normalized.density });
  const moveTo = (fromKey, toKey) => {
    if (fromKey === toKey || !byKey[fromKey] || !byKey[toKey] || byKey[fromKey].locked || byKey[toKey].locked) return;
    const next = order.filter((key) => key !== fromKey);
    const target = next.indexOf(toKey);
    if (target < 0) return;
    next.splice(target, 0, fromKey);
    setConfig({ ...snapshot(), order: next });
  };
  const setSort = (sort) => setConfig({ ...snapshot(), sort });
  const setWidth = (key, width) => setConfig({ ...snapshot(), widths: { ...normalized.widths, [key]: width } });
  const resetWidths = () => setConfig({ ...snapshot(), widths: {} });
  const setDensity = (density) => setConfig({ ...snapshot(), density });
  return { columns, allColumns, hidden, order, widths: normalized.widths, sort: normalized.sort, density: normalized.density, snapshot, setConfig, moveTo, setSort, setWidth, resetWidths, setDensity, reset };
}

function ColumnsMenu({ cols, advanced = false, buttonLabel = 'Columns' }) {
  const { allColumns, hidden, order, setConfig, reset } = cols;
  const [open, setOpen] = React.useState(false);
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [open]);
  const move = (key, dir) => {
    const idx = order.indexOf(key); const swap = idx + dir;
    if (swap < 0 || swap >= order.length || allColumns[swap].locked) return;
    const next = order.slice();
    next.splice(idx, 1);
    next.splice(swap, 0, key);
    setConfig({ ...cols.snapshot(), order: next });
  };
  const toggle = (key) => {
    if (!hidden.has(key) && allColumns.filter((column) => !hidden.has(column.key)).length === 1) return;
    const h = new Set(hidden); h.has(key) ? h.delete(key) : h.add(key);
    setConfig({ ...cols.snapshot(), hidden: Array.from(h) });
  };
  return (
    <div className="e8-colswrap" ref={ref}>
      <DSf.Button variant="ghost" size="sm" icon="view_column" aria-expanded={open} onClick={() => setOpen(!open)}>{buttonLabel}</DSf.Button>
      {open ? (
        <div className="e8-colsmenu" role="dialog" aria-label="Table options">
          <div className="e8-colsmenu-h">Show &amp; reorder columns</div>
          {allColumns.map((c, i) => (
            <div key={c.key} className="e8-colsmenu-row">
              <label className="e8-colsmenu-lab" style={{ opacity: c.locked ? 0.55 : 1 }}>
                <input type="checkbox" checked={!hidden.has(c.key)} disabled={c.locked} onChange={() => toggle(c.key)} />
                <span>{c.menuLabel || (typeof c.label === 'string' && c.label) || c.key}</span>
              </label>
              <button type="button" className="e8-iconbtn" disabled={i === 0 || c.locked || allColumns[i - 1].locked} onClick={() => move(c.key, -1)} aria-label={'Move ' + (c.menuLabel || c.key) + ' left'}>←</button>
              <button type="button" className="e8-iconbtn" disabled={i === allColumns.length - 1 || c.locked || allColumns[i + 1].locked} onClick={() => move(c.key, 1)} aria-label={'Move ' + (c.menuLabel || c.key) + ' right'}>→</button>
            </div>
          ))}
          {advanced ? (
            <React.Fragment>
              <div className="e8-colsmenu-h">Row density</div>
              <div className="e8-density-options" role="group" aria-label="Row density">
                <button type="button" aria-pressed={cols.density === 'comfortable'} onClick={() => cols.setDensity('comfortable')}>Comfortable</button>
                <button type="button" aria-pressed={cols.density === 'compact'} onClick={() => cols.setDensity('compact')}>Compact</button>
              </div>
              <button type="button" className="e8-colsmenu-reset" onClick={cols.resetWidths}>Reset column widths</button>
            </React.Fragment>
          ) : null}
          <button type="button" className="e8-colsmenu-reset" onClick={reset}>Reset to default</button>
        </div>
      ) : null}
    </div>
  );
}

// Saved views: named snapshots of a list's column layout, stored at e8-views-<listKey>-v1.
function useSavedViews(listKey) {
  const storeKey = 'e8-views-' + listKey + '-v1';
  const read = () => {
    try {
      const parsed = JSON.parse(localStorage.getItem(storeKey));
      return Array.isArray(parsed) ? parsed.filter((view) => view && typeof view === 'object' && typeof view.name === 'string') : [];
    } catch (e) { return []; }
  };
  const [views, setViews] = React.useState(read);
  React.useEffect(() => {
    const onViewsReset = () => setViews(read());
    window.addEventListener('e8-workspace-views-reset', onViewsReset);
    return () => window.removeEventListener('e8-workspace-views-reset', onViewsReset);
  }, [storeKey]);
  const persist = (next) => { setViews(next); try { localStorage.setItem(storeKey, JSON.stringify(next)); } catch (e) {} };
  const save = (name, cfg) => {
    const n = views.reduce((m, v) => Math.max(m, parseInt(String(v.id).slice(1), 10) || 0), 0) + 1;
    persist(views.concat([{ id: 'v' + n, name: name, ...cfg }]));
  };
  const remove = (id) => persist(views.filter((v) => v.id !== id));
  return { views, save, remove };
}

function ViewsMenu({ listKey, cols, viewState, onApplyState }) {
  const sv = useSavedViews(listKey);
  const [open, setOpen] = React.useState(false);
  const [name, setName] = React.useState('');
  const ref = React.useRef(null);
  React.useEffect(() => {
    if (!open) return;
    const onDoc = (e) => { if (ref.current && !ref.current.contains(e.target)) setOpen(false); };
    const onKey = (e) => { if (e.key === 'Escape') setOpen(false); };
    document.addEventListener('mousedown', onDoc);
    document.addEventListener('keydown', onKey);
    return () => { document.removeEventListener('mousedown', onDoc); document.removeEventListener('keydown', onKey); };
  }, [open]);
  const apply = (saved) => {
    const colsConfig = saved.cols || saved;
    if (colsConfig && Array.isArray(colsConfig.order)) cols.setConfig(colsConfig);
    if (onApplyState) onApplyState(saved.state || {});
    setOpen(false);
  };
  const saveCurrent = () => {
    const n = name.trim(); if (!n) return;
    sv.save(n, {
      cols: cols.snapshot(),
      state: viewState || {},
    });
    setName('');
  };
  return (
    <div className="e8-colswrap" ref={ref}>
      <DSf.Button variant="ghost" size="sm" icon="bookmark" onClick={() => setOpen(!open)}>Views</DSf.Button>
      {open ? (
        <div className="e8-colsmenu" role="dialog" aria-label="Saved views">
          <div className="e8-colsmenu-h">Saved views</div>
          {sv.views.length === 0 ? <div className="e8-views-empty">No saved views yet. Set your columns, then save this layout as a view.</div> : null}
          {sv.views.map((v) => (
            <div key={v.id} className="e8-colsmenu-row">
              <button type="button" className="e8-views-apply" onClick={() => apply(v)}>{v.name}</button>
              <button type="button" className="e8-iconbtn" onClick={() => sv.remove(v.id)} aria-label="Delete view">×</button>
            </div>
          ))}
          <div className="e8-views-save">
            <input type="text" value={name} placeholder="Name this view" onChange={(e) => setName(e.target.value)} onKeyDown={(e) => { if (e.key === 'Enter') saveCurrent(); }} />
            <button type="button" className="e8-views-savebtn" onClick={saveCurrent} disabled={!name.trim()}>Save</button>
          </div>
        </div>
      ) : null}
    </div>
  );
}

// Default mobile card derived from the column registry: the first column is the title, the next
// few visible columns become labeled detail rows. Screens can override with a `mobileCard` render.
// Columns can opt out of the card with `hideOnCard: true`.
function defaultRecordCard(row, columns) {
  const first = columns[0];
  const title = first ? (first.render ? first.render(row) : row[first.key]) : null;
  const details = columns.slice(1).filter((c) => !c.hideOnCard).slice(0, 4);
  return (
    <React.Fragment>
      <div className="e8-rcard-title">{title}</div>
      {details.length ? (
        <div className="e8-rcard-rows">
          {details.map((c) => (
            <div className="e8-rcard-row" key={c.key}>
              <span className="e8-rcard-k">{c.menuLabel || (typeof c.label === 'string' ? c.label : c.key)}</span>
              <span className="e8-rcard-v">{c.render ? c.render(row) : (row[c.key] != null ? row[c.key] : '-')}</span>
            </div>
          ))}
        </div>
      ) : null}
    </React.Fragment>
  );
}

function MobileRecordCard({ row, columns, render, onClick }) {
  const clickable = !!onClick;
  const props = clickable
    ? { className: 'e8-rcard is-click', role: 'button', tabIndex: 0, onClick,
        onKeyDown: (e) => { if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(); } } }
    : { className: 'e8-rcard', role: 'listitem' };
  return React.createElement('div', props, render ? render(row) : defaultRecordCard(row, columns));
}

// Convenience wrapper for NEW lists: renders an optional toolbar row (host controls + Views + ColumnsMenu) then the table.
// On phones it falls back to a stacked card list (record tables overflow horizontally on narrow screens).
// R98 Task 6: pages at `pageSize` (default 50) with a "Show more" control so scale-mode lists
// (100+ rows) stay fast without virtualization. Pass pageSize={0} to opt out.
function ConfigurableTable({ listKey, registry, rows = [], toolbar, empty, mobileCard, onRowClick, pageSize = 50, viewState, onApplyViewState, primary = true, controls = true, columnsState, ...rest }) {
  const internalCols = useColumns(listKey, registry, rest.density, rows);
  const cols = columnsState || internalCols;
  const isMobile = useIsMobile();
  const isEmpty = !rows || rows.length === 0;
  const total = rows ? rows.length : 0;
  const [shown, setShown] = React.useState(pageSize || total);
  // Reset the window when the underlying list shrinks (e.g. a filter narrows it).
  React.useEffect(() => { if (pageSize) setShown(Math.min(pageSize, total) || pageSize); }, [total, cols.sort.key, cols.sort.dir]);
  const sortedRows = sortRowsForTable(rows, cols.columns, cols.sort);
  const paged = pageSize && total > shown ? sortedRows.slice(0, shown) : sortedRows;
  const remaining = total - (paged ? paged.length : 0);
  const showMore = remaining > 0 ? (
    <div className="e8-showmore">
      <button type="button" className="e8-showmore-btn" onClick={() => setShown(shown + pageSize)}>
        Show more<span className="e8-showmore-n">{remaining} more</span>
      </button>
    </div>
  ) : null;
  return (
    <div className="e8-ctable">
      {(toolbar || (controls && !isMobile)) ? (
        <div className="e8-ctable-toolbar">
          <div className="e8-ctable-toolbar-left">{toolbar}</div>
          {controls && !isMobile && primary ? <ViewsMenu listKey={listKey} cols={cols} viewState={viewState} onApplyState={onApplyViewState} /> : null}
          {controls && !isMobile ? <ColumnsMenu cols={cols} advanced={primary} buttonLabel={primary ? 'Columns' : 'Table options'} /> : null}
        </div>
      ) : null}
      {isEmpty && empty ? empty
        : isMobile ? (
          <React.Fragment>
            <div className="e8-rcardlist" role="list">
              {paged.map((row) => (
                <MobileRecordCard key={row.id} row={row} columns={cols.columns} render={mobileCard} onClick={onRowClick ? () => onRowClick(row) : null} />
              ))}
            </div>
            {showMore}
          </React.Fragment>
        )
        : (
          <React.Fragment>
            <DSf.DataTable {...rest} columns={cols.columns} rows={paged} onRowClick={onRowClick} onColumnMove={cols.moveTo}
              sort={cols.sort} onSortChange={cols.setSort} presorted widths={cols.widths} onColumnResize={primary ? cols.setWidth : undefined}
              resizable={primary} density={primary ? cols.density : (rest.density || cols.density)} stickyIdentity={primary} />
            {showMore}
          </React.Fragment>
        )}
    </div>
  );
}

/* ---- Dashboard layout ---------------------------------------------------- */
// saved: localStorage['e8-dash-layout-v1'] = { order:[widgetId], hidden:[widgetId] }
function useDashLayout(storeKey, allIds, defaultVisible) {
  const read = () => { try { return JSON.parse(localStorage.getItem(storeKey)) || null; } catch (e) { return null; } };
  const [cfg, setCfg] = React.useState(read);
  const save = (next) => { setCfg(next); try { localStorage.setItem(storeKey, JSON.stringify(next)); } catch (e) {} };
  const reset = () => { setCfg(null); try { localStorage.removeItem(storeKey); } catch (e) {} };
  const vis = defaultVisible || allIds;
  const savedOrder = ((cfg && cfg.order) || []).filter((id) => allIds.includes(id));
  const order = savedOrder.concat(allIds.filter((id) => !savedOrder.includes(id)));
  const hidden = new Set(cfg && cfg.hidden ? cfg.hidden : allIds.filter((id) => !vis.includes(id)));
  const move = (id, dir) => { const i = order.indexOf(id), j = i + dir; if (j < 0 || j >= order.length) return;
    const n = order.slice(); n.splice(i, 1); n.splice(j, 0, id); save({ order: n, hidden: Array.from(hidden) }); };
  const remove = (id) => { const h = new Set(hidden); h.add(id); save({ order, hidden: Array.from(h) }); };
  const add = (id) => { const h = new Set(hidden); h.delete(id); save({ order, hidden: Array.from(h) }); };
  return { order, hidden, visible: order.filter((id) => !hidden.has(id)), move, remove, add, reset };
}

/* Shared "Viewing as" persona switch - one control for Candidates / Consultants / Sequences.
   Reads the active persona + roster from the app globals and writes through window.e8SetPersona
   (which persists to localStorage['e8-persona'] and re-renders via the bus). Presentation only. */
function PersonaViewAs() {
  const DS = window.Stand8DesignSystem_b5c975;
  if (!DS || !DS.Select) return null;
  const persona = window.e8ActivePersona ? window.e8ActivePersona() : null;
  const options = (window.e8Personas ? window.e8Personas() : []).map((p) => ({ value: p.name, label: p.label + ' · ' + p.name.split(' ')[0] }));
  return (
    <label className="e8-cown-viewas">
      <span className="e8-cown-viewas-lab">Viewing as</span>
      <DS.Select
        value={persona ? persona.name : ''}
        onChange={(e) => { if (window.e8SetPersona) window.e8SetPersona(e.target.value); }}
        options={options}
        aria-label="Viewing as persona"
      />
    </label>
  );
}

/* Shared keyboard affordance for clickable non-button elements (div/span/li with onClick).
   Mirrors the DataTable row pattern (components/ats-table/DataTable.jsx): role=button + tabIndex
   so it is Tab-reachable + focus-ring-visible, and Enter/Space fires the same onClick. Presentation
   only - the element does exactly what it did on click, now also on keyboard. Spread onto the
   element: <div {...clickableProps(handler)}>. Do NOT use on real <button>s or elements that
   already have keyboard handling. */
function clickableProps(onClick) {
  return {
    role: 'button',
    tabIndex: 0,
    onClick,
    onKeyDown: (e) => {
      if (e.target !== e.currentTarget) return;
      if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); onClick(e); }
    },
  };
}

/* Keyboard activation for LINK-STYLED anchors (<a className="e8-link"> with an onClick and no
   href): an anchor with no href is not keyboard-activatable, so add tabIndex={0} to make it
   Tab-reachable and this onKeyDown so Enter/Space fire its existing onClick - without changing
   its role (it stays a link, not a button; the .e8-link:focus-visible ring covers it). Handler-
   agnostic: it replays the element's own click, so the anchor keeps whatever onClick it had.
   Use: <a className="e8-link" tabIndex={0} onKeyDown={window.keyActivate} onClick={...}>. */
function keyActivate(e) {
  if (e.key === 'Enter' || e.key === ' ') { e.preventDefault(); e.currentTarget.click(); }
}

Object.assign(window, { useColumns, ColumnsMenu, ConfigurableTable, useDashLayout, useSavedViews, ViewsMenu, PersonaViewAs, clickableProps, keyActivate });
