import type { Meta, StoryObj } from '../../../story.types';
import { DataTable } from './data-table.component';

interface Permission {
  id: string;
  name: string;
  scope: string;
}

// biome-ignore lint/suspicious/noExplicitAny: Story type must be concrete to be rendered by CSF tooling
type Story = StoryObj<DataTable<any>>;

const meta: Meta<DataTable<Permission>> = {
  title: '@aurora/components/data-table/data-table',
  component: DataTable<Permission>,
  tags: ['autodocs'],
  argTypes: {
    table: {
      control: false,
      description:
        'TanStack `Table<T>` instance. Built by the host via `useDataTable` (binding-only) or `usePaginatedDataTable` (binding + query + refetch). The component reads `getHeaderGroups()` and `getRowModel().rows` to render header + body, and `getCanSelect()` / `getRowModel()` for the optional row-click predicate.',
    },
    loading: {
      control: 'boolean',
      description:
        'Controls the empty-state fallback only. When `loading=true` AND `table.getRowModel().rows.length === 0`, the empty `<td>` shows `<hlm-spinner>`. When rows are present, this input is ignored — old rows stay visible during refetch (this is intentional; replacing rows with a spinner causes TBODY collapse and page-scroll yanks).',
    },
    emptyMessage: {
      control: 'text',
      description:
        'Text shown in the empty state when `loading=false` and there are zero rows. Hosts pass an i18n string (e.g. `t("iam.permission.NoPermissionsFound")`).',
    },
    rowClickable: {
      control: false,
      description:
        'Predicate `(row: Row<T>) => boolean`. When it returns `true` for a row, that row gets `cursor-pointer hover:bg-muted` styling and a click handler that emits `(rowClick)`. Default: `() => false` (no row-click; rows render as static).',
    },
    maxHeight: {
      control: 'text',
      description:
        'CSS max-height for the scroll wrapper. Default `"700px"` matches the standalone list-component sizing. Dialog hosts (e.g. `<au-grid-pick-dialog>`) pass `"480px"` so the table fits inside the modal frame without forcing the dialog itself to scroll.',
    },
    rowClick: {
      action: 'rowClick',
      description:
        'Emitted with `row.original` when the user clicks a row whose `rowClickable(row)` returned `true`. Hosts wire this to single-pick or detail-navigation flows.',
    },
  },
  args: {
    loading: false,
    emptyMessage: 'No items',
  },
  parameters: {
    aurora: {
      category: 'data-table',
      when: 'A presentational TanStack `<table>` wrapper. Use it whenever you have a `Table<T>` to render with a sticky header, scroll wrapper, and the standard loading/empty UX. Compose toolbar (search, filters, actions) and `<au-table-pagination>` as siblings — this component is ONLY the table.',
      whenNot:
        'For client-side filtering or non-paginated lists, use a plain HTML table. For a fully composed list page (toolbar + table + paginator + dialog), use the codegen-generated `<au-X-list>` directly. Do NOT replace `<au-table-pagination>` or the toolbar with this — it is intentionally just the `<table>`.',
    },
  },
};

export default meta;

export const Default: Story = {
  name: 'Default (with rows)',
  parameters: {
    docs: {
      description: {
        story:
          'Standard render path: a populated `Table<T>` with rows. Header is sticky to the top of the scroll wrapper. No row-click — rows render as static.',
      },
    },
  },
};

export const Empty: Story = {
  name: 'Empty (no rows, not loading)',
  args: {
    emptyMessage: 'No permissions found',
    loading: false,
  },
  parameters: {
    docs: {
      description: {
        story:
          'Genuine empty state — host filtered down to zero rows or the data set is empty. Shows the `emptyMessage` text inside a centered `<td>` spanning the full row.',
      },
    },
  },
};

export const Loading: Story = {
  name: 'Loading (no rows yet, initial fetch)',
  args: {
    loading: true,
  },
  parameters: {
    docs: {
      description: {
        story:
          'Initial-fetch state — data has not arrived yet AND there are no previous rows to display. Shows `<hlm-spinner>` inside the empty `<td>`. After data arrives, the empty branch is replaced by the row `@for` and the spinner disappears.',
      },
    },
  },
};

export const RowClickable: Story = {
  name: 'Row-clickable (single-pick variant)',
  parameters: {
    docs: {
      description: {
        story:
          'Opt-in row-click. Used by `<au-grid-pick-dialog>` in single-pick mode: clicking a row that is selectable (per `row.getCanSelect()` and the dialog\'s `disabledIds`) immediately emits the pick. Already-linked rows in the candidates table return `false` from the predicate and stay non-interactive.\n\n```ts\n<au-data-table\n  [table]=\"binding.table\"\n  [rowClickable]=\"(row) => !multi() && row.getCanSelect()\"\n  (rowClick)=\"onRowClick($event)\"\n/>\n```',
      },
    },
  },
};

export const RefetchKeepsOldRows: Story = {
  name: 'Refetch keeps old rows visible',
  parameters: {
    docs: {
      description: {
        story:
          "Critical behavior — during a paginate or search refetch, the OLD rows stay in the DOM until the new rows arrive (the `@for` block is fed from `table().getRowModel().rows`, which the host swaps atomically when the new data lands). The component does NOT replace rows with a spinner during loading — that pattern caused TBODY collapse, which dropped page height below scrollTop and made the browser auto-clamp scroll to 0. Documented in the `grid-multi-render-as-shell-section` archive memo.\n\nA host that wants a transient loading indicator during refetch should add it at the toolbar level, not by toggling the table's rows.",
      },
    },
  },
};

export const FewColumnsWideViewport: Story = {
  name: 'Few columns + wide viewport (elastic absorbs surplus)',
  parameters: {
    docs: {
      description: {
        story:
          "Layout under generous space. The `<table>` uses `table-layout: fixed`, `width: 100%`, and a computed `min-width` equal to the sum of `minSize` (elastic) or `size` (rigid) across visible columns. With only 2–3 columns in a viewport much wider than the total min-width, the table stretches to 100% of its wrapper. **Elastic columns** — those declaring both `size` (preferred) and `minSize` (minimum) with `minSize < size` — absorb the surplus space proportionally; **rigid columns** keep their declared `size`. Truncation is invisible because nothing overflows.\n\n```ts\nconst columns: DataTableColumnDef<T>[] = [\n  { id: 'select', size: 40, sticky: 'left' },                       // rigid + sticky\n  { id: 'name', accessorKey: 'name', size: 240, minSize: 140 },     // elastic — absorbs surplus\n];\n```",
      },
    },
  },
};

export const ManyColumnsWideViewport: Story = {
  name: 'Many columns + wide viewport (all visible, no scroll)',
  parameters: {
    docs: {
      description: {
        story:
          "Layout when the wrapper is wider than the table's `min-width` but the column count is large enough that elastic columns receive only modest surplus. All columns render at or near their preferred `size`; no horizontal scroll appears on the wrapper. Sticky columns (e.g. `actions`, `select`) render at their natural position and the sticky behavior is transparent — there is nothing to scroll under them.\n\nThis is the most common state in standard list pages on desktop. The cost of the layout strategy is paid here as a clean stable visual: row heights and column widths are identical across pagination because `table-layout: fixed` ignores per-page content variance.",
      },
    },
  },
};

export const ManyColumnsNarrowViewport: Story = {
  name: 'Many columns + narrow viewport (sticky + truncate active)',
  parameters: {
    docs: {
      description: {
        story:
          "The state that motivates the design. When the wrapper is narrower than the table's `min-width`, the wrapper exposes a horizontal scroll bar. Columns declaring `sticky: 'left'` (typically `actions` and `select`) anchor to the left edge with cumulative offsets — `actions(size=50, sticky='left')` renders at `left: 0`, `select(size=40, sticky='left')` renders at `left: 50` — and remain visible while non-sticky columns scroll under them. Columns declaring `sticky: 'right'` mirror the behavior on the right edge.\n\nElastic columns shrink down to their `minSize` and clip overflowing text via Tailwind's `truncate` utility (`overflow-hidden text-ellipsis whitespace-nowrap`). The full text is exposed via `[attr.title]` so hovering a truncated cell shows the native browser tooltip after the standard hover delay — no JavaScript, no popover infrastructure.\n\nA cell with floating UI (a dropdown, a popover) escapes the `<td>` boundary by declaring `meta: { overflow: 'visible' }` on its column. The truncate classes are then omitted on that `<td>` and the cell can render content beyond its width without being clipped. This is opt-in to keep the default behavior simple for the common case.",
      },
    },
  },
};
