/**
 * Picking the input and resolved datasets from a rendered Vega view (the data
 * inspector — spec §04; arch 05 → "the data inspector rides the boundary").
 *
 * A compiled Vega dataflow holds many named datasets. Vega-Lite names the ones a
 * spec produces by convention: `source_<n>` for each parsed data source, and
 * `data_<n>` for each transform/aggregation stage. So a single view carries both
 * ends of the pipeline:
 *
 * - the **input** is the most-upstream source (`source_0`) — the parsed rows
 *   before the spec's transforms;
 * - the **resolved** output is the most-downstream `data_<n>` — the rows the marks
 *   of a single-view chart actually draw.
 *
 * Everything else in the dataflow (`root`, `marks`, layout/scale tables, selection
 * `*_store`s, `_`-prefixed internals, faceted `a:b` names) is plumbing the user
 * never authored. A spec with no transforms has only a source, so input and
 * resolved resolve to the same table — correct: no transforms means output = input.
 *
 * This is the pure half of the inspector — given the view's dataset names, choose
 * one per direction. The view access itself (listing names, reading rows) lives
 * behind the renderer's `RenderHandle` boundary (services/chart-renderer; arch 05).
 *
 * Limitation: a single name can't represent a multi-view spec (layer/concat/facet
 * produce several `source_<n>` / `data_<n>`, one per view). We return the
 * most-upstream source and most-downstream output — both real, drawn tables — and
 * leave a full dataset selector as a future option.
 */

/** Datasets that are dataflow plumbing, never a table the user would inspect. */
function isInternalDataset(name: string): boolean {
  return (
    name === 'root' ||
    name === 'marks' ||
    name === 'layout' ||
    name === 'cell' ||
    name.startsWith('_') ||
    name.endsWith('_store') || // selection tuple stores
    name.includes(':') // faceted / cross-context child sources
  );
}

/** The trailing index of a `prefix_<n>` name (e.g. `data_12` → 12), or null. */
function suffixIndex(name: string, prefix: string): number | null {
  if (!name.startsWith(`${prefix}_`)) return null;
  const n = Number(name.slice(prefix.length + 1));
  return Number.isInteger(n) ? n : null;
}

/** The `prefix_<n>` name with the highest (`'max'`) or lowest (`'min'`) suffix. */
function bySuffix(names: readonly string[], prefix: string, end: 'min' | 'max'): string | null {
  let best: string | null = null;
  let bestN = end === 'max' ? -Infinity : Infinity;
  for (const name of names) {
    const n = suffixIndex(name, prefix);
    if (n === null) continue;
    if (end === 'max' ? n > bestN : n < bestN) {
      bestN = n;
      best = name;
    }
  }
  return best;
}

/**
 * Choose the compiled dataset representing the rows the chart draws, given all of
 * the view's dataset names. Prefers the most-downstream transform output
 * (`data_<max n>`), then the most-downstream source (`source_<max n>`), then any
 * remaining non-internal table. Returns null when nothing is inspectable.
 */
export function pickResultDataset(names: readonly string[]): string | null {
  const candidates = names.filter((n) => !isInternalDataset(n));
  return (
    bySuffix(candidates, 'data', 'max') ??
    bySuffix(candidates, 'source', 'max') ??
    candidates[0] ??
    null
  );
}

/**
 * Choose the compiled dataset representing the chart's input — the parsed rows
 * before the spec's transforms. Prefers the most-upstream source (`source_<min n>`),
 * then the earliest transform stage (`data_<min n>`) for the rare source-less spec,
 * then any remaining non-internal table. Returns null when nothing is inspectable.
 */
export function pickSourceDataset(names: readonly string[]): string | null {
  const candidates = names.filter((n) => !isInternalDataset(n));
  return (
    bySuffix(candidates, 'source', 'min') ??
    bySuffix(candidates, 'data', 'min') ??
    candidates[0] ??
    null
  );
}
