/**
 * Read-time migration for Dataset records (docs/architecture/02 §4, spec §09B).
 *
 * Mirrors snippet-migrations: every dataset read from storage passes through
 * `migrateDataset`, which fills missing/old fields and stamps the current
 * version. It tolerates unknown fields (spread the original, only fill gaps) so a
 * record written by a newer build round-trips without data loss. Records written
 * before versioning existed (no `version`) are treated as v1.
 */

import { CURRENT_DATASET_VERSION, type DataSource, type Dataset } from '@core/dataset';
import type { DataFormat } from '@core/format-detection';
import type { ColumnStats } from '@core/profile';
import type { ColumnType } from '@core/type-inference';

const FORMATS: ReadonlyArray<DataFormat> = ['json', 'csv', 'tsv', 'topojson'];

function asFormat(v: unknown): DataFormat {
  return typeof v === 'string' && (FORMATS as string[]).includes(v) ? (v as DataFormat) : 'json';
}

function asSource(v: unknown): DataSource {
  return v === 'url' ? 'url' : 'inline';
}

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

/** Upgrade a raw stored record to the current Dataset shape. */
export function migrateDataset(raw: unknown): Dataset {
  const r = { ...(raw as Record<string, unknown>) };
  const source = asSource(r.source);

  // v1→v2: a URL dataset used to store its address in `data`; it now stores the
  // fetched snapshot in `data` and keeps the address in `url` (+ a `fetchedAt`).
  // A legacy record (url source, no `url` field) becomes an *unfetched* reference:
  // move the address to `url` and clear `data`. The N/A profile carries over as-is;
  // rendering falls back to the live URL until a Refresh fetches + profiles it.
  const legacyUrl = source === 'url' && typeof r.url !== 'string';
  const url = legacyUrl
    ? typeof r.data === 'string'
      ? r.data
      : undefined
    : typeof r.url === 'string'
      ? r.url
      : undefined;
  const fetchedAt = legacyUrl ? null : typeof r.fetchedAt === 'string' ? r.fetchedAt : null;

  return {
    ...r,
    id: typeof r.id === 'number' ? r.id : Number(r.id),
    version: CURRENT_DATASET_VERSION,
    name: typeof r.name === 'string' ? r.name : 'Untitled',
    data: legacyUrl ? null : r.data,
    format: asFormat(r.format),
    source,
    // Only URL datasets carry url/fetchedAt; inline records stay clean.
    ...(source === 'url' ? { url, fetchedAt } : {}),
    comment: typeof r.comment === 'string' ? r.comment : '',
    rowCount: asCount(r.rowCount),
    columnCount: asCount(r.columnCount),
    columns: Array.isArray(r.columns) ? (r.columns as string[]) : [],
    columnTypes: Array.isArray(r.columnTypes)
      ? (r.columnTypes as Array<{ name: string; type: ColumnType }>)
      : [],
    // Records predating cardinality/extent profiling default to empty stats; the
    // Chart Builder's data-aware hints simply stay quiet until the dataset is
    // re-saved (which re-profiles it) — graceful, no forced re-profile on read.
    columnStats: Array.isArray(r.columnStats) ? (r.columnStats as ColumnStats[]) : [],
    size: typeof r.size === 'number' ? r.size : 0,
    created: typeof r.created === 'string' ? r.created : new Date(0).toISOString(),
    modified: typeof r.modified === 'string' ? r.modified : new Date(0).toISOString(),
  };
}
