// Shared column-sort primitives, lifted out of DataTable so a NON-table renderer (the channel
// manager's card layout) can sort the same rows the same way — one comparator, one source of truth.
// Kept structural (no `Column` import) so it has zero coupling to DataTable.

export type SortState = { key: string; dir: "asc" | "desc" } | null;

interface Sortable<T> {
  key: string;
  sortValue?: (row: T) => string | number;
}

/** Sort a copy of `rows` by the sortable column named in `sort` (numeric diff or locale compare).
 * Returns `rows` unchanged when there's no sort or the column has no `sortValue`. */
export function sortRows<T>(rows: T[], columns: Sortable<T>[], sort: SortState): T[] {
  if (!sort) return rows;
  const get = columns.find((c) => c.key === sort.key)?.sortValue;
  if (!get) return rows;
  return [...rows].sort((a, b) => {
    const av = get(a);
    const bv = get(b);
    const cmp =
      typeof av === "number" && typeof bv === "number"
        ? av - bv
        : String(av).localeCompare(String(bv));
    return sort.dir === "asc" ? cmp : -cmp;
  });
}

/** Coerce a stored value to a SortState. Anything malformed (corrupt storage, a shape from an older
 * build) becomes "no sort" rather than flowing a junk `dir` into the comparator. */
export function parseSortState(raw: unknown): SortState {
  if (!raw || typeof raw !== "object") return null;
  const { key, dir } = raw as { key?: unknown; dir?: unknown };
  return typeof key === "string" && (dir === "asc" || dir === "desc") ? { key, dir } : null;
}

/** The header-click cycle: unsorted → ascending → descending → unsorted. */
export function cycleSort(current: SortState, key: string): SortState {
  if (!current || current.key !== key) return { key, dir: "asc" };
  if (current.dir === "asc") return { key, dir: "desc" };
  return null;
}
