---
/* ---------------------------------------------------------------------------
 * ResourceListView — shared list rendering for every Backstage resource.
 *
 * Reads URL state (?filter, ?q, ?page) and derives:
 *   - active filter (with isDefault fallback)
 *   - active column set (columnsByFilter override → columns)
 *   - filtered + searched + sorted + paginated row set
 *
 * Rows are full anchor elements pointing at ?edit=<id> so the table is
 * fully keyboard-navigable and works without JS. The panel that consumes
 * the edit param ships in step 6.
 * ------------------------------------------------------------------------- */

import ListCell from './ListCell.astro';
import type { Column, Resource, ResourceGroup } from '../resource-types';

interface Props {
  resource: Resource;
  groups: ResourceGroup[];
}

const { resource, groups } = Astro.props;
const url = Astro.url;

// ── Resolve state from URL ────────────────────────────────────────────────
const filters = resource.list.filters ?? [];
const defaultFilterKey =
  filters.find((f) => f.isDefault)?.key ?? filters[0]?.key ?? 'all';
const filterKey = url.searchParams.get('filter') ?? defaultFilterKey;
const activeFilter = filters.find((f) => f.key === filterKey);

const search = (url.searchParams.get('q') ?? '').trim();
const pageParam = Number(url.searchParams.get('page') ?? '1');
const requestedPage = Number.isFinite(pageParam) && pageParam > 0 ? Math.floor(pageParam) : 1;
const pageSize = resource.list.pageSize ?? 25;

// ── Load + transform ──────────────────────────────────────────────────────
const queried = await resource.list.queryFn();
const allItems = (Array.isArray(queried) ? queried : []) as Record<string, unknown>[];

const filtered = activeFilter
  ? allItems.filter((item) => activeFilter.predicate(item))
  : allItems;

const searched = search && resource.list.search
  ? filtered.filter((item) => {
      const q = search.toLowerCase();
      return resource.list.search!.fields.some((field) => {
        const v = item[field as string];
        return typeof v === 'string' && v.toLowerCase().includes(q);
      });
    })
  : filtered;

const sort = resource.list.defaultSort;
const sorted = sort
  ? [...searched].sort((a, b) => {
      const av = a[sort.key];
      const bv = b[sort.key];
      if (av === bv) return 0;
      if (av == null) return 1;
      if (bv == null) return -1;
      const cmp = av < bv ? -1 : 1;
      return sort.direction === 'desc' ? -cmp : cmp;
    })
  : searched;

const totalPages = Math.max(1, Math.ceil(sorted.length / pageSize));
const page = Math.min(requestedPage, totalPages);
const start = (page - 1) * pageSize;
const pageItems = sorted.slice(start, start + pageSize);

// ── Resolve column set (columnsByFilter override → columns) ───────────────
const columns: Column<Record<string, unknown>>[] =
  (resource.list.columnsByFilter && resource.list.columnsByFilter[filterKey]) ??
  resource.list.columns;

const gridTemplate = columns.map((c) => c.width ?? '1fr').join(' ');

// ── Group eyebrow ─────────────────────────────────────────────────────────
const group = groups.find((g) => g.key === resource.groupKey);

// ── Helper: build a query string preserving the other params ──────────────
function withParams(overrides: Record<string, string | number | null>): string {
  const next = new URLSearchParams(url.searchParams);
  for (const [k, v] of Object.entries(overrides)) {
    if (v == null) next.delete(k);
    else next.set(k, String(v));
  }
  const s = next.toString();
  return s ? `${url.pathname}?${s}` : url.pathname;
}

const showNewButton = resource.form !== null && resource.ops.create !== undefined;
const hasItems = allItems.length > 0;
const hasMatches = pageItems.length > 0;
// A row is only clickable when the panel has something to render — either an
// editable form or a review summary. Pure debug resources (activity) skip the
// anchor wrapper so clicks don't dirty the URL with a ?edit= that goes nowhere.
const rowsClickable = resource.form !== null || resource.summary !== undefined;
---

<section class="bs-list">
  <!-- ── Page header ─────────────────────────────────────────────── -->
  <header class="bs-list-header">
    <div class="bs-list-heading">
      {group && <p class="bs-list-eyebrow">{group.label}</p>}
      <h1 class="bs-list-title">{resource.pluralLabel}</h1>
      {resource.description && <p class="bs-list-desc">{resource.description}</p>}
    </div>
    {showNewButton && (
      <a href={withParams({ new: '1', edit: null })} class="bs-list-new label-sm">
        + New {resource.singularLabel.toLowerCase()}
      </a>
    )}
  </header>

  <!-- ── Toolbar: search + filter chips ──────────────────────────── -->
  {(resource.list.search || filters.length > 0) && (
    <div class="bs-toolbar">
      {resource.list.search && (
        <form method="get" action={url.pathname} class="bs-search-form" role="search">
          {/* Preserve the active filter when submitting a search */}
          {filterKey !== defaultFilterKey && (
            <input type="hidden" name="filter" value={filterKey} />
          )}
          <input
            type="search"
            name="q"
            class="bs-search-input"
            placeholder={resource.list.search.placeholder}
            value={search}
            aria-label={resource.list.search.placeholder}
          />
        </form>
      )}
      {filters.length > 0 && (
        <div class="bs-filters" role="tablist" aria-label="Filter">
          {filters.map((f) => {
            const isActive = f.key === filterKey;
            const href = withParams({ filter: f.key === defaultFilterKey ? null : f.key, page: null });
            return (
              <a
                href={href}
                class:list={['bs-chip', { active: isActive }]}
                role="tab"
                aria-selected={isActive}
              >
                {f.label}
              </a>
            );
          })}
        </div>
      )}
    </div>
  )}

  <!-- ── Grid table ──────────────────────────────────────────────── -->
  {hasMatches ? (
    <div class="bs-grid" role="table" style={`--bs-grid-cols: ${gridTemplate}`}>
      <div class="bs-grid-head" role="row">
        {columns.map((col) => (
          <div class="bs-grid-th" role="columnheader">{col.label}</div>
        ))}
      </div>
      {pageItems.map((item) => {
        const id = Number(item.id);
        const Tag = rowsClickable ? 'a' : 'div';
        const linkProps = rowsClickable
          ? {
              href: withParams({ edit: id, new: null }),
              'aria-label': `Open ${resource.singularLabel.toLowerCase()} ${id}`,
            }
          : {};
        return (
          <Tag class="bs-grid-row" role="row" {...linkProps}>
            {columns.map((col) => (
              <div class="bs-grid-td" role="cell">
                <ListCell column={col} item={item} />
              </div>
            ))}
          </Tag>
        );
      })}
    </div>
  ) : (
    <p class="bs-list-empty">
      {hasItems
        ? 'No items match the current filters.'
        : `No ${resource.pluralLabel.toLowerCase()} yet.`}
    </p>
  )}

  <!-- ── Pagination (only when paged) ────────────────────────────── -->
  {totalPages > 1 && (
    <nav class="bs-pagination" aria-label="Pagination">
      {page > 1 ? (
        <a class="bs-page-link" href={withParams({ page: page - 1 })}>← Previous</a>
      ) : (
        <span class="bs-page-link disabled" aria-hidden="true">← Previous</span>
      )}
      <span class="bs-page-status">Page {page} of {totalPages}</span>
      {page < totalPages ? (
        <a class="bs-page-link" href={withParams({ page: page + 1 })}>Next →</a>
      ) : (
        <span class="bs-page-link disabled" aria-hidden="true">Next →</span>
      )}
    </nav>
  )}
</section>
