/**
 * Loader for the models.dev snapshot (#2547 4b).
 *
 * The committed snapshot at `./models-dev-snapshot.json` is generated
 * by `scripts/sync-models-dev.ts` and shipped with the package. This
 * loader reads it at registry construction so models the in-tree
 * authoritative list doesn't know about still get pricing / context /
 * modality data from models.dev as a tier-3 fallback.
 *
 * Resolution chain (most specific first):
 *   1. operator manifest overlay   (tier 1; #2547 4a)
 *   2. in-tree authoritative       (tier 2)
 *   3. models.dev snapshot         (this file; tier 3)
 *   4. derived from vendor+family  (tier 4)
 *   5. universal default           (tier 5)
 *
 * Like the manifest loader, this never throws — a missing or malformed
 * snapshot returns an empty entry list with a warn log.
 *
 * @module config/models-dev-snapshot-loader
 */

import { existsSync, readFileSync } from 'node:fs';
import { fileURLToPath } from 'node:url';
import { dirname, join } from 'node:path';

import { createLogger } from '../core/index.js';
import type { ModelEntry } from './model-registry.js';

// Path resolution: try the source-tree location first (development);
// fall back to a sibling of this file (built dist). Both paths
// shouldn't exist simultaneously, so the first hit wins.
function defaultSnapshotPath(): string {
  // import.meta.url points at the .ts source in dev (via tsx), or at
  // the compiled .js in dist. The snapshot lives next to either.
  const here = dirname(fileURLToPath(import.meta.url));
  return join(here, 'models-dev-snapshot.json');
}

interface SnapshotShape {
  version: number;
  fetchedAt?: string;
  sourceUrl?: string;
  entries?: unknown[];
}

export interface ModelsDevSnapshotLoadResult {
  readonly entries: readonly ModelEntry[];
  readonly path: string;
  readonly status: 'missing' | 'malformed' | 'loaded';
  readonly fetchedAt?: string;
}

export function loadModelsDevSnapshot(options?: {
  readonly path?: string;
}): ModelsDevSnapshotLoadResult {
  const logger = createLogger({ component: 'models-dev-snapshot' });
  const path = options?.path ?? defaultSnapshotPath();

  // Defensive against partial `vi.mock('node:fs', ...)` in tests that
  // omit `existsSync` — match manifest-overlay's behaviour and treat
  // any probe failure as "no snapshot."
  let exists = false;
  try {
    exists = existsSync(path);
  } catch {
    return { entries: [], path, status: 'missing' };
  }
  if (!exists) {
    return { entries: [], path, status: 'missing' };
  }

  let parsed: SnapshotShape;
  try {
    parsed = JSON.parse(readFileSync(path, 'utf-8')) as SnapshotShape;
  } catch (e: unknown) {
    logger.warn('models.dev snapshot parse failure; skipping', {
      path,
      error: e instanceof Error ? e.message : String(e),
    });
    return { entries: [], path, status: 'malformed' };
  }

  if (parsed.version !== 1) {
    logger.warn('models.dev snapshot version unsupported; skipping', {
      path,
      version: parsed.version,
    });
    return { entries: [], path, status: 'malformed' };
  }

  if (!Array.isArray(parsed.entries)) {
    logger.warn('models.dev snapshot missing entries array; skipping', { path });
    return { entries: [], path, status: 'malformed' };
  }

  // Trust the snapshot's shape — it was generated by our own script
  // (`scripts/sync-models-dev.ts`) which already produces valid
  // `ModelEntry` records. Defensive Zod re-validation would catch
  // corruption-in-transit but is unnecessary for a file we author
  // and commit ourselves.
  const entries = parsed.entries as ModelEntry[];

  return {
    entries,
    path,
    status: 'loaded',
    ...(typeof parsed.fetchedAt === 'string' ? { fetchedAt: parsed.fetchedAt } : {}),
  };
}
