/**
 * Narrowing the authored JSON a template carries in `fields`.
 *
 * One rule per shape, in one place, because there are two readers of the same
 * bytes and they must not disagree. `FieldsView` renders a qualification
 * framework's dimensions and `QualificationScorer` turns the same dimensions
 * into a form — so the day whitespace-only anchors are made to render as an
 * em-dash rather than vanish, a second copy of `text()` would leave the scorer
 * still dropping them, and the reader would be shown five dimensions while the
 * score was computed against four.
 *
 * Everything here is total: an unrecognised shape yields null or an empty
 * list, never a throw. An omitted section is a bug someone reports; a white
 * screen on one badly-typed row is an outage.
 */
import { MOTION_MAX_DIMENSION_SCORE, MOTION_MIN_DIMENSION_SCORE } from '@pig/core';

export function asRecord(value: unknown): Record<string, unknown> | null {
  // `typeof null` is 'object' and an array is one too; both would satisfy a
  // naive check and then read `undefined` off every property.
  if (typeof value !== 'object' || value === null || Array.isArray(value)) return null;
  return value as Record<string, unknown>;
}

/** A non-empty string, or null. Whitespace-only is treated as absent. */
export function text(value: unknown): string | null {
  if (typeof value !== 'string') return null;
  const trimmed = value.trim();
  return trimmed.length === 0 ? null : trimmed;
}

export function number(value: unknown): number | null {
  return typeof value === 'number' && Number.isFinite(value) ? value : null;
}

/** The entries of a list that are objects. Anything else is dropped silently. */
export function recordList(value: unknown): Record<string, unknown>[] {
  if (!Array.isArray(value)) return [];
  return value
    .map((entry) => asRecord(entry))
    .filter((entry): entry is Record<string, unknown> => entry !== null);
}

export function textList(value: unknown): string[] {
  if (!Array.isArray(value)) return [];
  return value.map((entry) => text(entry)).filter((entry): entry is string => entry !== null);
}

export interface AnchorRow {
  score: number;
  anchor: string;
}

/**
 * The 0–4 scale, from `@pig/core` rather than from the keys present in the
 * JSON, so a framework that forgot to write an anchor for 2 still offers the
 * same scale everywhere it is read.
 */
export function anchorScale(anchors: unknown): AnchorRow[] {
  const record = asRecord(anchors);
  if (!record) return [];

  const rows: AnchorRow[] = [];
  for (let score = MOTION_MIN_DIMENSION_SCORE; score <= MOTION_MAX_DIMENSION_SCORE; score += 1) {
    const anchor = text(record[String(score)]);
    if (anchor) rows.push({ score, anchor });
  }
  return rows;
}
